commit 7e8d8ef77f55e0b34f7058999b1cc7b7d270cff7
parent 6e08fb97072f34b5b4ed7c0554b317831741396c
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 29 Jul 2026 16:56:47 +0200
report failures from anastasis-discover and bound the state read from stdin
Diffstat:
5 files changed, 226 insertions(+), 10 deletions(-)
diff --git a/src/cli/anastasis-cli-common.c b/src/cli/anastasis-cli-common.c
@@ -0,0 +1,147 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2026 Anastasis SARL
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file cli/anastasis-cli-common.c
+ * @brief helpers shared by the Anastasis command line tools
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "anastasis-cli-common.h"
+
+#ifndef MAP_ANONYMOUS
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+
+/**
+ * Report that the input was larger than #ANASTASIS_CLI_MAX_STATE_SIZE.
+ *
+ * @param[out] error error structure to fill in
+ */
+static void
+set_too_big (json_error_t *error)
+{
+ GNUNET_snprintf (error->text,
+ sizeof (error->text),
+ "input exceeds the limit of %llu bytes",
+ (unsigned long long) ANASTASIS_CLI_MAX_STATE_SIZE);
+}
+
+
+json_t *
+ANASTASIS_CLI_json_load_capped (FILE *f,
+ json_error_t *error)
+{
+ int fd = fileno (f);
+ struct stat sb;
+ off_t fsize = -1;
+ size_t map_size;
+ size_t len;
+ void *map;
+ json_t *ret;
+
+ memset (error,
+ 0,
+ sizeof (*error));
+ if ( (-1 != fd) &&
+ (0 == fstat (fd,
+ &sb)) &&
+ (S_ISREG (sb.st_mode)) &&
+ (0 == ftello (f)) )
+ fsize = sb.st_size;
+ if (-1 != fsize)
+ {
+ /* Regular file positioned at the start: the size is known before we touch
+ the data, so the limit is a single comparison and the file itself can
+ serve as the buffer. */
+ if (fsize > (off_t) ANASTASIS_CLI_MAX_STATE_SIZE)
+ {
+ set_too_big (error);
+ return NULL;
+ }
+ if (0 == fsize)
+ return json_loadb ("", /* mmap() rejects a zero-length mapping */
+ 0,
+ JSON_DECODE_ANY,
+ error);
+ map_size = (size_t) fsize;
+ len = map_size;
+ map = mmap (NULL,
+ map_size,
+ PROT_READ,
+ MAP_PRIVATE,
+ fd,
+ 0);
+ }
+ else
+ {
+ /* A pipe or terminal has no size to stat and nothing to map, so read into
+ an anonymous mapping one byte larger than the limit: the pages we never
+ fill cost nothing, and filling the last one means the input is too big. */
+ map_size = ANASTASIS_CLI_MAX_STATE_SIZE + 1;
+ len = 0;
+ map = mmap (NULL,
+ map_size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS,
+ -1,
+ 0);
+ }
+ if (MAP_FAILED == map)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "mmap");
+ GNUNET_snprintf (error->text,
+ sizeof (error->text),
+ "failed to map input: %s",
+ strerror (errno));
+ return NULL;
+ }
+ if (-1 == fsize)
+ {
+ while (len < map_size)
+ {
+ size_t got;
+
+ got = fread ((char *) map + len,
+ 1,
+ map_size - len,
+ f);
+ if (0 == got)
+ break; /* EOF or error; ferror() is reported as a parse failure below */
+ len += got;
+ }
+ if (len == map_size)
+ {
+ GNUNET_break (0 == munmap (map,
+ map_size));
+ set_too_big (error);
+ return NULL;
+ }
+ }
+ ret = json_loadb (map,
+ len,
+ JSON_DECODE_ANY,
+ error);
+ GNUNET_break (0 == munmap (map,
+ map_size));
+ return ret;
+}
+
+
+/* end of anastasis-cli-common.c */
diff --git a/src/cli/anastasis-cli-common.h b/src/cli/anastasis-cli-common.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2026 Anastasis SARL
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file cli/anastasis-cli-common.h
+ * @brief helpers shared by the Anastasis command line tools
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_CLI_COMMON_H
+#define ANASTASIS_CLI_COMMON_H
+
+#include <jansson.h>
+#include <stdio.h>
+
+
+/**
+ * Largest reducer state we are willing to read from a stream. Matches the
+ * 16 MB upload limit the provider backend applies to recovery documents, so
+ * anything bigger could not have come from (or be sent to) a provider anyway.
+ */
+#define ANASTASIS_CLI_MAX_STATE_SIZE (16 * 1024 * 1024)
+
+
+/**
+ * Read a JSON value from @a f, refusing inputs larger than
+ * #ANASTASIS_CLI_MAX_STATE_SIZE. Unlike json_loadf(), this cannot be made to
+ * consume unbounded memory by a caller that keeps writing to our stdin. If
+ * @a f is a regular file we simply map it and check st_size against the limit;
+ * otherwise the limit bounds how much we read from the stream.
+ *
+ * @param f stream to read from, read until EOF
+ * @param[out] error set to the parse (or size) error on failure
+ * @return the parsed JSON, or NULL on error
+ */
+json_t *
+ANASTASIS_CLI_json_load_capped (FILE *f,
+ json_error_t *error);
+
+
+#endif
diff --git a/src/cli/anastasis-cli-discover.c b/src/cli/anastasis-cli-discover.c
@@ -27,6 +27,7 @@
#include <taler/taler_error_codes.h>
#include <taler/taler_json_lib.h>
#include "anastasis_util_lib.h"
+#include "anastasis-cli-common.h"
/**
* Closure for #GNUNET_CURL_gnunet_scheduler_reschedule().
@@ -59,9 +60,11 @@ static json_t *arguments;
struct ANASTASIS_PolicyDiscovery *pd;
/**
- * Return value from main.
+ * Return value from main. Defaults to failure so that every path which
+ * shuts down without discovery having run to completion — a parse error, a
+ * failed start, or a signal — is reported to the caller's shell.
*/
-static int global_ret;
+static int global_ret = 1;
/**
@@ -143,6 +146,10 @@ shutdown_task (void *cls)
GNUNET_CURL_gnunet_rc_destroy (rc);
rc = NULL;
}
+ json_decref (prev_state);
+ prev_state = NULL;
+ json_decref (arguments);
+ arguments = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Shutdown complete\n");
}
@@ -196,9 +203,8 @@ run (void *cls,
}
else
{
- prev_state = json_loadf (stdin,
- JSON_DECODE_ANY,
- &error);
+ prev_state = ANASTASIS_CLI_json_load_capped (stdin,
+ &error);
}
if (NULL == prev_state)
{
@@ -219,6 +225,13 @@ run (void *cls,
arguments,
&print_policy_cb,
NULL);
+ if (NULL == pd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Failed to start policy discovery\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
}
diff --git a/src/cli/anastasis-cli-redux.c b/src/cli/anastasis-cli-redux.c
@@ -29,6 +29,7 @@
#include <taler/taler_error_codes.h>
#include <taler/taler_json_lib.h>
#include "anastasis_util_lib.h"
+#include "anastasis-cli-common.h"
/**
* Closure for #GNUNET_CURL_gnunet_scheduler_reschedule().
@@ -196,6 +197,10 @@ shutdown_task (void *cls)
GNUNET_CURL_gnunet_rc_destroy (rc);
rc = NULL;
}
+ json_decref (prev_state);
+ prev_state = NULL;
+ json_decref (arguments);
+ arguments = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Shutdown complete\n");
}
@@ -310,9 +315,8 @@ run (void *cls,
}
else
{
- prev_state = json_loadf (stdin,
- JSON_DECODE_ANY,
- &error);
+ prev_state = ANASTASIS_CLI_json_load_capped (stdin,
+ &error);
}
if (NULL == prev_state)
{
diff --git a/src/cli/meson.build b/src/cli/meson.build
@@ -56,7 +56,7 @@ subdir('resources')
executable(
'anastasis-reducer',
- ['anastasis-cli-redux.c'],
+ ['anastasis-cli-redux.c', 'anastasis-cli-common.c'],
dependencies: [
libanastasisutil_dep,
libanastasisredux_dep,
@@ -73,7 +73,7 @@ executable(
executable(
'anastasis-discover',
- ['anastasis-cli-discover.c'],
+ ['anastasis-cli-discover.c', 'anastasis-cli-common.c'],
dependencies: [
libanastasisutil_dep,
libanastasisredux_dep,