* [PATCH v2 bpf-next 0/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc
@ 2026-04-10 6:10 Feng Yang
2026-04-10 6:10 ` [PATCH v2 bpf-next 1/2] " Feng Yang
2026-04-10 6:10 ` [PATCH v2 bpf-next 2/2] selftests/bpf: Add selftests for verifying return values of fmod_ret Feng Yang
0 siblings, 2 replies; 13+ messages in thread
From: Feng Yang @ 2026-04-10 6:10 UTC (permalink / raw)
To: ast, daniel, andrii, martin.lau, eddyz87, memxor, song,
yonghong.song, jolsa, john.fastabend, kpsingh, mattbobrowski,
jiayuan.chen
Cc: bpf, linux-kernel, linux-kselftest
From: Feng Yang <yangfeng@kylinos.cn>
This patch set adds return value validation for fmod_ret
to prevent system crashes caused by incorrect return values.
Changes in v2:
- Add validation for fault injection and include selftests. Thanks, Jiri
Olsa, Jiayuan Chen.
- Link to v1: https://lore.kernel.org/all/20260408094816.228322-1-yangfeng59949@163.com/
Feng Yang (2):
bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret
on security_task_alloc
selftests/bpf: Add selftests for verifying return values of fmod_ret.
kernel/bpf/verifier.c | 261 +++++++++++-------
.../selftests/bpf/prog_tests/verifier.c | 2 +
.../bpf/progs/verifier_fmod_ret_return.c | 60 ++++
3 files changed, 229 insertions(+), 94 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/verifier_fmod_ret_return.c
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 6:10 [PATCH v2 bpf-next 0/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc Feng Yang @ 2026-04-10 6:10 ` Feng Yang 2026-04-10 7:00 ` bot+bpf-ci ` (3 more replies) 2026-04-10 6:10 ` [PATCH v2 bpf-next 2/2] selftests/bpf: Add selftests for verifying return values of fmod_ret Feng Yang 1 sibling, 4 replies; 13+ messages in thread From: Feng Yang @ 2026-04-10 6:10 UTC (permalink / raw) To: ast, daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, john.fastabend, kpsingh, mattbobrowski, jiayuan.chen Cc: bpf, linux-kernel, linux-kselftest From: Feng Yang <yangfeng@kylinos.cn> Using the following BPF program will cause a kernel panic: SEC("fmod_ret/security_task_alloc") int fmod_task_alloc(void *ctx) { return 1; } [ 383.899321] BUG: kernel NULL pointer dereference, address: 0000000000000b99 [ 383.899327] #PF: supervisor read access in kernel mode [ 383.899330] #PF: error_code(0x0000) - not-present page [ 383.899332] PGD 8000000108a60067 P4D 8000000108a60067 PUD 104550067 PMD 0 [ 383.899341] Oops: Oops: 0000 [#1] SMP PTI [ 383.899346] CPU: 1 UID: 0 PID: 12925 Comm: test Kdump: loaded Not tainted 7.0.0-rc6+ #1 PREEMPT(lazy) [ 383.899349] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006 [ 383.899351] RIP: 0010:get_task_pid+0x20/0x80 [ 383.899358] Code: 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa 0f 1f 44 00 00 55 89 f5 53 48 89 fb e8 0b 98 0a 00 85 ed 75 32 48 81 c3 98 0b 00 00 <48> 8b 1b 48 85 db 74 14 b8 01 00 00 00 f0 0f c1 03 85 c0 74 27 8d [ 383.899362] RSP: 0018:ffffd3ca0ab13b38 EFLAGS: 00010206 [ 383.899367] RAX: 0000000000000001 RBX: 0000000000000b99 RCX: 0000000000000008 [ 383.899371] RDX: ffff8b2c85860000 RSI: 0000000000000000 RDI: 0000000000000001 [ 383.899374] RBP: 0000000000000000 R08: 0000010754cf50c8 R09: 0000010754cf50c8 [ 383.899377] R10: ffffffff95a6c000 R11: 0000000000000024 R12: 0000000000000000 [ 383.899380] R13: 0000000000000000 R14: 0000000000000001 R15: 0000000001200000 [ 383.899384] FS: 00007f3eae907740(0000) GS:ffff8b2d05e5c000(0000) knlGS:0000000000000000 [ 383.899388] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 383.899390] CR2: 0000000000000b99 CR3: 0000000106a94003 CR4: 00000000000706f0 [ 383.899393] Call Trace: [ 383.899397] <TASK> [ 383.899401] kernel_clone+0xe8/0x480 [ 383.899406] __do_sys_clone+0x65/0x90 [ 383.899410] do_syscall_64+0xca/0x860 [ 383.899421] ? next_uptodate_folio+0x85/0x2a0 [ 383.899427] ? percpu_counter_add_batch+0x4c/0x90 [ 383.899434] ? filemap_map_pages+0x3b7/0x4d0 [ 383.899437] ? do_read_fault+0x107/0x210 [ 383.899442] ? do_fault+0x1b2/0x330 [ 383.899445] ? __handle_mm_fault+0x49b/0x7a0 [ 383.899448] ? count_memcg_events+0xc4/0x160 [ 383.899453] ? handle_mm_fault+0xbb/0x370 [ 383.899456] ? do_user_addr_fault+0x209/0x680 [ 383.899459] ? irqentry_exit+0x7a/0x660 [ 383.899462] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 383.899466] RIP: 0033:0x7f3eaeb426e7 [ 383.899470] Code: 5d c3 90 f3 0f 1e fa 64 48 8b 04 25 10 00 00 00 45 31 c0 31 d2 31 f6 bf 11 00 20 01 4c 8d 90 d0 02 00 00 b8 38 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 39 89 c2 85 c0 75 2c 64 48 8b 04 25 10 00 00 [ 383.899472] RSP: 002b:00007fff23a2c838 EFLAGS: 00000246 ORIG_RAX: 0000000000000038 [ 383.899477] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f3eaeb426e7 [ 383.899479] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000001200011 [ 383.899481] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 [ 383.899483] R10: 00007f3eae907a10 R11: 0000000000000246 R12: 0000000000000001 [ 383.899485] R13: 00007fff23a2dbe8 R14: 0000000000403de0 R15: 00007f3eaecf5000 [ 383.899488] </TASK> [ 383.899489] Modules linked in: nft_fib_inet nft_fib_ipv4 nft_fib_ipv6 nft_fib nft_reject_inet nf_reject_ipv4 nf_reject_ipv6 nft_reject nft_ct nft_chain_nat nf_nat nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 ip_set rfkill nf_tables sunrpc intel_rapl_msr intel_rapl_common intel_uncore_frequency_common intel_pmc_core pmt_telemetry snd_intel8x0 pmt_discovery pmt_class snd_ac97_codec intel_pmc_ssram_telemetry ac97_bus intel_vsec snd_pcm snd_timer snd rapl soundcore joydev sg i2c_piix4 pcspkr vboxguest i2c_smbus nfnetlink sr_mod cdrom sd_mod ata_generic vmwgfx ahci libahci ata_piix drm_ttm_helper ghash_clmulni_intel video ttm e1000 wmi libata serio_raw dm_mirror dm_region_hash dm_log dm_multipath dm_mod fuse i2c_dev autofs4 [ 383.899624] CR2: 0000000000000b99 This is because 1. The BPF program is designed to override the normal behavior of `security_task_alloc` and forcibly return a non-zero error value (e.g., `-ENOMEM` or `1`). 2. User space triggers a process creation syscall (like `fork` or `clone`). The kernel invokes `copy_process` to allocate and initialize a new `task_struct` for the child process. 3. `copy_process` invokes `security_task_alloc`. Due to the attached BPF program, it returns the positive value `1`. 4. `copy_process` treats any non-zero return from `security_task_alloc` as a failure. It aborts initialization, cleans up, and returns the error code cast to a pointer via `ERR_PTR(1)` (which evaluates to the memory address `0x1`). 5. In `kernel_clone()`, the kernel uses the `IS_ERR()` macro to check if the returned `task_struct *p` is an error pointer. However, `IS_ERR()` only checks for negative error codes in the range `[-MAX_ERRNO, -1]`. Since `0x1` is not in this range, the check `IS_ERR(p)` evaluates to `false`. 6. The kernel incorrectly assumes `p` is a valid `task_struct` pointer and proceeds to call `get_task_pid(p, PIDTYPE_PID)`. This dereferences the invalid address `0x1` (plus the offset of the `thread_pid` field), triggering a general protection fault or null-pointer dereference. Therefore, for security-related functions, similar to LSM, the validity of their return values is verified to fix the issue. Return value validation for fault injection is also added accordingly. Reported-by: Quan Sun <2022090917019@std.uestc.edu.cn> Reported-by: Yinhao Hu <dddddd@hust.edu.cn> Reported-by: Kaiyan Mei <M202472210@hust.edu.cn> Reviewed-by: Dongliang Mu <dzm91@hust.edu.cn> Closes: https://lore.kernel.org/bpf/973a1b7b-8ee7-407a-890a-11455d9cc5bf@std.uestc.edu.cn/ Signed-off-by: Feng Yang <yangfeng@kylinos.cn> --- kernel/bpf/verifier.c | 261 +++++++++++++++++++++++++++--------------- 1 file changed, 167 insertions(+), 94 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9c1135d373e2..37975e9a291c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -18391,6 +18391,167 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn) return 0; } +#define SECURITY_PREFIX "security_" + +#ifdef CONFIG_FUNCTION_ERROR_INJECTION + +/* list of non-sleepable functions that are otherwise on + * ALLOW_ERROR_INJECTION list + */ +BTF_SET_START(btf_non_sleepable_error_inject) +/* Three functions below can be called from sleepable and non-sleepable context. + * Assume non-sleepable from bpf safety point of view. + */ +BTF_ID(func, __filemap_add_folio) +#ifdef CONFIG_FAIL_PAGE_ALLOC +BTF_ID(func, should_fail_alloc_page) +#endif +#ifdef CONFIG_FAILSLAB +BTF_ID(func, should_failslab) +#endif +BTF_SET_END(btf_non_sleepable_error_inject) + +static int check_non_sleepable_error_inject(u32 btf_id) +{ + return btf_id_set_contains(&btf_non_sleepable_error_inject, btf_id); +} + +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) +{ + /* fentry/fexit/fmod_ret progs can be sleepable if they are + * attached to ALLOW_ERROR_INJECTION and are not in denylist. + */ + if (!check_non_sleepable_error_inject(btf_id) && + within_error_injection_list(addr)) + return 0; + + return -EINVAL; +} + +static int check_attach_modify_return(unsigned long addr, const char *func_name) +{ + if (within_error_injection_list(addr) || + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) + return 0; + + return -EINVAL; +} + +static int modify_return_get_retval_range(const struct bpf_prog *prog, + struct bpf_retval_range *retval_range) +{ + unsigned long addr = (unsigned long)prog->aux->dst_trampoline->func.addr; + + if (within_error_injection_list(addr)) { + switch (get_injectable_error_type(addr)) { + case EI_ETYPE_NULL: + retval_range->minval = 0; + retval_range->maxval = 0; + break; + case EI_ETYPE_ERRNO: + retval_range->minval = -MAX_ERRNO; + retval_range->maxval = -1; + break; + case EI_ETYPE_ERRNO_NULL: + retval_range->minval = -MAX_ERRNO; + retval_range->maxval = 0; + break; + case EI_ETYPE_TRUE: + retval_range->minval = 1; + retval_range->maxval = 1; + break; + } + retval_range->return_32bit = true; + + return 0; + } + + return -EINVAL; +} + +#else + +/* Unfortunately, the arch-specific prefixes are hard-coded in arch syscall code + * so we need to hard-code them, too. Ftrace has arch_syscall_match_sym_name() + * but that just compares two concrete function names. + */ +static bool has_arch_syscall_prefix(const char *func_name) +{ +#if defined(__x86_64__) + return !strncmp(func_name, "__x64_", 6); +#elif defined(__i386__) + return !strncmp(func_name, "__ia32_", 7); +#elif defined(__s390x__) + return !strncmp(func_name, "__s390x_", 8); +#elif defined(__aarch64__) + return !strncmp(func_name, "__arm64_", 8); +#elif defined(__riscv) + return !strncmp(func_name, "__riscv_", 8); +#elif defined(__powerpc__) || defined(__powerpc64__) + return !strncmp(func_name, "sys_", 4); +#elif defined(__loongarch__) + return !strncmp(func_name, "sys_", 4); +#else + return false; +#endif +} + +/* Without error injection, allow sleepable and fmod_ret progs on syscalls. */ + +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) +{ + if (has_arch_syscall_prefix(func_name)) + return 0; + + return -EINVAL; +} + +static int check_attach_modify_return(unsigned long addr, const char *func_name) +{ + if (has_arch_syscall_prefix(func_name) || + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) + return 0; + + return -EINVAL; +} + +/* The system call return value is allowed to be an arbitrary value. */ +static int modify_return_get_retval_range(const struct bpf_prog *prog, + struct bpf_retval_range *retval_range) +{ + return -EINVAL; +} + +#endif /* CONFIG_FUNCTION_ERROR_INJECTION */ + +/* hooks return 0 or 1 */ +BTF_SET_START(bool_security_hooks) +BTF_ID(func, security_xfrm_state_pol_flow_match) +BTF_ID(func, security_audit_rule_known) +BTF_ID(func, security_inode_xattr_skipcap) +BTF_SET_END(bool_security_hooks) + +/* Similar to bpf_lsm_get_retval_range, + * ensure that the return values of fmod_ret are valid. + */ +static int bpf_security_get_retval_range(const struct bpf_prog *prog, + struct bpf_retval_range *retval_range) +{ + if (strncmp(SECURITY_PREFIX, prog->aux->attach_func_name, + sizeof(SECURITY_PREFIX) - 1)) + return -EINVAL; + + if (btf_id_set_contains(&bool_security_hooks, prog->aux->attach_btf_id)) { + retval_range->minval = 0; + retval_range->maxval = 1; + } else { + retval_range->minval = -MAX_ERRNO; + retval_range->maxval = 0; + } + retval_range->return_32bit = true; + + return 0; +} static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_range *range) { @@ -18444,8 +18605,13 @@ static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_ *range = retval_range(0, 0); break; case BPF_TRACE_RAW_TP: - case BPF_MODIFY_RETURN: return false; + case BPF_MODIFY_RETURN: + if (!bpf_security_get_retval_range(env->prog, range)) + break; + if (modify_return_get_retval_range(env->prog, range)) + return false; + break; case BPF_TRACE_ITER: default: break; @@ -25487,99 +25653,6 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env) return bpf_prog_ctx_arg_info_init(prog, st_ops_desc->arg_info[member_idx].info, st_ops_desc->arg_info[member_idx].cnt); } -#define SECURITY_PREFIX "security_" - -#ifdef CONFIG_FUNCTION_ERROR_INJECTION - -/* list of non-sleepable functions that are otherwise on - * ALLOW_ERROR_INJECTION list - */ -BTF_SET_START(btf_non_sleepable_error_inject) -/* Three functions below can be called from sleepable and non-sleepable context. - * Assume non-sleepable from bpf safety point of view. - */ -BTF_ID(func, __filemap_add_folio) -#ifdef CONFIG_FAIL_PAGE_ALLOC -BTF_ID(func, should_fail_alloc_page) -#endif -#ifdef CONFIG_FAILSLAB -BTF_ID(func, should_failslab) -#endif -BTF_SET_END(btf_non_sleepable_error_inject) - -static int check_non_sleepable_error_inject(u32 btf_id) -{ - return btf_id_set_contains(&btf_non_sleepable_error_inject, btf_id); -} - -static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) -{ - /* fentry/fexit/fmod_ret progs can be sleepable if they are - * attached to ALLOW_ERROR_INJECTION and are not in denylist. - */ - if (!check_non_sleepable_error_inject(btf_id) && - within_error_injection_list(addr)) - return 0; - - return -EINVAL; -} - -static int check_attach_modify_return(unsigned long addr, const char *func_name) -{ - if (within_error_injection_list(addr) || - !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) - return 0; - - return -EINVAL; -} - -#else - -/* Unfortunately, the arch-specific prefixes are hard-coded in arch syscall code - * so we need to hard-code them, too. Ftrace has arch_syscall_match_sym_name() - * but that just compares two concrete function names. - */ -static bool has_arch_syscall_prefix(const char *func_name) -{ -#if defined(__x86_64__) - return !strncmp(func_name, "__x64_", 6); -#elif defined(__i386__) - return !strncmp(func_name, "__ia32_", 7); -#elif defined(__s390x__) - return !strncmp(func_name, "__s390x_", 8); -#elif defined(__aarch64__) - return !strncmp(func_name, "__arm64_", 8); -#elif defined(__riscv) - return !strncmp(func_name, "__riscv_", 8); -#elif defined(__powerpc__) || defined(__powerpc64__) - return !strncmp(func_name, "sys_", 4); -#elif defined(__loongarch__) - return !strncmp(func_name, "sys_", 4); -#else - return false; -#endif -} - -/* Without error injection, allow sleepable and fmod_ret progs on syscalls. */ - -static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) -{ - if (has_arch_syscall_prefix(func_name)) - return 0; - - return -EINVAL; -} - -static int check_attach_modify_return(unsigned long addr, const char *func_name) -{ - if (has_arch_syscall_prefix(func_name) || - !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) - return 0; - - return -EINVAL; -} - -#endif /* CONFIG_FUNCTION_ERROR_INJECTION */ int bpf_check_attach_target(struct bpf_verifier_log *log, const struct bpf_prog *prog, -- 2.43.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 6:10 ` [PATCH v2 bpf-next 1/2] " Feng Yang @ 2026-04-10 7:00 ` bot+bpf-ci 2026-04-10 8:24 ` Feng Yang 2026-04-10 7:21 ` Leon Hwang ` (2 subsequent siblings) 3 siblings, 1 reply; 13+ messages in thread From: bot+bpf-ci @ 2026-04-10 7:00 UTC (permalink / raw) To: yangfeng59949, ast, daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, john.fastabend, kpsingh, mattbobrowski, jiayuan.chen Cc: bpf, linux-kernel, linux-kselftest, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 2318 bytes --] > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 9c1135d373e2..37975e9a291c 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c [ ... ] > +/* hooks return 0 or 1 */ > +BTF_SET_START(bool_security_hooks) > +BTF_ID(func, security_xfrm_state_pol_flow_match) > +BTF_ID(func, security_audit_rule_known) > +BTF_ID(func, security_inode_xattr_skipcap) > +BTF_SET_END(bool_security_hooks) There are two issues with this BTF set. First, security_inode_xattr_skipcap does not exist as a kernel function. The LSM hook inode_xattr_skipcap is only invoked via call_int_hook() inside security_inode_setxattr() and security_inode_removexattr(). There is no standalone security_inode_xattr_skipcap() wrapper. The equivalent entry in bool_lsm_hooks in kernel/bpf/bpf_lsm.c correctly references bpf_lsm_inode_xattr_skipcap instead. Second, the v1 of this patch had #ifdef guards matching those in bool_lsm_hooks: #ifdef CONFIG_SECURITY_NETWORK_XFRM BTF_ID(func, security_xfrm_state_pol_flow_match) #endif #ifdef CONFIG_AUDIT BTF_ID(func, security_audit_rule_known) #endif These guards were dropped in v2. security_xfrm_state_pol_flow_match is defined under CONFIG_SECURITY_NETWORK_XFRM in security/security.c, and security_audit_rule_known is defined under CONFIG_AUDIT. Without the guards, BTF_IDs will be unresolvable when those configs are disabled. > + > +/* Similar to bpf_lsm_get_retval_range, > + * ensure that the return values of fmod_ret are valid. > + */ This isn't a bug, but the BPF subsystem uses the kernel multi-line comment style where the opening /* goes on its own line: /* * Similar to bpf_lsm_get_retval_range, * ensure that the return values of fmod_ret are valid. */ [ ... ] This commit appears to be a major bug fix addressing a kernel NULL pointer dereference but is missing a Fixes: tag. The bug was introduced when fmod_ret was first allowed on security_* functions without return value validation: Fixes: 6ba43b761c41 ("bpf: Attachment verification for BPF_MODIFY_RETURN") --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/24229694699 ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 7:00 ` bot+bpf-ci @ 2026-04-10 8:24 ` Feng Yang 0 siblings, 0 replies; 13+ messages in thread From: Feng Yang @ 2026-04-10 8:24 UTC (permalink / raw) To: bot+bpf-ci Cc: andrii, ast, bpf, clm, daniel, eddyz87, ihor.solodrai, jiayuan.chen, john.fastabend, jolsa, kpsingh, linux-kernel, linux-kselftest, martin.lau, martin.lau, mattbobrowski, memxor, song, yangfeng59949, yonghong.song On Fri, 10 Apr 2026 07:00:25, bot+bpf-ci@kernel.org wrote: > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > index 9c1135d373e2..37975e9a291c 100644 > > --- a/kernel/bpf/verifier.c > > +++ b/kernel/bpf/verifier.c > > [ ... ] > > > +/* hooks return 0 or 1 */ > > +BTF_SET_START(bool_security_hooks) > > +BTF_ID(func, security_xfrm_state_pol_flow_match) > > +BTF_ID(func, security_audit_rule_known) > > +BTF_ID(func, security_inode_xattr_skipcap) > > +BTF_SET_END(bool_security_hooks) > > There are two issues with this BTF set. > > First, security_inode_xattr_skipcap does not exist as a kernel function. > The LSM hook inode_xattr_skipcap is only invoked via call_int_hook() > inside security_inode_setxattr() and security_inode_removexattr(). > There is no standalone security_inode_xattr_skipcap() wrapper. The > equivalent entry in bool_lsm_hooks in kernel/bpf/bpf_lsm.c correctly > references bpf_lsm_inode_xattr_skipcap instead. > Yes, and in fact, the return values of `security_inode_removexattr` and `security_inode_setxattr` can be those of `inode_removexattr` and `inode_setxattr` respectively, so the 0-1 restriction is not necessary here. > Second, the v1 of this patch had #ifdef guards matching those in > bool_lsm_hooks: > > #ifdef CONFIG_SECURITY_NETWORK_XFRM > BTF_ID(func, security_xfrm_state_pol_flow_match) > #endif > #ifdef CONFIG_AUDIT > BTF_ID(func, security_audit_rule_known) > #endif > > These guards were dropped in v2. security_xfrm_state_pol_flow_match > is defined under CONFIG_SECURITY_NETWORK_XFRM in security/security.c, > and security_audit_rule_known is defined under CONFIG_AUDIT. Without > the guards, BTF_IDs will be unresolvable when those configs are > disabled. I saw there was a default return prototype even without the corresponding config, and assumed that hooking would still work without it, so I removed the config guards. However, I didn't notice that these are inline functions and cannot be hooked. I will revert this change in the next version. > > + > > +/* Similar to bpf_lsm_get_retval_range, > > + * ensure that the return values of fmod_ret are valid. > > + */ > > This isn't a bug, but the BPF subsystem uses the kernel multi-line > comment style where the opening /* goes on its own line: > > /* > * Similar to bpf_lsm_get_retval_range, > * ensure that the return values of fmod_ret are valid. > */ > > [ ... ] > > This commit appears to be a major bug fix addressing a kernel NULL > pointer dereference but is missing a Fixes: tag. The bug was > introduced when fmod_ret was first allowed on security_* functions > without return value validation: > > Fixes: 6ba43b761c41 ("bpf: Attachment verification for BPF_MODIFY_RETURN") > Thanks. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 6:10 ` [PATCH v2 bpf-next 1/2] " Feng Yang 2026-04-10 7:00 ` bot+bpf-ci @ 2026-04-10 7:21 ` Leon Hwang 2026-04-10 7:36 ` Leon Hwang 2026-04-10 7:40 ` Feng Yang 2026-04-10 8:03 ` Feng Yang 2026-04-10 9:20 ` Menglong Dong 3 siblings, 2 replies; 13+ messages in thread From: Leon Hwang @ 2026-04-10 7:21 UTC (permalink / raw) To: Feng Yang, ast, daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, john.fastabend, kpsingh, mattbobrowski, jiayuan.chen Cc: bpf, linux-kernel, linux-kselftest On 10/4/26 14:10, Feng Yang wrote: > From: Feng Yang <yangfeng@kylinos.cn> > [...] > + > +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) > +{ > + /* fentry/fexit/fmod_ret progs can be sleepable if they are > + * attached to ALLOW_ERROR_INJECTION and are not in denylist. > + */ > + if (!check_non_sleepable_error_inject(btf_id) && > + within_error_injection_list(addr)) > + return 0; > + > + return -EINVAL; > +} > + > +static int check_attach_modify_return(unsigned long addr, const char *func_name) > +{ > + if (within_error_injection_list(addr) || > + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) > + return 0; > + > + return -EINVAL; > +} Why did you move them here? Seems that you didn't use them. > + > +static int modify_return_get_retval_range(const struct bpf_prog *prog, > + struct bpf_retval_range *retval_range) NIT: code format issue here. Thanks, Leon > +{ [...] ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 7:21 ` Leon Hwang @ 2026-04-10 7:36 ` Leon Hwang 2026-04-10 7:40 ` Feng Yang 1 sibling, 0 replies; 13+ messages in thread From: Leon Hwang @ 2026-04-10 7:36 UTC (permalink / raw) To: Feng Yang, ast, daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, john.fastabend, kpsingh, mattbobrowski, jiayuan.chen Cc: bpf, linux-kernel, linux-kselftest On 10/4/26 15:21, Leon Hwang wrote: > On 10/4/26 14:10, Feng Yang wrote: >> From: Feng Yang <yangfeng@kylinos.cn> >> > > [...] > >> + >> +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) >> +{ >> + /* fentry/fexit/fmod_ret progs can be sleepable if they are >> + * attached to ALLOW_ERROR_INJECTION and are not in denylist. >> + */ >> + if (!check_non_sleepable_error_inject(btf_id) && >> + within_error_injection_list(addr)) >> + return 0; >> + >> + return -EINVAL; >> +} >> + >> +static int check_attach_modify_return(unsigned long addr, const char *func_name) >> +{ >> + if (within_error_injection_list(addr) || >> + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) >> + return 0; >> + >> + return -EINVAL; >> +} > > Why did you move them here? Seems that you didn't use them. > >> + >> +static int modify_return_get_retval_range(const struct bpf_prog *prog, >> + struct bpf_retval_range *retval_range) > > NIT: code format issue here. > Sorry about this. It is false. I was misled by thunderbird. > Thanks, > Leon > >> +{ > [...] > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 7:21 ` Leon Hwang 2026-04-10 7:36 ` Leon Hwang @ 2026-04-10 7:40 ` Feng Yang 2026-04-10 7:49 ` Leon Hwang 1 sibling, 1 reply; 13+ messages in thread From: Feng Yang @ 2026-04-10 7:40 UTC (permalink / raw) To: leon.hwang Cc: andrii, ast, bpf, daniel, eddyz87, jiayuan.chen, john.fastabend, jolsa, kpsingh, linux-kernel, linux-kselftest, martin.lau, mattbobrowski, memxor, song, yangfeng59949 On Fri, 10 Apr 2026 15:21:26 +0800 Leon Hwang wrote: > On 10/4/26 14:10, Feng Yang wrote: > > From: Feng Yang <yangfeng@kylinos.cn> > > > > [...] > > > + > > +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) > > +{ > > + /* fentry/fexit/fmod_ret progs can be sleepable if they are > > + * attached to ALLOW_ERROR_INJECTION and are not in denylist. > > + */ > > + if (!check_non_sleepable_error_inject(btf_id) && > > + within_error_injection_list(addr)) > > + return 0; > > + > > + return -EINVAL; > > +} > > + > > +static int check_attach_modify_return(unsigned long addr, const char *func_name) > > +{ > > + if (within_error_injection_list(addr) || > > + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) > > + return 0; > > + > > + return -EINVAL; > > +} > > Why did you move them here? Seems that you didn't use them. Because CONFIG_FUNCTION_ERROR_INJECTION is directly reused here, and the function has_arch_syscall_prefix is intended to be used. > > + > > +static int modify_return_get_retval_range(const struct bpf_prog *prog, > > + struct bpf_retval_range *retval_range) > > NIT: code format issue here. Thanks. > Thanks, > Leon > > > +{ > [...] ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 7:40 ` Feng Yang @ 2026-04-10 7:49 ` Leon Hwang 2026-04-10 8:07 ` Feng Yang 0 siblings, 1 reply; 13+ messages in thread From: Leon Hwang @ 2026-04-10 7:49 UTC (permalink / raw) To: Feng Yang Cc: andrii, ast, bpf, daniel, eddyz87, jiayuan.chen, john.fastabend, jolsa, kpsingh, linux-kernel, linux-kselftest, martin.lau, mattbobrowski, memxor, song On 10/4/26 15:40, Feng Yang wrote: > On Fri, 10 Apr 2026 15:21:26 +0800 Leon Hwang wrote: >> On 10/4/26 14:10, Feng Yang wrote: >>> From: Feng Yang <yangfeng@kylinos.cn> >>> >> >> [...] >> >>> + >>> +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) >>> +{ >>> + /* fentry/fexit/fmod_ret progs can be sleepable if they are >>> + * attached to ALLOW_ERROR_INJECTION and are not in denylist. >>> + */ >>> + if (!check_non_sleepable_error_inject(btf_id) && >>> + within_error_injection_list(addr)) >>> + return 0; >>> + >>> + return -EINVAL; >>> +} >>> + >>> +static int check_attach_modify_return(unsigned long addr, const char *func_name) >>> +{ >>> + if (within_error_injection_list(addr) || >>> + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) >>> + return 0; >>> + >>> + return -EINVAL; >>> +} >> >> Why did you move them here? Seems that you didn't use them. > > Because CONFIG_FUNCTION_ERROR_INJECTION is directly reused here, > and the function has_arch_syscall_prefix is intended to be used. > You can declare the function instead. No? But, the function has_arch_syscall_prefix was not used in your new code? Thanks, Leon ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 7:49 ` Leon Hwang @ 2026-04-10 8:07 ` Feng Yang 0 siblings, 0 replies; 13+ messages in thread From: Feng Yang @ 2026-04-10 8:07 UTC (permalink / raw) To: leon.hwang Cc: andrii, ast, bpf, daniel, eddyz87, jiayuan.chen, john.fastabend, jolsa, kpsingh, linux-kernel, linux-kselftest, martin.lau, mattbobrowski, memxor, song, yangfeng59949 On Fri, 10 Apr 2026 15:49:15 +0800, Leon Hwang wrote: > On 10/4/26 15:40, Feng Yang wrote: > > On Fri, 10 Apr 2026 15:21:26 +0800 Leon Hwang wrote: > >> On 10/4/26 14:10, Feng Yang wrote: > >>> From: Feng Yang <yangfeng@kylinos.cn> > >>> > >> > >> [...] > >> > >>> + > >>> +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) > >>> +{ > >>> + /* fentry/fexit/fmod_ret progs can be sleepable if they are > >>> + * attached to ALLOW_ERROR_INJECTION and are not in denylist. > >>> + */ > >>> + if (!check_non_sleepable_error_inject(btf_id) && > >>> + within_error_injection_list(addr)) > >>> + return 0; > >>> + > >>> + return -EINVAL; > >>> +} > >>> + > >>> +static int check_attach_modify_return(unsigned long addr, const char *func_name) > >>> +{ > >>> + if (within_error_injection_list(addr) || > >>> + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) > >>> + return 0; > >>> + > >>> + return -EINVAL; > >>> +} > >> > >> Why did you move them here? Seems that you didn't use them. > > > > Because CONFIG_FUNCTION_ERROR_INJECTION is directly reused here, > > and the function has_arch_syscall_prefix is intended to be used. > > > > You can declare the function instead. No? > > But, the function has_arch_syscall_prefix was not used in your new code? Indeed, I will fix it in the next version. Thank you. > Thanks, > Leon ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 6:10 ` [PATCH v2 bpf-next 1/2] " Feng Yang 2026-04-10 7:00 ` bot+bpf-ci 2026-04-10 7:21 ` Leon Hwang @ 2026-04-10 8:03 ` Feng Yang 2026-04-10 8:27 ` Leon Hwang 2026-04-10 9:20 ` Menglong Dong 3 siblings, 1 reply; 13+ messages in thread From: Feng Yang @ 2026-04-10 8:03 UTC (permalink / raw) To: yangfeng59949 Cc: andrii, ast, bpf, daniel, eddyz87, jiayuan.chen, john.fastabend, jolsa, kpsingh, linux-kernel, linux-kselftest, martin.lau, mattbobrowski, memxor, song, yonghong.song [...] > +static int modify_return_get_retval_range(const struct bpf_prog *prog, > + struct bpf_retval_range *retval_range) > +{ > + unsigned long addr = (unsigned long)prog->aux->dst_trampoline->func.addr; > + > + if (within_error_injection_list(addr)) { > + switch (get_injectable_error_type(addr)) { > + case EI_ETYPE_NULL: > + retval_range->minval = 0; > + retval_range->maxval = 0; > + break; > + case EI_ETYPE_ERRNO: > + retval_range->minval = -MAX_ERRNO; > + retval_range->maxval = -1; > + break; This refers to the documentation in fault-injection.rst: Each error injectable functions will have the error type specified by the ALLOW_ERROR_INJECTION() macro. You have to choose it carefully if you add a new error injectable function. If the wrong error type is chosen, the kernel may crash because it may not be able to handle the error. There are 4 types of errors defined in include/asm-generic/error-injection.h EI_ETYPE_NULL This function will return `NULL` if it fails. e.g. return an allocated object address. EI_ETYPE_ERRNO This function will return an `-errno` error code if it fails. e.g. return -EINVAL if the input is wrong. This will include the functions which will return an address which encodes `-errno` by ERR_PTR() macro. EI_ETYPE_ERRNO_NULL This function will return an `-errno` or `NULL` if it fails. If the caller of this function checks the return value with IS_ERR_OR_NULL() macro, this type will be appropriate. EI_ETYPE_TRUE This function will return `true` (non-zero positive value) if it fails. Restrict EI_ETYPE_ERRNO to only return error codes. However, it was noticed that the self-test bpf_testmod_test_read uses ALLOW_ERROR_INJECTION(bpf_testmod_test_read, ERRNO); and returns 0, which causes a failure. So should returning 0 be considered valid for the EI_ETYPE_ERRNO type, or should the self-test be modified instead? Thanks. > + case EI_ETYPE_ERRNO_NULL: > + retval_range->minval = -MAX_ERRNO; > + retval_range->maxval = 0; > + break; > + case EI_ETYPE_TRUE: > + retval_range->minval = 1; > + retval_range->maxval = 1; > + break; > + } > + retval_range->return_32bit = true; > + > + return 0; > + } > + > + return -EINVAL; > +} ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 8:03 ` Feng Yang @ 2026-04-10 8:27 ` Leon Hwang 0 siblings, 0 replies; 13+ messages in thread From: Leon Hwang @ 2026-04-10 8:27 UTC (permalink / raw) To: Feng Yang Cc: andrii, ast, bpf, daniel, eddyz87, jiayuan.chen, john.fastabend, jolsa, kpsingh, linux-kernel, linux-kselftest, martin.lau, mattbobrowski, memxor, song, yonghong.song On 10/4/26 16:03, Feng Yang wrote: > [...] >> +static int modify_return_get_retval_range(const struct bpf_prog *prog, >> + struct bpf_retval_range *retval_range) How about 'range' instead of 'retval_range' like its caller? >> +{ >> + unsigned long addr = (unsigned long)prog->aux->dst_trampoline->func.addr; >> + >> + if (within_error_injection_list(addr)) { >> + switch (get_injectable_error_type(addr)) { >> + case EI_ETYPE_NULL: >> + retval_range->minval = 0; >> + retval_range->maxval = 0; >> + break; >> + case EI_ETYPE_ERRNO: >> + retval_range->minval = -MAX_ERRNO; >> + retval_range->maxval = -1; >> + break; > > This refers to the documentation in fault-injection.rst: > > Each error injectable functions will have the error type specified by the > ALLOW_ERROR_INJECTION() macro. You have to choose it carefully if you add > a new error injectable function. If the wrong error type is chosen, the > kernel may crash because it may not be able to handle the error. > There are 4 types of errors defined in include/asm-generic/error-injection.h > > EI_ETYPE_NULL > This function will return `NULL` if it fails. e.g. return an allocated > object address. > > EI_ETYPE_ERRNO > This function will return an `-errno` error code if it fails. e.g. return > -EINVAL if the input is wrong. This will include the functions which will > return an address which encodes `-errno` by ERR_PTR() macro. > > EI_ETYPE_ERRNO_NULL > This function will return an `-errno` or `NULL` if it fails. If the caller > of this function checks the return value with IS_ERR_OR_NULL() macro, this > type will be appropriate. > > EI_ETYPE_TRUE > This function will return `true` (non-zero positive value) if it fails. > > Restrict EI_ETYPE_ERRNO to only return error codes. > However, it was noticed that the self-test bpf_testmod_test_read uses > ALLOW_ERROR_INJECTION(bpf_testmod_test_read, ERRNO); and returns 0, which causes a failure. > So should returning 0 be considered valid for the EI_ETYPE_ERRNO type, or should the self-test be modified instead? > I think it would be better to add such explanation in code comment. Thanks, Leon ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 bpf-next 1/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc 2026-04-10 6:10 ` [PATCH v2 bpf-next 1/2] " Feng Yang ` (2 preceding siblings ...) 2026-04-10 8:03 ` Feng Yang @ 2026-04-10 9:20 ` Menglong Dong 3 siblings, 0 replies; 13+ messages in thread From: Menglong Dong @ 2026-04-10 9:20 UTC (permalink / raw) To: ast, daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, john.fastabend, kpsingh, mattbobrowski, jiayuan.chen, Feng Yang Cc: bpf, linux-kernel, linux-kselftest On 2026/4/10 14:10 Feng Yang <yangfeng59949@163.com> write: > From: Feng Yang <yangfeng@kylinos.cn> > > Using the following BPF program will cause a kernel panic: > SEC("fmod_ret/security_task_alloc") > int fmod_task_alloc(void *ctx) > { > return 1; > } > [...] > + > +static int check_attach_modify_return(unsigned long addr, const char *func_name) > +{ > + if (within_error_injection_list(addr) || > + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) > + return 0; > + > + return -EINVAL; > +} > + > +static int modify_return_get_retval_range(const struct bpf_prog *prog, > + struct bpf_retval_range *retval_range) > +{ > + unsigned long addr = (unsigned long)prog->aux->dst_trampoline->func.addr; > + > + if (within_error_injection_list(addr)) { > + switch (get_injectable_error_type(addr)) { > + case EI_ETYPE_NULL: > + retval_range->minval = 0; > + retval_range->maxval = 0; > + break; > + case EI_ETYPE_ERRNO: > + retval_range->minval = -MAX_ERRNO; > + retval_range->maxval = -1; > + break; > + case EI_ETYPE_ERRNO_NULL: > + retval_range->minval = -MAX_ERRNO; > + retval_range->maxval = 0; > + break; > + case EI_ETYPE_TRUE: > + retval_range->minval = 1; > + retval_range->maxval = 1; > + break; > + } MODIFY_RETURN should always be able to return 0. 0 means that "not modify the return and call the target function". So the return value here should not restrict it. I think it's a limitation of the MODIFY_RETURN, as it can't modify the return value to 0/false. Maybe we can introduce a kfunc, such as bpf_set_return_zero(), but it will be complex, as we need do some adjustment to the trampoline, which I suspect Alexei won't like it. However, it's another problem. So, for this patch, I think we need to make retval_range always cover "0". BTW, I see you've moved some code upwards, which is not very friendly for review and will also make the patch relatively large. I suggest you declare modify_return_get_retval_range and bpf_security_get_retval_range at the beginning of the file instead. Thanks! Menglong Dong > + retval_range->return_32bit = true; > + > + return 0; > + } > + > + return -EINVAL; > +} > + > +#else > + > +/* Unfortunately, the arch-specific prefixes are hard-coded in arch syscall code > + * so we need to hard-code them, too. Ftrace has arch_syscall_match_sym_name() > + * but that just compares two concrete function names. > + */ > +static bool has_arch_syscall_prefix(const char *func_name) > +{ > +#if defined(__x86_64__) > + return !strncmp(func_name, "__x64_", 6); > +#elif defined(__i386__) > + return !strncmp(func_name, "__ia32_", 7); > +#elif defined(__s390x__) > + return !strncmp(func_name, "__s390x_", 8); > +#elif defined(__aarch64__) > + return !strncmp(func_name, "__arm64_", 8); > +#elif defined(__riscv) > + return !strncmp(func_name, "__riscv_", 8); > +#elif defined(__powerpc__) || defined(__powerpc64__) > + return !strncmp(func_name, "sys_", 4); > +#elif defined(__loongarch__) > + return !strncmp(func_name, "sys_", 4); > +#else > + return false; > +#endif > +} > + > +/* Without error injection, allow sleepable and fmod_ret progs on syscalls. */ > + > +static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) > +{ > + if (has_arch_syscall_prefix(func_name)) > + return 0; > + > + return -EINVAL; > +} > + > +static int check_attach_modify_return(unsigned long addr, const char *func_name) > +{ > + if (has_arch_syscall_prefix(func_name) || > + !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) > + return 0; > + > + return -EINVAL; > +} > + > +/* The system call return value is allowed to be an arbitrary value. */ > +static int modify_return_get_retval_range(const struct bpf_prog *prog, > + struct bpf_retval_range *retval_range) > +{ > + return -EINVAL; > +} > + > +#endif /* CONFIG_FUNCTION_ERROR_INJECTION */ > + > +/* hooks return 0 or 1 */ > +BTF_SET_START(bool_security_hooks) > +BTF_ID(func, security_xfrm_state_pol_flow_match) > +BTF_ID(func, security_audit_rule_known) > +BTF_ID(func, security_inode_xattr_skipcap) > +BTF_SET_END(bool_security_hooks) > + > +/* Similar to bpf_lsm_get_retval_range, > + * ensure that the return values of fmod_ret are valid. > + */ > +static int bpf_security_get_retval_range(const struct bpf_prog *prog, > + struct bpf_retval_range *retval_range) > +{ > + if (strncmp(SECURITY_PREFIX, prog->aux->attach_func_name, > + sizeof(SECURITY_PREFIX) - 1)) > + return -EINVAL; > + > + if (btf_id_set_contains(&bool_security_hooks, prog->aux->attach_btf_id)) { > + retval_range->minval = 0; > + retval_range->maxval = 1; > + } else { > + retval_range->minval = -MAX_ERRNO; > + retval_range->maxval = 0; > + } > + retval_range->return_32bit = true; > + > + return 0; > +} > > static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_range *range) > { > @@ -18444,8 +18605,13 @@ static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_ > *range = retval_range(0, 0); > break; > case BPF_TRACE_RAW_TP: > - case BPF_MODIFY_RETURN: > return false; > + case BPF_MODIFY_RETURN: > + if (!bpf_security_get_retval_range(env->prog, range)) > + break; > + if (modify_return_get_retval_range(env->prog, range)) > + return false; > + break; > case BPF_TRACE_ITER: > default: > break; > @@ -25487,99 +25653,6 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env) > return bpf_prog_ctx_arg_info_init(prog, st_ops_desc->arg_info[member_idx].info, > st_ops_desc->arg_info[member_idx].cnt); > } > -#define SECURITY_PREFIX "security_" > - > -#ifdef CONFIG_FUNCTION_ERROR_INJECTION > - > -/* list of non-sleepable functions that are otherwise on > - * ALLOW_ERROR_INJECTION list > - */ > -BTF_SET_START(btf_non_sleepable_error_inject) > -/* Three functions below can be called from sleepable and non-sleepable context. > - * Assume non-sleepable from bpf safety point of view. > - */ > -BTF_ID(func, __filemap_add_folio) > -#ifdef CONFIG_FAIL_PAGE_ALLOC > -BTF_ID(func, should_fail_alloc_page) > -#endif > -#ifdef CONFIG_FAILSLAB > -BTF_ID(func, should_failslab) > -#endif > -BTF_SET_END(btf_non_sleepable_error_inject) > - > -static int check_non_sleepable_error_inject(u32 btf_id) > -{ > - return btf_id_set_contains(&btf_non_sleepable_error_inject, btf_id); > -} > - > -static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) > -{ > - /* fentry/fexit/fmod_ret progs can be sleepable if they are > - * attached to ALLOW_ERROR_INJECTION and are not in denylist. > - */ > - if (!check_non_sleepable_error_inject(btf_id) && > - within_error_injection_list(addr)) > - return 0; > - > - return -EINVAL; > -} > - > -static int check_attach_modify_return(unsigned long addr, const char *func_name) > -{ > - if (within_error_injection_list(addr) || > - !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) > - return 0; > - > - return -EINVAL; > -} > - > -#else > - > -/* Unfortunately, the arch-specific prefixes are hard-coded in arch syscall code > - * so we need to hard-code them, too. Ftrace has arch_syscall_match_sym_name() > - * but that just compares two concrete function names. > - */ > -static bool has_arch_syscall_prefix(const char *func_name) > -{ > -#if defined(__x86_64__) > - return !strncmp(func_name, "__x64_", 6); > -#elif defined(__i386__) > - return !strncmp(func_name, "__ia32_", 7); > -#elif defined(__s390x__) > - return !strncmp(func_name, "__s390x_", 8); > -#elif defined(__aarch64__) > - return !strncmp(func_name, "__arm64_", 8); > -#elif defined(__riscv) > - return !strncmp(func_name, "__riscv_", 8); > -#elif defined(__powerpc__) || defined(__powerpc64__) > - return !strncmp(func_name, "sys_", 4); > -#elif defined(__loongarch__) > - return !strncmp(func_name, "sys_", 4); > -#else > - return false; > -#endif > -} > - > -/* Without error injection, allow sleepable and fmod_ret progs on syscalls. */ > - > -static int check_attach_sleepable(u32 btf_id, unsigned long addr, const char *func_name) > -{ > - if (has_arch_syscall_prefix(func_name)) > - return 0; > - > - return -EINVAL; > -} > - > -static int check_attach_modify_return(unsigned long addr, const char *func_name) > -{ > - if (has_arch_syscall_prefix(func_name) || > - !strncmp(SECURITY_PREFIX, func_name, sizeof(SECURITY_PREFIX) - 1)) > - return 0; > - > - return -EINVAL; > -} > - > -#endif /* CONFIG_FUNCTION_ERROR_INJECTION */ > > int bpf_check_attach_target(struct bpf_verifier_log *log, > const struct bpf_prog *prog, > -- > 2.43.0 > > > ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 bpf-next 2/2] selftests/bpf: Add selftests for verifying return values of fmod_ret. 2026-04-10 6:10 [PATCH v2 bpf-next 0/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc Feng Yang 2026-04-10 6:10 ` [PATCH v2 bpf-next 1/2] " Feng Yang @ 2026-04-10 6:10 ` Feng Yang 1 sibling, 0 replies; 13+ messages in thread From: Feng Yang @ 2026-04-10 6:10 UTC (permalink / raw) To: ast, daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, john.fastabend, kpsingh, mattbobrowski, jiayuan.chen Cc: bpf, linux-kernel, linux-kselftest From: Feng Yang <yangfeng@kylinos.cn> Add selftests for verifying return values of fmod_ret. test_progs -t verifier_fmod_ret_return 569/1 verifier_fmod_ret_return/fmod_task_alloc_1:OK 569/2 verifier_fmod_ret_return/fmod_task_alloc_2:OK 569/3 verifier_fmod_ret_return/fmod_ret_sys_open_1:OK 569/4 verifier_fmod_ret_return/fmod_ret_sys_open_2:OK 569/5 verifier_fmod_ret_return/fmod_ret_sys_open_3:OK 569/6 verifier_fmod_ret_return/fmod_page_pool_alloc_netmems_1:OK 569/7 verifier_fmod_ret_return/fmod_page_pool_alloc_netmems_2:OK 569 verifier_fmod_ret_return:OK Summary: 1/7 PASSED, 0 SKIPPED, 0 FAILED Signed-off-by: Feng Yang <yangfeng@kylinos.cn> --- .../selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/progs/verifier_fmod_ret_return.c | 60 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/verifier_fmod_ret_return.c diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 169cf7fbf40f..6e67f69d88fa 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -37,6 +37,7 @@ #include "verifier_div0.skel.h" #include "verifier_div_mod_bounds.skel.h" #include "verifier_div_overflow.skel.h" +#include "verifier_fmod_ret_return.skel.h" #include "verifier_global_subprogs.skel.h" #include "verifier_global_ptr_args.skel.h" #include "verifier_gotol.skel.h" @@ -184,6 +185,7 @@ void test_verifier_direct_stack_access_wraparound(void) { RUN(verifier_direct_st void test_verifier_div0(void) { RUN(verifier_div0); } void test_verifier_div_mod_bounds(void) { RUN(verifier_div_mod_bounds); } void test_verifier_div_overflow(void) { RUN(verifier_div_overflow); } +void test_verifier_fmod_ret_return(void) { RUN(verifier_fmod_ret_return); } void test_verifier_global_subprogs(void) { RUN(verifier_global_subprogs); } void test_verifier_global_ptr_args(void) { RUN(verifier_global_ptr_args); } void test_verifier_gotol(void) { RUN(verifier_gotol); } diff --git a/tools/testing/selftests/bpf/progs/verifier_fmod_ret_return.c b/tools/testing/selftests/bpf/progs/verifier_fmod_ret_return.c new file mode 100644 index 000000000000..11d303b01c80 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_fmod_ret_return.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bpf.h> +#include <bpf/bpf_tracing.h> +#include "bpf_misc.h" + +SEC("fmod_ret/security_task_alloc") +__success +int BPF_PROG(fmod_task_alloc_1) +{ + return -1; +} + +SEC("fmod_ret/security_task_alloc") +__failure __log_level(2) +__msg("At program exit the register R0 has smin=1 smax=1 should have been in [-4095, 0]") +int BPF_PROG(fmod_task_alloc_2) +{ + return 1; +} + +SEC("fmod_ret/" SYS_PREFIX "sys_open") +__failure __log_level(2) +__msg("At program exit the register R0 has smin=0 smax=0 should have been in [-4095, -1]") +int BPF_PROG(fmod_ret_sys_open_1) +{ + return 0; +} + +SEC("fmod_ret/" SYS_PREFIX "sys_open") +__failure __log_level(2) +__msg("At program exit the register R0 has smin=1 smax=1 should have been in [-4095, -1]") +int BPF_PROG(fmod_ret_sys_open_2) +{ + return 1; +} + +SEC("fmod_ret/" SYS_PREFIX "sys_open") +__success +int BPF_PROG(fmod_ret_sys_open_3) +{ + return -1; +} + +SEC("fmod_ret/page_pool_alloc_netmems") +__failure __log_level(2) +__msg("At program exit the register R0 has smin=1 smax=1 should have been in [0, 0]") +int BPF_PROG(fmod_page_pool_alloc_netmems_1) +{ + return 1; +} + +SEC("fmod_ret/page_pool_alloc_netmems") +__success +int BPF_PROG(fmod_page_pool_alloc_netmems_2) +{ + return 0; +} + +char _license[] SEC("license") = "GPL"; -- 2.43.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-04-10 9:20 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-10 6:10 [PATCH v2 bpf-next 0/2] bpf: Fix Null-Pointer Dereference in kernel_clone() via BPF fmod_ret on security_task_alloc Feng Yang 2026-04-10 6:10 ` [PATCH v2 bpf-next 1/2] " Feng Yang 2026-04-10 7:00 ` bot+bpf-ci 2026-04-10 8:24 ` Feng Yang 2026-04-10 7:21 ` Leon Hwang 2026-04-10 7:36 ` Leon Hwang 2026-04-10 7:40 ` Feng Yang 2026-04-10 7:49 ` Leon Hwang 2026-04-10 8:07 ` Feng Yang 2026-04-10 8:03 ` Feng Yang 2026-04-10 8:27 ` Leon Hwang 2026-04-10 9:20 ` Menglong Dong 2026-04-10 6:10 ` [PATCH v2 bpf-next 2/2] selftests/bpf: Add selftests for verifying return values of fmod_ret Feng Yang
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox