commit 902b2e7ca5946190de0163ee56ff90247a9df19a
parent c663b879320216d93db9f21ff76257e724b2291d
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 29 Jul 2026 18:33:14 +0200
resolve validation-logic against a fixed table instead of dlsym
Diffstat:
12 files changed, 494 insertions(+), 12 deletions(-)
diff --git a/src/cli/resources/03-backup.json b/src/cli/resources/03-backup.json
@@ -117,7 +117,7 @@
},
"widget": "anastasis_gtk_ia_ahv",
"validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
- "validation-logic": "CH_AVH_check"
+ "validation-logic": "CH_AHV_check"
}
],
"identity_attributes": {
diff --git a/src/cli/resources/04-backup.json b/src/cli/resources/04-backup.json
@@ -118,7 +118,7 @@
},
"widget": "anastasis_gtk_ia_ahv",
"validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
- "validation-logic": "CH_AVH_check"
+ "validation-logic": "CH_AHV_check"
}
],
"identity_attributes": {
diff --git a/src/cli/resources/05-backup.json b/src/cli/resources/05-backup.json
@@ -141,7 +141,7 @@
},
"widget": "anastasis_gtk_ia_ahv",
"validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
- "validation-logic": "CH_AVH_check"
+ "validation-logic": "CH_AHV_check"
}
],
"identity_attributes": {
diff --git a/src/cli/resources/06-backup.json b/src/cli/resources/06-backup.json
@@ -141,7 +141,7 @@
},
"widget": "anastasis_gtk_ia_ahv",
"validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
- "validation-logic": "CH_AVH_check"
+ "validation-logic": "CH_AHV_check"
}
],
"identity_attributes": {
diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c
@@ -27,7 +27,7 @@
#include <taler/taler_json_lib.h>
#include "anastasis_api_redux.h"
#include "anastasis_api_redux_state.h"
-#include <dlfcn.h>
+#include "validation.h"
/**
@@ -1510,18 +1510,24 @@ enter_user_attributes (struct ANASTASIS_ReduxState *rs,
}
if (NULL != a->validation_logic)
{
- bool (*regfun)(const char *);
+ ANASTASIS_ValidationLogic regfun;
- regfun = dlsym (RTLD_DEFAULT,
- a->validation_logic);
+ regfun = ANASTASIS_REDUX_validation_lookup_ (a->validation_logic);
if (NULL == regfun)
{
+ /* The name comes from the (untrusted) state, so an unknown one must
+ fail the validation instead of skipping it. */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Custom validation function `%s' is not available: %s\n",
- a->validation_logic,
- dlerror ());
+ "Validation function `%s' is not known\n",
+ a->validation_logic);
+ ANASTASIS_REDUX_fail_ (rs,
+ cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED,
+ a->name);
+ return NULL;
}
- else if (! regfun (attribute_value))
+ if (! regfun (attribute_value))
{
ANASTASIS_REDUX_fail_ (rs,
cb,
diff --git a/src/reducer/anastasis_api_redux_parse.c b/src/reducer/anastasis_api_redux_parse.c
@@ -30,6 +30,7 @@
#include "anastasis_redux.h"
#include "anastasis_api_redux.h"
#include "anastasis_api_redux_state.h"
+#include "validation.h"
#include <taler/taler_json_lib.h>
@@ -447,6 +448,18 @@ parse_required_attributes (const json_t *arr,
a->uuid = dup_or_null (uuid);
a->tooltip = dup_or_null (tooltip);
a->validation_regex = dup_or_null (regex);
+ if ( (NULL != logic) &&
+ (NULL == ANASTASIS_REDUX_validation_lookup_ (logic)) )
+ {
+ /* reject unknown validation logic already here, so that it cannot even
+ be round-tripped through a state we hand back to the application */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Validation function `%s' is not known\n",
+ logic);
+ *detail = set_detail ("required_attributes",
+ "validation-logic");
+ return GNUNET_SYSERR;
+ }
a->validation_logic = dup_or_null (logic);
a->autocomplete = dup_or_null (autocomplete);
a->have_optional = ! no_optional;
diff --git a/src/reducer/meson.build b/src/reducer/meson.build
@@ -9,6 +9,9 @@ libanastasisredux_SOURCES = [
'anastasis_api_redux_free.c',
'anastasis_api_recovery_redux.c',
'anastasis_api_backup_redux.c',
+ 'validation.c',
+ 'validation_AL_NID.c',
+ 'validation_BE_NRN.c',
'validation_CH_AHV.c',
'validation_CZ_BN.c',
'validation_DE_SVN.c',
@@ -75,6 +78,24 @@ test(
suite: ['reducer'],
)
+test_validation = executable(
+ 'test_validation',
+ ['test_validation.c'],
+ dependencies: [
+ gnunetutil_dep,
+ json_dep,
+ declare_dependency(link_with: libanastasisredux),
+ ],
+ include_directories: [incdir, configuration_inc],
+ build_by_default: false,
+ install: false,
+)
+test(
+ 'test_validation',
+ test_validation,
+ suite: ['reducer'],
+)
+
libanastasisredux_dep = declare_dependency(link_with: libanastasisredux)
pkg.generate(
libanastasisredux,
diff --git a/src/reducer/test_validation.c b/src/reducer/test_validation.c
@@ -0,0 +1,154 @@
+/*
+ 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 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file reducer/test_validation.c
+ * @brief testcase for the validation-logic dispatch table
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "validation.h"
+
+/**
+ * Every name that appears as a "validation-logic" value in
+ * contrib/redux.*.json. All of them must resolve.
+ */
+static const char *known[] = {
+ "AL_NID_check",
+ "BE_NRN_check",
+ "CH_AHV_check",
+ "CZ_BN_check",
+ "DE_SVN_check",
+ "DE_TIN_check",
+ "ES_DNI_check",
+ "FR_INSEE_check",
+ "IN_AADHAR_check",
+ "IT_CF_check",
+ "NL_BSN_check",
+ "XX_SQUARE_check",
+ "XY_PRIME_check",
+ NULL
+};
+
+/**
+ * Names that must NOT resolve; "system" and "abort" are the reason the old
+ * dlsym() dispatch was a remote code execution bug.
+ */
+static const char *unknown[] = {
+ "system",
+ "abort",
+ "printf",
+ "",
+ "NL_BSN_check ",
+ "nl_bsn_check",
+ NULL
+};
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ GNUNET_log_setup ("test-validation",
+ "WARNING",
+ NULL);
+ for (unsigned int i = 0; NULL != known[i]; i++)
+ if (NULL == ANASTASIS_REDUX_validation_lookup_ (known[i]))
+ {
+ GNUNET_break (0);
+ fprintf (stderr,
+ "validation logic `%s' does not resolve\n",
+ known[i]);
+ return 1;
+ }
+ for (unsigned int i = 0; NULL != unknown[i]; i++)
+ if (NULL != ANASTASIS_REDUX_validation_lookup_ (unknown[i]))
+ {
+ GNUNET_break (0);
+ fprintf (stderr,
+ "`%s' must not resolve to a validation function\n",
+ unknown[i]);
+ return 1;
+ }
+ if (NULL != ANASTASIS_REDUX_validation_lookup_ (NULL))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+
+ /* Belgian national register number: 93.05.18-223.61 is the documented
+ example; 97 - (930518223 % 97) == 61. As the century is not encoded we
+ also accept the variant computed with the '2' prefix (check digits 90). */
+ if (! BE_NRN_check ("93051822361"))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (! BE_NRN_check ("93051822390"))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (BE_NRN_check ("93051822362"))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ /* born in 2017: only the '2'-prefixed variant matches */
+ if (! BE_NRN_check ("17060612328"))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (BE_NRN_check ("17060612327"))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ /* malformed input */
+ if (BE_NRN_check (""))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (BE_NRN_check ("9305182236")) /* too short */
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (BE_NRN_check ("930518223611")) /* too long */
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (BE_NRN_check ("9305182236x")) /* not a digit */
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+
+ /* AL_NID_check has no implementation yet and deliberately accepts
+ everything; when it is implemented, this expectation must change. */
+ if (! AL_NID_check ("I05101999I"))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* end of test_validation.c */
diff --git a/src/reducer/validation.c b/src/reducer/validation.c
@@ -0,0 +1,79 @@
+/*
+ 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ Anastasis; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file reducer/validation.c
+ * @brief table of the national identity number validation functions
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "validation.h"
+
+
+/**
+ * Mapping of a "validation-logic" name to its implementation.
+ */
+struct ValidationLogic
+{
+ /**
+ * Name used in the redux.<cc>.json country files.
+ */
+ const char *name;
+
+ /**
+ * Function implementing the check.
+ */
+ ANASTASIS_ValidationLogic logic;
+};
+
+
+/**
+ * All validation functions we support. Every "validation-logic" value
+ * appearing in contrib/redux.*.json must have an entry here; anything else is
+ * rejected by #ANASTASIS_REDUX_validation_lookup_().
+ */
+static const struct ValidationLogic logics[] = {
+ { "AL_NID_check", &AL_NID_check },
+ { "BE_NRN_check", &BE_NRN_check },
+ { "CH_AHV_check", &CH_AHV_check },
+ { "CZ_BN_check", &CZ_BN_check },
+ { "DE_SVN_check", &DE_SVN_check },
+ { "DE_TIN_check", &DE_TIN_check },
+ { "ES_DNI_check", &ES_DNI_check },
+ { "FR_INSEE_check", &FR_INSEE_check },
+ { "IN_AADHAR_check", &IN_AADHAR_check },
+ { "IT_CF_check", &IT_CF_check },
+ { "NL_BSN_check", &NL_BSN_check },
+ { "XX_SQUARE_check", &XX_SQUARE_check },
+ { "XY_PRIME_check", &XY_PRIME_check },
+ { NULL, NULL }
+};
+
+
+ANASTASIS_ValidationLogic
+ANASTASIS_REDUX_validation_lookup_ (const char *name)
+{
+ if (NULL == name)
+ return NULL;
+ for (unsigned int i = 0; NULL != logics[i].name; i++)
+ if (0 == strcmp (name,
+ logics[i].name))
+ return logics[i].logic;
+ return NULL;
+}
+
+
+/* end of validation.c */
diff --git a/src/reducer/validation.h b/src/reducer/validation.h
@@ -0,0 +1,90 @@
+/*
+ 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ Anastasis; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file reducer/validation.h
+ * @brief lookup of the national identity number validation functions
+ * @author Christian Grothoff
+ *
+ * The `validation-logic` name of an attribute comes from the reducer state,
+ * which is attacker-influenceable. It must therefore never be used to look up
+ * a symbol at run time; the name is resolved against the fixed table in
+ * validation.c instead, and an unknown name is a validation failure.
+ */
+#ifndef ANASTASIS_VALIDATION_H
+#define ANASTASIS_VALIDATION_H
+
+#include <stdbool.h>
+
+/**
+ * Signature of the per-country identity number check functions.
+ *
+ * @param number the identity number entered by the user
+ * @return true if @a number is well-formed
+ */
+typedef bool
+(*ANASTASIS_ValidationLogic)(const char *number);
+
+
+/**
+ * Resolve @a name to the validation function implementing it.
+ *
+ * @param name value of the "validation-logic" field, must not be NULL
+ * @return NULL if @a name is not a known validation function
+ */
+ANASTASIS_ValidationLogic
+ANASTASIS_REDUX_validation_lookup_ (const char *name);
+
+
+bool
+AL_NID_check (const char *nid_number);
+
+bool
+BE_NRN_check (const char *nrn_number);
+
+bool
+CH_AHV_check (const char *ahv_number);
+
+bool
+CZ_BN_check (const char *b_number);
+
+bool
+DE_SVN_check (const char *ssn_number);
+
+bool
+DE_TIN_check (const char *tin_number);
+
+bool
+ES_DNI_check (const char *dni_number);
+
+bool
+FR_INSEE_check (const char *insee_number);
+
+bool
+IN_AADHAR_check (const char *aadhar_number);
+
+bool
+IT_CF_check (const char *cf_number);
+
+bool
+NL_BSN_check (const char *bsn_number);
+
+bool
+XX_SQUARE_check (const char *sq_number);
+
+bool
+XY_PRIME_check (const char *pr_number);
+
+#endif
diff --git a/src/reducer/validation_AL_NID.c b/src/reducer/validation_AL_NID.c
@@ -0,0 +1,50 @@
+/*
+ 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 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file reducer/validation_AL_NID.c
+ * @brief Validation for Albanian national identity numbers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <string.h>
+#include <stdbool.h>
+
+
+/**
+ * Function to validate an Albanian national identity number (NIPT/NID).
+ *
+ * NOT YET IMPLEMENTED: the algorithm computing the trailing check letter has
+ * not been confirmed against an authoritative specification, and guessing it
+ * would reject valid numbers. Until it is, this deliberately accepts any
+ * input and logs that no check was performed. The
+ * "validation-regex" in contrib/redux.al.json still applies.
+ *
+ * @param nid_number identity number to validate (input)
+ * @return true, always, as no check is implemented yet
+ */
+bool
+AL_NID_check (const char *nid_number);
+
+/* declaration to fix compiler warning */
+bool
+AL_NID_check (const char *nid_number)
+{
+ (void) nid_number;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Albanian identity numbers are not checked: AL_NID_check is not implemented\n");
+ return true;
+}
diff --git a/src/reducer/validation_BE_NRN.c b/src/reducer/validation_BE_NRN.c
@@ -0,0 +1,69 @@
+/*
+ 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 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file reducer/validation_BE_NRN.c
+ * @brief Validation for Belgian national register numbers
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+
+/**
+ * Function to validate a Belgian national register number (rijksregisternummer
+ * / numéro de registre national).
+ *
+ * The number is YYMMDD (date of birth) + SSS (serial) + CC (check). The check
+ * is 97 minus the first nine digits taken as a number, modulo 97. The century
+ * is not encoded, so for people born from 2000 on a '2' is prepended to the
+ * nine digits before the computation. Since we cannot tell the two cases
+ * apart from the number alone, either variant matching is accepted.
+ *
+ * See https://nl.wikipedia.org/wiki/Rijksregisternummer
+ *
+ * @param nrn_number national register number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+BE_NRN_check (const char *nrn_number);
+
+/* declaration to fix compiler warning */
+bool
+BE_NRN_check (const char *nrn_number)
+{
+ unsigned long long base = 0;
+ unsigned int check = 0;
+
+ if (strlen (nrn_number) != 11)
+ return false;
+ for (unsigned int i = 0; i<11; i++)
+ {
+ unsigned char c = (unsigned char) nrn_number[i];
+
+ if ( ('0' > c) || ('9' < c) )
+ return false;
+ if (i < 9)
+ base = base * 10 + (c - '0');
+ else
+ check = check * 10 + (c - '0');
+ }
+ /* born before 2000: the nine digits are used as they are */
+ if (check == 97 - (base % 97))
+ return true;
+ /* born from 2000 on: a '2' is prepended */
+ return (check == 97 - ((2000000000ULL + base) % 97));
+}