The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH] sched_ext: Reject task setter kfuncs outside SCX contexts
@ 2026-07-01  9:19 zhidao su (Xiaomi)
  2026-07-01 16:47 ` Andrea Righi
  2026-07-01 18:26 ` Tejun Heo
  0 siblings, 2 replies; 3+ messages in thread
From: zhidao su (Xiaomi) @ 2026-07-01  9:19 UTC (permalink / raw)
  To: Tejun Heo, David Vernet, Andrea Righi, Changwoo Min, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot
  Cc: zhidao su, Dietmar Eggemann, Steven Rostedt, Ben Segall,
	Mel Gorman, Valentin Schneider, K Prateek Nayak, Shuah Khan,
	Joel Fernandes, Christian Loehle, zhidao su, Cheng-Yang Chou,
	Zhao Mengmeng, linux-kernel, sched-ext, linux-kselftest, bpf

scx_bpf_task_set_slice() and scx_bpf_task_set_dsq_vtime() mutate
sched_ext task state and rely on scx_prog_sched() and
scx_task_on_sched() to validate the calling scheduler's authority over
the target task.

However, the kfuncs were part of scx_kfunc_ids_any. That set is also
registered for tracing and syscall programs and the verifier-time
context filter allows any-set kfuncs from non-STRUCT_OPS program types.
As a result, a non-SCX tracing program can load after calling the task
setter wrappers, reaching the mutator kfuncs outside an SCX scheduler
context.

Split the task setter kfuncs into their own filtered kfunc set. Register
that set for STRUCT_OPS, TRACING, and SYSCALL so the verifier can find
the kfuncs, but let scx_kfunc_context_filter() reject them outside SCX
struct_ops contexts. SCX schedulers can still call the setters, and the
existing runtime task authority checks remain in place.

Add a negative sched_ext selftest which tries to call both task setter
kfuncs from a tracing program. The test fails before this change because
the program loads successfully, and passes after this change because the
verifier rejects the task setter call.

Signed-off-by: zhidao su (Xiaomi) <soolaugust@gmail.com>

---
Validation:
- Before fix: task_setter_kfunc_deny failed because non-SCX BPF loaded after calling task setter kfuncs
- After fix: runner -t task_setter_kfunc_deny passed (PASSED=1 FAILED=0)
- checkpatch --strict: 0 errors, 1 warning (MAINTAINERS new-file prompt), 0 checks

 kernel/sched/ext.c                                      | 24 ++++++++++++++++++------
 tools/testing/selftests/sched_ext/Makefile              |  1 +
 tools/testing/selftests/sched_ext/task_setter_kfunc_deny.bpf.c | 17 +++++++++++++++++
 tools/testing/selftests/sched_ext/task_setter_kfunc_deny.c     | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
index b8dd3358959d..257ffaca45f6 100644
--- a/kernel/sched/ext.c
+++ b/kernel/sched/ext.c
@@ -10254,9 +10254,12 @@ __bpf_kfunc struct cgroup *scx_bpf_task_cgroup(struct task_struct *p,
 
 __bpf_kfunc_end_defs();
 
-BTF_KFUNCS_START(scx_kfunc_ids_any)
+BTF_KFUNCS_START(scx_kfunc_ids_task_setter)
 BTF_ID_FLAGS(func, scx_bpf_task_set_slice, KF_IMPLICIT_ARGS | KF_RCU);
 BTF_ID_FLAGS(func, scx_bpf_task_set_dsq_vtime, KF_IMPLICIT_ARGS | KF_RCU);
+BTF_KFUNCS_END(scx_kfunc_ids_task_setter)
+
+BTF_KFUNCS_START(scx_kfunc_ids_any)
 BTF_ID_FLAGS(func, scx_bpf_kick_cpu, KF_IMPLICIT_ARGS)
 BTF_ID_FLAGS(func, scx_bpf_kick_cid, KF_IMPLICIT_ARGS)
 BTF_ID_FLAGS(func, scx_bpf_dsq_nr_queued, KF_IMPLICIT_ARGS)
@@ -10299,6 +10302,12 @@ BTF_ID_FLAGS(func, scx_bpf_task_cgroup, KF_IMPLICIT_ARGS | KF_RCU | KF_ACQUIRE)
 #endif
 BTF_KFUNCS_END(scx_kfunc_ids_any)
 
+static const struct btf_kfunc_id_set scx_kfunc_set_task_setter = {
+	.owner			= THIS_MODULE,
+	.set			= &scx_kfunc_ids_task_setter,
+	.filter			= scx_kfunc_context_filter,
+};
+
 static const struct btf_kfunc_id_set scx_kfunc_set_any = {
 	.owner			= THIS_MODULE,
 	.set			= &scx_kfunc_ids_any,
@@ -10408,13 +10417,14 @@ int scx_kfunc_context_filter(const struct bpf_prog *prog, u32 kfunc_id)
 	bool in_dispatch = btf_id_set8_contains(&scx_kfunc_ids_dispatch, kfunc_id);
 	bool in_cpu_release = btf_id_set8_contains(&scx_kfunc_ids_cpu_release, kfunc_id);
 	bool in_idle = btf_id_set8_contains(&scx_kfunc_ids_idle, kfunc_id);
+	bool in_task_setter = btf_id_set8_contains(&scx_kfunc_ids_task_setter, kfunc_id);
 	bool in_any = btf_id_set8_contains(&scx_kfunc_ids_any, kfunc_id);
 	bool in_cpu_only = btf_id_set8_contains(&scx_kfunc_ids_cpu_only, kfunc_id);
 	u32 moff, flags;
 
 	/* Not an SCX kfunc - allow. */
 	if (!(in_unlocked || in_init || in_select_cpu || in_enqueue || in_dispatch ||
-	      in_cpu_release || in_idle || in_any))
+	      in_cpu_release || in_idle || in_task_setter || in_any))
 		return 0;
 
 	/* SYSCALL progs (e.g. BPF test_run()) may call unlocked and select_cpu kfuncs. */
@@ -10457,7 +10467,7 @@ int scx_kfunc_context_filter(const struct bpf_prog *prog, u32 kfunc_id)
 		return -EACCES;
 
 	/* SCX struct_ops: check the per-op allow list. */
-	if (in_any || in_idle)
+	if (in_any || in_idle || in_task_setter)
 		return 0;
 
 	moff = prog->aux->attach_st_ops_member_off;
@@ -10572,6 +10582,12 @@ static int __init scx_init(void)
 					     &scx_kfunc_set_unlocked)) ||
 	    (ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
 					     &scx_kfunc_set_unlocked)) ||
+	    (ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+					     &scx_kfunc_set_task_setter)) ||
+	    (ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING,
+					     &scx_kfunc_set_task_setter)) ||
+	    (ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+					     &scx_kfunc_set_task_setter)) ||
 	    (ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
 					     &scx_kfunc_set_any)) ||
 	    (ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING,
diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile
index 5d2dffca0e91..000000000000 100644
--- a/tools/testing/selftests/sched_ext/Makefile
+++ b/tools/testing/selftests/sched_ext/Makefile
@@ -176,6 +176,7 @@ auto-test-targets :=			\
 	maybe_null			\
 	minimal				\
 	non_scx_kfunc_deny		\
+	task_setter_kfunc_deny		\
 	numa				\
 	allowed_cpus			\
 	peek_dsq			\
diff --git a/tools/testing/selftests/sched_ext/task_setter_kfunc_deny.bpf.c b/tools/testing/selftests/sched_ext/task_setter_kfunc_deny.bpf.c
new file mode 100644
index 000000000000..79b619a3445f
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/task_setter_kfunc_deny.bpf.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verify that non-SCX programs cannot call task-mutating SCX kfuncs.
+ */
+
+#include <scx/common.bpf.h>
+
+SEC("tp_btf/sched_switch")
+int BPF_PROG(non_scx_task_setter, bool preempt, struct task_struct *prev,
+	     struct task_struct *next, unsigned int prev_state)
+{
+	scx_bpf_task_set_slice(next, 1000000);
+	scx_bpf_task_set_dsq_vtime(next, 1);
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/sched_ext/task_setter_kfunc_deny.c b/tools/testing/selftests/sched_ext/task_setter_kfunc_deny.c
new file mode 100644
index 000000000000..1450a7133a5c
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/task_setter_kfunc_deny.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verify that task-mutating SCX kfuncs are rejected outside SCX contexts.
+ */
+
+#include <bpf/libbpf.h>
+#include <stdarg.h>
+#include <scx/common.h>
+#include <stdio.h>
+#include <string.h>
+#include "scx_test.h"
+#include "task_setter_kfunc_deny.bpf.skel.h"
+
+static char verifier_log[8192];
+static size_t verifier_log_pos;
+
+static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
+{
+	if (level == LIBBPF_DEBUG && verifier_log_pos < sizeof(verifier_log)) {
+		int ret = vsnprintf(verifier_log + verifier_log_pos,
+				  sizeof(verifier_log) - verifier_log_pos, format, args);
+
+		if (ret > 0)
+			verifier_log_pos += ret;
+	}
+
+	return 0;
+}
+
+static bool expected_rejection_seen(void)
+{
+	return strstr(verifier_log, "scx_bpf_task_set_slice") ||
+	       strstr(verifier_log, "scx_bpf_task_set_dsq_vtime");
+}
+
+static enum scx_test_status run(void *ctx)
+{
+	struct task_setter_kfunc_deny *skel;
+	libbpf_print_fn_t old_libbpf_print_fn;
+	int err;
+
+	old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn);
+	verifier_log[0] = '\0';
+	verifier_log_pos = 0;
+
+	skel = task_setter_kfunc_deny__open();
+	if (!skel) {
+		SCX_ERR("Failed to open skel");
+		libbpf_set_print(old_libbpf_print_fn);
+		return SCX_TEST_FAIL;
+	}
+
+	err = task_setter_kfunc_deny__load(skel);
+	task_setter_kfunc_deny__destroy(skel);
+	libbpf_set_print(old_libbpf_print_fn);
+
+	if (!err) {
+		SCX_ERR("non-SCX BPF program loaded after calling task setter kfuncs");
+		return SCX_TEST_FAIL;
+	}
+
+	if (!expected_rejection_seen()) {
+		SCX_ERR("load failed without expected sched_ext task setter rejection: %s",
+			verifier_log);
+		return SCX_TEST_FAIL;
+	}
+
+	return SCX_TEST_PASS;
+}
+
+struct scx_test task_setter_kfunc_deny = {
+	.name = "task_setter_kfunc_deny",
+	.description = "Verify non-SCX programs cannot call SCX task setter kfuncs",
+	.run = run,
+};
+
+REGISTER_SCX_TEST(&task_setter_kfunc_deny)

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-01 18:26 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-01  9:19 [PATCH] sched_ext: Reject task setter kfuncs outside SCX contexts zhidao su (Xiaomi)
2026-07-01 16:47 ` Andrea Righi
2026-07-01 18:26 ` Tejun Heo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox