[RFC PATCH 11/13] selftests/powerpc: Add DEXCR prctl, sysctl interface test
From: Benjamin Gray
Date: Sun Nov 27 2022 - 21:46:33 EST
Test the prctl and sysctl interfaces of the DEXCR.
This adds a new capabilities util for getting and setting CAP_SYS_ADMIN.
Adding this avoids depending on an external libcap package. There is a
similar implementation (and reason) in the tools/testing/selftests/bpf
subtree but there's no obvious place to move it for sharing.
Signed-off-by: Benjamin Gray <bgray@xxxxxxxxxxxxx>
---
.../selftests/powerpc/dexcr/.gitignore | 1 +
.../testing/selftests/powerpc/dexcr/Makefile | 4 +-
tools/testing/selftests/powerpc/dexcr/cap.c | 72 ++++++
tools/testing/selftests/powerpc/dexcr/cap.h | 18 ++
tools/testing/selftests/powerpc/dexcr/dexcr.h | 2 +
.../selftests/powerpc/dexcr/dexcr_test.c | 241 ++++++++++++++++++
6 files changed, 336 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/powerpc/dexcr/cap.c
create mode 100644 tools/testing/selftests/powerpc/dexcr/cap.h
create mode 100644 tools/testing/selftests/powerpc/dexcr/dexcr_test.c
diff --git a/tools/testing/selftests/powerpc/dexcr/.gitignore b/tools/testing/selftests/powerpc/dexcr/.gitignore
index 37adb7f47832..035a1fcd8fb3 100644
--- a/tools/testing/selftests/powerpc/dexcr/.gitignore
+++ b/tools/testing/selftests/powerpc/dexcr/.gitignore
@@ -1 +1,2 @@
+dexcr_test
hashchk_user
diff --git a/tools/testing/selftests/powerpc/dexcr/Makefile b/tools/testing/selftests/powerpc/dexcr/Makefile
index 4b4380d4d986..9814e72a4afa 100644
--- a/tools/testing/selftests/powerpc/dexcr/Makefile
+++ b/tools/testing/selftests/powerpc/dexcr/Makefile
@@ -1,4 +1,4 @@
-TEST_GEN_PROGS := hashchk_test
+TEST_GEN_PROGS := dexcr_test hashchk_test
TEST_FILES := settings
top_srcdir = ../../../../..
@@ -6,4 +6,4 @@ include ../../lib.mk
HASHCHK_TEST_CFLAGS = -no-pie $(call cc-option,-mno-rop-protect)
-$(TEST_GEN_PROGS): ../harness.c ../utils.c ./dexcr.c
+$(TEST_GEN_PROGS): ../harness.c ../utils.c ./dexcr.c ./cap.c
diff --git a/tools/testing/selftests/powerpc/dexcr/cap.c b/tools/testing/selftests/powerpc/dexcr/cap.c
new file mode 100644
index 000000000000..3c9b1f27345d
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/cap.c
@@ -0,0 +1,72 @@
+#include <linux/capability.h>
+#include <string.h>
+#include <sys/syscall.h>
+
+#include "cap.h"
+#include "utils.h"
+
+struct kernel_capabilities {
+ struct __user_cap_header_struct header;
+
+ struct __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3];
+};
+
+static void get_caps(struct kernel_capabilities *caps)
+{
+ FAIL_IF_EXIT_MSG(syscall(SYS_capget, &caps->header, &caps->data),
+ "cannot get capabilities");
+}
+
+static void set_caps(struct kernel_capabilities *caps)
+{
+ FAIL_IF_EXIT_MSG(syscall(SYS_capset, &caps->header, &caps->data),
+ "cannot set capabilities");
+}
+
+static void init_caps(struct kernel_capabilities *caps, pid_t pid)
+{
+ memset(caps, 0, sizeof(*caps));
+
+ caps->header.version = _LINUX_CAPABILITY_VERSION_3;
+ caps->header.pid = pid;
+
+ get_caps(caps);
+}
+
+static bool has_cap(struct kernel_capabilities *caps, size_t cap)
+{
+ size_t data_index = cap / 32;
+ size_t offset = cap % 32;
+
+ FAIL_IF_EXIT_MSG(data_index >= ARRAY_SIZE(caps->data), "cap out of range");
+
+ return caps->data[data_index].effective & (1 << offset);
+}
+
+static void drop_cap(struct kernel_capabilities *caps, size_t cap)
+{
+ size_t data_index = cap / 32;
+ size_t offset = cap % 32;
+
+ FAIL_IF_EXIT_MSG(data_index >= ARRAY_SIZE(caps->data), "cap out of range");
+
+ caps->data[data_index].effective &= ~(1 << offset);
+}
+
+bool check_cap_sysadmin(void)
+{
+ struct kernel_capabilities caps;
+
+ init_caps(&caps, 0);
+
+ return has_cap(&caps, CAP_SYS_ADMIN);
+}
+
+void drop_cap_sysadmin(void)
+{
+ struct kernel_capabilities caps;
+
+ init_caps(&caps, 0);
+ drop_cap(&caps, CAP_SYS_ADMIN);
+ set_caps(&caps);
+}
diff --git a/tools/testing/selftests/powerpc/dexcr/cap.h b/tools/testing/selftests/powerpc/dexcr/cap.h
new file mode 100644
index 000000000000..41f41dda9862
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/cap.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Simple capabilities getter/setter
+ *
+ * This header file contains helper functions and macros
+ * required to get and set capabilities(7). Introduced so
+ * we aren't the first to rely on libcap.
+ */
+#ifndef _SELFTESTS_POWERPC_DEXCR_CAP_H
+#define _SELFTESTS_POWERPC_DEXCR_CAP_H
+
+#include <stdbool.h>
+
+bool check_cap_sysadmin(void);
+
+void drop_cap_sysadmin(void);
+
+#endif /* _SELFTESTS_POWERPC_DEXCR_CAP_H */
diff --git a/tools/testing/selftests/powerpc/dexcr/dexcr.h b/tools/testing/selftests/powerpc/dexcr/dexcr.h
index fb8007bf19f8..b90633ae49e9 100644
--- a/tools/testing/selftests/powerpc/dexcr/dexcr.h
+++ b/tools/testing/selftests/powerpc/dexcr/dexcr.h
@@ -21,6 +21,8 @@
#define DEXCR_PRO_SRAPD DEXCR_PRO_MASK(4)
#define DEXCR_PRO_NPHIE DEXCR_PRO_MASK(5)
+#define SYSCTL_DEXCR_SBHE "/proc/sys/kernel/speculative_branch_hint_enable"
+
enum DexcrSource {
UDEXCR, /* Userspace DEXCR value */
ENFORCED, /* Enforced by hypervisor */
diff --git a/tools/testing/selftests/powerpc/dexcr/dexcr_test.c b/tools/testing/selftests/powerpc/dexcr/dexcr_test.c
new file mode 100644
index 000000000000..5446cb350c84
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/dexcr_test.c
@@ -0,0 +1,241 @@
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "cap.h"
+#include "dexcr.h"
+#include "utils.h"
+
+/*
+ * Test that an editable aspect
+ * - Current prctl state reported by the getter
+ * - Can be toggled on and off when process has CAP_SYS_ADMIN
+ * - Can't be edited if CAP_SYS_ADMIN not present
+ * - Can't be modified after force set
+ */
+static int dexcr_prctl_editable_aspect_test(unsigned long which)
+{
+ pid_t pid;
+
+ SKIP_IF_MSG(!check_cap_sysadmin(), "must have capability CAP_SYS_ADMIN");
+ SKIP_IF_MSG(!pr_aspect_supported(which), "aspect not supported");
+
+ FAIL_IF_MSG(!(pr_aspect_get(which) & PR_PPC_DEXCR_PRCTL), "aspect not editable");
+
+ FAIL_IF_MSG(!pr_aspect_edit(which, PR_PPC_DEXCR_CLEAR_ASPECT), "prctl failed");
+ FAIL_IF_MSG(pr_aspect_check(which, UDEXCR),
+ "resetting aspect did not take effect");
+
+ FAIL_IF_MSG(pr_aspect_get(which) != (PR_PPC_DEXCR_CLEAR_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "prctl getter not reporting aspect state");
+
+ FAIL_IF_MSG(!pr_aspect_edit(which, PR_PPC_DEXCR_SET_ASPECT), "prctl failed");
+ FAIL_IF_MSG(!pr_aspect_check(which, UDEXCR),
+ "setting aspect did not take effect");
+
+ FAIL_IF_MSG(pr_aspect_get(which) != (PR_PPC_DEXCR_SET_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "prctl getter not reporting aspect state");
+
+ FAIL_IF_MSG(!pr_aspect_edit(which, PR_PPC_DEXCR_CLEAR_ASPECT), "prctl failed");
+ FAIL_IF_MSG(pr_aspect_check(which, UDEXCR),
+ "clearing aspect did not take effect");
+
+ FAIL_IF_MSG(pr_aspect_get(which) != (PR_PPC_DEXCR_CLEAR_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "prctl getter not reporting aspect state");
+
+ pid = fork();
+ if (pid == 0) {
+ drop_cap_sysadmin();
+ FAIL_IF_EXIT_MSG(pr_aspect_edit(which, PR_PPC_DEXCR_SET_ASPECT),
+ "prctl success when nonprivileged");
+ FAIL_IF_EXIT_MSG(pr_aspect_check(which, UDEXCR),
+ "edited aspect when nonprivileged");
+ _exit(0);
+ }
+ await_child_success(pid);
+
+ FAIL_IF_MSG(!pr_aspect_edit(which, PR_PPC_DEXCR_FORCE_SET_ASPECT), "prctl force set failed");
+ FAIL_IF_MSG(!pr_aspect_check(which, UDEXCR),
+ "force setting aspect did not take effect");
+
+ FAIL_IF_MSG(pr_aspect_get(which) != (PR_PPC_DEXCR_FORCE_SET_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "prctl getter not reporting aspect state");
+
+ FAIL_IF_MSG(pr_aspect_edit(which, PR_PPC_DEXCR_CLEAR_ASPECT), "prctl success when forced");
+ FAIL_IF_MSG(!pr_aspect_check(which, UDEXCR),
+ "edited aspect when forced");
+
+ return 0;
+}
+
+static int dexcr_prctl_sbhe_test(void)
+{
+ sysctl_set_sbhe(-1);
+ return dexcr_prctl_editable_aspect_test(PR_PPC_DEXCR_SBHE);
+}
+
+static int dexcr_prctl_ibrtpd_test(void)
+{
+ return dexcr_prctl_editable_aspect_test(PR_PPC_DEXCR_IBRTPD);
+}
+
+static int dexcr_prctl_srapd_test(void)
+{
+ return dexcr_prctl_editable_aspect_test(PR_PPC_DEXCR_SRAPD);
+}
+
+static int dexcr_sysctl_sbhe_test(void)
+{
+ SKIP_IF_MSG(!check_cap_sysadmin(), "must have capability CAP_SYS_ADMIN");
+ SKIP_IF_MSG(!pr_aspect_supported(PR_PPC_DEXCR_SBHE), "aspect not supported");
+
+ sysctl_set_sbhe(0);
+ FAIL_IF_MSG(sysctl_get_sbhe() != 0, "failed to clear sysctl SBHE");
+ FAIL_IF_MSG(pr_aspect_check(PR_PPC_DEXCR_SBHE, UDEXCR),
+ "SBHE failed to clear");
+
+ sysctl_set_sbhe(1);
+ FAIL_IF_MSG(sysctl_get_sbhe() != 1, "failed to set sysctl SBHE");
+ FAIL_IF_MSG(!pr_aspect_check(PR_PPC_DEXCR_SBHE, UDEXCR),
+ "SBHE failed to set");
+
+ sysctl_set_sbhe(-1);
+ FAIL_IF_MSG(sysctl_get_sbhe() != -1, "failed to default sysctl SBHE");
+ FAIL_IF_MSG(!pr_aspect_edit(PR_PPC_DEXCR_SBHE, PR_PPC_DEXCR_CLEAR_ASPECT), "prctl failed");
+ FAIL_IF_MSG(pr_aspect_check(PR_PPC_DEXCR_SBHE, UDEXCR),
+ "SBHE failed to default to prctl clear setting");
+
+ FAIL_IF_MSG(!pr_aspect_edit(PR_PPC_DEXCR_SBHE, PR_PPC_DEXCR_SET_ASPECT), "prctl failed");
+ FAIL_IF_MSG(!pr_aspect_check(PR_PPC_DEXCR_SBHE, UDEXCR),
+ "SBHE failed to default to prctl set setting");
+
+ sysctl_set_sbhe(0);
+ FAIL_IF_MSG(sysctl_get_sbhe() != 0, "failed to clear sysctl SBHE");
+ FAIL_IF_MSG(pr_aspect_check(PR_PPC_DEXCR_SBHE, UDEXCR),
+ "SBHE failed to override prctl setting");
+
+ return 0;
+}
+
+static int dexcr_test_inherit_execve(char expected_dexcr)
+{
+ switch (expected_dexcr) {
+ case '0':
+ FAIL_IF_EXIT_MSG(pr_aspect_get(PR_PPC_DEXCR_IBRTPD) !=
+ (PR_PPC_DEXCR_CLEAR_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "clearing IBRTPD across exec not inherited");
+
+ FAIL_IF_EXIT_MSG(pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "clearing IBRTPD across exec not applied");
+ break;
+ case '1':
+ FAIL_IF_EXIT_MSG(pr_aspect_get(PR_PPC_DEXCR_IBRTPD) !=
+ (PR_PPC_DEXCR_SET_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "setting IBRTPD across exec not inherited");
+
+ FAIL_IF_EXIT_MSG(!pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "setting IBRTPD across exec not applied");
+ break;
+ case '2':
+ FAIL_IF_EXIT_MSG(pr_aspect_get(PR_PPC_DEXCR_IBRTPD) !=
+ (PR_PPC_DEXCR_FORCE_SET_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "force setting IBRTPD across exec not inherited");
+
+ FAIL_IF_EXIT_MSG(!pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "force setting IBRTPD across exec not applied");
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Check that a child process inherits the DEXCR over fork and execve
+ */
+static int dexcr_inherit_test(void)
+{
+ pid_t pid;
+
+ SKIP_IF_MSG(!check_cap_sysadmin(), "must have capability CAP_SYS_ADMIN");
+ SKIP_IF_MSG(!pr_aspect_supported(PR_PPC_DEXCR_IBRTPD), "IBRTPD not supported");
+
+ pr_aspect_edit(PR_PPC_DEXCR_IBRTPD, PR_PPC_DEXCR_CLEAR_ASPECT);
+ FAIL_IF_MSG(pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "IBRTPD failed to clear");
+
+ pid = fork();
+ if (pid == 0) {
+ char *args[] = { "dexcr_test_inherit_execve", "0", NULL };
+
+ FAIL_IF_EXIT_MSG(pr_aspect_get(PR_PPC_DEXCR_IBRTPD) !=
+ (PR_PPC_DEXCR_CLEAR_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "clearing IBRTPD not inherited");
+
+ FAIL_IF_EXIT_MSG(pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "clearing IBRTPD not applied");
+
+ execve("/proc/self/exe", args, NULL);
+ _exit(errno);
+ }
+ await_child_success(pid);
+
+ pr_aspect_edit(PR_PPC_DEXCR_IBRTPD, PR_PPC_DEXCR_SET_ASPECT);
+ FAIL_IF_MSG(!pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "IBRTPD failed to set");
+
+ pid = fork();
+ if (pid == 0) {
+ char *args[] = { "dexcr_test_inherit_execve", "1", NULL };
+
+ FAIL_IF_EXIT_MSG(pr_aspect_get(PR_PPC_DEXCR_IBRTPD) !=
+ (PR_PPC_DEXCR_SET_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "setting IBRTPD not inherited");
+
+ FAIL_IF_EXIT_MSG(!pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "setting IBRTPD not applied");
+
+ execve("/proc/self/exe", args, NULL);
+ _exit(errno);
+ }
+ await_child_success(pid);
+
+ pr_aspect_edit(PR_PPC_DEXCR_IBRTPD, PR_PPC_DEXCR_FORCE_SET_ASPECT);
+ FAIL_IF_MSG(!pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "IBRTPD failed to force set");
+
+ pid = fork();
+ if (pid == 0) {
+ char *args[] = { "dexcr_test_inherit_execve", "2", NULL };
+
+ FAIL_IF_EXIT_MSG(pr_aspect_get(PR_PPC_DEXCR_IBRTPD) !=
+ (PR_PPC_DEXCR_FORCE_SET_ASPECT | PR_PPC_DEXCR_PRCTL),
+ "force setting IBRTPD not inherited");
+
+ FAIL_IF_EXIT_MSG(!pr_aspect_check(PR_PPC_DEXCR_IBRTPD, UDEXCR),
+ "force setting IBRTPD not applied");
+
+ execve("/proc/self/exe", args, NULL);
+ _exit(errno);
+ }
+ await_child_success(pid);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int err = 0;
+
+ if (argc >= 2 && strcmp(argv[0], "dexcr_test_inherit_execve") == 0)
+ return dexcr_test_inherit_execve(argv[1][0]);
+
+ err |= test_harness(dexcr_prctl_sbhe_test, "dexcr_prctl_sbhe");
+ err |= test_harness(dexcr_prctl_ibrtpd_test, "dexcr_prctl_ibrtpd");
+ err |= test_harness(dexcr_prctl_srapd_test, "dexcr_prctl_srapd");
+ err |= test_harness(dexcr_sysctl_sbhe_test, "dexcr_sysctl_sbhe");
+ err |= test_harness(dexcr_inherit_test, "dexcr_inherit");
+
+ return err;
+}
--
2.38.1