This change restores interpreter fallback capability for BPF programs with stack size <= 512 bytes when jit fails. Add selftest for socket filter to test it.
changes: v2: - Addressed comments from Alexei - Add selftest
v1: https://lore.kernel.org/all/20250805115513.4018532-1-kafai.wan@linux.dev/
--- KaFai Wan (2): bpf: Allow fall back to interpreter for programs with stack size <= 512 selftests/bpf: Add socket filter attach test
kernel/bpf/core.c | 16 ++- .../selftests/bpf/prog_tests/socket_filter.c | 124 ++++++++++++++++++ .../selftests/bpf/progs/socket_filter.c | 16 +++ 3 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/socket_filter.c create mode 100644 tools/testing/selftests/bpf/progs/socket_filter.c
OpenWRT users reported regression on ARMv6 devices after updating to latest HEAD, where tcpdump filter:
tcpdump "not ether host 3c37121a2b3c and not ether host 184ecbca2a3a \ and not ether host 14130b4d3f47 and not ether host f0f61cf440b7 \ and not ether host a84b4dedf471 and not ether host d022be17e1d7 \ and not ether host 5c497967208b and not ether host 706655784d5b"
fails with warning: "Kernel filter failed: No error information" when using config: # CONFIG_BPF_JIT_ALWAYS_ON is not set CONFIG_BPF_JIT_DEFAULT_ON=y
The issue arises because commits: 1. "bpf: Fix array bounds error with may_goto" changed default runtime to __bpf_prog_ret0_warn when jit_requested = 1 2. "bpf: Avoid __bpf_prog_ret0_warn when jit fails" returns error when jit_requested = 1 but jit fails
This change restores interpreter fallback capability for BPF programs with stack size <= 512 bytes when jit fails.
Reported-by: Felix Fietkau nbd@nbd.name Closes: https://lore.kernel.org/bpf/2e267b4b-0540-45d8-9310-e127bf95fc63@nbd.name/ Fixes: 6ebc5030e0c5 ("bpf: Fix array bounds error with may_goto") Signed-off-by: KaFai Wan kafai.wan@linux.dev --- kernel/bpf/core.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 5d1650af899d..f8f8ac3b5513 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -2366,8 +2366,7 @@ static unsigned int __bpf_prog_ret0_warn(const void *ctx, const struct bpf_insn *insn) { /* If this handler ever gets executed, then BPF_JIT_ALWAYS_ON - * is not working properly, or interpreter is being used when - * prog->jit_requested is not 0, so warn about it! + * is not working properly, so warn about it! */ WARN_ON_ONCE(1); return 0; @@ -2468,8 +2467,9 @@ static int bpf_check_tail_call(const struct bpf_prog *fp) return ret; }
-static void bpf_prog_select_func(struct bpf_prog *fp) +static bool bpf_prog_select_interpreter(struct bpf_prog *fp) { + bool select_interpreter = false; #ifndef CONFIG_BPF_JIT_ALWAYS_ON u32 stack_depth = max_t(u32, fp->aux->stack_depth, 1); u32 idx = (round_up(stack_depth, 32) / 32) - 1; @@ -2478,15 +2478,16 @@ static void bpf_prog_select_func(struct bpf_prog *fp) * But for non-JITed programs, we don't need bpf_func, so no bounds * check needed. */ - if (!fp->jit_requested && - !WARN_ON_ONCE(idx >= ARRAY_SIZE(interpreters))) { + if (idx < ARRAY_SIZE(interpreters)) { fp->bpf_func = interpreters[idx]; + select_interpreter = true; } else { fp->bpf_func = __bpf_prog_ret0_warn; } #else fp->bpf_func = __bpf_prog_ret0_warn; #endif + return select_interpreter; }
/** @@ -2505,7 +2506,7 @@ struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err) /* In case of BPF to BPF calls, verifier did all the prep * work with regards to JITing, etc. */ - bool jit_needed = fp->jit_requested; + bool jit_needed = false;
if (fp->bpf_func) goto finalize; @@ -2514,7 +2515,8 @@ struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err) bpf_prog_has_kfunc_call(fp)) jit_needed = true;
- bpf_prog_select_func(fp); + if (!bpf_prog_select_interpreter(fp)) + jit_needed = true;
/* eBPF JITs can rewrite the program in case constant * blinding is active. However, in case of error during
This test verifies socket filter attachment functionality on architectures supporting either BPF JIT compilation or the interpreter.
It specifically validates the fallback to interpreter behavior when JIT fails, particularly targeting ARMv6 devices with the following configuration: # CONFIG_BPF_JIT_ALWAYS_ON is not set CONFIG_BPF_JIT_DEFAULT_ON=y
Signed-off-by: KaFai Wan kafai.wan@linux.dev --- .../selftests/bpf/prog_tests/socket_filter.c | 124 ++++++++++++++++++ .../selftests/bpf/progs/socket_filter.c | 16 +++ 2 files changed, 140 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/socket_filter.c create mode 100644 tools/testing/selftests/bpf/progs/socket_filter.c
diff --git a/tools/testing/selftests/bpf/prog_tests/socket_filter.c b/tools/testing/selftests/bpf/prog_tests/socket_filter.c new file mode 100644 index 000000000000..ee3a4cc1a992 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/socket_filter.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <test_progs.h> +#include <sys/utsname.h> +#include <uapi/linux/filter.h> +#include "socket_filter.skel.h" + +static int duration; + +void do_test(void) +{ + /* the filter below is the tcpdump filter: + * tcpdump "not ether host 3c37121a2b3c and not ether host 184ecbca2a3a \ + * and not ether host 14130b4d3f47 and not ether host f0f61cf440b7 \ + * and not ether host a84b4dedf471 and not ether host d022be17e1d7 \ + * and not ether host 5c497967208b and not ether host 706655784d5b" + */ + struct sock_filter code[] = { + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0x121a2b3c }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 60, 0, 0x00003c37 }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 2, 0x121a2b3c }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 56, 0, 0x00003c37 }, + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0xcbca2a3a }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 52, 0, 0x0000184e }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 2, 0xcbca2a3a }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 48, 0, 0x0000184e }, + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0x0b4d3f47 }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 44, 0, 0x00001413 }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 2, 0x0b4d3f47 }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 40, 0, 0x00001413 }, + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0x1cf440b7 }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 36, 0, 0x0000f0f6 }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 2, 0x1cf440b7 }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 32, 0, 0x0000f0f6 }, + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0x4dedf471 }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 28, 0, 0x0000a84b }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 2, 0x4dedf471 }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 24, 0, 0x0000a84b }, + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0xbe17e1d7 }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 20, 0, 0x0000d022 }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 2, 0xbe17e1d7 }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 16, 0, 0x0000d022 }, + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0x7967208b }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 12, 0, 0x00005c49 }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 2, 0x7967208b }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 8, 0, 0x00005c49 }, + { 0x20, 0, 0, 0x00000008 }, + { 0x15, 0, 2, 0x55784d5b }, + { 0x28, 0, 0, 0x00000006 }, + { 0x15, 4, 0, 0x00007066 }, + { 0x20, 0, 0, 0x00000002 }, + { 0x15, 0, 3, 0x55784d5b }, + { 0x28, 0, 0, 0x00000000 }, + { 0x15, 0, 1, 0x00007066 }, + { 0x06, 0, 0, 0x00000000 }, + { 0x06, 0, 0, 0x00040000 }, + }; + struct sock_fprog bpf = { + .len = ARRAY_SIZE(code), + .filter = code, + }; + int ret, sock = 0; + + sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (CHECK(sock < 0, "create socket", "errno %d\n", errno)) + return; + + ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); + CHECK(ret < 0, "attach filter", "errno %d\n", errno); + + close(sock); +} + +void test_socket_filter(void) +{ + struct socket_filter *skel; + struct utsname uts; + int err; + + err = uname(&uts); + if (err < 0) + return; + + skel = socket_filter__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open")) + return; + + /* The filter JIT fails on armv6 */ + if (strncmp(uts.machine, "armv6", strlen("armv6")) == 0 && + skel->kconfig->CONFIG_BPF_JIT_ALWAYS_ON) + test__skip(); + else + do_test(); + + socket_filter__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/socket_filter.c b/tools/testing/selftests/bpf/progs/socket_filter.c new file mode 100644 index 000000000000..f93623ec7ec0 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/socket_filter.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> + +char _license[] SEC("license") = "GPL"; + +extern bool CONFIG_BPF_JIT_ALWAYS_ON __kconfig __weak; + +/* This function is here to have CONFIG_BPF_JIT_ALWAYS_ON + * used and added to object BTF. + */ +int unused(void) +{ + return CONFIG_BPF_JIT_ALWAYS_ON ? 0 : 1; +}
On Wed, 2025-08-13 at 23:29 +0800, KaFai Wan wrote:
This test verifies socket filter attachment functionality on architectures supporting either BPF JIT compilation or the interpreter.
It specifically validates the fallback to interpreter behavior when JIT fails, particularly targeting ARMv6 devices with the following configuration: # CONFIG_BPF_JIT_ALWAYS_ON is not set CONFIG_BPF_JIT_DEFAULT_ON=y
Signed-off-by: KaFai Wan kafai.wan@linux.dev
This test should not be landed as-is, first let's do an analysis for why the program fails to jit compile on arm.
I modified kernel to dump BPF program before jit attempt, but don't see anything obviously wrong with it. The patch to get disassembly and disassembly itself with resolved kallsyms are attached.
Can someone with access to ARM vm/machine take a looks at this? Puranjay, Xu, would you have some time?
[...]
On Thu, Aug 14, 2025 at 2:35 AM Eduard Zingerman eddyz87@gmail.com wrote:
On Wed, 2025-08-13 at 23:29 +0800, KaFai Wan wrote:
This test verifies socket filter attachment functionality on architectures supporting either BPF JIT compilation or the interpreter.
It specifically validates the fallback to interpreter behavior when JIT fails, particularly targeting ARMv6 devices with the following configuration: # CONFIG_BPF_JIT_ALWAYS_ON is not set CONFIG_BPF_JIT_DEFAULT_ON=y
Signed-off-by: KaFai Wan kafai.wan@linux.dev
This test should not be landed as-is, first let's do an analysis for why the program fails to jit compile on arm.
I modified kernel to dump BPF program before jit attempt, but don't see anything obviously wrong with it. The patch to get disassembly and disassembly itself with resolved kallsyms are attached.
Can someone with access to ARM vm/machine take a looks at this? Puranjay, Xu, would you have some time?
Hi Eduard, Thanks for the email, I will look into it.
Let me try to boot a kernel on ARMv6 qemu and reproduce this.
Thanks, Puranjay
On Thu, 2025-08-14 at 13:23 +0200, Puranjay Mohan wrote:
On Thu, Aug 14, 2025 at 2:35 AM Eduard Zingerman eddyz87@gmail.com wrote:
On Wed, 2025-08-13 at 23:29 +0800, KaFai Wan wrote:
This test verifies socket filter attachment functionality on architectures supporting either BPF JIT compilation or the interpreter.
It specifically validates the fallback to interpreter behavior when JIT fails, particularly targeting ARMv6 devices with the following configuration: # CONFIG_BPF_JIT_ALWAYS_ON is not set CONFIG_BPF_JIT_DEFAULT_ON=y
Signed-off-by: KaFai Wan kafai.wan@linux.dev
This test should not be landed as-is, first let's do an analysis for why the program fails to jit compile on arm.
I modified kernel to dump BPF program before jit attempt, but don't see anything obviously wrong with it. The patch to get disassembly and disassembly itself with resolved kallsyms are attached.
Can someone with access to ARM vm/machine take a looks at this? Puranjay, Xu, would you have some time?
Hi Eduard, Thanks for the email, I will look into it.
Let me try to boot a kernel on ARMv6 qemu and reproduce this.
Thank you, Puranjay,
While looking at the code yesterday I found a legit case for failing to jit on armv6:
https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/arch/a... https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/arch/a...
But attached program does not seem to be that big to hit 0xfff boundary.
On Thu, 2025-08-14 at 09:06 -0700, Eduard Zingerman wrote:
On Thu, 2025-08-14 at 13:23 +0200, Puranjay Mohan wrote:
On Thu, Aug 14, 2025 at 2:35 AM Eduard Zingerman eddyz87@gmail.com wrote:
On Wed, 2025-08-13 at 23:29 +0800, KaFai Wan wrote:
This test verifies socket filter attachment functionality on architectures supporting either BPF JIT compilation or the interpreter.
It specifically validates the fallback to interpreter behavior when JIT fails, particularly targeting ARMv6 devices with the following configuration: # CONFIG_BPF_JIT_ALWAYS_ON is not set CONFIG_BPF_JIT_DEFAULT_ON=y
Signed-off-by: KaFai Wan kafai.wan@linux.dev
This test should not be landed as-is, first let's do an analysis for why the program fails to jit compile on arm.
I modified kernel to dump BPF program before jit attempt, but don't see anything obviously wrong with it. The patch to get disassembly and disassembly itself with resolved kallsyms are attached.
Can someone with access to ARM vm/machine take a looks at this? Puranjay, Xu, would you have some time?
Hi Eduard, Thanks for the email, I will look into it.
Let me try to boot a kernel on ARMv6 qemu and reproduce this.
Thank you, Puranjay,
While looking at the code yesterday I found a legit case for failing to jit on armv6:
https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/arch/a... https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/arch/a...
But attached program does not seem to be that big to hit 0xfff boundary.
Hi Eduard, Puranjay
OpenWRT users reported several tests that aren't working properly, which may be helpful.
https://github.com/openwrt/openwrt/issues/19405#issuecomment-3121390534 https://github.com/openwrt/openwrt/issues/19405#issuecomment-3176820629
linux-kselftest-mirror@lists.linaro.org