Skip to contentSkip to conversation

bpf: Check tail zero of bpf_map_info and bpf_prog_info

PATCH Applied Leon Hwang 3 participants 6 messages Jun 5

Subsystems (4)
lore
Share & tools
lore

Leon Hwang

author
#0 lore raw

v3 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:
v2 -> v3:
* Add "__u32 :32" to bpf_map_info and bpf_prog_info (per Alexei).
* v2: https://lore.kernel.org/bpf/[email protected]/

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
 include/uapi/linux/bpf.h                      |  2 +
 kernel/bpf/syscall.c                          | 10 ++--
 tools/include/uapi/linux/bpf.h                |  2 +
 .../selftests/bpf/prog_tests/bpf_attr_size.c  | 55 +++++++++++++++++++
 4 files changed, 65 insertions(+), 4 deletions(-)
Signature · 1 line
2.54.0
author
#1 lore raw

PATCH v3 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) to
offsetofend(struct bpf_map_info, hash_size).

And, add "__u32 :32" to the tail of struct bpf_map_info.

[1] https://lore.kernel.org/bpf/[email protected]/

Fixes ea2e6467ac36 ("bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD")
---
 include/uapi/linux/bpf.h       | 1 +
 kernel/bpf/syscall.c           | 5 +++--
 tools/include/uapi/linux/bpf.h | 1 +
 3 files changed, 5 insertions(+), 2 deletions(-)
include/uapi/linux/bpf.h +1 -0
index bed9b1b4d5ef..e1730f449d9e 100644
@@ -6733,6 +6733,7 @@ struct bpf_map_info {
 	__u64 map_extra;
 	__aligned_u64 hash;
 	__u32 hash_size;
+	__u32 :32;
 } __attribute__((aligned(8)));
 
 struct bpf_btf_info {
kernel/bpf/syscall.c +3 -2
index 31a3b70a0b5d..89f020a44fc9 100644
@@ -5406,10 +5406,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);
tools/include/uapi/linux/bpf.h +1 -0
index 7d0b282ba674..7caf667e86fe 100644
@@ -6733,6 +6733,7 @@ struct bpf_map_info {
 	__u64 map_extra;
 	__aligned_u64 hash;
 	__u32 hash_size;
+	__u32 :32;
 } __attribute__((aligned(8)));
 
 struct bpf_btf_info {
Signature · 1 line
2.54.0
author
#2 lore raw

PATCH v3 2/3 bpf: Check tail zero of bpf_prog_info

In reply to Leon Hwang

Since 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) to
offsetofend(struct bpf_prog_info, attach_btf_id).

And, add "__u32 :32" to the tail of struct bpf_prog_info.

[1] https://lore.kernel.org/bpf/[email protected]/

Fixes aba64c7da983 ("bpf: Add verified_insns to bpf_prog_info and fdinfo")
---
 include/uapi/linux/bpf.h       | 1 +
 kernel/bpf/syscall.c           | 5 +++--
 tools/include/uapi/linux/bpf.h | 1 +
 3 files changed, 5 insertions(+), 2 deletions(-)
include/uapi/linux/bpf.h +1 -0
index e1730f449d9e..d5238df5e5eb 100644
@@ -6712,6 +6712,7 @@ struct bpf_prog_info {
 	__u32 verified_insns;
 	__u32 attach_btf_obj_id;
 	__u32 attach_btf_id;
+	__u32 :32;
 } __attribute__((aligned(8)));
 
 struct bpf_map_info {
kernel/bpf/syscall.c +3 -2
index 89f020a44fc9..c5d4ae957e87 100644
@@ -5121,10 +5121,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);
tools/include/uapi/linux/bpf.h +1 -0
index 7caf667e86fe..3829db087449 100644
@@ -6712,6 +6712,7 @@ struct bpf_prog_info {
 	__u32 verified_insns;
 	__u32 attach_btf_obj_id;
 	__u32 attach_btf_id;
+	__u32 :32;
 } __attribute__((aligned(8)));
 
 struct bpf_map_info {
Signature · 1 line
2.54.0
#3 lore raw

Re: [PATCH bpf-next v3 2/3] bpf: Check tail zero of bpf_prog_info

Is the Fixes: tag pointing to the right commit? The padding bug was actually introduced by commit b79c9fc9551b ("bpf: implement BPF_PROG_QUERY for BPF_LSM_CGROUP"), which added attach_btf_obj_id and…
Quoted 28 lines

commit acd0d2042870d7613c0c27932c41262d1c41b4e1
Author: Leon Hwang <[email protected]>
Commit: Kernel Patches Daemon <[email protected]>

bpf: Check tail zero of bpf_prog_info

Since 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]

Quoted 8 lines

Fix it by changing sizeof(info) to offsetofend(struct bpf_prog_info, attach_btf_id).

And, add "__u32 :32" to the tail of struct bpf_prog_info.

[1] https://lore.kernel.org/bpf/[email protected]/

Fixes aba64c7da983 ("bpf: Add verified_insns to bpf_prog_info and fdinfo")

Is the Fixes: tag pointing to the right commit? The padding bug was actually introduced by commit b79c9fc9551b ("bpf: implement BPF_PROG_QUERY for BPF_LSM_CGROUP"), which added attach_btf_obj_id and attach_btf_id after verified_insns, creating the 4 bytes of padding at the end of the structure. Commit aba64c7da983 added verified_insns as the last field but did not create padding.

Suggested: Fixes: b79c9fc9551b ("bpf: implement BPF_PROG_QUERY for BPF_LSM_CGROUP")

Quoted 2 lines

[ ... ]

Quoted 8 lines
kernel/bpf/syscall.c +0 -0
index 89f020a44fc9..c5d4ae957e87 100644
@@ -5121,10 +5121,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;

kernel/bpf/syscall.c static int bpf_prog_get_info_by_fd(struct file *file, · line 5131

Quoted 10 lines
kernel/bpf/syscall.c +3 -2
-	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);

Does this change break the ABI for existing deployed binaries?

Previously, expected_size was 232 (sizeof(info)). If an existing binary passes info_len = 232 with uninitialized stack garbage in the 4-byte padding, bpf_check_uarg_tail_zero() would see actual_size <= expected_size (232 <= 232) and immediately return 0.

By reducing expected_size to 228, actual_size > expected_size (232 > 228), 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.

sashiko-bot and bot+bpf-ci raised this concern in v1 and v2:
https://lore.kernel.org/bpf/[email protected]/

While v3 adds '__u32 :32;' to prevent future field additions in the padding space (addressing forward compatibility), the backward compatibility concern about breaking existing binaries remains unresolved.

[ ... ]

---
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/27025811732

author
#4 lore raw

PATCH v3 3/3 selftests/bpf: Add tests to verify checking padding bytes for bpf_[map,prog]_info

In reply to Leon Hwang

Add 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
index 32159dc64da8..87842c4347a6 100644
@@ -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
#5 lore raw
In reply to Leon Hwang
This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:

Hello:

This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:

Quoted 9 lines · On Fri, 5 Jun 2026 23:52:46 +0800 you wrote:

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]/

[...]

Here is the summary with links:
- [bpf-next,v3,1/3] bpf: Check tail zero of bpf_map_info

    https://git.kernel.org/bpf/bpf-next/c/e2a49fdb1bee
  - [bpf-next,v3,2/3] bpf: Check tail zero of bpf_prog_info
    https://git.kernel.org/bpf/bpf-next/c/786be2b05980
  - [bpf-next,v3,3/3] selftests/bpf: Add tests to verify checking padding bytes for bpf_[map,prog]_info
    https://git.kernel.org/bpf/bpf-next/c/d47e67a487bf

You are awesome, thank you!

Signature · 2 lines
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html