[PATCH v3] elf: Fix tunable comma iterator
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Mon Sep 28 13:11:22 GMT 2026
On 25/09/26 11:04, Daniel Fellows wrote:
> Empty comma-separated entries leave disable uninitialized. Initialize
> it to false and only set it for entries beginning with a minus sign.
>
> Check the input length before accessing the string, and only advance
> past a comma. This avoids reading beyond the supplied string length.
>
> Add an internal regression test which uses guarded buffers to verify the
> iterator does not read past the supplied length.
LGTM, thanks.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> ---
> Changes in v3:
> - Add a tests-internal regression test suggested by Adhemerval.
> - Use guarded buffers to verify the iterator honors the supplied length.
> - Cover normal, disabled and empty suboptions, explicit lengths and null
> termination.
> - Tested on AArch64 with the default -O2 and with -O1.
>
> Changes in v2:
> - Check the supplied length before reading from the string.
> - Stop at the end of the string and only advance past commas.
> - Add a comment explaining why decrementing the entry length is safe.
>
> elf/Makefile | 1 +
> elf/tst-tunables-parse.c | 145 ++++++++++++++++++++++++++++
> sysdeps/generic/dl-tunables-parse.h | 29 +++---
> 3 files changed, 163 insertions(+), 12 deletions(-)
> create mode 100644 elf/tst-tunables-parse.c
>
> diff --git a/elf/Makefile b/elf/Makefile
> index b0f594f5b3..59c3f5a0d9 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -339,6 +339,7 @@ tests-internal := \
> $(tests-static-internal) \
> tst-tls1 \
> tst-tls_tp_offset \
> + tst-tunables-parse \
> tst-tunconf1 \
> # tests-internal
>
> diff --git a/elf/tst-tunables-parse.c b/elf/tst-tunables-parse.c
> new file mode 100644
> index 0000000000..e5475dec19
> --- /dev/null
> +++ b/elf/tst-tunables-parse.c
> @@ -0,0 +1,145 @@
> +/* Test the comma-separated tunable string iterator.
> + Copyright (C) 2026 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library 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 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library 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 the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <dl-tunables.h>
> +#include <dl-tunables-parse.h>
> +
> +#include <array_length.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/next_to_fault.h>
> +
> +struct token
> +{
> + const char *str;
> + bool disable;
> +};
> +
> +struct test_case
> +{
> + /* The suboptions string. It is copied to a guarded buffer without the null
> + terminator, so the iterator must honor the supplied length instead of
> + relying on the terminator. */
> + const char *input;
> + /* The expected suboptions. */
> + const struct token *tokens;
> + size_t ntokens;
> +};
> +
> +#define TOKENS(...) \
> + (const struct token[]) { __VA_ARGS__ }, \
> + array_length (((const struct token[]) { __VA_ARGS__ }))
> +
> +static const struct test_case tests[] =
> +{
> + { "", NULL, 0 },
> + { "abc", TOKENS ({ "abc", false }) },
> + { "a,b", TOKENS ({ "a", false }, { "b", false }) },
> + { "a,-b,c",
> + TOKENS ({ "a", false }, { "b", true }, { "c", false }) },
> + { "-", TOKENS ({ "", true }) },
> + { "--a", TOKENS ({ "-a", true }) },
> + { "-a,-", TOKENS ({ "a", true }, { "", true }) },
> + /* Empty suboptions are returned as empty strings, not disabled. */
> + { ",", TOKENS ({ "", false }) },
> + { ",,", TOKENS ({ "", false }, { "", false }) },
> + { ",a", TOKENS ({ "", false }, { "a", false }) },
> + { "a,", TOKENS ({ "a", false }) },
> + { "a,,b", TOKENS ({ "a", false }, { "", false }, { "b", false }) },
> + { "-avx2,,-,avx512f,",
> + TOKENS ({ "avx2", true }, { "", false }, { "", true },
> + { "avx512f", false }) },
> +};
> +
> +static void
> +check_tokens (const char *input, tunable_val_t *valp,
> + const struct token *tokens, size_t ntokens)
> +{
> + struct tunable_str_comma_state_t state;
> + tunable_str_comma_init (&state, valp);
> +
> + size_t i = 0;
> + while (true)
> + {
> + /* Poison the result so an uninitialized field does not pass by
> + accident. */
> + struct tunable_str_comma_t t;
> + memset (&t, 0xff, sizeof (t));
> +
> + if (!tunable_str_comma_next (&state, &t))
> + break;
> +
> + if (i >= ntokens)
> + FAIL_EXIT1 ("\"%s\": unexpected suboption %zu \"%.*s\"",
> + input, i, (int) t.len, t.str);
> +
> + printf ("info: \"%s\": suboption %zu \"%.*s\" (disable=%d)\n",
> + input, i, (int) t.len, t.str, (int) t.disable);
> + TEST_COMPARE_BLOB (t.str, t.len, tokens[i].str,
> + strlen (tokens[i].str));
> + TEST_COMPARE (t.disable, tokens[i].disable);
> + i++;
> + }
> +
> + TEST_COMPARE (i, ntokens);
> +}
> +
> +static void
> +check_guarded (const char *input, const struct token *tokens, size_t ntokens)
> +{
> + size_t len = strlen (input);
> + struct support_next_to_fault ntf = support_next_to_fault_allocate (len);
> + memcpy (ntf.buffer, input, len);
> +
> + tunable_val_t val = { .strval = { ntf.buffer, len } };
> + check_tokens (input, &val, tokens, ntokens);
> +
> + support_next_to_fault_free (&ntf);
> +}
> +
> +static void
> +check_length (const char *input, size_t len, const struct token *tokens,
> + size_t ntokens)
> +{
> + tunable_val_t val = { .strval = { input, len } };
> + check_tokens (input, &val, tokens, ntokens);
> +}
> +
> +static int
> +do_test (void)
> +{
> + for (size_t i = 0; i < array_length (tests); i++)
> + check_guarded (tests[i].input, tests[i].tokens, tests[i].ntokens);
> +
> + /* The suboptions after the supplied length are not visible. */
> + check_length ("abcdef,gh", 4, TOKENS ({ "abcd", false }));
> + check_length ("abc,def", 3, TOKENS ({ "abc", false }));
> + check_length ("abc,def", 4, TOKENS ({ "abc", false }));
> + check_length ("-abc,def", 4, TOKENS ({ "abc", true }));
> +
> + /* The null terminator stops the iteration before the supplied length. */
> + check_length ("a,b", 10, TOKENS ({ "a", false }, { "b", false }));
> + check_length ("a,", 10, TOKENS ({ "a", false }));
> + check_length (",", 10, TOKENS ({ "", false }));
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/generic/dl-tunables-parse.h b/sysdeps/generic/dl-tunables-parse.h
> index 8ac49bff0b..f69f6d9a3d 100644
> --- a/sysdeps/generic/dl-tunables-parse.h
> +++ b/sysdeps/generic/dl-tunables-parse.h
> @@ -93,29 +93,34 @@ static inline bool
> tunable_str_comma_next (struct tunable_str_comma_state_t *state,
> struct tunable_str_comma_t *str)
> {
> - if (*state->p == '\0' || state->plen >= state->maxplen)
> + if (state->plen >= state->maxplen || *state->p == '\0')
> return false;
>
> const char *c;
> - for (c = state->p; *c != ','; c++, state->plen++)
> - if (*c == '\0' || state->plen == state->maxplen)
> + for (c = state->p; state->plen < state->maxplen;
> + c++, state->plen++)
> + if (*c == '\0' || *c == ',')
> break;
>
> str->str = state->p;
> str->len = c - state->p;
> + str->disable = false;
>
> - if (str->len > 0)
> + /* For an empty suboption, *str->str is ',' or '\0', so a leading
> + '-' implies str->len > 0. */
> + if (*str->str == '-')
> {
> - str->disable = *str->str == '-';
> - if (str->disable)
> - {
> - str->str = str->str + 1;
> - str->len = str->len - 1;
> - }
> + str->disable = true;
> + str->str = str->str + 1;
> + str->len = str->len - 1;
> }
>
> - state->p = c + 1;
> - state->plen++;
> + state->p = c;
> + if (state->plen < state->maxplen && *c == ',')
> + {
> + state->p++;
> + state->plen++;
> + }
>
> return true;
> }
More information about the Libc-alpha
mailing list