]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/bpf: More precise cpu_mitigations state detection
authorEduard Zingerman <eddyz87@gmail.com>
Tue, 17 Jun 2025 00:57:10 +0000 (17:57 -0700)
committerAndrii Nakryiko <andrii@kernel.org>
Tue, 17 Jun 2025 20:23:49 +0000 (13:23 -0700)
test_progs and test_verifier binaries execute unpriv tests under the
following conditions:
- unpriv BPF is enabled;
- CPU mitigations are enabled (see [1] for details).

The detection of the "mitigations enabled" state is performed by
unpriv_helpers.c:get_mitigations_off() via inspecting kernel boot
command line, looking for a parameter "mitigations=off".

Such detection scheme won't work for certain configurations,
e.g. when CONFIG_CPU_MITIGATIONS is disabled and boot parameter is
not supplied.

Miss-detection leads to test_progs executing tests meant to be run
only with mitigations enabled, e.g.
verifier_and.c:known_subreg_with_unknown_reg(), and reporting false
failures.

Internally, verifier sets bpf_verifier_env->bypass_spec_{v1,v4}
basing on the value returned by kernel/cpu.c:cpu_mitigations_off().
This function is backed by a variable kernel/cpu.c:cpu_mitigations.

This state is not fully introspect-able via sysfs. The closest proxy
is /sys/devices/system/cpu/vulnerabilities/spectre_v1, but it reports
"vulnerable" state only if mitigations are disabled *and* current cpu
is vulnerable, while verifier does not check cpu state.

There are only two ways the kernel/cpu.c:cpu_mitigations can be set:
- via boot parameter;
- via CONFIG_CPU_MITIGATIONS option.

This commit updates unpriv_helpers.c:get_mitigations_off() to scan
/boot/config-$(uname -r) and /proc/config.gz for
CONFIG_CPU_MITIGATIONS value in addition to boot command line check.

Tested using the following configurations:
- mitigations enabled (unpriv tests are enabled)
- mitigations disabled via boot cmdline (unpriv tests skipped)
- mitigations disabled via CONFIG_CPU_MITIGATIONS
  (unpriv tests skipped)

[1] https://lore.kernel.org/bpf/20231025031144.5508-1-laoar.shao@gmail.com/

Reported-by: Mykyta Yatsenko <mykyta.yatsenko5@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20250617005710.1066165-2-eddyz87@gmail.com
tools/testing/selftests/bpf/unpriv_helpers.c

index 220f6a9638134544205e2cc889f0354c9f9815c0..3aa9ee80a55e4d9c5bcbe549b47e4ee2e64e6d27 100644 (file)
@@ -1,15 +1,75 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
+#include <errno.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/utsname.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <zlib.h>
 
 #include "unpriv_helpers.h"
 
-static bool get_mitigations_off(void)
+static gzFile open_config(void)
+{
+       struct utsname uts;
+       char buf[PATH_MAX];
+       gzFile config;
+
+       if (uname(&uts)) {
+               perror("uname");
+               goto config_gz;
+       }
+
+       snprintf(buf, sizeof(buf), "/boot/config-%s", uts.release);
+       config = gzopen(buf, "rb");
+       if (config)
+               return config;
+       fprintf(stderr, "gzopen %s: %s\n", buf, strerror(errno));
+
+config_gz:
+       config = gzopen("/proc/config.gz", "rb");
+       if (!config)
+               perror("gzopen /proc/config.gz");
+       return config;
+}
+
+static int config_contains(const char *pat)
+{
+       const char *msg;
+       char buf[1024];
+       gzFile config;
+       int n, err;
+
+       config = open_config();
+       if (!config)
+               return -1;
+
+       for (;;) {
+               if (!gzgets(config, buf, sizeof(buf))) {
+                       msg = gzerror(config, &err);
+                       if (err == Z_ERRNO)
+                               perror("gzgets /proc/config.gz");
+                       else if (err != Z_OK)
+                               fprintf(stderr, "gzgets /proc/config.gz: %s", msg);
+                       gzclose(config);
+                       return -1;
+               }
+               n = strlen(buf);
+               if (buf[n - 1] == '\n')
+                       buf[n - 1] = 0;
+               if (strcmp(buf, pat) == 0) {
+                       gzclose(config);
+                       return 1;
+               }
+       }
+       gzclose(config);
+       return 0;
+}
+
+static bool cmdline_contains(const char *pat)
 {
        char cmdline[4096], *c;
        int fd, ret = false;
@@ -27,7 +87,7 @@ static bool get_mitigations_off(void)
 
        cmdline[sizeof(cmdline) - 1] = '\0';
        for (c = strtok(cmdline, " \n"); c; c = strtok(NULL, " \n")) {
-               if (strncmp(c, "mitigations=off", strlen(c)))
+               if (strncmp(c, pat, strlen(c)))
                        continue;
                ret = true;
                break;
@@ -37,8 +97,21 @@ out:
        return ret;
 }
 
+static int get_mitigations_off(void)
+{
+       int enabled_in_config;
+
+       if (cmdline_contains("mitigations=off"))
+               return 1;
+       enabled_in_config = config_contains("CONFIG_CPU_MITIGATIONS=y");
+       if (enabled_in_config < 0)
+               return -1;
+       return !enabled_in_config;
+}
+
 bool get_unpriv_disabled(void)
 {
+       int mitigations_off;
        bool disabled;
        char buf[2];
        FILE *fd;
@@ -52,5 +125,19 @@ bool get_unpriv_disabled(void)
                disabled = true;
        }
 
-       return disabled ? true : get_mitigations_off();
+       if (disabled)
+               return true;
+
+       /*
+        * Some unpriv tests rely on spectre mitigations being on.
+        * If mitigations are off or status can't be determined
+        * assume that unpriv tests are disabled.
+        */
+       mitigations_off = get_mitigations_off();
+       if (mitigations_off < 0) {
+               fprintf(stderr,
+                       "Can't determine if mitigations are enabled, disabling unpriv tests.");
+               return true;
+       }
+       return mitigations_off;
 }