* 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


Reply via email to