Netdev List
 help / color / mirror / Atom feed
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

      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