| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/30 08:54 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF"
],
"FocusSymbols": [
"bpf_task_work_schedule_signal",
"bpf_task_work_schedule_resume",
"bpf_task_work_cancel_and_free",
"bpf_task_work_irq",
"bpf_task_work_acquire_ctx",
"bpf_task_work_cancel",
"bpf_task_work_cancel_scheduled"
],
"KMSANReasoning": "The patch addresses concurrency race conditions in BPF task_work scheduling and cancellation (specifically, capturing the task pointer in bpf_task_work_irq before task_work publication to avoid racing with ctx-\u003etask clearing in bpf_task_work_callback, moving init_irq_work to ctx allocation time, and checking irq_work_is_busy() during acquisition). \n\nAll structures (such as bpf_task_work_ctx) continue to be fully zero-initialized with memset() upon allocation. The changes do not introduce or modify any reads of uninitialized stack or heap memory, nor do they copy any kernel structures or padding to user space. Potential issues arising from these concurrency and lifetime changes (such as use-after-free or NULL pointer dereferences) are covered by KASAN and standard kernel debug checks rather than KMSAN. Therefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies BPF task_work scheduling, cancellation, and synchronization logic in kernel/bpf/helpers.c to prevent race conditions during concurrent execution and map element deletion. These modifications affect reachable core kernel code exercised via the bpf() system call and BPF program execution.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/30 08:54 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit fa623d5d642f4cb7295b7b16abe98c52a899dfe5\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Sep 30 08:54:20 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c\nindex b3cc5c8fc8756..350269b8646e4 100644\n--- a/kernel/bpf/helpers.c\n+++ b/kernel/bpf/helpers.c\n@@ -4437,15 +4437,21 @@ static void bpf_task_work_ctx_put(struct bpf_task_work_ctx *ctx)\n \t}\n }\n \n-static void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx)\n+static void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx,\n+\t\t\t\t struct task_struct *task)\n {\n \t/*\n \t * Scheduled task_work callback holds ctx ref, so if we successfully\n \t * cancelled, we put that ref on callback's behalf. If we couldn't\n \t * cancel, callback will inevitably run or has already completed\n \t * running, and it would have taken care of its ctx ref itself.\n+\t *\n+\t * The caller passes the task explicitly: the scheduling handler must\n+\t * not read ctx-\u003etask after the work is published (the callback may\n+\t * have reset it), while in the asynchronous cancellation path\n+\t * ctx-\u003etask is stable, see bpf_task_work_cancel_scheduled().\n \t */\n-\tif (task_work_cancel(ctx-\u003etask, \u0026ctx-\u003ework))\n+\tif (task_work_cancel(task, \u0026ctx-\u003ework))\n \t\tbpf_task_work_ctx_put(ctx);\n }\n \n@@ -4486,6 +4492,7 @@ static void bpf_task_work_callback(struct callback_head *cb)\n static void bpf_task_work_irq(struct irq_work *irq_work)\n {\n \tstruct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);\n+\tstruct task_struct *task;\n \tenum bpf_task_work_state state;\n \tint err;\n \n@@ -4496,7 +4503,16 @@ static void bpf_task_work_irq(struct irq_work *irq_work)\n \t\treturn;\n \t}\n \n-\terr = task_work_add(ctx-\u003etask, \u0026ctx-\u003ework, ctx-\u003emode);\n+\t/*\n+\t * Capture the task pointer before the work is published: once the\n+\t * callback runs, it may reset ctx-\u003etask. The capture happens while\n+\t * this round's task reference is held, and every path that can\n+\t * release it runs after this handler entered its rcu read-side\n+\t * section. bpf_task_release() is put_task_struct_rcu_user(), so the\n+\t * task_struct itself cannot be freed before this section ends.\n+\t */\n+\ttask = ctx-\u003etask;\n+\terr = task_work_add(task, \u0026ctx-\u003ework, ctx-\u003emode);\n \tif (err) {\n \t\tbpf_task_work_ctx_reset(ctx);\n \t\t/*\n@@ -4510,14 +4526,14 @@ static void bpf_task_work_irq(struct irq_work *irq_work)\n \n \t/*\n \t * It's technically possible for just scheduled task_work callback to\n-\t * complete running by now, going SCHEDULING -\u003e RUNNING and then\n-\t * dropping its ctx refcount. Instead of capturing an extra ref just\n-\t * to protect below ctx-\u003estate access, we rely on rcu_read_lock\n-\t * above to prevent kfree_rcu from freeing ctx before we return.\n+\t * complete running by now, going SCHEDULING -\u003e RUNNING, resetting\n+\t * ctx-\u003etask and dropping its ctx refcount. The rcu read-side section\n+\t * above keeps both the ctx memory and the captured task pointer\n+\t * valid until this handler returns.\n \t */\n \tstate = cmpxchg(\u0026ctx-\u003estate, BPF_TW_SCHEDULING, BPF_TW_SCHEDULED);\n \tif (state == BPF_TW_FREED)\n-\t\tbpf_task_work_cancel(ctx); /* clean up if we switched into FREED state */\n+\t\tbpf_task_work_cancel(ctx, task); /* clean up if we switched into FREED state */\n }\n \n static struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *tw,\n@@ -4537,6 +4553,7 @@ static struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *t\n \tmemset(ctx, 0, sizeof(*ctx));\n \trefcount_set(\u0026ctx-\u003erefcnt, 1); /* map's own ref */\n \tctx-\u003estate = BPF_TW_STANDBY;\n+\tinit_irq_work(\u0026ctx-\u003eirq_work, bpf_task_work_irq);\n \n \told_ctx = cmpxchg(\u0026twk-\u003ectx, NULL, ctx);\n \tif (old_ctx) {\n@@ -4579,6 +4596,18 @@ static struct bpf_task_work_ctx *bpf_task_work_acquire_ctx(struct bpf_task_work\n \t\tbpf_task_work_ctx_put(ctx);\n \t\treturn ERR_PTR(-EBUSY);\n \t}\n+\t/*\n+\t * STANDBY can be published before the previous scheduling irq_work\n+\t * returns. Claim PENDING before checking BUSY to order the check after\n+\t * that publication. No irq_work has been queued for this attempt yet,\n+\t * so reject reuse and roll back to STANDBY while BUSY is set. Preserve\n+\t * FREED if deletion wins the rollback race.\n+\t */\n+\tif (unlikely(irq_work_is_busy(\u0026ctx-\u003eirq_work))) {\n+\t\t(void)cmpxchg(\u0026ctx-\u003estate, BPF_TW_PENDING, BPF_TW_STANDBY);\n+\t\tbpf_task_work_ctx_put(ctx);\n+\t\treturn ERR_PTR(-EBUSY);\n+\t}\n \n \t/*\n \t * If no process or bpffs is holding a reference to the map, no new callbacks should be\n@@ -4628,7 +4657,6 @@ static int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work\n \tctx-\u003emap = map;\n \tctx-\u003emap_val = (void *)tw - map-\u003erecord-\u003etask_work_off;\n \tinit_task_work(\u0026ctx-\u003ework, bpf_task_work_callback);\n-\tinit_irq_work(\u0026ctx-\u003eirq_work, bpf_task_work_irq);\n \n \tirq_work_queue(\u0026ctx-\u003eirq_work);\n \treturn 0;\n@@ -4777,7 +4805,13 @@ static void bpf_task_work_cancel_scheduled(struct irq_work *irq_work)\n {\n \tstruct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);\n \n-\tbpf_task_work_cancel(ctx); /* this might put task_work callback's ref */\n+\t/*\n+\t * Deletion observed SCHEDULED and won against the callback's\n+\t * transition to RUNNING. The callback takes its FREED exit without\n+\t * resetting ctx-\u003etask, and the transferred map reference prevents\n+\t * destruction until cancellation completes.\n+\t */\n+\tbpf_task_work_cancel(ctx, ctx-\u003etask); /* this might put task_work callback's ref */\n \tbpf_task_work_ctx_put(ctx); /* and here we put map's own ref that was transferred to us */\n }\n \ndiff --git a/tools/testing/selftests/bpf/prog_tests/test_task_work.c b/tools/testing/selftests/bpf/prog_tests/test_task_work.c\nindex 774b31a5f6ca1..ff56976f4226e 100644\n--- a/tools/testing/selftests/bpf/prog_tests/test_task_work.c\n+++ b/tools/testing/selftests/bpf/prog_tests/test_task_work.c\n@@ -5,10 +5,14 @@\n #include \u003cstdio.h\u003e\n #include \"task_work.skel.h\"\n #include \"task_work_fail.skel.h\"\n+#include \"task_work_race.skel.h\"\n #include \u003clinux/bpf.h\u003e\n #include \u003clinux/perf_event.h\u003e\n #include \u003csys/syscall.h\u003e\n #include \u003ctime.h\u003e\n+#include \u003cpthread.h\u003e\n+#include \u003csched.h\u003e\n+#include \u003cunistd.h\u003e\n \n static int perf_event_open(__u32 type, __u64 config, int pid)\n {\n@@ -155,3 +159,282 @@ void test_task_work(void)\n \n \tRUN_TESTS(task_work_fail);\n }\n+\n+#define TASK_WORK_RACE_ROUNDS\t1500\n+\n+/* Must match progs/task_work_race.c. */\n+enum task_work_race_status {\n+\tRACE_ARM_SEQ,\n+\tRACE_READY_SEQ,\n+\t/* High 32 bits hold the sequence, low 32 bits the signed error. */\n+\tRACE_RESULT,\n+};\n+\n+struct task_work_race_value {\n+\t__u32 seq;\n+\tchar data[60];\n+\tstruct bpf_task_work tw;\n+};\n+\n+struct task_work_race_ctx {\n+\tint stop;\n+\tint setup_err;\n+\tint trigger_tid;\n+\tint target_tid;\n+\tint trigger_cpu;\n+\tint target_cpu;\n+};\n+\n+static int task_work_race_pin_cpu(int cpu)\n+{\n+\tcpu_set_t set;\n+\n+\tCPU_ZERO(\u0026set);\n+\tCPU_SET(cpu, \u0026set);\n+\treturn pthread_setaffinity_np(pthread_self(), sizeof(set), \u0026set);\n+}\n+\n+static void *task_work_race_trigger(void *arg)\n+{\n+\tstruct task_work_race_ctx *ctx = arg;\n+\tint err;\n+\n+\terr = task_work_race_pin_cpu(ctx-\u003etrigger_cpu);\n+\tif (err)\n+\t\t__atomic_store_n(\u0026ctx-\u003esetup_err, err, __ATOMIC_RELEASE);\n+\t__atomic_store_n(\u0026ctx-\u003etrigger_tid, syscall(__NR_gettid),\n+\t\t\t __ATOMIC_RELEASE);\n+\twhile (!__atomic_load_n(\u0026ctx-\u003estop, __ATOMIC_ACQUIRE))\n+\t\tgetppid();\n+\treturn NULL;\n+}\n+\n+static void *task_work_race_target(void *arg)\n+{\n+\tstruct task_work_race_ctx *ctx = arg;\n+\tint err;\n+\n+\terr = task_work_race_pin_cpu(ctx-\u003etarget_cpu);\n+\tif (err)\n+\t\t__atomic_store_n(\u0026ctx-\u003esetup_err, err, __ATOMIC_RELEASE);\n+\t__atomic_store_n(\u0026ctx-\u003etarget_tid, syscall(__NR_gettid),\n+\t\t\t __ATOMIC_RELEASE);\n+\twhile (!__atomic_load_n(\u0026ctx-\u003estop, __ATOMIC_ACQUIRE))\n+\t\tgetppid();\n+\treturn NULL;\n+}\n+\n+static int task_work_race_status(struct task_work_race *skel, int idx,\n+\t\t\t\t __s64 *value)\n+{\n+\treturn bpf_map_lookup_elem(bpf_map__fd(skel-\u003emaps.status), \u0026idx,\n+\t\t\t\t value);\n+}\n+\n+static int task_work_race_set_status(struct task_work_race *skel, int idx,\n+\t\t\t\t __s64 value)\n+{\n+\treturn bpf_map_update_elem(bpf_map__fd(skel-\u003emaps.status), \u0026idx,\n+\t\t\t\t \u0026value, BPF_ANY);\n+}\n+\n+static int task_work_race_wait_ready(struct task_work_race *skel, __u32 seq)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c 2000000 / 100; i++) {\n+\t\t__s64 value = 0;\n+\n+\t\tif (!task_work_race_status(skel, RACE_READY_SEQ, \u0026value) \u0026\u0026\n+\t\t value == seq)\n+\t\t\treturn 0;\n+\t\tif (!task_work_race_status(skel, RACE_RESULT, \u0026value) \u0026\u0026\n+\t\t (__u32)((__u64)value \u003e\u003e 32) == seq)\n+\t\t\t/*\n+\t\t\t * The BPF program finished between the two reads:\n+\t\t\t * RESULT makes waiting for READY unnecessary. Any error\n+\t\t\t * that occurred before READY is checked by the caller.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\tusleep(100);\n+\t}\n+\treturn -ETIMEDOUT;\n+}\n+\n+static int task_work_race_prepare_elem(struct task_work_race *skel)\n+{\n+\tstruct task_work_race_value value = {};\n+\tint key = 0;\n+\n+\tif (!bpf_map_lookup_elem(bpf_map__fd(skel-\u003emaps.hmap), \u0026key, \u0026value))\n+\t\treturn 0;\n+\tif (errno != ENOENT)\n+\t\treturn -errno;\n+\tif (bpf_map_update_elem(bpf_map__fd(skel-\u003emaps.hmap), \u0026key, \u0026value,\n+\t\t\t\tBPF_NOEXIST))\n+\t\treturn -errno;\n+\treturn 0;\n+}\n+\n+static int task_work_race_arm(struct task_work_race *skel, __u32 seq)\n+{\n+\tif (task_work_race_set_status(skel, RACE_ARM_SEQ, seq))\n+\t\treturn -errno;\n+\treturn task_work_race_wait_ready(skel, seq);\n+}\n+\n+static int task_work_race_check_done(struct task_work_race *skel, __u32 seq)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c 2000000 / 100; i++) {\n+\t\t__s64 result = 0;\n+\n+\t\tif (!task_work_race_status(skel, RACE_RESULT, \u0026result) \u0026\u0026\n+\t\t (__u32)((__u64)result \u003e\u003e 32) == seq)\n+\t\t\treturn (__s32)result;\n+\t\tusleep(100);\n+\t}\n+\treturn -ETIMEDOUT;\n+}\n+\n+static int task_work_race_wait_callback(struct task_work_race *skel,\n+\t\t\t\t\t__u32 seq)\n+{\n+\tint i, key = seq;\n+\n+\tfor (i = 0; i \u003c 2000000 / 100; i++) {\n+\t\t__u64 completed = 0;\n+\n+\t\tif (!bpf_map_lookup_elem(bpf_map__fd(skel-\u003emaps.completed),\n+\t\t\t\t\t \u0026key, \u0026completed) \u0026\u0026 completed)\n+\t\t\treturn 0;\n+\t\tusleep(100);\n+\t}\n+\treturn -ETIMEDOUT;\n+}\n+\n+void serial_test_task_work_race(void)\n+{\n+\tstruct task_work_race_ctx ctx = {};\n+\tstruct task_work_race *skel;\n+\tpthread_t trigger, target;\n+\tcpu_set_t allowed;\n+\tint cpu, cpu_count = 0;\n+\tbool target_started = false, trigger_started = false;\n+\tint err, i, key = 0;\n+\n+\tif (sched_getaffinity(0, sizeof(allowed), \u0026allowed)) {\n+\t\tASSERT_OK(-errno, \"sched_getaffinity\");\n+\t\treturn;\n+\t}\n+\tfor (cpu = 0; cpu \u003c CPU_SETSIZE \u0026\u0026 cpu_count \u003c 2; cpu++) {\n+\t\tif (!CPU_ISSET(cpu, \u0026allowed))\n+\t\t\tcontinue;\n+\t\tif (!cpu_count)\n+\t\t\tctx.trigger_cpu = cpu;\n+\t\telse\n+\t\t\tctx.target_cpu = cpu;\n+\t\tcpu_count++;\n+\t}\n+\tif (cpu_count \u003c 2) {\n+\t\tprintf(\"%s:SKIP:need two CPUs in the process affinity mask\\n\",\n+\t\t __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tskel = task_work_race__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"open_and_load\"))\n+\t\treturn;\n+\tif (!ASSERT_OK(task_work_race__attach(skel), \"attach\"))\n+\t\tgoto cleanup;\n+\n+\terr = pthread_create(\u0026target, NULL, task_work_race_target, \u0026ctx);\n+\tif (!ASSERT_OK(err, \"pthread_create target\"))\n+\t\tgoto cleanup;\n+\ttarget_started = true;\n+\terr = pthread_create(\u0026trigger, NULL, task_work_race_trigger, \u0026ctx);\n+\tif (!ASSERT_OK(err, \"pthread_create trigger\"))\n+\t\tgoto stop;\n+\ttrigger_started = true;\n+\n+\tfor (i = 0; i \u003c 5000000 / 100; i++) {\n+\t\tif (__atomic_load_n(\u0026ctx.trigger_tid, __ATOMIC_ACQUIRE) \u0026\u0026\n+\t\t __atomic_load_n(\u0026ctx.target_tid, __ATOMIC_ACQUIRE))\n+\t\t\tbreak;\n+\t\tusleep(100);\n+\t}\n+\tif (!ASSERT_TRUE(__atomic_load_n(\u0026ctx.trigger_tid, __ATOMIC_ACQUIRE) \u0026\u0026\n+\t\t\t __atomic_load_n(\u0026ctx.target_tid, __ATOMIC_ACQUIRE),\n+\t\t\t \"thread start\"))\n+\t\tgoto stop;\n+\tif (!ASSERT_OK(__atomic_load_n(\u0026ctx.setup_err, __ATOMIC_ACQUIRE),\n+\t\t \"thread affinity\"))\n+\t\tgoto stop;\n+\n+\tskel-\u003ebss-\u003etrigger_tid = __atomic_load_n(\u0026ctx.trigger_tid, __ATOMIC_ACQUIRE);\n+\tskel-\u003ebss-\u003etarget_tid = __atomic_load_n(\u0026ctx.target_tid, __ATOMIC_ACQUIRE);\n+\n+\tfor (i = 0; i \u003c TASK_WORK_RACE_ROUNDS; i++) {\n+\t\t__u32 seq = i + 1;\n+\t\tint variant = i % 3;\n+\n+\t\tif (!ASSERT_OK(task_work_race_prepare_elem(skel), \"prepare elem\") ||\n+\t\t !ASSERT_OK(task_work_race_arm(skel, seq), \"round ready\"))\n+\t\t\tgoto stop;\n+\n+\t\tif (variant == 2 \u0026\u0026\n+\t\t !ASSERT_OK(bpf_map_delete_elem(bpf_map__fd(skel-\u003emaps.hmap),\n+\t\t\t\t\t\t \u0026key), \"early delete\"))\n+\t\t\tgoto stop;\n+\n+\t\terr = task_work_race_check_done(skel, seq);\n+\t\t/*\n+\t\t * Variant 2 deletes the element right after READY, so losing\n+\t\t * the race against the scheduling kfunc is a valid outcome:\n+\t\t * the schedule itself fails with -EBUSY (ctx already FREED),\n+\t\t * and the retry on the next tracepoint invocation fails with\n+\t\t * -ENOENT because the element is gone. Either way the round\n+\t\t * exercised the deletion paths; a later variant 0 round\n+\t\t * recreates the element.\n+\t\t */\n+\t\tif (variant == 2 \u0026\u0026 (err == -EBUSY || err == -ENOENT))\n+\t\t\terr = 0;\n+\t\tif (!ASSERT_OK(err, \"schedule\")) {\n+\t\t\tfprintf(stderr, \"round %d schedule failed: %d\\n\", i, err);\n+\t\t\tgoto stop;\n+\t\t}\n+\n+\t\tif (variant != 2 \u0026\u0026\n+\t\t !ASSERT_OK(task_work_race_wait_callback(skel, seq),\n+\t\t\t \"callback\"))\n+\t\t\tgoto stop;\n+\n+\t\tif (variant == 1 \u0026\u0026\n+\t\t !ASSERT_OK(bpf_map_delete_elem(bpf_map__fd(skel-\u003emaps.hmap),\n+\t\t\t\t\t\t \u0026key), \"late delete\"))\n+\t\t\tgoto stop;\n+\t}\n+\n+\t/* A distinct completion generation prevents an old callback satisfying this. */\n+\tif (!ASSERT_OK(task_work_race_prepare_elem(skel), \"final prepare\") ||\n+\t !ASSERT_OK(task_work_race_arm(skel, TASK_WORK_RACE_ROUNDS + 1),\n+\t\t \"final ready\") ||\n+\t !ASSERT_OK(task_work_race_check_done(skel,\n+\t\t\t\t\tTASK_WORK_RACE_ROUNDS + 1),\n+\t\t \"final schedule\") ||\n+\t !ASSERT_OK(task_work_race_wait_callback(skel,\n+\t\t\t\t\t TASK_WORK_RACE_ROUNDS + 1),\n+\t\t \"final callback\"))\n+\t\tgoto stop;\n+\n+stop:\n+\t__atomic_store_n(\u0026ctx.stop, 1, __ATOMIC_RELEASE);\n+\tif (trigger_started)\n+\t\tpthread_join(trigger, NULL);\n+\tif (target_started)\n+\t\tpthread_join(target, NULL);\n+cleanup:\n+\ttask_work_race__destroy(skel);\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/task_work_race.c b/tools/testing/selftests/bpf/progs/task_work_race.c\nnew file mode 100644\nindex 0000000000000..c94eca510cb37\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/task_work_race.c\n@@ -0,0 +1,126 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 KylinSoft Corporation. */\n+#include \u003cvmlinux.h\u003e\n+#include \u003cstdbool.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"bpf_misc.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+#define TASK_WORK_RACE_MAX_SEQ\t2048\n+\n+enum task_work_race_status {\n+\tRACE_ARM_SEQ,\n+\tRACE_READY_SEQ,\n+\t/* High 32 bits hold the sequence, low 32 bits the signed error. */\n+\tRACE_RESULT,\n+\tRACE_STATUS_MAX,\n+};\n+\n+int trigger_tid;\n+int target_tid;\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY);\n+\t__uint(max_entries, RACE_STATUS_MAX);\n+\t__type(key, int);\n+\t__type(value, __s64);\n+} status SEC(\".maps\");\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY);\n+\t__uint(max_entries, TASK_WORK_RACE_MAX_SEQ);\n+\t__type(key, int);\n+\t__type(value, __u64);\n+} completed SEC(\".maps\");\n+\n+struct task_work_race_value {\n+\t__u32 seq;\n+\tchar data[60];\n+\tstruct bpf_task_work tw;\n+};\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_HASH);\n+\t__uint(map_flags, BPF_F_NO_PREALLOC);\n+\t__uint(max_entries, 4);\n+\t__type(key, int);\n+\t__type(value, struct task_work_race_value);\n+} hmap SEC(\".maps\");\n+\n+static __always_inline void set_status(int key, __s64 value)\n+{\n+\t__s64 *slot;\n+\n+\tslot = bpf_map_lookup_elem(\u0026status, \u0026key);\n+\tif (slot)\n+\t\t*slot = value;\n+}\n+\n+static int process_work(struct bpf_map *map, void *key, void *value)\n+{\n+\tstruct task_work_race_value *work = value;\n+\t__u64 *done;\n+\tint seq = work-\u003eseq;\n+\n+\tif (seq \u003c= 0 || seq \u003e= TASK_WORK_RACE_MAX_SEQ)\n+\t\treturn 0;\n+\tdone = bpf_map_lookup_elem(\u0026completed, \u0026seq);\n+\tif (done)\n+\t\t*done = 1;\n+\treturn 0;\n+}\n+\n+SEC(\"tracepoint/syscalls/sys_enter_getppid\")\n+int race_sched_work(void *ctx)\n+{\n+\tstruct task_work_race_value *work;\n+\tstruct task_struct *task;\n+\t__s64 *arm, *result;\n+\t__u32 tid = (__u32)bpf_get_current_pid_tgid();\n+\tint key = 0, err;\n+\t__u32 seq;\n+\n+\tif (tid != trigger_tid)\n+\t\treturn 0;\n+\n+\tkey = RACE_ARM_SEQ;\n+\tarm = bpf_map_lookup_elem(\u0026status, \u0026key);\n+\tkey = RACE_RESULT;\n+\tresult = bpf_map_lookup_elem(\u0026status, \u0026key);\n+\tif (!arm || !result || *arm \u003c= 0 ||\n+\t (__u32)((__u64)*result \u003e\u003e 32) == *arm)\n+\t\treturn 0;\n+\tseq = *arm;\n+\n+\ttask = bpf_task_from_pid(target_tid);\n+\tif (!task) {\n+\t\terr = -ESRCH;\n+\t\tgoto out_done;\n+\t}\n+\n+\tkey = 0;\n+\twork = bpf_map_lookup_elem(\u0026hmap, \u0026key);\n+\tif (!work) {\n+\t\terr = -ENOENT;\n+\t\tgoto out_task;\n+\t}\n+\n+\twork-\u003eseq = seq;\n+\tset_status(RACE_READY_SEQ, seq);\n+\terr = bpf_task_work_schedule_signal(task, \u0026work-\u003etw, \u0026hmap,\n+\t\t\t\t\t process_work);\n+\tif (err == -EBUSY) {\n+\t\tbpf_task_release(task);\n+\t\treturn 0;\n+\t}\n+\n+out_task:\n+\tbpf_task_release(task);\n+out_done:\n+\t/* Publish the sequence and its error in one store. */\n+\t*result = ((__u64)seq \u003c\u003c 32) | (__u32)err;\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/09/30 08:54 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF"
],
"FocusSymbols": [
"bpf_task_work_schedule_signal",
"bpf_task_work_schedule_resume",
"bpf_task_work_cancel_and_free",
"bpf_task_work_irq",
"bpf_task_work_acquire_ctx",
"bpf_task_work_cancel",
"bpf_task_work_cancel_scheduled"
],
"Reasoning": "The patch modifies BPF task_work scheduling, cancellation, and synchronization logic in kernel/bpf/helpers.c to prevent race conditions during concurrent execution and map element deletion. These modifications affect reachable core kernel code exercised via the bpf() system call and BPF program execution.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit fa623d5d642f4cb7295b7b16abe98c52a899dfe5
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Sep 30 08:54:20 2026 +0000
syz-cluster: applied patch under review
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..350269b8646e4 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4437,15 +4437,21 @@ static void bpf_task_work_ctx_put(struct bpf_task_work_ctx *ctx)
}
}
-static void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx)
+static void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx,
+ struct task_struct *task)
{
/*
* Scheduled task_work callback holds ctx ref, so if we successfully
* cancelled, we put that ref on callback's behalf. If we couldn't
* cancel, callback will inevitably run or has already completed
* running, and it would have taken care of its ctx ref itself.
+ *
+ * The caller passes the task explicitly: the scheduling handler must
+ * not read ctx->task after the work is published (the callback may
+ * have reset it), while in the asynchronous cancellation path
+ * ctx->task is stable, see bpf_task_work_cancel_scheduled().
*/
- if (task_work_cancel(ctx->task, &ctx->work))
+ if (task_work_cancel(task, &ctx->work))
bpf_task_work_ctx_put(ctx);
}
@@ -4486,6 +4492,7 @@ static void bpf_task_work_callback(struct callback_head *cb)
static void bpf_task_work_irq(struct irq_work *irq_work)
{
struct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);
+ struct task_struct *task;
enum bpf_task_work_state state;
int err;
@@ -4496,7 +4503,16 @@ static void bpf_task_work_irq(struct irq_work *irq_work)
return;
}
- err = task_work_add(ctx->task, &ctx->work, ctx->mode);
+ /*
+ * Capture the task pointer before the work is published: once the
+ * callback runs, it may reset ctx->task. The capture happens while
+ * this round's task reference is held, and every path that can
+ * release it runs after this handler entered its rcu read-side
+ * section. bpf_task_release() is put_task_struct_rcu_user(), so the
+ * task_struct itself cannot be freed before this section ends.
+ */
+ task = ctx->task;
+ err = task_work_add(task, &ctx->work, ctx->mode);
if (err) {
bpf_task_work_ctx_reset(ctx);
/*
@@ -4510,14 +4526,14 @@ static void bpf_task_work_irq(struct irq_work *irq_work)
/*
* It's technically possible for just scheduled task_work callback to
- * complete running by now, going SCHEDULING -> RUNNING and then
- * dropping its ctx refcount. Instead of capturing an extra ref just
- * to protect below ctx->state access, we rely on rcu_read_lock
- * above to prevent kfree_rcu from freeing ctx before we return.
+ * complete running by now, going SCHEDULING -> RUNNING, resetting
+ * ctx->task and dropping its ctx refcount. The rcu read-side section
+ * above keeps both the ctx memory and the captured task pointer
+ * valid until this handler returns.
*/
state = cmpxchg(&ctx->state, BPF_TW_SCHEDULING, BPF_TW_SCHEDULED);
if (state == BPF_TW_FREED)
- bpf_task_work_cancel(ctx); /* clean up if we switched into FREED state */
+ bpf_task_work_cancel(ctx, task); /* clean up if we switched into FREED state */
}
static struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *tw,
@@ -4537,6 +4553,7 @@ static struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *t
memset(ctx, 0, sizeof(*ctx));
refcount_set(&ctx->refcnt, 1); /* map's own ref */
ctx->state = BPF_TW_STANDBY;
+ init_irq_work(&ctx->irq_work, bpf_task_work_irq);
old_ctx = cmpxchg(&twk->ctx, NULL, ctx);
if (old_ctx) {
@@ -4579,6 +4596,18 @@ static struct bpf_task_work_ctx *bpf_task_work_acquire_ctx(struct bpf_task_work
bpf_task_work_ctx_put(ctx);
return ERR_PTR(-EBUSY);
}
+ /*
+ * STANDBY can be published before the previous scheduling irq_work
+ * returns. Claim PENDING before checking BUSY to order the check after
+ * that publication. No irq_work has been queued for this attempt yet,
+ * so reject reuse and roll back to STANDBY while BUSY is set. Preserve
+ * FREED if deletion wins the rollback race.
+ */
+ if (unlikely(irq_work_is_busy(&ctx->irq_work))) {
+ (void)cmpxchg(&ctx->state, BPF_TW_PENDING, BPF_TW_STANDBY);
+ bpf_task_work_ctx_put(ctx);
+ return ERR_PTR(-EBUSY);
+ }
/*
* If no process or bpffs is holding a reference to the map, no new callbacks should be
@@ -4628,7 +4657,6 @@ static int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work
ctx->map = map;
ctx->map_val = (void *)tw - map->record->task_work_off;
init_task_work(&ctx->work, bpf_task_work_callback);
- init_irq_work(&ctx->irq_work, bpf_task_work_irq);
irq_work_queue(&ctx->irq_work);
return 0;
@@ -4777,7 +4805,13 @@ static void bpf_task_work_cancel_scheduled(struct irq_work *irq_work)
{
struct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);
- bpf_task_work_cancel(ctx); /* this might put task_work callback's ref */
+ /*
+ * Deletion observed SCHEDULED and won against the callback's
+ * transition to RUNNING. The callback takes its FREED exit without
+ * resetting ctx->task, and the transferred map reference prevents
+ * destruction until cancellation completes.
+ */
+ bpf_task_work_cancel(ctx, ctx->task); /* this might put task_work callback's ref */
bpf_task_work_ctx_put(ctx); /* and here we put map's own ref that was transferred to us */
}
diff --git a/tools/testing/selftests/bpf/prog_tests/test_task_work.c b/tools/testing/selftests/bpf/prog_tests/test_task_work.c
index 774b31a5f6ca1..ff56976f4226e 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_task_work.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_task_work.c
@@ -5,10 +5,14 @@
#include <stdio.h>
#include "task_work.skel.h"
#include "task_work_fail.skel.h"
+#include "task_work_race.skel.h"
#include <linux/bpf.h>
#include <linux/perf_event.h>
#include <sys/syscall.h>
#include <time.h>
+#include <pthread.h>
+#include <sched.h>
+#include <unistd.h>
static int perf_event_open(__u32 type, __u64 config, int pid)
{
@@ -155,3 +159,282 @@ void test_task_work(void)
RUN_TESTS(task_work_fail);
}
+
+#define TASK_WORK_RACE_ROUNDS 1500
+
+/* Must match progs/task_work_race.c. */
+enum task_work_race_status {
+ RACE_ARM_SEQ,
+ RACE_READY_SEQ,
+ /* High 32 bits hold the sequence, low 32 bits the signed error. */
+ RACE_RESULT,
+};
+
+struct task_work_race_value {
+ __u32 seq;
+ char data[60];
+ struct bpf_task_work tw;
+};
+
+struct task_work_race_ctx {
+ int stop;
+ int setup_err;
+ int trigger_tid;
+ int target_tid;
+ int trigger_cpu;
+ int target_cpu;
+};
+
+static int task_work_race_pin_cpu(int cpu)
+{
+ cpu_set_t set;
+
+ CPU_ZERO(&set);
+ CPU_SET(cpu, &set);
+ return pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
+}
+
+static void *task_work_race_trigger(void *arg)
+{
+ struct task_work_race_ctx *ctx = arg;
+ int err;
+
+ err = task_work_race_pin_cpu(ctx->trigger_cpu);
+ if (err)
+ __atomic_store_n(&ctx->setup_err, err, __ATOMIC_RELEASE);
+ __atomic_store_n(&ctx->trigger_tid, syscall(__NR_gettid),
+ __ATOMIC_RELEASE);
+ while (!__atomic_load_n(&ctx->stop, __ATOMIC_ACQUIRE))
+ getppid();
+ return NULL;
+}
+
+static void *task_work_race_target(void *arg)
+{
+ struct task_work_race_ctx *ctx = arg;
+ int err;
+
+ err = task_work_race_pin_cpu(ctx->target_cpu);
+ if (err)
+ __atomic_store_n(&ctx->setup_err, err, __ATOMIC_RELEASE);
+ __atomic_store_n(&ctx->target_tid, syscall(__NR_gettid),
+ __ATOMIC_RELEASE);
+ while (!__atomic_load_n(&ctx->stop, __ATOMIC_ACQUIRE))
+ getppid();
+ return NULL;
+}
+
+static int task_work_race_status(struct task_work_race *skel, int idx,
+ __s64 *value)
+{
+ return bpf_map_lookup_elem(bpf_map__fd(skel->maps.status), &idx,
+ value);
+}
+
+static int task_work_race_set_status(struct task_work_race *skel, int idx,
+ __s64 value)
+{
+ return bpf_map_update_elem(bpf_map__fd(skel->maps.status), &idx,
+ &value, BPF_ANY);
+}
+
+static int task_work_race_wait_ready(struct task_work_race *skel, __u32 seq)
+{
+ int i;
+
+ for (i = 0; i < 2000000 / 100; i++) {
+ __s64 value = 0;
+
+ if (!task_work_race_status(skel, RACE_READY_SEQ, &value) &&
+ value == seq)
+ return 0;
+ if (!task_work_race_status(skel, RACE_RESULT, &value) &&
+ (__u32)((__u64)value >> 32) == seq)
+ /*
+ * The BPF program finished between the two reads:
+ * RESULT makes waiting for READY unnecessary. Any error
+ * that occurred before READY is checked by the caller.
+ */
+ return 0;
+ usleep(100);
+ }
+ return -ETIMEDOUT;
+}
+
+static int task_work_race_prepare_elem(struct task_work_race *skel)
+{
+ struct task_work_race_value value = {};
+ int key = 0;
+
+ if (!bpf_map_lookup_elem(bpf_map__fd(skel->maps.hmap), &key, &value))
+ return 0;
+ if (errno != ENOENT)
+ return -errno;
+ if (bpf_map_update_elem(bpf_map__fd(skel->maps.hmap), &key, &value,
+ BPF_NOEXIST))
+ return -errno;
+ return 0;
+}
+
+static int task_work_race_arm(struct task_work_race *skel, __u32 seq)
+{
+ if (task_work_race_set_status(skel, RACE_ARM_SEQ, seq))
+ return -errno;
+ return task_work_race_wait_ready(skel, seq);
+}
+
+static int task_work_race_check_done(struct task_work_race *skel, __u32 seq)
+{
+ int i;
+
+ for (i = 0; i < 2000000 / 100; i++) {
+ __s64 result = 0;
+
+ if (!task_work_race_status(skel, RACE_RESULT, &result) &&
+ (__u32)((__u64)result >> 32) == seq)
+ return (__s32)result;
+ usleep(100);
+ }
+ return -ETIMEDOUT;
+}
+
+static int task_work_race_wait_callback(struct task_work_race *skel,
+ __u32 seq)
+{
+ int i, key = seq;
+
+ for (i = 0; i < 2000000 / 100; i++) {
+ __u64 completed = 0;
+
+ if (!bpf_map_lookup_elem(bpf_map__fd(skel->maps.completed),
+ &key, &completed) && completed)
+ return 0;
+ usleep(100);
+ }
+ return -ETIMEDOUT;
+}
+
+void serial_test_task_work_race(void)
+{
+ struct task_work_race_ctx ctx = {};
+ struct task_work_race *skel;
+ pthread_t trigger, target;
+ cpu_set_t allowed;
+ int cpu, cpu_count = 0;
+ bool target_started = false, trigger_started = false;
+ int err, i, key = 0;
+
+ if (sched_getaffinity(0, sizeof(allowed), &allowed)) {
+ ASSERT_OK(-errno, "sched_getaffinity");
+ return;
+ }
+ for (cpu = 0; cpu < CPU_SETSIZE && cpu_count < 2; cpu++) {
+ if (!CPU_ISSET(cpu, &allowed))
+ continue;
+ if (!cpu_count)
+ ctx.trigger_cpu = cpu;
+ else
+ ctx.target_cpu = cpu;
+ cpu_count++;
+ }
+ if (cpu_count < 2) {
+ printf("%s:SKIP:need two CPUs in the process affinity mask\n",
+ __func__);
+ test__skip();
+ return;
+ }
+
+ skel = task_work_race__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+ if (!ASSERT_OK(task_work_race__attach(skel), "attach"))
+ goto cleanup;
+
+ err = pthread_create(&target, NULL, task_work_race_target, &ctx);
+ if (!ASSERT_OK(err, "pthread_create target"))
+ goto cleanup;
+ target_started = true;
+ err = pthread_create(&trigger, NULL, task_work_race_trigger, &ctx);
+ if (!ASSERT_OK(err, "pthread_create trigger"))
+ goto stop;
+ trigger_started = true;
+
+ for (i = 0; i < 5000000 / 100; i++) {
+ if (__atomic_load_n(&ctx.trigger_tid, __ATOMIC_ACQUIRE) &&
+ __atomic_load_n(&ctx.target_tid, __ATOMIC_ACQUIRE))
+ break;
+ usleep(100);
+ }
+ if (!ASSERT_TRUE(__atomic_load_n(&ctx.trigger_tid, __ATOMIC_ACQUIRE) &&
+ __atomic_load_n(&ctx.target_tid, __ATOMIC_ACQUIRE),
+ "thread start"))
+ goto stop;
+ if (!ASSERT_OK(__atomic_load_n(&ctx.setup_err, __ATOMIC_ACQUIRE),
+ "thread affinity"))
+ goto stop;
+
+ skel->bss->trigger_tid = __atomic_load_n(&ctx.trigger_tid, __ATOMIC_ACQUIRE);
+ skel->bss->target_tid = __atomic_load_n(&ctx.target_tid, __ATOMIC_ACQUIRE);
+
+ for (i = 0; i < TASK_WORK_RACE_ROUNDS; i++) {
+ __u32 seq = i + 1;
+ int variant = i % 3;
+
+ if (!ASSERT_OK(task_work_race_prepare_elem(skel), "prepare elem") ||
+ !ASSERT_OK(task_work_race_arm(skel, seq), "round ready"))
+ goto stop;
+
+ if (variant == 2 &&
+ !ASSERT_OK(bpf_map_delete_elem(bpf_map__fd(skel->maps.hmap),
+ &key), "early delete"))
+ goto stop;
+
+ err = task_work_race_check_done(skel, seq);
+ /*
+ * Variant 2 deletes the element right after READY, so losing
+ * the race against the scheduling kfunc is a valid outcome:
+ * the schedule itself fails with -EBUSY (ctx already FREED),
+ * and the retry on the next tracepoint invocation fails with
+ * -ENOENT because the element is gone. Either way the round
+ * exercised the deletion paths; a later variant 0 round
+ * recreates the element.
+ */
+ if (variant == 2 && (err == -EBUSY || err == -ENOENT))
+ err = 0;
+ if (!ASSERT_OK(err, "schedule")) {
+ fprintf(stderr, "round %d schedule failed: %d\n", i, err);
+ goto stop;
+ }
+
+ if (variant != 2 &&
+ !ASSERT_OK(task_work_race_wait_callback(skel, seq),
+ "callback"))
+ goto stop;
+
+ if (variant == 1 &&
+ !ASSERT_OK(bpf_map_delete_elem(bpf_map__fd(skel->maps.hmap),
+ &key), "late delete"))
+ goto stop;
+ }
+
+ /* A distinct completion generation prevents an old callback satisfying this. */
+ if (!ASSERT_OK(task_work_race_prepare_elem(skel), "final prepare") ||
+ !ASSERT_OK(task_work_race_arm(skel, TASK_WORK_RACE_ROUNDS + 1),
+ "final ready") ||
+ !ASSERT_OK(task_work_race_check_done(skel,
+ TASK_WORK_RACE_ROUNDS + 1),
+ "final schedule") ||
+ !ASSERT_OK(task_work_race_wait_callback(skel,
+ TASK_WORK_RACE_ROUNDS + 1),
+ "final callback"))
+ goto stop;
+
+stop:
+ __atomic_store_n(&ctx.stop, 1, __ATOMIC_RELEASE);
+ if (trigger_started)
+ pthread_join(trigger, NULL);
+ if (target_started)
+ pthread_join(target, NULL);
+cleanup:
+ task_work_race__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/task_work_race.c b/tools/testing/selftests/bpf/progs/task_work_race.c
new file mode 100644
index 0000000000000..c94eca510cb37
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/task_work_race.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 KylinSoft Corporation. */
+#include <vmlinux.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+#define TASK_WORK_RACE_MAX_SEQ 2048
+
+enum task_work_race_status {
+ RACE_ARM_SEQ,
+ RACE_READY_SEQ,
+ /* High 32 bits hold the sequence, low 32 bits the signed error. */
+ RACE_RESULT,
+ RACE_STATUS_MAX,
+};
+
+int trigger_tid;
+int target_tid;
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, RACE_STATUS_MAX);
+ __type(key, int);
+ __type(value, __s64);
+} status SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, TASK_WORK_RACE_MAX_SEQ);
+ __type(key, int);
+ __type(value, __u64);
+} completed SEC(".maps");
+
+struct task_work_race_value {
+ __u32 seq;
+ char data[60];
+ struct bpf_task_work tw;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __uint(max_entries, 4);
+ __type(key, int);
+ __type(value, struct task_work_race_value);
+} hmap SEC(".maps");
+
+static __always_inline void set_status(int key, __s64 value)
+{
+ __s64 *slot;
+
+ slot = bpf_map_lookup_elem(&status, &key);
+ if (slot)
+ *slot = value;
+}
+
+static int process_work(struct bpf_map *map, void *key, void *value)
+{
+ struct task_work_race_value *work = value;
+ __u64 *done;
+ int seq = work->seq;
+
+ if (seq <= 0 || seq >= TASK_WORK_RACE_MAX_SEQ)
+ return 0;
+ done = bpf_map_lookup_elem(&completed, &seq);
+ if (done)
+ *done = 1;
+ return 0;
+}
+
+SEC("tracepoint/syscalls/sys_enter_getppid")
+int race_sched_work(void *ctx)
+{
+ struct task_work_race_value *work;
+ struct task_struct *task;
+ __s64 *arm, *result;
+ __u32 tid = (__u32)bpf_get_current_pid_tgid();
+ int key = 0, err;
+ __u32 seq;
+
+ if (tid != trigger_tid)
+ return 0;
+
+ key = RACE_ARM_SEQ;
+ arm = bpf_map_lookup_elem(&status, &key);
+ key = RACE_RESULT;
+ result = bpf_map_lookup_elem(&status, &key);
+ if (!arm || !result || *arm <= 0 ||
+ (__u32)((__u64)*result >> 32) == *arm)
+ return 0;
+ seq = *arm;
+
+ task = bpf_task_from_pid(target_tid);
+ if (!task) {
+ err = -ESRCH;
+ goto out_done;
+ }
+
+ key = 0;
+ work = bpf_map_lookup_elem(&hmap, &key);
+ if (!work) {
+ err = -ENOENT;
+ goto out_task;
+ }
+
+ work->seq = seq;
+ set_status(RACE_READY_SEQ, seq);
+ err = bpf_task_work_schedule_signal(task, &work->tw, &hmap,
+ process_work);
+ if (err == -EBUSY) {
+ bpf_task_release(task);
+ return 0;
+ }
+
+out_task:
+ bpf_task_release(task);
+out_done:
+ /* Publish the sequence and its error in one store. */
+ *result = ((__u64)seq << 32) | (__u32)err;
+ return 0;
+}
|
| 3/2 | 2026/09/30 08:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 9559 output: 37 thoughts: 343 Thoughts: **Analyzing the BPF Task Work Patch** Okay, let's break this down. My initial impression is that this patch is touching the BPF task work mechanism, specifically within the kernel. The files modified – `kernel/bpf/helpers.c` and selftests – indicate we're dealing with core functionality and its associated testing. First, focusing on `kernel/bpf/helpers.c`, it looks like the developers are refining how task work interacts with the kernel's interrupt context. The key functions being adjusted are `bpf_task_work_cancel`, `bpf_task_work_irq`, `bpf_task_work_fetch_ctx`, `bpf_task_work_acquire_ctx`, `bpf_task_work_schedule`, and `bpf_task_work_cancel_scheduled`. Specifically, it looks like: * **Explicit Task Passing:** `bpf_task_work_cancel` explicitly receives the `task` as an argument. * **Context Capture:** In `bpf_task_work_irq`, the code now captures `task = ctx->task` before the `task_work_add` call, which is interesting. * **Initialization Shift:** `init_irq_work` is now being handled in `bpf_task_work_fetch_ctx` instead of the original location in `bpf_task_work_schedule`. This placement can be important for performance. * **Concurrency Checks:** `bpf_task_work_acquire_ctx` seems to be adding a check to prevent races using `irq_work_is_busy`. * **Schedule Optimization:** The removal of `init_irq_work` from `bpf_task_work_schedule` suggests an optimization; initializing the irq work only once. * **Cancel Consistency:** `bpf_task_work_cancel_scheduled` also passes `ctx->task` to `bpf_task_work_cancel`, maintaining consistency. Now, I want to delve deeper into the context. I'll take a look at `kernel/bpf/helpers.c` around lines 4430-4800 to fully understand the "why" behind these changes. Understanding the surrounding code will shed light on what features are in play and what specific issue this patch is addressing. I'm especially interested in how task work interacts with interrupts and the overall BPF execution model. |
| 4/2 | 2026/09/30 08:54 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4430,
"LineCount": 80
}Results: {
"Contents": "4430:\t\t\treturn;\n4431:\t\n4432:\t\tif (irqs_disabled()) {\n4433:\t\t\tctx-\u003eirq_work = IRQ_WORK_INIT(bpf_task_work_destroy);\n4434:\t\t\tirq_work_queue(\u0026ctx-\u003eirq_work);\n4435:\t\t} else {\n4436:\t\t\tbpf_task_work_destroy(\u0026ctx-\u003eirq_work);\n4437:\t\t}\n4438:\t}\n4439:\t\n4440:\tstatic void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx,\n4441:\t\t\t\t\t struct task_struct *task)\n4442:\t{\n4443:\t\t/*\n4444:\t\t * Scheduled task_work callback holds ctx ref, so if we successfully\n4445:\t\t * cancelled, we put that ref on callback's behalf. If we couldn't\n4446:\t\t * cancel, callback will inevitably run or has already completed\n4447:\t\t * running, and it would have taken care of its ctx ref itself.\n4448:\t\t *\n4449:\t\t * The caller passes the task explicitly: the scheduling handler must\n4450:\t\t * not read ctx-\u003etask after the work is published (the callback may\n4451:\t\t * have reset it), while in the asynchronous cancellation path\n4452:\t\t * ctx-\u003etask is stable, see bpf_task_work_cancel_scheduled().\n4453:\t\t */\n4454:\t\tif (task_work_cancel(task, \u0026ctx-\u003ework))\n4455:\t\t\tbpf_task_work_ctx_put(ctx);\n4456:\t}\n4457:\t\n4458:\tstatic void bpf_task_work_callback(struct callback_head *cb)\n4459:\t{\n4460:\t\tstruct bpf_task_work_ctx *ctx = container_of(cb, struct bpf_task_work_ctx, work);\n4461:\t\tenum bpf_task_work_state state;\n4462:\t\tu32 idx;\n4463:\t\tvoid *key;\n4464:\t\n4465:\t\t/* Read lock is needed to protect ctx and map key/value access */\n4466:\t\tguard(rcu_tasks_trace)();\n4467:\t\t/*\n4468:\t\t * This callback may start running before bpf_task_work_irq() switched to\n4469:\t\t * SCHEDULED state, so handle both transition variants SCHEDULING|SCHEDULED -\u003e RUNNING.\n4470:\t\t */\n4471:\t\tstate = cmpxchg(\u0026ctx-\u003estate, BPF_TW_SCHEDULING, BPF_TW_RUNNING);\n4472:\t\tif (state == BPF_TW_SCHEDULED)\n4473:\t\t\tstate = cmpxchg(\u0026ctx-\u003estate, BPF_TW_SCHEDULED, BPF_TW_RUNNING);\n4474:\t\tif (state == BPF_TW_FREED) {\n4475:\t\t\tbpf_task_work_ctx_put(ctx);\n4476:\t\t\treturn;\n4477:\t\t}\n4478:\t\n4479:\t\tkey = (void *)map_key_from_value(ctx-\u003emap, ctx-\u003emap_val, \u0026idx);\n4480:\t\n4481:\t\tmigrate_disable();\n4482:\t\tctx-\u003ecallback_fn((u64)(long)ctx-\u003emap, (u64)(long)key,\n4483:\t\t\t\t (u64)(long)ctx-\u003emap_val, 0, 0);\n4484:\t\tmigrate_enable();\n4485:\t\n4486:\t\tbpf_task_work_ctx_reset(ctx);\n4487:\t\t(void)cmpxchg(\u0026ctx-\u003estate, BPF_TW_RUNNING, BPF_TW_STANDBY);\n4488:\t\n4489:\t\tbpf_task_work_ctx_put(ctx);\n4490:\t}\n4491:\t\n4492:\tstatic void bpf_task_work_irq(struct irq_work *irq_work)\n4493:\t{\n4494:\t\tstruct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);\n4495:\t\tstruct task_struct *task;\n4496:\t\tenum bpf_task_work_state state;\n4497:\t\tint err;\n4498:\t\n4499:\t\tguard(rcu)();\n4500:\t\n4501:\t\tif (cmpxchg(\u0026ctx-\u003estate, BPF_TW_PENDING, BPF_TW_SCHEDULING) != BPF_TW_PENDING) {\n4502:\t\t\tbpf_task_work_ctx_put(ctx);\n4503:\t\t\treturn;\n4504:\t\t}\n4505:\t\n4506:\t\t/*\n4507:\t\t * Capture the task pointer before the work is published: once the\n4508:\t\t * callback runs, it may reset ctx-\u003etask. The capture happens while\n4509:\t\t * this round's task reference is held, and every path that can\n"
} |
| 5/2 | 2026/09/30 08:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 11841 output: 38 thoughts: 17 Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person: **Diving into `kernel/bpf/helpers.c`** Alright, time to dig deeper. I need to understand this behavior, and the best place to start, given the context, is definitely within the BPF helper implementation. So, my focus is now shifting to `kernel/bpf/helpers.c`. I expect to find the core logic for the BPF helper functions here. This is where the magic happens, where the user-space calls translate into kernel-level actions. I'm looking for the definitions, the implementations of those functions, especially those relevant to [mention the specific topic, if known from context, otherwise keep it general, e.g., "memory access," or "tracing"]. I'm mentally parsing the structure; function prototypes, any internal helper functions called within. I'll be looking for potential performance bottlenecks, any subtle race conditions, or anything else that might have implications for the current challenge. I'll pay attention to any use of locks, atomic operations, and how the functions interact with different kernel subsystems. I'll need to look for calls to other kernel functions that might be relevant, and track those down, if relevant. Time to get my hands dirty. |
| 6/2 | 2026/09/30 08:54 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4510,
"LineCount": 100
}Results: {
"Contents": "4510:\t\t * release it runs after this handler entered its rcu read-side\n4511:\t\t * section. bpf_task_release() is put_task_struct_rcu_user(), so the\n4512:\t\t * task_struct itself cannot be freed before this section ends.\n4513:\t\t */\n4514:\t\ttask = ctx-\u003etask;\n4515:\t\terr = task_work_add(task, \u0026ctx-\u003ework, ctx-\u003emode);\n4516:\t\tif (err) {\n4517:\t\t\tbpf_task_work_ctx_reset(ctx);\n4518:\t\t\t/*\n4519:\t\t\t * try to switch back to STANDBY for another task_work reuse, but we might have\n4520:\t\t\t * gone to FREED already, which is fine as we already cleaned up after ourselves\n4521:\t\t\t */\n4522:\t\t\t(void)cmpxchg(\u0026ctx-\u003estate, BPF_TW_SCHEDULING, BPF_TW_STANDBY);\n4523:\t\t\tbpf_task_work_ctx_put(ctx);\n4524:\t\t\treturn;\n4525:\t\t}\n4526:\t\n4527:\t\t/*\n4528:\t\t * It's technically possible for just scheduled task_work callback to\n4529:\t\t * complete running by now, going SCHEDULING -\u003e RUNNING, resetting\n4530:\t\t * ctx-\u003etask and dropping its ctx refcount. The rcu read-side section\n4531:\t\t * above keeps both the ctx memory and the captured task pointer\n4532:\t\t * valid until this handler returns.\n4533:\t\t */\n4534:\t\tstate = cmpxchg(\u0026ctx-\u003estate, BPF_TW_SCHEDULING, BPF_TW_SCHEDULED);\n4535:\t\tif (state == BPF_TW_FREED)\n4536:\t\t\tbpf_task_work_cancel(ctx, task); /* clean up if we switched into FREED state */\n4537:\t}\n4538:\t\n4539:\tstatic struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *tw,\n4540:\t\t\t\t\t\t\t\t struct bpf_map *map)\n4541:\t{\n4542:\t\tstruct bpf_task_work_kern *twk = (void *)tw;\n4543:\t\tstruct bpf_task_work_ctx *ctx, *old_ctx;\n4544:\t\n4545:\t\tctx = READ_ONCE(twk-\u003ectx);\n4546:\t\tif (ctx)\n4547:\t\t\treturn ctx;\n4548:\t\n4549:\t\tctx = bpf_map_kmalloc_nolock(map, sizeof(*ctx), 0, NUMA_NO_NODE);\n4550:\t\tif (!ctx)\n4551:\t\t\treturn ERR_PTR(-ENOMEM);\n4552:\t\n4553:\t\tmemset(ctx, 0, sizeof(*ctx));\n4554:\t\trefcount_set(\u0026ctx-\u003erefcnt, 1); /* map's own ref */\n4555:\t\tctx-\u003estate = BPF_TW_STANDBY;\n4556:\t\tinit_irq_work(\u0026ctx-\u003eirq_work, bpf_task_work_irq);\n4557:\t\n4558:\t\told_ctx = cmpxchg(\u0026twk-\u003ectx, NULL, ctx);\n4559:\t\tif (old_ctx) {\n4560:\t\t\t/*\n4561:\t\t\t * tw-\u003ectx is set by concurrent BPF program, release allocated\n4562:\t\t\t * memory and try to reuse already set context.\n4563:\t\t\t */\n4564:\t\t\tkfree_nolock(ctx);\n4565:\t\t\treturn old_ctx;\n4566:\t\t}\n4567:\t\n4568:\t\treturn ctx; /* Success */\n4569:\t}\n4570:\t\n4571:\tstatic struct bpf_task_work_ctx *bpf_task_work_acquire_ctx(struct bpf_task_work *tw,\n4572:\t\t\t\t\t\t\t\t struct bpf_map *map)\n4573:\t{\n4574:\t\tstruct bpf_task_work_ctx *ctx;\n4575:\t\n4576:\t\t/*\n4577:\t\t * Sleepable BPF programs hold rcu_read_lock_trace but not\n4578:\t\t * regular rcu_read_lock. Since kfree_rcu waits for regular\n4579:\t\t * RCU GP, the ctx can be freed while we're between reading\n4580:\t\t * the pointer and incrementing the refcount. Take regular\n4581:\t\t * rcu_read_lock to prevent kfree_rcu from freeing the ctx\n4582:\t\t * before we can tryget it.\n4583:\t\t */\n4584:\t\tscoped_guard(rcu) {\n4585:\t\t\tctx = bpf_task_work_fetch_ctx(tw, map);\n4586:\t\t\tif (IS_ERR(ctx))\n4587:\t\t\t\treturn ctx;\n4588:\t\n4589:\t\t\t/* try to get ref for task_work callback to hold */\n4590:\t\t\tif (!bpf_task_work_ctx_tryget(ctx))\n4591:\t\t\t\treturn ERR_PTR(-EBUSY);\n4592:\t\t}\n4593:\t\n4594:\t\tif (cmpxchg(\u0026ctx-\u003estate, BPF_TW_STANDBY, BPF_TW_PENDING) != BPF_TW_STANDBY) {\n4595:\t\t\t/* lost acquiring race or map_release_uref() stole it from us, put ref and bail */\n4596:\t\t\tbpf_task_work_ctx_put(ctx);\n4597:\t\t\treturn ERR_PTR(-EBUSY);\n4598:\t\t}\n4599:\t\t/*\n4600:\t\t * STANDBY can be published before the previous scheduling irq_work\n4601:\t\t * returns. Claim PENDING before checking BUSY to order the check after\n4602:\t\t * that publication. No irq_work has been queued for this attempt yet,\n4603:\t\t * so reject reuse and roll back to STANDBY while BUSY is set. Preserve\n4604:\t\t * FREED if deletion wins the rollback race.\n4605:\t\t */\n4606:\t\tif (unlikely(irq_work_is_busy(\u0026ctx-\u003eirq_work))) {\n4607:\t\t\t(void)cmpxchg(\u0026ctx-\u003estate, BPF_TW_PENDING, BPF_TW_STANDBY);\n4608:\t\t\tbpf_task_work_ctx_put(ctx);\n4609:\t\t\treturn ERR_PTR(-EBUSY);\n"
} |
| 7/2 | 2026/09/30 08:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 13926 output: 38 thoughts: 29 Thoughts: Okay, here's my attempt at summarizing the thought process, as requested, geared toward an expert audience, and written in the first person: **Diving into `kernel/bpf/helpers.c`** Alright, I need to get a handle on how this specific helper function operates. Given the context, my initial thought is to zero in on lines 4610 to 4820 in `kernel/bpf/helpers.c`. That range likely houses the core logic I'm interested in; it's probably where the meat of the implementation resides. My immediate goal is to quickly extract the key data structures and function calls in this section. I'll need to pay close attention to the parameter passing and any potential interactions with other BPF subsystems. I expect to be able to then quickly grasp the overall operation and identify any opportunities for optimization. I'll be looking specifically at the nuances around memory access, error handling, and any potential for contention. Let's get to it. |
| 8/2 | 2026/09/30 08:54 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4610,
"LineCount": 100
}Results: {
"Contents": "4610:\t\t}\n4611:\t\n4612:\t\t/*\n4613:\t\t * If no process or bpffs is holding a reference to the map, no new callbacks should be\n4614:\t\t * scheduled. This does not address any race or correctness issue, but rather is a policy\n4615:\t\t * choice: dropping user references should stop everything.\n4616:\t\t */\n4617:\t\tif (!atomic64_read(\u0026map-\u003eusercnt)) {\n4618:\t\t\t/* drop ref we just got for task_work callback itself */\n4619:\t\t\tbpf_task_work_ctx_put(ctx);\n4620:\t\t\t/* transfer map's ref into cancel_and_free() */\n4621:\t\t\tbpf_task_work_cancel_and_free(tw);\n4622:\t\t\treturn ERR_PTR(-EBUSY);\n4623:\t\t}\n4624:\t\n4625:\t\treturn ctx;\n4626:\t}\n4627:\t\n4628:\tstatic int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work *tw,\n4629:\t\t\t\t\t struct bpf_map *map, void *callback_fn,\n4630:\t\t\t\t\t struct bpf_prog_aux *aux, enum task_work_notify_mode mode)\n4631:\t{\n4632:\t\tstruct bpf_prog *prog;\n4633:\t\tstruct bpf_task_work_ctx *ctx;\n4634:\t\tint err;\n4635:\t\n4636:\t\tBTF_TYPE_EMIT(struct bpf_task_work);\n4637:\t\n4638:\t\tprog = bpf_prog_inc_not_zero(aux-\u003eprog);\n4639:\t\tif (IS_ERR(prog))\n4640:\t\t\treturn -EBADF;\n4641:\t\ttask = bpf_task_acquire(task);\n4642:\t\tif (!task) {\n4643:\t\t\terr = -EBADF;\n4644:\t\t\tgoto release_prog;\n4645:\t\t}\n4646:\t\n4647:\t\tctx = bpf_task_work_acquire_ctx(tw, map);\n4648:\t\tif (IS_ERR(ctx)) {\n4649:\t\t\terr = PTR_ERR(ctx);\n4650:\t\t\tgoto release_all;\n4651:\t\t}\n4652:\t\n4653:\t\tctx-\u003etask = task;\n4654:\t\tctx-\u003ecallback_fn = (bpf_callback_t)callback_fn;\n4655:\t\tctx-\u003eprog = prog;\n4656:\t\tctx-\u003emode = mode;\n4657:\t\tctx-\u003emap = map;\n4658:\t\tctx-\u003emap_val = (void *)tw - map-\u003erecord-\u003etask_work_off;\n4659:\t\tinit_task_work(\u0026ctx-\u003ework, bpf_task_work_callback);\n4660:\t\n4661:\t\tirq_work_queue(\u0026ctx-\u003eirq_work);\n4662:\t\treturn 0;\n4663:\t\n4664:\trelease_all:\n4665:\t\tbpf_task_release(task);\n4666:\trelease_prog:\n4667:\t\tbpf_prog_put(prog);\n4668:\t\treturn err;\n4669:\t}\n4670:\t\n4671:\t/**\n4672:\t * bpf_task_work_schedule_signal - Schedule BPF callback using task_work_add with TWA_SIGNAL\n4673:\t * mode\n4674:\t * @task: Task struct for which callback should be scheduled\n4675:\t * @tw: Pointer to struct bpf_task_work in BPF map value for internal bookkeeping\n4676:\t * @map__const_map: bpf_map that embeds struct bpf_task_work in the values\n4677:\t * @callback: pointer to BPF subprogram to call\n4678:\t * @aux: pointer to bpf_prog_aux of the caller BPF program, implicitly set by the verifier\n4679:\t *\n4680:\t * Return: 0 if task work has been scheduled successfully, negative error code otherwise\n4681:\t */\n4682:\t__bpf_kfunc int bpf_task_work_schedule_signal(struct task_struct *task, struct bpf_task_work *tw,\n4683:\t\t\t\t\t\t void *map__const_map, bpf_task_work_callback_t callback,\n4684:\t\t\t\t\t\t struct bpf_prog_aux *aux)\n4685:\t{\n4686:\t\treturn bpf_task_work_schedule(task, tw, map__const_map, callback, aux, TWA_SIGNAL);\n4687:\t}\n4688:\t\n4689:\t/**\n4690:\t * bpf_task_work_schedule_resume - Schedule BPF callback using task_work_add with TWA_RESUME\n4691:\t * mode\n4692:\t * @task: Task struct for which callback should be scheduled\n4693:\t * @tw: Pointer to struct bpf_task_work in BPF map value for internal bookkeeping\n4694:\t * @map__const_map: bpf_map that embeds struct bpf_task_work in the values\n4695:\t * @callback: pointer to BPF subprogram to call\n4696:\t * @aux: pointer to bpf_prog_aux of the caller BPF program, implicitly set by the verifier\n4697:\t *\n4698:\t * Return: 0 if task work has been scheduled successfully, negative error code otherwise\n4699:\t */\n4700:\t__bpf_kfunc int bpf_task_work_schedule_resume(struct task_struct *task, struct bpf_task_work *tw,\n4701:\t\t\t\t\t\t void *map__const_map, bpf_task_work_callback_t callback,\n4702:\t\t\t\t\t\t struct bpf_prog_aux *aux)\n4703:\t{\n4704:\t\treturn bpf_task_work_schedule(task, tw, map__const_map, callback, aux, TWA_RESUME);\n4705:\t}\n4706:\t\n4707:\tstatic int make_file_dynptr(struct file *file, u32 flags, bool may_sleep,\n4708:\t\t\t\t struct bpf_dynptr_kern *ptr)\n4709:\t{\n"
} |
| 9/2 | 2026/09/30 08:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 15907 output: 35 thoughts: 27 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person: **Investigating `bpf_task_work_cancel_scheduled`** Alright, so we need to get a deeper understanding of how this cancellation function, `bpf_task_work_cancel_scheduled`, really operates. My immediate focus is to dissect its functionality and pinpoint every single call site. I need to know the entire landscape of its usage. Is it used in contexts I would expect? Are there any surprising integrations or edge cases I should anticipate? My goal here is to be able to fully understand its operational characteristics and impact. I need to determine how it interacts with the broader BPF infrastructure, particularly around scheduling and context switches. It is vital to fully appreciate the impact of this function within the system. Let's dig in and see what's actually going on. |
| 10/2 | 2026/09/30 08:54 | tool | grepper |
0mArgs:{
"Expression": "bpf_task_work_cancel_scheduled",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/helpers.c=4440=static void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx,\n--\nkernel/bpf/helpers.c-4451-\t * have reset it), while in the asynchronous cancellation path\nkernel/bpf/helpers.c:4452:\t * ctx-\u003etask is stable, see bpf_task_work_cancel_scheduled().\nkernel/bpf/helpers.c-4453-\t */\n--\nkernel/bpf/helpers.c=4802=__bpf_kfunc_end_defs();\nkernel/bpf/helpers.c-4803-\nkernel/bpf/helpers.c:4804:static void bpf_task_work_cancel_scheduled(struct irq_work *irq_work)\nkernel/bpf/helpers.c-4805-{\n--\nkernel/bpf/helpers.c=4818=void bpf_task_work_cancel_and_free(void *val)\n--\nkernel/bpf/helpers.c-4830-\t\t/* run in irq_work to avoid locks in NMI */\nkernel/bpf/helpers.c:4831:\t\tinit_irq_work(\u0026ctx-\u003eirq_work, bpf_task_work_cancel_scheduled);\nkernel/bpf/helpers.c-4832-\t\tirq_work_queue(\u0026ctx-\u003eirq_work);\n"
} |
| 11/2 | 2026/09/30 08:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16522 output: 37 thoughts: 27 Thoughts: Okay, let's dive into this. Here's my take on those lines: **Analyzing `kernel/bpf/helpers.c` - Lines 4800-4860** Alright, time to get into the details. I need to understand what's happening within lines 4800 to 4860 of `kernel/bpf/helpers.c`. I have a feeling this section might be dealing with a critical BPF helper function, judging by the filename. My initial scan will be for function calls, particularly ones that seem low-level and potentially problematic from a security or performance standpoint. The helper functions are the core of BPF's functionality; they're the interface between user-space BPF programs and the kernel. Understanding the intricacies of how these interact is crucial for efficient and safe BPF programming, which is something I deal with frequently. I'll be looking for any indications of resource allocation or deallocation – memory, file descriptors, network sockets. I'll need to see if there are any potential race conditions, or lock operations that may present a problem. Any interaction with kernel data structures or user-provided data will be carefully scrutinized for potential vulnerabilities. I'm also going to be examining the error handling: are there appropriate checks and returns, especially for any potentially failing operations? Do these operations handle edge cases effectively and securely? This is a key area for assessing overall program robustness. The goal is a complete understanding of the logic within this section, any potential limitations, and any security implications. I'll need to consider how this particular block fits into the broader picture of the BPF ecosystem. Time to get down to the code itself! |
| 12/2 | 2026/09/30 08:54 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4800,
"LineCount": 60
}Results: {
"Contents": "4800:\t}\n4801:\t\n4802:\t__bpf_kfunc_end_defs();\n4803:\t\n4804:\tstatic void bpf_task_work_cancel_scheduled(struct irq_work *irq_work)\n4805:\t{\n4806:\t\tstruct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);\n4807:\t\n4808:\t\t/*\n4809:\t\t * Deletion observed SCHEDULED and won against the callback's\n4810:\t\t * transition to RUNNING. The callback takes its FREED exit without\n4811:\t\t * resetting ctx-\u003etask, and the transferred map reference prevents\n4812:\t\t * destruction until cancellation completes.\n4813:\t\t */\n4814:\t\tbpf_task_work_cancel(ctx, ctx-\u003etask); /* this might put task_work callback's ref */\n4815:\t\tbpf_task_work_ctx_put(ctx); /* and here we put map's own ref that was transferred to us */\n4816:\t}\n4817:\t\n4818:\tvoid bpf_task_work_cancel_and_free(void *val)\n4819:\t{\n4820:\t\tstruct bpf_task_work_kern *twk = val;\n4821:\t\tstruct bpf_task_work_ctx *ctx;\n4822:\t\tenum bpf_task_work_state state;\n4823:\t\n4824:\t\tctx = xchg(\u0026twk-\u003ectx, NULL);\n4825:\t\tif (!ctx)\n4826:\t\t\treturn;\n4827:\t\n4828:\t\tstate = xchg(\u0026ctx-\u003estate, BPF_TW_FREED);\n4829:\t\tif (state == BPF_TW_SCHEDULED) {\n4830:\t\t\t/* run in irq_work to avoid locks in NMI */\n4831:\t\t\tinit_irq_work(\u0026ctx-\u003eirq_work, bpf_task_work_cancel_scheduled);\n4832:\t\t\tirq_work_queue(\u0026ctx-\u003eirq_work);\n4833:\t\t\treturn;\n4834:\t\t}\n4835:\t\n4836:\t\tbpf_task_work_ctx_put(ctx); /* put bpf map's ref */\n4837:\t}\n4838:\t\n4839:\tBTF_KFUNCS_START(generic_btf_ids)\n4840:\t#ifdef CONFIG_CRASH_DUMP\n4841:\tBTF_ID_FLAGS(func, crash_kexec, KF_DESTRUCTIVE)\n4842:\t#endif\n4843:\tBTF_ID_FLAGS(func, bpf_obj_new, KF_ACQUIRE | KF_RET_NULL | KF_IMPLICIT_ARGS)\n4844:\tBTF_ID_FLAGS(func, bpf_obj_new_impl, KF_ACQUIRE | KF_RET_NULL)\n4845:\tBTF_ID_FLAGS(func, bpf_percpu_obj_new, KF_ACQUIRE | KF_RET_NULL | KF_IMPLICIT_ARGS)\n4846:\tBTF_ID_FLAGS(func, bpf_percpu_obj_new_impl, KF_ACQUIRE | KF_RET_NULL)\n4847:\tBTF_ID_FLAGS(func, bpf_obj_drop, KF_RELEASE | KF_IMPLICIT_ARGS)\n4848:\tBTF_ID_FLAGS(func, bpf_obj_drop_impl, KF_RELEASE)\n4849:\tBTF_ID_FLAGS(func, bpf_percpu_obj_drop, KF_RELEASE | KF_IMPLICIT_ARGS)\n4850:\tBTF_ID_FLAGS(func, bpf_percpu_obj_drop_impl, KF_RELEASE)\n4851:\tBTF_ID_FLAGS(func, bpf_refcount_acquire,\n4852:\t\t KF_ACQUIRE | KF_RET_NULL | KF_RCU | KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)\n4853:\tBTF_ID_FLAGS(func, bpf_refcount_acquire_impl,\n4854:\t\t KF_ACQUIRE | KF_RET_NULL | KF_RCU | KF_SPINLOCK_SAFE)\n4855:\tBTF_ID_FLAGS(func, bpf_list_push_front, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)\n4856:\tBTF_ID_FLAGS(func, bpf_list_push_front_impl, KF_SPINLOCK_SAFE)\n4857:\tBTF_ID_FLAGS(func, bpf_list_push_back, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)\n4858:\tBTF_ID_FLAGS(func, bpf_list_push_back_impl, KF_SPINLOCK_SAFE)\n4859:\tBTF_ID_FLAGS(func, bpf_list_add, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)\n"
} |
| 13/2 | 2026/09/30 08:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 18280 output: 30 thoughts: 1835 Thoughts: **Analyzing a BPF Task Work Concurrency Patch** Okay, so here's what's running through my head. I'm looking at this patch modifying `kernel/bpf/helpers.c`, specifically around the `bpf_task_work` implementation. The core of it seems to be about refining the concurrency and lifetime management of `bpf_task_work` contexts. It looks like they've added more checks, tweaked the cancel logic, and introduced some new states and transitions (STANDBY, PENDING, SCHEDULED, FREED, BUSY). My immediate thought is that this is a *very* interesting area for fuzzing. It's touching the heart of BPF, which is definitely a core subsystem according to the criteria. Any modifications to concurrency, locking, and lifetime management in BPF are prime candidates for finding subtle races, use-after-frees, or other memory safety issues. Given my experience, I know how complex the interactions between BPF programs and the kernel can become. I'm seeing changes to `bpf_task_work_cancel`, `bpf_task_work_irq`, `bpf_task_work_acquire_ctx`, and `bpf_task_work_schedule`. These are critical functions that handle the scheduling and cleanup of task work within a BPF context. The introduction of `irq_work_is_busy` and the careful ordering of state checks suggest that the original code had potential race conditions that are now being addressed. The patch includes additions to `tools/testing/selftests/bpf/prog_tests/test_task_work.c` and `tools/testing/selftests/bpf/progs/task_work_race.c`. This is promising, because these tests should exercise the new code paths. I wonder if they’re sufficient to cover the subtle races. The changes involve restructuring of data structures (the `bpf_task_work_ctx`), and code flow around scheduling and cancellation. The core point here is that this patch changes how the BPF subsystem handles task work. Because BPF programs are executable code that is loadable, it’s definitely reachable in a standard virtualized environment. Now, I'm thinking I should dig into the commit message and look for details and the design decisions. My experience tells me that these types of patches often have obscure corner cases that could be exposed through fuzzing. Given the BPF’s reachability, this seems like a solid candidate for a fuzzing campaign to identify concurrency bugs, memory corruption, and other security flaws. |
| 14/2 | 2026/09/30 08:55 | tool | grepper |
0mArgs:{
"Expression": "task_work_off",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/btf.c=4134=struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type *t,\n--\nkernel/bpf/btf.c-4160-\trec-\u003erefcount_off = -EINVAL;\nkernel/bpf/btf.c:4161:\trec-\u003etask_work_off = -EINVAL;\nkernel/bpf/btf.c-4162-\tfor (i = 0; i \u003c cnt; i++) {\n--\nkernel/bpf/btf.c-4201-\t\tcase BPF_TASK_WORK:\nkernel/bpf/btf.c:4202:\t\t\tWARN_ON_ONCE(rec-\u003etask_work_off \u003e= 0);\nkernel/bpf/btf.c:4203:\t\t\trec-\u003etask_work_off = rec-\u003efields[i].offset;\nkernel/bpf/btf.c-4204-\t\t\tbreak;\n--\nkernel/bpf/helpers.c=4628=static int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work *tw,\n--\nkernel/bpf/helpers.c-4657-\tctx-\u003emap = map;\nkernel/bpf/helpers.c:4658:\tctx-\u003emap_val = (void *)tw - map-\u003erecord-\u003etask_work_off;\nkernel/bpf/helpers.c-4659-\tinit_task_work(\u0026ctx-\u003ework, bpf_task_work_callback);\n--\nkernel/bpf/syscall.c=802=void bpf_obj_free_task_work(const struct btf_record *rec, void *obj)\n--\nkernel/bpf/syscall.c-805-\t\treturn;\nkernel/bpf/syscall.c:806:\tbpf_task_work_cancel_and_free(obj + rec-\u003etask_work_off);\nkernel/bpf/syscall.c-807-}\n--\nkernel/bpf/verifier.c=7455=static int check_map_field_pointer(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n--\nkernel/bpf/verifier.c-7484-\tcase BPF_TASK_WORK:\nkernel/bpf/verifier.c:7485:\t\tfield_off = map-\u003erecord-\u003etask_work_off;\nkernel/bpf/verifier.c-7486-\t\tbreak;\n--\nkernel/bpf/verifier.c=8686=static int process_map_ptr_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-8708-\t\t\tobj_name = \"timer\";\nkernel/bpf/verifier.c:8709:\t\telse if (rec-\u003etask_work_off \u003e= 0)\nkernel/bpf/verifier.c-8710-\t\t\tobj_name = \"bpf_task_work\";\n"
} |
| 15/2 | 2026/09/30 08:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 21466 output: 32 thoughts: 24 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Examining the BPF Helper Compilation Process** Alright, let's dive right in. My first thought is to zero in on how `helpers.c` gets built, specifically within the BPF subsystem of the kernel. The most logical place to start that investigation is the `Makefile` located at `kernel/bpf/Makefile`. That's where I expect to find the rules and dependencies that govern the compilation process for all the BPF-related code, including the crucial helper functions. I need to understand the flags, any custom build targets, or specific include paths used for that build, as this might be key to my current issue. This Makefile should provide all the necessary clues. I'm hoping it's straightforward, but given the nuances of kernel builds, I'm prepared for a bit of digging if necessary. I'll be looking specifically for any unusual compilation steps or potentially custom linker commands that are relevant to generating the BPF helper object files. |
| 16/2 | 2026/09/30 08:55 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/Makefile",
"FirstLine": 1,
"LineCount": 50
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\tobj-y := core.o\n 3:\tifneq ($(CONFIG_BPF_JIT_ALWAYS_ON),y)\n 4:\t# ___bpf_prog_run() needs GCSE disabled on x86; see 3193c0836f203 for details\n 5:\tcflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse\n 6:\tendif\n 7:\tCFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)\n 8:\t\n 9:\tobj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o diagnostics.o\n 10:\tobj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o\n 11:\tobj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o\n 12:\tobj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o\n 13:\tobj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o\n 14:\tobj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o backtrack.o check_btf.o\n 15:\tobj-${CONFIG_BPF_LSM}\t += bpf_inode_storage.o\n 16:\tobj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o\n 17:\tobj-$(CONFIG_BPF_JIT) += trampoline.o\n 18:\tobj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o stream.o\n 19:\tifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\n 20:\tobj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\n 21:\tendif\n 22:\tobj-$(CONFIG_BPF_JIT) += dispatcher.o\n 23:\tifeq ($(CONFIG_NET),y)\n 24:\tobj-$(CONFIG_BPF_SYSCALL) += devmap.o\n 25:\tobj-$(CONFIG_BPF_SYSCALL) += cpumap.o\n 26:\tobj-$(CONFIG_BPF_SYSCALL) += offload.o\n 27:\tobj-$(CONFIG_BPF_SYSCALL) += net_namespace.o\n 28:\tobj-$(CONFIG_BPF_SYSCALL) += tcx.o\n 29:\tendif\n 30:\tifeq ($(CONFIG_PERF_EVENTS),y)\n 31:\tobj-$(CONFIG_BPF_SYSCALL) += stackmap.o\n 32:\tendif\n 33:\tifeq ($(CONFIG_CGROUPS),y)\n 34:\tobj-$(CONFIG_BPF_SYSCALL) += cgroup_iter.o bpf_cgrp_storage.o\n 35:\tendif\n 36:\tobj-$(CONFIG_CGROUP_BPF) += cgroup.o\n 37:\tifeq ($(CONFIG_INET),y)\n 38:\tobj-$(CONFIG_BPF_SYSCALL) += reuseport_array.o\n 39:\tendif\n 40:\tifeq ($(CONFIG_SYSFS),y)\n 41:\tobj-$(CONFIG_DEBUG_INFO_BTF) += sysfs_btf.o\n 42:\tendif\n 43:\tifeq ($(CONFIG_BPF_JIT),y)\n 44:\tobj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o\n 45:\tobj-$(CONFIG_BPF_SYSCALL) += cpumask.o\n 46:\t# bpf_lsm_proto.o must precede bpf_lsm.o. The current pahole logic\n 47:\t# deduplicates function prototypes within\n 48:\t# btf_encoder__add_saved_func() by keeping the first instance seen. We\n 49:\t# need the function prototype(s) in bpf_lsm_proto.o to take precedence\n 50:\t# over those within bpf_lsm.o. Having bpf_lsm_proto.o precede\n"
} |
| 17/2 | 2026/09/30 08:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22941 output: 35 thoughts: 92 Thoughts: Okay, here's my summary, presented as a stream of thought: **Debugging Dependencies in the BPF Subsystem** Alright, let's get down to brass tacks. I'm looking at `helpers.o` and it's clear that it has a dependency on `CONFIG_BPF_SYSCALL`. No surprise there. Now, the bigger question is, does this module need either BTF or the BPF JIT compiler for kfuncs or the `bpf_task_work` feature to function correctly? I need to clarify those dependencies. So, where are `bpf_task_work_schedule_signal` and `bpf_task_work_schedule_resume` actually *registered*? I need to trace their usage. The best way to do that is to grep for `bpf_task_work_schedule_signal` and see where it shows up. That should give me the entry points. Let's see what that uncovers... |
| 18/2 | 2026/09/30 08:55 | tool | grepper |
0mArgs:{
"Expression": "bpf_task_work_schedule_signal",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/helpers.c=4628=static int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work *tw,\n--\nkernel/bpf/helpers.c-4671-/**\nkernel/bpf/helpers.c:4672: * bpf_task_work_schedule_signal - Schedule BPF callback using task_work_add with TWA_SIGNAL\nkernel/bpf/helpers.c-4673- * mode\n--\nkernel/bpf/helpers.c-4681- */\nkernel/bpf/helpers.c:4682:__bpf_kfunc int bpf_task_work_schedule_signal(struct task_struct *task, struct bpf_task_work *tw,\nkernel/bpf/helpers.c-4683-\t\t\t\t\t void *map__const_map, bpf_task_work_callback_t callback,\n--\nkernel/bpf/helpers.c=5003=BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE)\nkernel/bpf/helpers.c:5004:BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS)\nkernel/bpf/helpers.c-5005-BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS)\n--\nkernel/bpf/verifier.c=11678=enum special_kfunc_type {\n--\nkernel/bpf/verifier.c-11740-\tKF___bpf_trap,\nkernel/bpf/verifier.c:11741:\tKF_bpf_task_work_schedule_signal,\nkernel/bpf/verifier.c-11742-\tKF_bpf_task_work_schedule_resume,\n--\nkernel/bpf/verifier.c=11830=BTF_ID(func, __bpf_trap)\nkernel/bpf/verifier.c:11831:BTF_ID(func, bpf_task_work_schedule_signal)\nkernel/bpf/verifier.c-11832-BTF_ID(func, bpf_task_work_schedule_resume)\n--\nkernel/bpf/verifier.c=11886=static bool is_task_work_add_kfunc(u32 func_id)\nkernel/bpf/verifier.c-11887-{\nkernel/bpf/verifier.c:11888:\treturn func_id == special_kfunc_list[KF_bpf_task_work_schedule_signal] ||\nkernel/bpf/verifier.c-11889-\t func_id == special_kfunc_list[KF_bpf_task_work_schedule_resume];\n"
} |
| 19/2 | 2026/09/30 08:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24001 output: 21 thoughts: 80 Thoughts: Okay, let's break this down. **Investigating the Configuration Landscape for eBPF Kfuncs, BTF, and BPF_SYSCALL Integration** Right, so I'm thinking about the necessary kernel configurations to get kfuncs, specifically with BTF support, working seamlessly in my eBPF programs. My immediate thought is that `CONFIG_BPF_SYSCALL` is a given, obviously we need that to use the syscall interface. Then, I seem to recall that we'll likely need `CONFIG_DEBUG_INFO_BTF` too, to ensure we have the necessary BTF data available, which is critical. To confirm this, I'll need to go directly to the source. Time to dig into the kernel's configuration files. I'll check `kernel/bpf/Kconfig` first, it's the obvious place to start for anything BPF-related. If it's not explicitly defined there, I'll move on to `lib/Kconfig.debug`, as that's where other debug-related options are typically managed. I'm pretty sure I'll find my answers there. |
| 20/2 | 2026/09/30 08:55 | tool | grepper |
0mArgs:{
"Expression": "config DEBUG_INFO_BTF"
}Results: {
"Output": "lib/Kconfig.debug=378=config DEBUG_INFO_SPLIT\n--\nlib/Kconfig.debug-397-\nlib/Kconfig.debug:398:config DEBUG_INFO_BTF\nlib/Kconfig.debug-399-\tbool \"Generate BTF type information\"\n--\nlib/Kconfig.debug=419=config PAHOLE_HAS_LANG_EXCLUDE\n--\nlib/Kconfig.debug-427-\nlib/Kconfig.debug:428:config DEBUG_INFO_BTF_MODULES\nlib/Kconfig.debug-429-\tbool \"Generate BTF type information for kernel modules\"\n"
} |
| 21/2 | 2026/09/30 08:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24516 output: 21 thoughts: 13 Thoughts: Okay, here's my summarized thought process, keeping in mind the context: **Initial Assessment: Kconfig File for BPF** Right, let's dive straight in. The first thing I need to do to understand this situation is to check the `kernel/bpf/Kconfig` file. Given my expertise, I'm already anticipating what I might find. I'm looking for the configuration options that govern the BPF (Berkeley Packet Filter) subsystem within the kernel. I'm expecting to see definitions for enabling/disabling BPF, specific BPF features, and potentially options related to security or resource limits. This Kconfig file will be the central point for configuring the BPF aspect of the kernel. I need to understand what aspects are already configured and also what can be turned on or off. That file should define the various knobs and dials that control BPF's behaviour. Let's start there. |
| 22/2 | 2026/09/30 08:55 | tool | grepper |
0mArgs:{
"Expression": "config BPF_SYSCALL"
}Results: {
"Output": "kernel/bpf/Kconfig=25=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-26-\nkernel/bpf/Kconfig:27:config BPF_SYSCALL\nkernel/bpf/Kconfig-28-\tbool \"Enable bpf() system call\"\n"
} |
| 23/2 | 2026/09/30 08:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24826 output: 34 thoughts: 19 Thoughts: Here's my take on those thoughts: **Navigating the Kernel's BPF Configuration** Okay, so I need to dive into the kernel configuration for BPF. My initial instinct is to go straight to `kernel/bpf/Kconfig`. That's usually the place to start when I'm dealing with kernel configuration options related to BPF. I'm specifically thinking about around line 27. It's a quick way to zero in on the relevant sections, and with my experience, it will immediately provide valuable information. It should guide my investigation further. |
| 24/2 | 2026/09/30 08:55 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/Kconfig",
"FirstLine": 25,
"LineCount": 40
}Results: {
"Contents": " 25:\tmenu \"BPF subsystem\"\n 26:\t\n 27:\tconfig BPF_SYSCALL\n 28:\t\tbool \"Enable bpf() system call\"\n 29:\t\tselect BPF\n 30:\t\tselect IRQ_WORK\n 31:\t\tselect NEED_TASKS_RCU\n 32:\t\tselect TASKS_TRACE_RCU\n 33:\t\tselect BINARY_PRINTF\n 34:\t\tselect NET_SOCK_MSG if NET\n 35:\t\tselect NET_XGRESS if NET\n 36:\t\tselect PAGE_POOL if NET\n 37:\t\tdefault n\n 38:\t\thelp\n 39:\t\t Enable the bpf() system call that allows to manipulate BPF programs\n 40:\t\t and maps via file descriptors.\n 41:\t\n 42:\tconfig BPF_JIT\n 43:\t\tbool \"Enable BPF Just In Time compiler\"\n 44:\t\tdepends on BPF\n 45:\t\tdepends on HAVE_CBPF_JIT || HAVE_EBPF_JIT\n 46:\t\tselect EXECMEM\n 47:\t\thelp\n 48:\t\t BPF programs are normally handled by a BPF interpreter. This option\n 49:\t\t allows the kernel to generate native code when a program is loaded\n 50:\t\t into the kernel. This will significantly speed-up processing of BPF\n 51:\t\t programs.\n 52:\t\n 53:\t\t Note, an admin should enable this feature changing:\n 54:\t\t /proc/sys/net/core/bpf_jit_enable\n 55:\t\t /proc/sys/net/core/bpf_jit_harden (optional)\n 56:\t\t /proc/sys/net/core/bpf_jit_kallsyms (optional)\n 57:\t\n 58:\tconfig BPF_JIT_ALWAYS_ON\n 59:\t\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n 60:\t\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\n 61:\t\thelp\n 62:\t\t Enables BPF JIT and removes BPF interpreter to avoid speculative\n 63:\t\t execution of BPF instructions by the interpreter.\n 64:\t\n"
} |
| 25/2 | 2026/09/30 08:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25585 output: 31 thoughts: 1493 Thoughts: **Analyzing BPF Task Work for Fuzzing Potential** Okay, let's break this down systematically. I need to figure out which functions in `kernel/bpf/helpers.c` are relevant and whether the changes warrant fuzzing. First, I've identified the modified/added functions: `bpf_task_work_cancel`, `bpf_task_work_irq`, `bpf_task_work_fetch_ctx`, `bpf_task_work_acquire_ctx`, `bpf_task_work_schedule`, `bpf_task_work_cancel_scheduled`, `bpf_task_work_callback`, and `bpf_task_work_cancel_and_free`. I know static functions usually have symbols, unless inlined. But `bpf_task_work_schedule_signal` and `bpf_task_work_schedule_resume` are definitely global symbols, and `bpf_task_work_cancel_and_free` is global too. Based on Rule 4, I want entrypoints for the feature or specific logic, avoiding ubiquitous functions. I think `bpf_task_work_schedule_signal`, `bpf_task_work_schedule_resume`, and `bpf_task_work_cancel_and_free` are excellent choices, plus the other ones like `bpf_task_work_irq`, `bpf_task_work_cancel`, `bpf_task_work_acquire_ctx`, `bpf_task_work_callback`, and `bpf_task_work_cancel_scheduled` which implement the changed logic. Since `bpf_task_work_schedule_signal` and `bpf_task_work_schedule_resume` aren't ubiquitous, they are good. `bpf_obj_free_task_work` in `kernel/bpf/syscall.c` calls `bpf_task_work_cancel_and_free`, so that's relevant. So, here's my list for now: * `bpf_task_work_schedule_signal` * `bpf_task_work_schedule_resume` * `bpf_task_work_irq` * `bpf_task_work_cancel_scheduled` * `bpf_task_work_cancel_and_free` * `bpf_task_work_acquire_ctx` * `bpf_task_work_cancel` Now, let's determine if this is worth fuzzing. The changes involve concurrency control, state transitions, and memory lifecycle within BPF, which are major positives for fuzzing. Specifically, it fixes race conditions between `bpf_task_work_irq` and `bpf_task_work_callback`, safely reuses `ctx->task`, adds an `irq_work_is_busy` check, and moves `init_irq_work`. These modifications impact core BPF code, reachable via user-space syscalls like the `bpf()` system call and the `bpf_task_work_schedule_*` kfuncs. The changes directly affect executable code in a critical subsystem. Therefore, `WorthFuzzing` is unequivocally `true`. Finally, I must ensure I have the correct configurations: `BPF_SYSCALL` (since we're dealing with syscalls) and `DEBUG_INFO_BTF` (essential for kfuncs and BPF type information, especially for `bpf_task_work`). I'll double-check exactly how BTF parsing of `bpf_task_work` is guarded, just to be sure. |
| 26/2 | 2026/09/30 08:55 | tool | grepper |
0mArgs:{
"Expression": "BPF_TASK_WORK",
"PathPrefix": "kernel/bpf/"
}Results: {
"Output": "kernel/bpf/btf.c=3669=static int btf_get_field_type(const struct btf *btf, const struct btf_type *var_type,\n--\nkernel/bpf/btf.c-3680-\t\t{ BPF_WORKQUEUE, \"bpf_wq\", true },\nkernel/bpf/btf.c:3681:\t\t{ BPF_TASK_WORK, \"bpf_task_work\", true },\nkernel/bpf/btf.c-3682-\t\t{ BPF_LIST_HEAD, \"bpf_list_head\", false },\n--\nkernel/bpf/btf.c=3809=static int btf_find_field_one(const struct btf *btf,\n--\nkernel/bpf/btf.c-3865-\tcase BPF_REFCOUNT:\nkernel/bpf/btf.c:3866:\tcase BPF_TASK_WORK:\nkernel/bpf/btf.c-3867-\t\tret = btf_find_struct(btf, var_type, off, sz, field_type,\n--\nkernel/bpf/btf.c=4134=struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type *t,\n--\nkernel/bpf/btf.c-4200-\t\t\tbreak;\nkernel/bpf/btf.c:4201:\t\tcase BPF_TASK_WORK:\nkernel/bpf/btf.c-4202-\t\t\tWARN_ON_ONCE(rec-\u003etask_work_off \u003e= 0);\n--\nkernel/bpf/helpers.c=5067=void bpf_map_free_internal_structs(struct bpf_map *map, void *val)\n--\nkernel/bpf/helpers.c-5072-\t\tbpf_obj_free_workqueue(map-\u003erecord, val);\nkernel/bpf/helpers.c:5073:\tif (btf_record_has_field(map-\u003erecord, BPF_TASK_WORK))\nkernel/bpf/helpers.c-5074-\t\tbpf_obj_free_task_work(map-\u003erecord, val);\n--\nkernel/bpf/syscall.c=663=void btf_record_free(struct btf_record *rec)\n--\nkernel/bpf/syscall.c-688-\t\tcase BPF_WORKQUEUE:\nkernel/bpf/syscall.c:689:\t\tcase BPF_TASK_WORK:\nkernel/bpf/syscall.c-690-\t\t\t/* Nothing to release */\n--\nkernel/bpf/syscall.c=706=struct btf_record *btf_record_dup(const struct btf_record *rec)\n--\nkernel/bpf/syscall.c-742-\t\tcase BPF_WORKQUEUE:\nkernel/bpf/syscall.c:743:\t\tcase BPF_TASK_WORK:\nkernel/bpf/syscall.c-744-\t\t\t/* Nothing to acquire */\n--\nkernel/bpf/syscall.c=802=void bpf_obj_free_task_work(const struct btf_record *rec, void *obj)\nkernel/bpf/syscall.c-803-{\nkernel/bpf/syscall.c:804:\tif (WARN_ON_ONCE(!btf_record_has_field(rec, BPF_TASK_WORK)))\nkernel/bpf/syscall.c-805-\t\treturn;\n--\nkernel/bpf/syscall.c=814=void bpf_obj_free_fields(const struct btf_record *rec, void *obj)\n--\nkernel/bpf/syscall.c-837-\t\t\tbreak;\nkernel/bpf/syscall.c:838:\t\tcase BPF_TASK_WORK:\nkernel/bpf/syscall.c-839-\t\t\tbpf_task_work_cancel_and_free(field_ptr);\n--\nkernel/bpf/syscall.c=1255=static int map_check_btf(struct bpf_map *map, struct bpf_token *token,\n--\nkernel/bpf/syscall.c-1279-\t\t\t\t BPF_RB_ROOT | BPF_REFCOUNT | BPF_WORKQUEUE | BPF_UPTR |\nkernel/bpf/syscall.c:1280:\t\t\t\t BPF_TASK_WORK,\nkernel/bpf/syscall.c-1281-\t\t\t\t map-\u003evalue_size);\n--\nkernel/bpf/syscall.c-1312-\t\t\tcase BPF_WORKQUEUE:\nkernel/bpf/syscall.c:1313:\t\t\tcase BPF_TASK_WORK:\nkernel/bpf/syscall.c-1314-\t\t\t\tif (map-\u003emap_type != BPF_MAP_TYPE_HASH \u0026\u0026\n--\nkernel/bpf/verifier.c=1920=static void refine_map_lookup_value(struct bpf_reg_state *reg)\n--\nkernel/bpf/verifier.c-1931-\t\tif (btf_record_has_field(map-\u003einner_map_meta-\u003erecord,\nkernel/bpf/verifier.c:1932:\t\t\t\t\t BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK))\nkernel/bpf/verifier.c-1933-\t\t\treg-\u003emap_uid = reg-\u003eid;\n--\nkernel/bpf/verifier.c=7455=static int check_map_field_pointer(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n--\nkernel/bpf/verifier.c-7483-\t\tbreak;\nkernel/bpf/verifier.c:7484:\tcase BPF_TASK_WORK:\nkernel/bpf/verifier.c-7485-\t\tfield_off = map-\u003erecord-\u003etask_work_off;\n--\nkernel/bpf/verifier.c=12659=static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-13306-\t\t\t}\nkernel/bpf/verifier.c:13307:\t\t\tret = check_map_field_pointer(env, reg, argno, BPF_TASK_WORK, \u0026meta-\u003emap);\nkernel/bpf/verifier.c-13308-\t\t\tif (ret \u003c 0)\n"
} |
| 27/2 | 2026/09/30 08:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 29527 output: 194 thoughts: 185 Thoughts: Okay, here's my understanding, formatted as requested: **Internal Review of BPF Task Work Symbols** Right, so after a quick check, it's confirmed: `BPF_SYSCALL` and `DEBUG_INFO_BTF` are definitely the crucial configurations. No surprises there. Now, let's just run through the FocusSymbols again to make sure everything's in order, a quick sanity check before we move on. The ones flagged for review are: * `bpf_task_work_schedule_signal` * `bpf_task_work_schedule_resume` * `bpf_task_work_cancel_and_free` * `bpf_task_work_irq` * `bpf_task_work_acquire_ctx` * `bpf_task_work_cancel` Hmm, just to be absolutely thorough, are we missing anything obvious? Ah, yes, two more: * `bpf_task_work_callback` * `bpf_task_work_cancel_scheduled` Okay, that's a complete list. Good. These are all *very* specific to the task\_work functionality within BPF, which is what we expected. Alright, that's clean. Nothing jumps out as problematic or violating any of our established guidelines. Everything checks out perfectly. Excellent. Let's go ahead and trigger the `set-results` process now. |
| 28/2 | 2026/09/30 08:55 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF"
],
"FocusSymbols": [
"bpf_task_work_schedule_signal",
"bpf_task_work_schedule_resume",
"bpf_task_work_cancel_and_free",
"bpf_task_work_irq",
"bpf_task_work_acquire_ctx",
"bpf_task_work_cancel",
"bpf_task_work_cancel_scheduled"
],
"Reasoning": "The patch modifies BPF task_work scheduling, cancellation, and synchronization logic in kernel/bpf/helpers.c to prevent race conditions during concurrent execution and map element deletion. These modifications affect reachable core kernel code exercised via the bpf() system call and BPF program execution.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF"
],
"FocusSymbols": [
"bpf_task_work_schedule_signal",
"bpf_task_work_schedule_resume",
"bpf_task_work_cancel_and_free",
"bpf_task_work_irq",
"bpf_task_work_acquire_ctx",
"bpf_task_work_cancel",
"bpf_task_work_cancel_scheduled"
],
"Reasoning": "The patch modifies BPF task_work scheduling, cancellation, and synchronization logic in kernel/bpf/helpers.c to prevent race conditions during concurrent execution and map element deletion. These modifications affect reachable core kernel code exercised via the bpf() system call and BPF program execution.",
"WorthFuzzing": true
} |
| 29/1 | 2026/09/30 08:55 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 30/2 | 2026/09/30 08:55 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch addresses concurrency race conditions in BPF task_work scheduling and cancellation (specifically, capturing the task pointer in bpf_task_work_irq before task_work publication to avoid racing with ctx-\u003etask clearing in bpf_task_work_callback, moving init_irq_work to ctx allocation time, and checking irq_work_is_busy() during acquisition). \n\nAll structures (such as bpf_task_work_ctx) continue to be fully zero-initialized with memset() upon allocation. The changes do not introduce or modify any reads of uninitialized stack or heap memory, nor do they copy any kernel structures or padding to user space. Potential issues arising from these concurrency and lifetime changes (such as use-after-free or NULL pointer dereferences) are covered by KASAN and standard kernel debug checks rather than KMSAN. Therefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit fa623d5d642f4cb7295b7b16abe98c52a899dfe5
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Sep 30 08:54:20 2026 +0000
syz-cluster: applied patch under review
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..350269b8646e4 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4437,15 +4437,21 @@ static void bpf_task_work_ctx_put(struct bpf_task_work_ctx *ctx)
}
}
-static void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx)
+static void bpf_task_work_cancel(struct bpf_task_work_ctx *ctx,
+ struct task_struct *task)
{
/*
* Scheduled task_work callback holds ctx ref, so if we successfully
* cancelled, we put that ref on callback's behalf. If we couldn't
* cancel, callback will inevitably run or has already completed
* running, and it would have taken care of its ctx ref itself.
+ *
+ * The caller passes the task explicitly: the scheduling handler must
+ * not read ctx->task after the work is published (the callback may
+ * have reset it), while in the asynchronous cancellation path
+ * ctx->task is stable, see bpf_task_work_cancel_scheduled().
*/
- if (task_work_cancel(ctx->task, &ctx->work))
+ if (task_work_cancel(task, &ctx->work))
bpf_task_work_ctx_put(ctx);
}
@@ -4486,6 +4492,7 @@ static void bpf_task_work_callback(struct callback_head *cb)
static void bpf_task_work_irq(struct irq_work *irq_work)
{
struct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);
+ struct task_struct *task;
enum bpf_task_work_state state;
int err;
@@ -4496,7 +4503,16 @@ static void bpf_task_work_irq(struct irq_work *irq_work)
return;
}
- err = task_work_add(ctx->task, &ctx->work, ctx->mode);
+ /*
+ * Capture the task pointer before the work is published: once the
+ * callback runs, it may reset ctx->task. The capture happens while
+ * this round's task reference is held, and every path that can
+ * release it runs after this handler entered its rcu read-side
+ * section. bpf_task_release() is put_task_struct_rcu_user(), so the
+ * task_struct itself cannot be freed before this section ends.
+ */
+ task = ctx->task;
+ err = task_work_add(task, &ctx->work, ctx->mode);
if (err) {
bpf_task_work_ctx_reset(ctx);
/*
@@ -4510,14 +4526,14 @@ static void bpf_task_work_irq(struct irq_work *irq_work)
/*
* It's technically possible for just scheduled task_work callback to
- * complete running by now, going SCHEDULING -> RUNNING and then
- * dropping its ctx refcount. Instead of capturing an extra ref just
- * to protect below ctx->state access, we rely on rcu_read_lock
- * above to prevent kfree_rcu from freeing ctx before we return.
+ * complete running by now, going SCHEDULING -> RUNNING, resetting
+ * ctx->task and dropping its ctx refcount. The rcu read-side section
+ * above keeps both the ctx memory and the captured task pointer
+ * valid until this handler returns.
*/
state = cmpxchg(&ctx->state, BPF_TW_SCHEDULING, BPF_TW_SCHEDULED);
if (state == BPF_TW_FREED)
- bpf_task_work_cancel(ctx); /* clean up if we switched into FREED state */
+ bpf_task_work_cancel(ctx, task); /* clean up if we switched into FREED state */
}
static struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *tw,
@@ -4537,6 +4553,7 @@ static struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *t
memset(ctx, 0, sizeof(*ctx));
refcount_set(&ctx->refcnt, 1); /* map's own ref */
ctx->state = BPF_TW_STANDBY;
+ init_irq_work(&ctx->irq_work, bpf_task_work_irq);
old_ctx = cmpxchg(&twk->ctx, NULL, ctx);
if (old_ctx) {
@@ -4579,6 +4596,18 @@ static struct bpf_task_work_ctx *bpf_task_work_acquire_ctx(struct bpf_task_work
bpf_task_work_ctx_put(ctx);
return ERR_PTR(-EBUSY);
}
+ /*
+ * STANDBY can be published before the previous scheduling irq_work
+ * returns. Claim PENDING before checking BUSY to order the check after
+ * that publication. No irq_work has been queued for this attempt yet,
+ * so reject reuse and roll back to STANDBY while BUSY is set. Preserve
+ * FREED if deletion wins the rollback race.
+ */
+ if (unlikely(irq_work_is_busy(&ctx->irq_work))) {
+ (void)cmpxchg(&ctx->state, BPF_TW_PENDING, BPF_TW_STANDBY);
+ bpf_task_work_ctx_put(ctx);
+ return ERR_PTR(-EBUSY);
+ }
/*
* If no process or bpffs is holding a reference to the map, no new callbacks should be
@@ -4628,7 +4657,6 @@ static int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work
ctx->map = map;
ctx->map_val = (void *)tw - map->record->task_work_off;
init_task_work(&ctx->work, bpf_task_work_callback);
- init_irq_work(&ctx->irq_work, bpf_task_work_irq);
irq_work_queue(&ctx->irq_work);
return 0;
@@ -4777,7 +4805,13 @@ static void bpf_task_work_cancel_scheduled(struct irq_work *irq_work)
{
struct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);
- bpf_task_work_cancel(ctx); /* this might put task_work callback's ref */
+ /*
+ * Deletion observed SCHEDULED and won against the callback's
+ * transition to RUNNING. The callback takes its FREED exit without
+ * resetting ctx->task, and the transferred map reference prevents
+ * destruction until cancellation completes.
+ */
+ bpf_task_work_cancel(ctx, ctx->task); /* this might put task_work callback's ref */
bpf_task_work_ctx_put(ctx); /* and here we put map's own ref that was transferred to us */
}
diff --git a/tools/testing/selftests/bpf/prog_tests/test_task_work.c b/tools/testing/selftests/bpf/prog_tests/test_task_work.c
index 774b31a5f6ca1..ff56976f4226e 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_task_work.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_task_work.c
@@ -5,10 +5,14 @@
#include <stdio.h>
#include "task_work.skel.h"
#include "task_work_fail.skel.h"
+#include "task_work_race.skel.h"
#include <linux/bpf.h>
#include <linux/perf_event.h>
#include <sys/syscall.h>
#include <time.h>
+#include <pthread.h>
+#include <sched.h>
+#include <unistd.h>
static int perf_event_open(__u32 type, __u64 config, int pid)
{
@@ -155,3 +159,282 @@ void test_task_work(void)
RUN_TESTS(task_work_fail);
}
+
+#define TASK_WORK_RACE_ROUNDS 1500
+
+/* Must match progs/task_work_race.c. */
+enum task_work_race_status {
+ RACE_ARM_SEQ,
+ RACE_READY_SEQ,
+ /* High 32 bits hold the sequence, low 32 bits the signed error. */
+ RACE_RESULT,
+};
+
+struct task_work_race_value {
+ __u32 seq;
+ char data[60];
+ struct bpf_task_work tw;
+};
+
+struct task_work_race_ctx {
+ int stop;
+ int setup_err;
+ int trigger_tid;
+ int target_tid;
+ int trigger_cpu;
+ int target_cpu;
+};
+
+static int task_work_race_pin_cpu(int cpu)
+{
+ cpu_set_t set;
+
+ CPU_ZERO(&set);
+ CPU_SET(cpu, &set);
+ return pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
+}
+
+static void *task_work_race_trigger(void *arg)
+{
+ struct task_work_race_ctx *ctx = arg;
+ int err;
+
+ err = task_work_race_pin_cpu(ctx->trigger_cpu);
+ if (err)
+ __atomic_store_n(&ctx->setup_err, err, __ATOMIC_RELEASE);
+ __atomic_store_n(&ctx->trigger_tid, syscall(__NR_gettid),
+ __ATOMIC_RELEASE);
+ while (!__atomic_load_n(&ctx->stop, __ATOMIC_ACQUIRE))
+ getppid();
+ return NULL;
+}
+
+static void *task_work_race_target(void *arg)
+{
+ struct task_work_race_ctx *ctx = arg;
+ int err;
+
+ err = task_work_race_pin_cpu(ctx->target_cpu);
+ if (err)
+ __atomic_store_n(&ctx->setup_err, err, __ATOMIC_RELEASE);
+ __atomic_store_n(&ctx->target_tid, syscall(__NR_gettid),
+ __ATOMIC_RELEASE);
+ while (!__atomic_load_n(&ctx->stop, __ATOMIC_ACQUIRE))
+ getppid();
+ return NULL;
+}
+
+static int task_work_race_status(struct task_work_race *skel, int idx,
+ __s64 *value)
+{
+ return bpf_map_lookup_elem(bpf_map__fd(skel->maps.status), &idx,
+ value);
+}
+
+static int task_work_race_set_status(struct task_work_race *skel, int idx,
+ __s64 value)
+{
+ return bpf_map_update_elem(bpf_map__fd(skel->maps.status), &idx,
+ &value, BPF_ANY);
+}
+
+static int task_work_race_wait_ready(struct task_work_race *skel, __u32 seq)
+{
+ int i;
+
+ for (i = 0; i < 2000000 / 100; i++) {
+ __s64 value = 0;
+
+ if (!task_work_race_status(skel, RACE_READY_SEQ, &value) &&
+ value == seq)
+ return 0;
+ if (!task_work_race_status(skel, RACE_RESULT, &value) &&
+ (__u32)((__u64)value >> 32) == seq)
+ /*
+ * The BPF program finished between the two reads:
+ * RESULT makes waiting for READY unnecessary. Any error
+ * that occurred before READY is checked by the caller.
+ */
+ return 0;
+ usleep(100);
+ }
+ return -ETIMEDOUT;
+}
+
+static int task_work_race_prepare_elem(struct task_work_race *skel)
+{
+ struct task_work_race_value value = {};
+ int key = 0;
+
+ if (!bpf_map_lookup_elem(bpf_map__fd(skel->maps.hmap), &key, &value))
+ return 0;
+ if (errno != ENOENT)
+ return -errno;
+ if (bpf_map_update_elem(bpf_map__fd(skel->maps.hmap), &key, &value,
+ BPF_NOEXIST))
+ return -errno;
+ return 0;
+}
+
+static int task_work_race_arm(struct task_work_race *skel, __u32 seq)
+{
+ if (task_work_race_set_status(skel, RACE_ARM_SEQ, seq))
+ return -errno;
+ return task_work_race_wait_ready(skel, seq);
+}
+
+static int task_work_race_check_done(struct task_work_race *skel, __u32 seq)
+{
+ int i;
+
+ for (i = 0; i < 2000000 / 100; i++) {
+ __s64 result = 0;
+
+ if (!task_work_race_status(skel, RACE_RESULT, &result) &&
+ (__u32)((__u64)result >> 32) == seq)
+ return (__s32)result;
+ usleep(100);
+ }
+ return -ETIMEDOUT;
+}
+
+static int task_work_race_wait_callback(struct task_work_race *skel,
+ __u32 seq)
+{
+ int i, key = seq;
+
+ for (i = 0; i < 2000000 / 100; i++) {
+ __u64 completed = 0;
+
+ if (!bpf_map_lookup_elem(bpf_map__fd(skel->maps.completed),
+ &key, &completed) && completed)
+ return 0;
+ usleep(100);
+ }
+ return -ETIMEDOUT;
+}
+
+void serial_test_task_work_race(void)
+{
+ struct task_work_race_ctx ctx = {};
+ struct task_work_race *skel;
+ pthread_t trigger, target;
+ cpu_set_t allowed;
+ int cpu, cpu_count = 0;
+ bool target_started = false, trigger_started = false;
+ int err, i, key = 0;
+
+ if (sched_getaffinity(0, sizeof(allowed), &allowed)) {
+ ASSERT_OK(-errno, "sched_getaffinity");
+ return;
+ }
+ for (cpu = 0; cpu < CPU_SETSIZE && cpu_count < 2; cpu++) {
+ if (!CPU_ISSET(cpu, &allowed))
+ continue;
+ if (!cpu_count)
+ ctx.trigger_cpu = cpu;
+ else
+ ctx.target_cpu = cpu;
+ cpu_count++;
+ }
+ if (cpu_count < 2) {
+ printf("%s:SKIP:need two CPUs in the process affinity mask\n",
+ __func__);
+ test__skip();
+ return;
+ }
+
+ skel = task_work_race__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+ if (!ASSERT_OK(task_work_race__attach(skel), "attach"))
+ goto cleanup;
+
+ err = pthread_create(&target, NULL, task_work_race_target, &ctx);
+ if (!ASSERT_OK(err, "pthread_create target"))
+ goto cleanup;
+ target_started = true;
+ err = pthread_create(&trigger, NULL, task_work_race_trigger, &ctx);
+ if (!ASSERT_OK(err, "pthread_create trigger"))
+ goto stop;
+ trigger_started = true;
+
+ for (i = 0; i < 5000000 / 100; i++) {
+ if (__atomic_load_n(&ctx.trigger_tid, __ATOMIC_ACQUIRE) &&
+ __atomic_load_n(&ctx.target_tid, __ATOMIC_ACQUIRE))
+ break;
+ usleep(100);
+ }
+ if (!ASSERT_TRUE(__atomic_load_n(&ctx.trigger_tid, __ATOMIC_ACQUIRE) &&
+ __atomic_load_n(&ctx.target_tid, __ATOMIC_ACQUIRE),
+ "thread start"))
+ goto stop;
+ if (!ASSERT_OK(__atomic_load_n(&ctx.setup_err, __ATOMIC_ACQUIRE),
+ "thread affinity"))
+ goto stop;
+
+ skel->bss->trigger_tid = __atomic_load_n(&ctx.trigger_tid, __ATOMIC_ACQUIRE);
+ skel->bss->target_tid = __atomic_load_n(&ctx.target_tid, __ATOMIC_ACQUIRE);
+
+ for (i = 0; i < TASK_WORK_RACE_ROUNDS; i++) {
+ __u32 seq = i + 1;
+ int variant = i % 3;
+
+ if (!ASSERT_OK(task_work_race_prepare_elem(skel), "prepare elem") ||
+ !ASSERT_OK(task_work_race_arm(skel, seq), "round ready"))
+ goto stop;
+
+ if (variant == 2 &&
+ !ASSERT_OK(bpf_map_delete_elem(bpf_map__fd(skel->maps.hmap),
+ &key), "early delete"))
+ goto stop;
+
+ err = task_work_race_check_done(skel, seq);
+ /*
+ * Variant 2 deletes the element right after READY, so losing
+ * the race against the scheduling kfunc is a valid outcome:
+ * the schedule itself fails with -EBUSY (ctx already FREED),
+ * and the retry on the next tracepoint invocation fails with
+ * -ENOENT because the element is gone. Either way the round
+ * exercised the deletion paths; a later variant 0 round
+ * recreates the element.
+ */
+ if (variant == 2 && (err == -EBUSY || err == -ENOENT))
+ err = 0;
+ if (!ASSERT_OK(err, "schedule")) {
+ fprintf(stderr, "round %d schedule failed: %d\n", i, err);
+ goto stop;
+ }
+
+ if (variant != 2 &&
+ !ASSERT_OK(task_work_race_wait_callback(skel, seq),
+ "callback"))
+ goto stop;
+
+ if (variant == 1 &&
+ !ASSERT_OK(bpf_map_delete_elem(bpf_map__fd(skel->maps.hmap),
+ &key), "late delete"))
+ goto stop;
+ }
+
+ /* A distinct completion generation prevents an old callback satisfying this. */
+ if (!ASSERT_OK(task_work_race_prepare_elem(skel), "final prepare") ||
+ !ASSERT_OK(task_work_race_arm(skel, TASK_WORK_RACE_ROUNDS + 1),
+ "final ready") ||
+ !ASSERT_OK(task_work_race_check_done(skel,
+ TASK_WORK_RACE_ROUNDS + 1),
+ "final schedule") ||
+ !ASSERT_OK(task_work_race_wait_callback(skel,
+ TASK_WORK_RACE_ROUNDS + 1),
+ "final callback"))
+ goto stop;
+
+stop:
+ __atomic_store_n(&ctx.stop, 1, __ATOMIC_RELEASE);
+ if (trigger_started)
+ pthread_join(trigger, NULL);
+ if (target_started)
+ pthread_join(target, NULL);
+cleanup:
+ task_work_race__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/task_work_race.c b/tools/testing/selftests/bpf/progs/task_work_race.c
new file mode 100644
index 0000000000000..c94eca510cb37
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/task_work_race.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 KylinSoft Corporation. */
+#include <vmlinux.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+#define TASK_WORK_RACE_MAX_SEQ 2048
+
+enum task_work_race_status {
+ RACE_ARM_SEQ,
+ RACE_READY_SEQ,
+ /* High 32 bits hold the sequence, low 32 bits the signed error. */
+ RACE_RESULT,
+ RACE_STATUS_MAX,
+};
+
+int trigger_tid;
+int target_tid;
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, RACE_STATUS_MAX);
+ __type(key, int);
+ __type(value, __s64);
+} status SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, TASK_WORK_RACE_MAX_SEQ);
+ __type(key, int);
+ __type(value, __u64);
+} completed SEC(".maps");
+
+struct task_work_race_value {
+ __u32 seq;
+ char data[60];
+ struct bpf_task_work tw;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __uint(max_entries, 4);
+ __type(key, int);
+ __type(value, struct task_work_race_value);
+} hmap SEC(".maps");
+
+static __always_inline void set_status(int key, __s64 value)
+{
+ __s64 *slot;
+
+ slot = bpf_map_lookup_elem(&status, &key);
+ if (slot)
+ *slot = value;
+}
+
+static int process_work(struct bpf_map *map, void *key, void *value)
+{
+ struct task_work_race_value *work = value;
+ __u64 *done;
+ int seq = work->seq;
+
+ if (seq <= 0 || seq >= TASK_WORK_RACE_MAX_SEQ)
+ return 0;
+ done = bpf_map_lookup_elem(&completed, &seq);
+ if (done)
+ *done = 1;
+ return 0;
+}
+
+SEC("tracepoint/syscalls/sys_enter_getppid")
+int race_sched_work(void *ctx)
+{
+ struct task_work_race_value *work;
+ struct task_struct *task;
+ __s64 *arm, *result;
+ __u32 tid = (__u32)bpf_get_current_pid_tgid();
+ int key = 0, err;
+ __u32 seq;
+
+ if (tid != trigger_tid)
+ return 0;
+
+ key = RACE_ARM_SEQ;
+ arm = bpf_map_lookup_elem(&status, &key);
+ key = RACE_RESULT;
+ result = bpf_map_lookup_elem(&status, &key);
+ if (!arm || !result || *arm <= 0 ||
+ (__u32)((__u64)*result >> 32) == *arm)
+ return 0;
+ seq = *arm;
+
+ task = bpf_task_from_pid(target_tid);
+ if (!task) {
+ err = -ESRCH;
+ goto out_done;
+ }
+
+ key = 0;
+ work = bpf_map_lookup_elem(&hmap, &key);
+ if (!work) {
+ err = -ENOENT;
+ goto out_task;
+ }
+
+ work->seq = seq;
+ set_status(RACE_READY_SEQ, seq);
+ err = bpf_task_work_schedule_signal(task, &work->tw, &hmap,
+ process_work);
+ if (err == -EBUSY) {
+ bpf_task_release(task);
+ return 0;
+ }
+
+out_task:
+ bpf_task_release(task);
+out_done:
+ /* Publish the sequence and its error in one store. */
+ *result = ((__u64)seq << 32) | (__u32)err;
+ return 0;
+}
|
| 31/3 | 2026/09/30 08:55 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 8771 output: 37 thoughts: 675 Thoughts: **Analyzing the BPF Task Work Patch** Okay, so I'm looking at this patch and it's modifying `kernel/bpf/helpers.c` and adding a new selftest in `tools/testing/selftests/bpf/...`. That immediately tells me this is about refining the BPF task work mechanism. Let's dig into the changes in `kernel/bpf/helpers.c`. First, in `bpf_task_work_cancel`, the `task` argument is now passed explicitly to `task_work_cancel` instead of using `ctx->task`. That's a good change; it makes the function's contract clearer and avoids potential confusion. Then, within `bpf_task_work_irq`, the code now sets `task = ctx->task`, then adds the work, and _then_ cancels. This suggests a race condition or potential for double work is being addressed. The explicit cancellation after the add hints at handling a scenario where the work might have been added but needs to be stopped. Now, here's a more interesting change: the `init_irq_work` call, which previously was within `bpf_task_work_schedule`, has been moved to `bpf_task_work_fetch_ctx`. Right above it is the initialization of the context structure itself: zeroing it out and setting the reference count and state. This placement suggests they are trying to improve the resource allocation, and ensure the `irq_work` is initialized *before* it's actually scheduled, which makes sense to ensure its state is always set. The changes in `bpf_task_work_acquire_ctx` caught my eye. The comment here talks about ordering the checks correctly when dealing with `irq_work`. They're trying to handle a race condition where the `irq_work` might be published before the previous scheduling call returns. It looks like they're trying to prevent a double execution here. The use of `cmpxchg` for state transitions to handle the race is key here. Finally, in `bpf_task_work_schedule`, the `init_irq_work` call is removed, confirming the move to `fetch_ctx`, and `bpf_task_work_cancel_scheduled` now also explicitly passes `ctx->task` to the cancel function, further reinforcing the clarity and consistency of how the function is used. I'm going to step back and re-read the surrounding code in `kernel/bpf/helpers.c` to fully understand what subtle race conditions or memory issues they're trying to address with these changes. This looks like a subtle but important refinement to the task work implementation within BPF, aimed at enhancing its robustness, especially under concurrency. |
| 32/3 | 2026/09/30 08:55 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4480,
"LineCount": 80
}Results: {
"Contents": "4480:\t\n4481:\t\tmigrate_disable();\n4482:\t\tctx-\u003ecallback_fn((u64)(long)ctx-\u003emap, (u64)(long)key,\n4483:\t\t\t\t (u64)(long)ctx-\u003emap_val, 0, 0);\n4484:\t\tmigrate_enable();\n4485:\t\n4486:\t\tbpf_task_work_ctx_reset(ctx);\n4487:\t\t(void)cmpxchg(\u0026ctx-\u003estate, BPF_TW_RUNNING, BPF_TW_STANDBY);\n4488:\t\n4489:\t\tbpf_task_work_ctx_put(ctx);\n4490:\t}\n4491:\t\n4492:\tstatic void bpf_task_work_irq(struct irq_work *irq_work)\n4493:\t{\n4494:\t\tstruct bpf_task_work_ctx *ctx = container_of(irq_work, struct bpf_task_work_ctx, irq_work);\n4495:\t\tstruct task_struct *task;\n4496:\t\tenum bpf_task_work_state state;\n4497:\t\tint err;\n4498:\t\n4499:\t\tguard(rcu)();\n4500:\t\n4501:\t\tif (cmpxchg(\u0026ctx-\u003estate, BPF_TW_PENDING, BPF_TW_SCHEDULING) != BPF_TW_PENDING) {\n4502:\t\t\tbpf_task_work_ctx_put(ctx);\n4503:\t\t\treturn;\n4504:\t\t}\n4505:\t\n4506:\t\t/*\n4507:\t\t * Capture the task pointer before the work is published: once the\n4508:\t\t * callback runs, it may reset ctx-\u003etask. The capture happens while\n4509:\t\t * this round's task reference is held, and every path that can\n4510:\t\t * release it runs after this handler entered its rcu read-side\n4511:\t\t * section. bpf_task_release() is put_task_struct_rcu_user(), so the\n4512:\t\t * task_struct itself cannot be freed before this section ends.\n4513:\t\t */\n4514:\t\ttask = ctx-\u003etask;\n4515:\t\terr = task_work_add(task, \u0026ctx-\u003ework, ctx-\u003emode);\n4516:\t\tif (err) {\n4517:\t\t\tbpf_task_work_ctx_reset(ctx);\n4518:\t\t\t/*\n4519:\t\t\t * try to switch back to STANDBY for another task_work reuse, but we might have\n4520:\t\t\t * gone to FREED already, which is fine as we already cleaned up after ourselves\n4521:\t\t\t */\n4522:\t\t\t(void)cmpxchg(\u0026ctx-\u003estate, BPF_TW_SCHEDULING, BPF_TW_STANDBY);\n4523:\t\t\tbpf_task_work_ctx_put(ctx);\n4524:\t\t\treturn;\n4525:\t\t}\n4526:\t\n4527:\t\t/*\n4528:\t\t * It's technically possible for just scheduled task_work callback to\n4529:\t\t * complete running by now, going SCHEDULING -\u003e RUNNING, resetting\n4530:\t\t * ctx-\u003etask and dropping its ctx refcount. The rcu read-side section\n4531:\t\t * above keeps both the ctx memory and the captured task pointer\n4532:\t\t * valid until this handler returns.\n4533:\t\t */\n4534:\t\tstate = cmpxchg(\u0026ctx-\u003estate, BPF_TW_SCHEDULING, BPF_TW_SCHEDULED);\n4535:\t\tif (state == BPF_TW_FREED)\n4536:\t\t\tbpf_task_work_cancel(ctx, task); /* clean up if we switched into FREED state */\n4537:\t}\n4538:\t\n4539:\tstatic struct bpf_task_work_ctx *bpf_task_work_fetch_ctx(struct bpf_task_work *tw,\n4540:\t\t\t\t\t\t\t\t struct bpf_map *map)\n4541:\t{\n4542:\t\tstruct bpf_task_work_kern *twk = (void *)tw;\n4543:\t\tstruct bpf_task_work_ctx *ctx, *old_ctx;\n4544:\t\n4545:\t\tctx = READ_ONCE(twk-\u003ectx);\n4546:\t\tif (ctx)\n4547:\t\t\treturn ctx;\n4548:\t\n4549:\t\tctx = bpf_map_kmalloc_nolock(map, sizeof(*ctx), 0, NUMA_NO_NODE);\n4550:\t\tif (!ctx)\n4551:\t\t\treturn ERR_PTR(-ENOMEM);\n4552:\t\n4553:\t\tmemset(ctx, 0, sizeof(*ctx));\n4554:\t\trefcount_set(\u0026ctx-\u003erefcnt, 1); /* map's own ref */\n4555:\t\tctx-\u003estate = BPF_TW_STANDBY;\n4556:\t\tinit_irq_work(\u0026ctx-\u003eirq_work, bpf_task_work_irq);\n4557:\t\n4558:\t\told_ctx = cmpxchg(\u0026twk-\u003ectx, NULL, ctx);\n4559:\t\tif (old_ctx) {\n"
} |
| 33/3 | 2026/09/30 08:55 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 11482 output: 37 thoughts: 41 Thoughts: Okay, here's my interpretation of what I need to do: **Analyzing BPF Task Work Acquisition and Scheduling** Alright, let's dive in. To get a comprehensive understanding of the BPF task work mechanics, I need to focus on lines 4560-4660. The specific functions that warrant attention are `bpf_task_work_acquire_ctx` and `bpf_task_work_schedule`. This section likely holds the core logic for how a BPF program interacts with the kernel's task work mechanism. I expect to find details about how contexts are acquired and managed and how work is scheduled within the task. I will look for how data structures are allocated, the locking mechanisms being used (if any), and the overall flow of execution. I'll need to pay close attention to the context switching implications, error handling, and any potential race conditions that might be present in this area. This deeper dive will solidify my comprehension of BPF's role in the task management subsystem. |
| 34/3 | 2026/09/30 08:56 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/helpers.c",
"FirstLine": 4560,
"LineCount": 80
}Results: {
"Contents": "4560:\t\t\t/*\n4561:\t\t\t * tw-\u003ectx is set by concurrent BPF program, release allocated\n4562:\t\t\t * memory and try to reuse already set context.\n4563:\t\t\t */\n4564:\t\t\tkfree_nolock(ctx);\n4565:\t\t\treturn old_ctx;\n4566:\t\t}\n4567:\t\n4568:\t\treturn ctx; /* Success */\n4569:\t}\n4570:\t\n4571:\tstatic struct bpf_task_work_ctx *bpf_task_work_acquire_ctx(struct bpf_task_work *tw,\n4572:\t\t\t\t\t\t\t\t struct bpf_map *map)\n4573:\t{\n4574:\t\tstruct bpf_task_work_ctx *ctx;\n4575:\t\n4576:\t\t/*\n4577:\t\t * Sleepable BPF programs hold rcu_read_lock_trace but not\n4578:\t\t * regular rcu_read_lock. Since kfree_rcu waits for regular\n4579:\t\t * RCU GP, the ctx can be freed while we're between reading\n4580:\t\t * the pointer and incrementing the refcount. Take regular\n4581:\t\t * rcu_read_lock to prevent kfree_rcu from freeing the ctx\n4582:\t\t * before we can tryget it.\n4583:\t\t */\n4584:\t\tscoped_guard(rcu) {\n4585:\t\t\tctx = bpf_task_work_fetch_ctx(tw, map);\n4586:\t\t\tif (IS_ERR(ctx))\n4587:\t\t\t\treturn ctx;\n4588:\t\n4589:\t\t\t/* try to get ref for task_work callback to hold */\n4590:\t\t\tif (!bpf_task_work_ctx_tryget(ctx))\n4591:\t\t\t\treturn ERR_PTR(-EBUSY);\n4592:\t\t}\n4593:\t\n4594:\t\tif (cmpxchg(\u0026ctx-\u003estate, BPF_TW_STANDBY, BPF_TW_PENDING) != BPF_TW_STANDBY) {\n4595:\t\t\t/* lost acquiring race or map_release_uref() stole it from us, put ref and bail */\n4596:\t\t\tbpf_task_work_ctx_put(ctx);\n4597:\t\t\treturn ERR_PTR(-EBUSY);\n4598:\t\t}\n4599:\t\t/*\n4600:\t\t * STANDBY can be published before the previous scheduling irq_work\n4601:\t\t * returns. Claim PENDING before checking BUSY to order the check after\n4602:\t\t * that publication. No irq_work has been queued for this attempt yet,\n4603:\t\t * so reject reuse and roll back to STANDBY while BUSY is set. Preserve\n4604:\t\t * FREED if deletion wins the rollback race.\n4605:\t\t */\n4606:\t\tif (unlikely(irq_work_is_busy(\u0026ctx-\u003eirq_work))) {\n4607:\t\t\t(void)cmpxchg(\u0026ctx-\u003estate, BPF_TW_PENDING, BPF_TW_STANDBY);\n4608:\t\t\tbpf_task_work_ctx_put(ctx);\n4609:\t\t\treturn ERR_PTR(-EBUSY);\n4610:\t\t}\n4611:\t\n4612:\t\t/*\n4613:\t\t * If no process or bpffs is holding a reference to the map, no new callbacks should be\n4614:\t\t * scheduled. This does not address any race or correctness issue, but rather is a policy\n4615:\t\t * choice: dropping user references should stop everything.\n4616:\t\t */\n4617:\t\tif (!atomic64_read(\u0026map-\u003eusercnt)) {\n4618:\t\t\t/* drop ref we just got for task_work callback itself */\n4619:\t\t\tbpf_task_work_ctx_put(ctx);\n4620:\t\t\t/* transfer map's ref into cancel_and_free() */\n4621:\t\t\tbpf_task_work_cancel_and_free(tw);\n4622:\t\t\treturn ERR_PTR(-EBUSY);\n4623:\t\t}\n4624:\t\n4625:\t\treturn ctx;\n4626:\t}\n4627:\t\n4628:\tstatic int bpf_task_work_schedule(struct task_struct *task, struct bpf_task_work *tw,\n4629:\t\t\t\t\t struct bpf_map *map, void *callback_fn,\n4630:\t\t\t\t\t struct bpf_prog_aux *aux, enum task_work_notify_mode mode)\n4631:\t{\n4632:\t\tstruct bpf_prog *prog;\n4633:\t\tstruct bpf_task_work_ctx *ctx;\n4634:\t\tint err;\n4635:\t\n4636:\t\tBTF_TYPE_EMIT(struct bpf_task_work);\n4637:\t\n4638:\t\tprog = bpf_prog_inc_not_zero(aux-\u003eprog);\n4639:\t\tif (IS_ERR(prog))\n"
} |
| 35/3 | 2026/09/30 08:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 13107 output: 218 thoughts: 1218 Thoughts: **KMSAN Analysis of a BPF Task Work Race Condition Fix** Okay, let's break down this patch and determine if KMSAN (Kernel Memory Sanitizer) is necessary. The patch addresses a race condition within BPF task work scheduling and cancellation. My initial thought is that the fix seems focused on concurrency, so it might not be directly related to the kinds of memory errors KMSAN is designed to detect. The race condition stems from the `ctx->task` pointer being potentially reset concurrently by the task work callback. Specifically, `bpf_task_work_callback` calls `bpf_task_work_ctx_reset(ctx)` which resets the `ctx->task` pointer, and operations on the `ctx->task` pointer after `task_work_add` published work may be performed on a NULL `ctx->task` or a `ctx->task` that has been changed. The fix explicitly captures `ctx->task` before the problematic call and passes it to the cancellation function, avoiding the race. Also, initializing `irq_work` only once at the time `ctx` is allocated fixes another related problem. A self-test has also been added to hammer the race condition, which is good. Now, KMSAN's purpose is to detect reads of uninitialized memory, and leaks of kernel memory into user space. Does this patch fall into those categories? Let's go through the checklist: 1. **Kernel structures to user space:** Nope, this patch is entirely within kernel concurrency primitives like `task_work`, `irq_work`, and associated synchronization mechanisms. No kernel data is exposed to user space directly that could be uninitialized. 2. **Conditional logic based on uninitialized data:** The core concern is `ctx->task`. However, `ctx->task` is initialized in `bpf_task_work_schedule` and then `memset` is called on the context in `bpf_task_work_fetch_ctx` which is called during allocation. Also, `init_irq_work` is called on `ctx` at allocation time. No uninitialized variables are read or branched on that I can see. 3. **Complex data structures with uninitialized fields:** The `irq_work` is initialized much earlier, which is good. No uninitialized fields are read in reachable code paths. 4. **Bounds checks exposing uninitialized data:** No. This patch doesn't involve changes to bounds checks. So, the types of bugs this patch *could* have are race conditions, use-after-free, or NULL pointer dereferences from concurrent access. But these are synchronization-related bugs. KASAN (Kernel Address Sanitizer), lockdep, or general kernel bug detectors (NULL dereference checkers) are probably more relevant, not KMSAN. The focus is on ensuring safe access and synchronization, not on preventing the leakage or reading of uninitialized memory. Let's do a quick diff-level check: * The crucial changes involve passing `task` explicitly to `bpf_task_work_cancel` * `bpf_task_work_irq`: Captures the `task` pointer and calls the cancellation function. * `bpf_task_work_fetch_ctx` has `init_irq_work` and `memset(ctx, 0, sizeof(*ctx))`. * `bpf_task_work_acquire_ctx` adds a check with `irq_work_is_busy`. * The `init_irq_work` call has been moved. * The call to cancel `task_work` is updated. * Self-tests are in user space. It's all well-defined, and fully initialized, dealing with concurrency synchronization. There's nothing here that exposes uninitialized memory to user space or reads uninitialized memory. Therefore, KMSAN is not applicable. KASAN, lockdep, and general bug detection should cover any use-after-free or race conditions here. Ready to set the results. |
| 36/3 | 2026/09/30 08:56 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch addresses concurrency race conditions in BPF task_work scheduling and cancellation (specifically, capturing the task pointer in bpf_task_work_irq before task_work publication to avoid racing with ctx-\u003etask clearing in bpf_task_work_callback, moving init_irq_work to ctx allocation time, and checking irq_work_is_busy() during acquisition). \n\nAll structures (such as bpf_task_work_ctx) continue to be fully zero-initialized with memset() upon allocation. The changes do not introduce or modify any reads of uninitialized stack or heap memory, nor do they copy any kernel structures or padding to user space. Potential issues arising from these concurrency and lifetime changes (such as use-after-free or NULL pointer dereferences) are covered by KASAN and standard kernel debug checks rather than KMSAN. Therefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch addresses concurrency race conditions in BPF task_work scheduling and cancellation (specifically, capturing the task pointer in bpf_task_work_irq before task_work publication to avoid racing with ctx-\u003etask clearing in bpf_task_work_callback, moving init_irq_work to ctx allocation time, and checking irq_work_is_busy() during acquisition). \n\nAll structures (such as bpf_task_work_ctx) continue to be fully zero-initialized with memset() upon allocation. The changes do not introduce or modify any reads of uninitialized stack or heap memory, nor do they copy any kernel structures or padding to user space. Potential issues arising from these concurrency and lifetime changes (such as use-after-free or NULL pointer dereferences) are covered by KASAN and standard kernel debug checks rather than KMSAN. Therefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|