bpf: Check tail zero of bpf_map_info and bpf_prog_info
Subsystems (4)
v2 cover bpf: Check tail zero of bpf_map_info and bpf_prog_info
Check the tail bytes of bpf_map_info and bpf_prog_info due to padding when getting map info and prog info via BPF_OBJ_GET_INFO_BY_FD, which was discussed in the thread
"bpf: Check tail zero of bpf_common_attr using offsetofend" [1].
Links:
[1] https://lore.kernel.org/bpf/[email protected]/
Changes:
v1 -> v2:
* Collect Acked-by tags from Mykyta, thanks.
* Update Fixes tag in patch #2 (per bot+bpf-ci)
* v1: https://lore.kernel.org/bpf/[email protected]/
Leon Hwang (3):
bpf: Check tail zero of bpf_map_info
bpf: Check tail zero of bpf_prog_info selftests/bpf: Add tests to verify checking padding bytes for
bpf_[map,prog]_info
kernel/bpf/syscall.c | 10 ++-- .../selftests/bpf/prog_tests/bpf_attr_size.c | 55 +++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-)
--
2.54.0
PATCH v2 1/3 bpf: Check tail zero of bpf_map_info
Since there're 4 bytes padding at the end of struct bpf_map_info, they won't be checked by bpf_check_uarg_tail_zero().
pahole -C bpf_map_info ./vmlinux
struct bpf_map_info {
...
__u64 hash __attribute__((__aligned__(8))); /* 88 8 */
__u32 hash_size; /* 96 4 */
/* size: 104, cachelines: 2, members: 18 */
/* padding: 4 */
/* forced alignments: 1 */
/* last cacheline: 40 bytes */
} __attribute__((__aligned__(8)));If a future kernel extension adds a new 4-byte field, older userspace programs allocating this structure on the stack might inadvertently pass uninitialized stack garbage into the new field, permanently breaking backward compatibility. -- sashiko [1]
Fix it by changing sizeof(info) tooffsetofend(struct bpf_map_info, hash_size).
[1] https://lore.kernel.org/bpf/[email protected]/
--- kernel/bpf/syscall.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/bpf/syscall.c +3 -2
@@ -5400,10 +5400,11 @@ static int bpf_map_get_info_by_fd(struct file *file, {struct bpf_map_info __user *uinfo =u64_to_user_ptr (attr->info.info);struct bpf_map_info info; -u32 info_len = attr->info.info_len; +u32 info_len = attr->info.info_len, len;int err; - err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo),sizeof (info), info_len); + len = offsetofend(struct bpf_map_info, hash_size); + err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo), len, info_len);if (err)return err; info_len =min_t (u32,sizeof (info), info_len);
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH bpf-next v2 1/3] bpf: Check tail zero of bpf_map_info
Does this change break the ABI for existing deployed binaries? Previously, expected_size was 104 (sizeof(info)). If an existing binary passes info_len = 104 with uninitialized stack garbage in the…
Quoted 5 lines
kernel/bpf/syscall.c +0
-0
kernel/bpf/syscall.c static int bpf_map_get_info_by_fd(struct file *file, · line 5407
Quoted 10 lines
kernel/bpf/syscall.c +3
-2@@ -5400,10 +5400,11 @@ static int bpf_map_get_info_by_fd(struct file *file, {struct bpf_map_info __user *uinfo =u64_to_user_ptr (attr->info.info);struct bpf_map_info info; -u32 info_len = attr->info.info_len; +u32 info_len = attr->info.info_len, len;int err; - err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo),sizeof (info), info_len); + len = offsetofend(struct bpf_map_info, hash_size); + err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo), len, info_len);
Does this change break the ABI for existing deployed binaries? Previously, expected_size was 104 (sizeof(info)). If an existing binary passes info_len = 104 with uninitialized stack garbage in the 4-byte padding, bpf_check_uarg_tail_zero() would see actual_size <= expected_size (104 <=
104) and immediately return 0.
By reducing expected_size to 100, actual_size > expected_size (104 > 100), so bpf_check_uarg_tail_zero() will actively check the 4 padding bytes and return -E2BIG if they contain garbage. This rejects previously working applications.
Additionally, does this actually protect against the stated future issue? If a future kernel adds a new 4-byte field, the struct size stays 104, and the new kernel's expected_size will be 104. At that point, those old buggy binaries passing info_len = 104 will once again hit the fast path (actual_size <= expected_size), skipping the check and allowing the uninitialized padding to be consumed as the new field.
Reference: https://lore.kernel.org/bpf/[email protected]/
From: [email protected]
Date: Wed, 03 Jun 2026 15:14:05 +0000
kernel/bpf/syscall.c static int bpf_map_get_info_by_fd(struct file *file, · line 5410
Quoted 3 lines
kernel/bpf/syscall.c +0
-0if (err)return err; info_len =min_t (u32,sizeof (info), info_len);
---
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/26960942650
Re: [PATCH bpf-next v2 1/3] bpf: Check tail zero of bpf_map_info
Quoted 28 lines · On Thu, Jun 4, 2026 at 8:45 AM <[email protected]> wrote:
Quoted 6 lines
kernel/bpf/syscall.c +0
-0kernel/bpf/syscall.c static int bpf_map_get_info_by_fd(struct file *file, · line 5407
Quoted 10 lines
kernel/bpf/syscall.c +3
-2@@ -5400,10 +5400,11 @@ static int bpf_map_get_info_by_fd(struct file *file, {struct bpf_map_info __user *uinfo =u64_to_user_ptr (attr->info.info);struct bpf_map_info info; -u32 info_len = attr->info.info_len; +u32 info_len = attr->info.info_len, len;int err; - err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo),sizeof (info), info_len); + len = offsetofend(struct bpf_map_info, hash_size); + err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo), len, info_len);Does this change break the ABI for existing deployed binaries? Previously,
expected_sizewas 104 (sizeof(info)). If an existing binary passesinfo_len= 104 with uninitialized stack garbage in the 4-byte padding,bpf_check_uarg_tail_zero()would seeactual_size<=expected_size(104 <=
104) and immediately return 0.By reducing
expected_sizeto 100,actual_size>expected_size(104 > 100), sobpf_check_uarg_tail_zero()will actively check the 4 padding bytes and return-E2BIGif they contain garbage. This rejects previously working applications.
Quoted 6 lines
Additionally, does this actually protect against the stated future issue? If a future kernel adds a new 4-byte field, the struct size stays 104, and the new kernel's
expected_sizewill be 104. At that point, those old buggy binaries passinginfo_len= 104 will once again hit the fast path (actual_size<=expected_size), skipping the check and allowing the uninitialized padding to be consumed as the new field.
bot's concerns are valid.
Let's add __u32 :32 to bpf_map_info at the same time.
pw-bot: cr
Re: [PATCH bpf-next v2 1/3] bpf: Check tail zero of bpf_map_info
Quoted 36 lines · Alexei Starovoitov
Quoted 28 lines · On Thu, Jun 4, 2026 at 8:45 AM <[email protected]> wrote:
Quoted 6 lines
kernel/bpf/syscall.c +0
-0kernel/bpf/syscall.c static int bpf_map_get_info_by_fd(struct file *file, · line 5407
Quoted 10 lines
kernel/bpf/syscall.c +3
-2@@ -5400,10 +5400,11 @@ static int bpf_map_get_info_by_fd(struct file *file, {struct bpf_map_info __user *uinfo =u64_to_user_ptr (attr->info.info);struct bpf_map_info info; -u32 info_len = attr->info.info_len; +u32 info_len = attr->info.info_len, len;int err; - err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo),sizeof (info), info_len); + len = offsetofend(struct bpf_map_info, hash_size); + err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo), len, info_len);Does this change break the ABI for existing deployed binaries? Previously,
expected_sizewas 104 (sizeof(info)). If an existing binary passesinfo_len= 104 with uninitialized stack garbage in the 4-byte padding,bpf_check_uarg_tail_zero()would seeactual_size<=expected_size(104 <=
104) and immediately return 0.By reducing
expected_sizeto 100,actual_size>expected_size(104 > 100), sobpf_check_uarg_tail_zero()will actively check the 4 padding bytes and return-E2BIGif they contain garbage. This rejects previously working applications.Quoted 6 lines
Additionally, does this actually protect against the stated future issue? If a future kernel adds a new 4-byte field, the struct size stays 104, and the new kernel's
expected_sizewill be 104. At that point, those old buggy binaries passinginfo_len= 104 will once again hit the fast path (actual_size<=expected_size), skipping the check and allowing the uninitialized padding to be consumed as the new field.
Quoted 3 lines
bot's concerns are valid.
Let's add__u32:32 tobpf_map_infoat the same time.
Will add it.
Thanks,
Leon
PATCH v2 2/3 bpf: Check tail zero of bpf_prog_info
In reply to Leon HwangSince there're 4 bytes padding at the end of struct bpf_prog_info, they won't be checked by bpf_check_uarg_tail_zero().
pahole -C bpf_prog_info ./vmlinux
struct bpf_prog_info {
...
__u32 attach_btf_obj_id; /* 220 4 */
__u32 attach_btf_id; /* 224 4 */
/* size: 232, cachelines: 4, members: 38 */
/* sum members: 224 */
/* sum bitfield members: 1 bits, bit holes: 1, sum bit holes: 31 bits */
/* padding: 4 */
/* forced alignments: 9 */
/* last cacheline: 40 bytes */
} __attribute__((__aligned__(8)));If a future kernel extension adds a new 4-byte field, older userspace programs allocating this structure on the stack might inadvertently pass uninitialized stack garbage into the new field, permanently breaking backward compatibility. -- sashiko [1]
Fix it by changing sizeof(info) tooffsetofend(struct bpf_prog_info, attach_btf_id).
[1] https://lore.kernel.org/bpf/[email protected]/
--- kernel/bpf/syscall.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/bpf/syscall.c +3 -2
@@ -5115,10 +5115,11 @@ static int bpf_prog_get_info_by_fd(struct file *file,u32 info_len = attr->info.info_len;struct bpf_prog_kstats stats;char __user *uinsns; -u32 ulen; +u32 ulen, len;int err; - err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo),sizeof (info), info_len); + len = offsetofend(struct bpf_prog_info, attach_btf_id); + err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo), len, info_len);if (err)return err; info_len =min_t (u32,sizeof (info), info_len);
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH bpf-next v2 2/3] bpf: Check tail zero of bpf_prog_info
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Critical] ABI Breakage / UAPI Regression: Enforcing a zero-check on previously unchecked UAPI struct…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] ABI Breakage / UAPI Regression: Enforcing a zero-check on previously unchecked UAPI struct padding actively breaks backwards compatibility for existing userspace applications. --
commit 5d5895c0d685510901c044426a4ec3d4faf9bea8
Author: Leon Hwang <[email protected]>
bpf: Check tail zero of bpf_prog_info
This commit changes the size passed to bpf_check_uarg_tail_zero() from sizeof(info) to offsetofend() to validate the padding bytes of struct bpf_prog_info.
Quoted 5 lines
kernel/bpf/syscall.c +0
-0
kernel/bpf/syscall.c static int bpf_prog_get_info_by_fd(struct file *file, · line 5122
Quoted 10 lines
kernel/bpf/syscall.c +3
-2@@ -5115,10 +5115,11 @@ static int bpf_prog_get_info_by_fd(struct file *file,u32 info_len = attr->info.info_len;struct bpf_prog_kstats stats;char __user *uinsns; -u32 ulen; +u32 ulen, len;int err; - err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo),sizeof (info), info_len); + len = offsetofend(struct bpf_prog_info, attach_btf_id); + err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo), len, info_len);
[Severity: Critical]
Will this cause a regression for existing userspace programs?
Since struct bpf_prog_info has trailing padding due to alignment rules, existing userspace programs allocating this struct on the stack might pass the full sizeof(info) via info_len without explicitly zero-initializing the trailing padding.
Previously, because expected_size was sizeof(info), the padding bytes were not checked. With this change, bpf_check_uarg_tail_zero() will now check those uninitialized padding bytes against zero.
If they contain stack garbage, the syscall will unexpectedly fail with -E2BIG. Does this actively reject previously functioning userspace code?
Quoted 3 lines
kernel/bpf/syscall.c +0
-0if (err)return err; info_len =min_t (u32,sizeof (info), info_len);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
[email protected]
AI reviewRe: [PATCH bpf-next v2 2/3] bpf: Check tail zero of bpf_prog_info
In reply to Leon HwangA concern raised during the v1 review on the list does not appear to have been resolved in the code, so it seems worth raising again here.
Quoted 5 lines
kernel/bpf/syscall.c +0
-0
kernel/bpf/syscall.c static int bpf_prog_get_info_by_fd(struct file *file, · line 5122
Quoted 10 lines
kernel/bpf/syscall.c +3
-2@@ -5115,10 +5115,11 @@ static int bpf_prog_get_info_by_fd(struct file *file,u32 info_len = attr->info.info_len;struct bpf_prog_kstats stats;char __user *uinsns; -u32 ulen; +u32 ulen, len;int err; - err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo),sizeof (info), info_len); + len = offsetofend(struct bpf_prog_info, attach_btf_id); + err =bpf_check_uarg_tail_zero (USER_BPFPTR (uinfo), len, info_len);
A concern raised during the v1 review on the list does not appear to have been resolved in the code, so it seems worth raising again here.
With len now equal to 228 instead of sizeof(info) == 232, a caller that passes info_len == 232 will have actual_size (232) greater than expected_size (228), so bpf_check_uarg_tail_zero() will actively check the 4 padding bytes and return -E2BIG when they contain garbage.
Could this reject callers that work today? An existing binary that allocates struct bpf_prog_info on the stack and passes info_len == 232 without zeroing the trailing padding would have hit the fast path (actual_size <= expected_size) before this change and returned 0.
There was also a question about whether this actually prevents the future-field scenario described in the commit message. If a later kernel adds a new 4-byte field, the struct size stays 232 and that kernel's expected_size becomes 232, so an old binary passing info_len == 232 hits the fast path again and the uninitialized padding is consumed as the new field.
For reference, the original discussion is here:
kernel/bpf/syscall.c static int bpf_prog_get_info_by_fd(struct file *file, · line 5125
Quoted 3 lines
kernel/bpf/syscall.c +0
-0if (err)return err; info_len =min_t (u32,sizeof (info), info_len);
---
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/26960942650
PATCH v2 3/3 selftests/bpf: Add tests to verify checking padding bytes for bpf_[map,prog]_info
In reply to Leon HwangAdd two tests to verify that the tail padding 4 bytes of struct bpf_map_info and bpf_prog_info are checked in syscall.c using bpf_check_uarg_tail_zero().
--- .../selftests/bpf/prog_tests/bpf_attr_size.c | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+)
tools/testing/selftests/bpf/prog_tests/bpf_attr_size.c +55 -0
@@ -62,8 +62,63 @@ static void test_query_size_boundaries(void)cgroup_skb_direct_packet_access__destroy (skel); } +static void test_map_info_tail_zero (void ) +{ +LIBBPF_OPTS (bpf_map_create_opts, map_opts); +struct bpf_map_info_fake { + __u8 info[offsetofend(struct bpf_map_info, hash_size)]; +__u32 pad; + } info = { + .pad =1 , + }; +int map_fd, err; +__u32 info_len; + + map_fd =bpf_map_create (BPF_MAP_TYPE_ARRAY ,"arr" ,sizeof (int ),1 ,1 , &map_opts); +if (!ASSERT_GE (map_fd,0 ,"bpf_map_create" )) +return ; + + info_len =sizeof (info); + err =bpf_obj_get_info_by_fd (map_fd, &info, &info_len); +ASSERT_EQ (err, -E2BIG ,"bpf_obj_get_info_by_fd" ); + +close (map_fd); +} + +static void test_prog_info_tail_zero (void ) +{ +LIBBPF_OPTS (bpf_prog_load_opts, prog_opts); +struct bpf_insn insns[] = { +BPF_MOV64_IMM (BPF_REG_0 ,0 ), +BPF_EXIT_INSN (), + }; +struct bpf_prog_info_fake { + __u8 info[offsetofend(struct bpf_prog_info, attach_btf_id)]; +__u32 pad; + } info = { + .pad =1 , + }; +int prog_fd, err; +__u32 info_len; + + prog_fd =bpf_prog_load (BPF_PROG_TYPE_SOCKET_FILTER ,"test_prog" ,"GPL" , insns, +ARRAY_SIZE (insns), &prog_opts); +if (!ASSERT_GE (prog_fd,0 ,"bpf_prog_load" )) +return ; + + info_len =sizeof (info); + err =bpf_obj_get_info_by_fd (prog_fd, &info, &info_len); +ASSERT_EQ (err, -E2BIG ,"bpf_obj_get_info_by_fd" ); + +close (prog_fd); +} +void test_bpf_attr_size (void ) {if (test__start_subtest ("query_size_boundaries" ))test_query_size_boundaries (); +if (test__start_subtest ("map_info_tail_zero" )) +test_map_info_tail_zero (); +if (test__start_subtest ("prog_info_tail_zero" )) +test_prog_info_tail_zero (); }
Signature · 1 line
2.54.0