From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Zhiling Zou" <zhilinz@nebusec.ai>, <ameryhung@gmail.com>,
<bpf@vger.kernel.org>, <netdev@vger.kernel.org>,
<llvm@lists.linux.dev>
Cc: <daniel@iogearbox.net>, <andrii@kernel.org>, <eddyz87@gmail.com>,
<memxor@gmail.com>, <martin.lau@linux.dev>, <song@kernel.org>,
<yonghong.song@linux.dev>, <jolsa@kernel.org>,
<emil@etsalapatis.com>, <ihor.solodrai@linux.dev>,
<john.fastabend@gmail.com>, <davem@davemloft.net>,
<edumazet@kernel.org>, <pabeni@redhat.com>, <horms@kernel.org>,
<nathan@kernel.org>, <ndesaulniers@google.com>,
<morbo@google.com>, <justinstitt@google.com>,
<menglong8.dong@gmail.com>, <bestswngs@gmail.com>,
<mattbobrowski@google.com>, <vega@nebusec.ai>,
<sashiko-bot@kernel.org>
Subject: Re: [PATCH bpf v2 1/1] bpf: reject raw iteration over managed map fields
Date: Tue, 20 Oct 2026 14:58:36 +0000 [thread overview]
Message-ID: <DM9R60QEU8RP.2MWT9CY6J4ZVF@gmail.com> (raw)
In-Reply-To: <96a6b5dcf721e49c049662cc269d8ac8c389b6af.1792217106.git.zhilinz@nebusec.ai>
On Mon, Oct 19, 2026 at 11:35 PM Zhiling Zou <zhilinz@nebusec.ai> wrote:
> Revalidate the replacement program against the map associated with an
> iterator link before bpf_link_update() swaps programs. Add selftests for
> allowed prefix access, rejected managed-field reads and writes, and link
> updates that introduce managed-field access.
The sockmap iterator has the same hole.
sock_map_iter_attach_target() checks max_rdonly_access against key_size
at attach time only, so bpf_link_update() can still swap in a prog that
reads past the key.
No need for a new callback that each target has to remember to set.
attach_target can record the limits in bpf_iter_aux_info and bpf_iter
core can check them on attach and on replace. That covers all three
targets. validate_prog, its typedef and the split of both attach
functions go away. Selftests are left out below.
Something like this (compile tested only):
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 0ecb941..ffb6fee 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -544,6 +544,14 @@ static inline void check_and_init_map_value(struct bpf_map *map, void *dst)
bpf_obj_init(map->record, dst);
}
+/* Part of map value before the first special field. Fields are sorted by offset. */
+static inline u32 bpf_map_raw_value_size(const struct bpf_map *map)
+{
+ if (IS_ERR_OR_NULL(map->record))
+ return map->value_size;
+ return map->record->fields[0].offset;
+}
+
/* memcpy that is used with 8-byte aligned pointers, power-of-8 size and
* forced to use 'long' read/writes to try to atomically copy long counters.
* Best-effort only. No barriers here, since it _will_ race with concurrent
@@ -2992,6 +3000,9 @@ enum bpf_iter_task_type {
struct bpf_iter_aux_info {
/* for map_elem iter */
struct bpf_map *map;
+ /* how far a prog may access key and value of the map */
+ u32 max_rdonly_access;
+ u32 max_rdwr_access;
/* for cgroup iter */
struct {
diff --git a/kernel/bpf/bpf_iter.c b/kernel/bpf/bpf_iter.c
index b40eb40..6f3ae92 100644
--- a/kernel/bpf/bpf_iter.c
+++ b/kernel/bpf/bpf_iter.c
@@ -405,10 +405,21 @@ static void bpf_iter_link_dealloc(struct bpf_link *link)
kfree(iter_link);
}
+static int bpf_iter_check_access(const struct bpf_prog *prog,
+ const struct bpf_iter_aux_info *aux)
+{
+ if (prog->aux->max_rdonly_access > aux->max_rdonly_access ||
+ prog->aux->max_rdwr_access > aux->max_rdwr_access)
+ return -EACCES;
+ return 0;
+}
+
static int bpf_iter_link_replace(struct bpf_link *link,
struct bpf_prog *new_prog,
struct bpf_prog *old_prog)
{
+ struct bpf_iter_link *iter_link =
+ container_of(link, struct bpf_iter_link, link);
int ret = 0;
mutex_lock(&link_mutex);
@@ -424,6 +435,10 @@ static int bpf_iter_link_replace(struct bpf_link *link,
goto out_unlock;
}
+ ret = bpf_iter_check_access(new_prog, &iter_link->aux);
+ if (ret)
+ goto out_unlock;
+
old_prog = xchg(&link->prog, new_prog);
bpf_prog_put(old_prog);
@@ -570,6 +585,14 @@ int bpf_iter_link_attach(const union bpf_attr *attr, bpfptr_t uattr,
}
}
+ err = bpf_iter_check_access(prog, &link->aux);
+ if (err) {
+ if (tinfo->reg_info->detach_target)
+ tinfo->reg_info->detach_target(&link->aux);
+ bpf_link_cleanup(&link_primer);
+ return err;
+ }
+
return bpf_link_settle(&link_primer);
}
diff --git a/kernel/bpf/map_iter.c b/kernel/bpf/map_iter.c
index c19b360..9fd5920 100644
--- a/kernel/bpf/map_iter.c
+++ b/kernel/bpf/map_iter.c
@@ -101,7 +101,6 @@ static int bpf_iter_attach_map(struct bpf_prog *prog,
union bpf_iter_link_info *linfo,
struct bpf_iter_aux_info *aux)
{
- u32 key_acc_size, value_acc_size, key_size, value_size;
struct bpf_map *map;
bool is_percpu = false;
int err = -EINVAL;
@@ -127,18 +126,12 @@ static int bpf_iter_attach_map(struct bpf_prog *prog,
map->map_type != BPF_MAP_TYPE_RHASH)
goto put_map;
- key_acc_size = prog->aux->max_rdonly_access;
- value_acc_size = prog->aux->max_rdwr_access;
- key_size = map->key_size;
+ aux->max_rdonly_access = map->key_size;
+ /* percpu values are copied with special fields zeroed */
if (!is_percpu)
- value_size = map->value_size;
+ aux->max_rdwr_access = bpf_map_raw_value_size(map);
else
- value_size = round_up(map->value_size, 8) * num_possible_cpus();
-
- if (key_acc_size > key_size || value_acc_size > value_size) {
- err = -EACCES;
- goto put_map;
- }
+ aux->max_rdwr_access = round_up(map->value_size, 8) * num_possible_cpus();
aux->map = map;
return 0;
diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c
index 1d295a8..44b7e61 100644
--- a/net/core/bpf_sk_storage.c
+++ b/net/core/bpf_sk_storage.c
@@ -863,11 +863,7 @@ static int bpf_iter_attach_map(struct bpf_prog *prog,
if (map->map_type != BPF_MAP_TYPE_SK_STORAGE)
goto put_map;
- if (prog->aux->max_rdwr_access > map->value_size) {
- err = -EACCES;
- goto put_map;
- }
-
+ aux->max_rdwr_access = bpf_map_raw_value_size(map);
aux->map = map;
return 0;
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index 38df842..37144bf 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -1951,11 +1951,7 @@ static int sock_map_iter_attach_target(struct bpf_prog *prog,
map->map_type != BPF_MAP_TYPE_SOCKHASH)
goto put_map;
- if (prog->aux->max_rdonly_access > map->key_size) {
- err = -EACCES;
- goto put_map;
- }
-
+ aux->max_rdonly_access = map->key_size;
aux->map = map;
return 0;
> Fixes: 9db44fdd8105 ("bpf: Support kptrs in local storage maps")
Wrong tag. Iterators could write special fields from the start:
commit d6c4503cc296 ("bpf: Implement bpf iterator for hash maps") and
commit 5ce6e77c7edf ("bpf: Implement bpf iterator for sock local storage map").
[...]
> + /* Fields are sorted by offset; max_rdwr_access is an exclusive end. */
> + if (map->record && value_acc_size > map->record->fields[0].offset)
This will crash. map->record is an ERR_PTR when btf_parse_fields()
failed, and map_check_btf() still creates the map. A value with two
struct bpf_timer gets -E2BIG, for example. That's why other users check
IS_ERR_OR_NULL(). Attaching any iterator to such a map now faults on
fields[0], whether the prog touches the value or not. Same in
bpf_iter_sk_storage_map_check_prog().
Percpu maps don't have this bug. Their iterator runs on
percpu_value_buf: copy_map_value_long() skips special fields and
check_and_init_map_value() zeroes them, so the prog sees zeros and
writes to a temporary copy. This check rejects a prog that sums a
counter over all cpus in a percpu hash that has a kptr, for no reason.
It's an old bug, it needs root and the fix rejects progs that load
today. Pls target bpf-next.
pw-bot: cr
prev parent reply other threads:[~2026-10-20 14:58 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 6:35 [PATCH bpf v2 0/1] bpf: reject raw iteration over managed map fields Zhiling Zou
2026-10-20 6:35 ` [PATCH bpf v2 1/1] " Zhiling Zou
2026-10-20 7:13 ` bot+bpf-ci
2026-10-20 14:58 ` Alexei Starovoitov [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DM9R60QEU8RP.2MWT9CY6J4ZVF@gmail.com \
--to=alexei.starovoitov@gmail.com \
--cc=ameryhung@gmail.com \
--cc=andrii@kernel.org \
--cc=bestswngs@gmail.com \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=eddyz87@gmail.com \
--cc=edumazet@kernel.org \
--cc=emil@etsalapatis.com \
--cc=horms@kernel.org \
--cc=ihor.solodrai@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=justinstitt@google.com \
--cc=llvm@lists.linux.dev \
--cc=martin.lau@linux.dev \
--cc=mattbobrowski@google.com \
--cc=memxor@gmail.com \
--cc=menglong8.dong@gmail.com \
--cc=morbo@google.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sashiko-bot@kernel.org \
--cc=song@kernel.org \
--cc=vega@nebusec.ai \
--cc=yonghong.song@linux.dev \
--cc=zhilinz@nebusec.ai \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox