* lib/cpu-supports.c (hwcap_allowed): Be more restrictive in matching as per the cases listed at: https://github.com/coreutils/coreutils/issues/385 * tests/test-cpu-supports.c: Add test cases. --- ChangeLog | 8 ++++++++ lib/cpu-supports.c | 42 +++++++++++++++++++++++++++------------ tests/test-cpu-supports.c | 30 ++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 13 deletions(-)
diff --git a/ChangeLog b/ChangeLog index a62a22bb20..9ca71b97c3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2026-10-21 Pádraig Brady <[email protected]> + + cpu-supports: align more closely with GLIBC_TUNABLES parsing + * lib/cpu-supports.c (hwcap_supported): Be more restrictive + in matching as per the cases listed at: + https://github.com/coreutils/coreutils/issues/385 + * tests/test-cpu-supports.c: Add test cases. + 2026-10-19 Paul Eggert <[email protected]> srclist-update respects strip-trailing-space diff --git a/lib/cpu-supports.c b/lib/cpu-supports.c index a1505f8bd6..290aa78cca 100644 --- a/lib/cpu-supports.c +++ b/lib/cpu-supports.c @@ -45,17 +45,33 @@ hwcap_allowed (char const *glibc_hwcap) static char const *hwcaps; if (hwcap_allowed_nocache || ! hwcaps) { /* Cache glibc.cpu.hwcaps once per process. */ - if ((hwcaps = getenv ("GLIBC_TUNABLES"))) - { - char const *tunables_start = hwcaps; - char const *last_hwcaps; - while ((last_hwcaps = strstr (hwcaps, "glibc.cpu.hwcaps="))) - hwcaps = last_hwcaps + sizeof "glibc.cpu.hwcaps=" - 1; - if (hwcaps == tunables_start) /* No match. */ - hwcaps = ""; - } - else - hwcaps = ""; + char const *p = getenv ("GLIBC_TUNABLES"); + hwcaps = ""; + if (p && *p) + while (true) + { + char const *name = p; + p += strcspn (p, "=:"); + if (! *p) /* Missing value invalidates all tunables. */ + { + hwcaps = ""; + break; + } + if (*p++ == ':') + continue; + char const *value = p; + p += strcspn (p, "=:"); + if (*p == '=') /* Values cannot contain '='. */ + { + hwcaps = ""; + break; + } + if (strncmp (name, "glibc.cpu.hwcaps=", + sizeof "glibc.cpu.hwcaps=" - 1) == 0) + hwcaps = value; + if (! *p) + break; + } } if (! hwcaps) /* Shouldn't happen. */ @@ -70,10 +86,10 @@ hwcap_allowed (char const *glibc_hwcap) char const *cap = hwcaps; while ((cap = strstr (cap, glibc_hwcap)) && cap < sentinel) { /* Check it's not a partial match. */ + bool start = cap == hwcaps || cap[-1] == ','; cap += strlen (glibc_hwcap); - if (*cap == ',' || *cap == ':' || *cap == '\0') + if (start && (*cap == ',' || *cap == ':' || *cap == '\0')) return false; /* Feature disabled. */ - /* glibc hwcaps can't have '-' in name so ok to search from here. */ } return true; diff --git a/tests/test-cpu-supports.c b/tests/test-cpu-supports.c index 1849b2101e..c90e1cac6f 100644 --- a/tests/test-cpu-supports.c +++ b/tests/test-cpu-supports.c @@ -83,6 +83,36 @@ main () ASSERT (hwcap_allowed ("-AVX2") == 0); } + { /* Only complete tunable names match. */ + setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX:" + "xglibc.cpu.hwcaps=x", 1); + ASSERT (! hwcap_allowed ("-AVX")); + setenv ("GLIBC_TUNABLES", "xglibc.cpu.hwcaps=-AVX", 1); + ASSERT (hwcap_allowed ("-AVX")); + } + + { /* Malformed tunables invalidate the entire variable. */ + static char const *const invalid[] = + { + "glibc.cpu.hwcaps=-AVX2:x=y=z", + "x=y=z:glibc.cpu.hwcaps=-AVX2", + "glibc.cpu.hwcaps=-AVX2:missing", + "glibc.cpu.hwcaps=-AVX2:" + }; + for (size_t i = 0; i < sizeof invalid / sizeof *invalid; i++) + { + setenv ("GLIBC_TUNABLES", invalid[i], 1); + ASSERT (hwcap_allowed ("-AVX2")); + } + } + + { /* Only complete feature names match. */ + setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=--AVX2,x-AVX2,-AVX2x", 1); + ASSERT (hwcap_allowed ("-AVX2")); + setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=--AVX2,x-AVX2,-AVX2", 1); + ASSERT (! hwcap_allowed ("-AVX2")); + } + { /* Unsupported features not matched by cpu_*(). */ setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-FOO", 1); ASSERT (cpu_may_support ("foo")); -- 2.55.0
