anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

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:
Asrc/cli/anastasis-cli-common.c | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/anastasis-cli-common.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/cli/anastasis-cli-discover.c | 23++++++++++++++++++-----
Msrc/cli/anastasis-cli-redux.c | 10+++++++---
Msrc/cli/meson.build | 4++--
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,