[PATCH v2] elf: Fix tunable comma iterator
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Thu Sep 24 11:37:42 GMT 2026
On 22/09/26 12:54, 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.
The patch looks ok, but I think it would be good to add a tests-internal regression
since we only test this indirectly. Something like:
--
#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
G 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>
> ---
> 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.
>
> sysdeps/generic/dl-tunables-parse.h | 29 +++++++++++++++++------------
> 1 file changed, 17 insertions(+), 12 deletions(-)
>
> 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