From: Jan-Gerd Tenberge <janten@gmail.com>
To: bpf@vger.kernel.org
Cc: ast@kernel.org, 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, kuba@kernel.org, pabeni@redhat.com,
horms@kernel.org, jakub@cloudflare.com, jiayuan.chen@linux.dev,
kuniyu@google.com, willemb@google.com, shuah@kernel.org,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org
Subject: [PATCH bpf 1/2] bpf: Revalidate iterator programs on link update
Date: Thu, 8 Oct 2026 18:13:08 +0200 [thread overview]
Message-ID: <20261008161309.8179-2-janten@gmail.com> (raw)
In-Reply-To: <20261008161309.8179-1-janten@gmail.com>
Iterator link creation validates constraints that depend on both the
program and the selected target. In particular, map iterators compare
program access sizes against the target map, and sleepable programs may
only attach to reschedulable iterators.
BPF_LINK_UPDATE only checks the program type, expected attach type and
attach BTF ID. A program rejected on direct attach can therefore be
installed by first attaching a compatible program and then replacing it.
For array maps, this allows an oversized value access to cross the source
map allocation. An isolated runtime test used the bypass to overwrite the
refcount of a separately allocated live map. Dropping one legitimate
reference then freed that map while a verified BPF program still held
another reference. After a same-size map reused the slab slot, the live
program accessed the replacement through its stale map pointer. The same
bypass can also corrupt a live map's ops pointer and panic the kernel.
Add an optional target validation callback and invoke it, together with
the sleepability check, before replacing the program. Factor the existing
map element, sk_storage and sockmap checks into validators shared by attach
and update. A failed validation leaves the old program attached.
Fixes: d6c4503cc296 ("bpf: Implement bpf iterator for hash maps")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Jan-Gerd Tenberge <janten@gmail.com>
---
include/linux/bpf.h | 3 ++
kernel/bpf/bpf_iter.c | 15 ++++++++++
kernel/bpf/map_iter.c | 58 +++++++++++++++++++++++----------------
net/core/bpf_sk_storage.c | 24 +++++++++++-----
net/core/sock_map.c | 26 ++++++++++++------
5 files changed, 87 insertions(+), 39 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 0ecb9418dfbd..5aa786eba3ea 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3007,6 +3007,8 @@ struct bpf_iter_aux_info {
typedef int (*bpf_iter_attach_target_t)(struct bpf_prog *prog,
union bpf_iter_link_info *linfo,
struct bpf_iter_aux_info *aux);
+typedef int (*bpf_iter_validate_target_t)(struct bpf_prog *prog,
+ const struct bpf_iter_aux_info *aux);
typedef void (*bpf_iter_detach_target_t)(struct bpf_iter_aux_info *aux);
typedef void (*bpf_iter_show_fdinfo_t) (const struct bpf_iter_aux_info *aux,
struct seq_file *seq);
@@ -3024,6 +3026,7 @@ enum bpf_iter_feature {
struct bpf_iter_reg {
const char *target;
bpf_iter_attach_target_t attach_target;
+ bpf_iter_validate_target_t validate_target;
bpf_iter_detach_target_t detach_target;
bpf_iter_show_fdinfo_t show_fdinfo;
bpf_iter_fill_link_info_t fill_link_info;
diff --git a/kernel/bpf/bpf_iter.c b/kernel/bpf/bpf_iter.c
index b40eb404adab..024419b3dd0a 100644
--- a/kernel/bpf/bpf_iter.c
+++ b/kernel/bpf/bpf_iter.c
@@ -409,6 +409,9 @@ 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);
+ const struct bpf_iter_reg *reg_info = iter_link->tinfo->reg_info;
int ret = 0;
mutex_lock(&link_mutex);
@@ -424,6 +427,18 @@ static int bpf_iter_link_replace(struct bpf_link *link,
goto out_unlock;
}
+ if (new_prog->sleepable &&
+ !bpf_iter_target_support_resched(iter_link->tinfo)) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (reg_info->validate_target) {
+ ret = reg_info->validate_target(new_prog, &iter_link->aux);
+ if (ret)
+ goto out_unlock;
+ }
+
old_prog = xchg(&link->prog, new_prog);
bpf_prog_put(old_prog);
diff --git a/kernel/bpf/map_iter.c b/kernel/bpf/map_iter.c
index c19b360bad9e..d038b795bf4e 100644
--- a/kernel/bpf/map_iter.c
+++ b/kernel/bpf/map_iter.c
@@ -97,13 +97,40 @@ static struct bpf_iter_reg bpf_map_reg_info = {
.seq_info = &bpf_map_seq_info,
};
+static int bpf_iter_validate_map(struct bpf_prog *prog,
+ const struct bpf_iter_aux_info *aux)
+{
+ struct bpf_map *map = aux->map;
+ u32 value_size;
+
+ switch (map->map_type) {
+ case BPF_MAP_TYPE_PERCPU_HASH:
+ case BPF_MAP_TYPE_LRU_PERCPU_HASH:
+ case BPF_MAP_TYPE_PERCPU_ARRAY:
+ value_size = round_up(map->value_size, 8) * num_possible_cpus();
+ break;
+ case BPF_MAP_TYPE_HASH:
+ case BPF_MAP_TYPE_LRU_HASH:
+ case BPF_MAP_TYPE_ARRAY:
+ case BPF_MAP_TYPE_RHASH:
+ value_size = map->value_size;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (prog->aux->max_rdonly_access > map->key_size ||
+ prog->aux->max_rdwr_access > value_size)
+ return -EACCES;
+
+ return 0;
+}
+
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;
if (!linfo->map.map_fd)
@@ -117,33 +144,15 @@ static int bpf_iter_attach_map(struct bpf_prog *prog,
goto put_map;
}
- if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
- map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH ||
- map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY)
- is_percpu = true;
- else if (map->map_type != BPF_MAP_TYPE_HASH &&
- map->map_type != BPF_MAP_TYPE_LRU_HASH &&
- map->map_type != BPF_MAP_TYPE_ARRAY &&
- 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;
- if (!is_percpu)
- value_size = map->value_size;
- 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;
+ aux->map = map;
+ err = bpf_iter_validate_map(prog, aux);
+ if (err)
goto put_map;
- }
- aux->map = map;
return 0;
put_map:
+ aux->map = NULL;
bpf_map_put_with_uref(map);
return err;
}
@@ -172,6 +181,7 @@ DEFINE_BPF_ITER_FUNC(bpf_map_elem, struct bpf_iter_meta *meta,
static const struct bpf_iter_reg bpf_map_elem_reg_info = {
.target = "bpf_map_elem",
.attach_target = bpf_iter_attach_map,
+ .validate_target = bpf_iter_validate_map,
.detach_target = bpf_iter_detach_map,
.show_fdinfo = bpf_iter_map_show_fdinfo,
.fill_link_info = bpf_iter_map_fill_link_info,
diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c
index 1d295a8769fa..0cae873f3ceb 100644
--- a/net/core/bpf_sk_storage.c
+++ b/net/core/bpf_sk_storage.c
@@ -846,6 +846,18 @@ static void bpf_iter_fini_sk_storage_map(void *priv_data)
bpf_map_put_with_uref(seq_info->map);
}
+static int bpf_iter_validate_map(struct bpf_prog *prog,
+ const struct bpf_iter_aux_info *aux)
+{
+ if (aux->map->map_type != BPF_MAP_TYPE_SK_STORAGE)
+ return -EINVAL;
+
+ if (prog->aux->max_rdwr_access > aux->map->value_size)
+ return -EACCES;
+
+ return 0;
+}
+
static int bpf_iter_attach_map(struct bpf_prog *prog,
union bpf_iter_link_info *linfo,
struct bpf_iter_aux_info *aux)
@@ -860,18 +872,15 @@ static int bpf_iter_attach_map(struct bpf_prog *prog,
if (IS_ERR(map))
return PTR_ERR(map);
- if (map->map_type != BPF_MAP_TYPE_SK_STORAGE)
- goto put_map;
-
- if (prog->aux->max_rdwr_access > map->value_size) {
- err = -EACCES;
+ aux->map = map;
+ err = bpf_iter_validate_map(prog, aux);
+ if (err)
goto put_map;
- }
- aux->map = map;
return 0;
put_map:
+ aux->map = NULL;
bpf_map_put_with_uref(map);
return err;
}
@@ -898,6 +907,7 @@ static const struct bpf_iter_seq_info iter_seq_info = {
static struct bpf_iter_reg bpf_sk_storage_map_reg_info = {
.target = "bpf_sk_storage_map",
.attach_target = bpf_iter_attach_map,
+ .validate_target = bpf_iter_validate_map,
.detach_target = bpf_iter_detach_map,
.show_fdinfo = bpf_iter_map_show_fdinfo,
.fill_link_info = bpf_iter_map_fill_link_info,
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index 38df84284328..31f7c3a1667e 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -1933,6 +1933,19 @@ int sock_map_link_create(const union bpf_attr *attr, struct bpf_prog *prog)
return ret;
}
+static int sock_map_iter_validate_target(struct bpf_prog *prog,
+ const struct bpf_iter_aux_info *aux)
+{
+ if (aux->map->map_type != BPF_MAP_TYPE_SOCKMAP &&
+ aux->map->map_type != BPF_MAP_TYPE_SOCKHASH)
+ return -EINVAL;
+
+ if (prog->aux->max_rdonly_access > aux->map->key_size)
+ return -EACCES;
+
+ return 0;
+}
+
static int sock_map_iter_attach_target(struct bpf_prog *prog,
union bpf_iter_link_info *linfo,
struct bpf_iter_aux_info *aux)
@@ -1947,19 +1960,15 @@ static int sock_map_iter_attach_target(struct bpf_prog *prog,
if (IS_ERR(map))
return PTR_ERR(map);
- if (map->map_type != BPF_MAP_TYPE_SOCKMAP &&
- map->map_type != BPF_MAP_TYPE_SOCKHASH)
- goto put_map;
-
- if (prog->aux->max_rdonly_access > map->key_size) {
- err = -EACCES;
+ aux->map = map;
+ err = sock_map_iter_validate_target(prog, aux);
+ if (err)
goto put_map;
- }
- aux->map = map;
return 0;
put_map:
+ aux->map = NULL;
bpf_map_put_with_uref(map);
return err;
}
@@ -1972,6 +1981,7 @@ static void sock_map_iter_detach_target(struct bpf_iter_aux_info *aux)
static struct bpf_iter_reg sock_map_iter_reg = {
.target = "sockmap",
.attach_target = sock_map_iter_attach_target,
+ .validate_target = sock_map_iter_validate_target,
.detach_target = sock_map_iter_detach_target,
.show_fdinfo = bpf_iter_map_show_fdinfo,
.fill_link_info = bpf_iter_map_fill_link_info,
--
2.54.0 (Apple Git-157)
next prev parent reply other threads:[~2026-10-08 16:13 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-08 16:13 [PATCH bpf 0/2] bpf: Fix iterator link update target validation Jan-Gerd Tenberge
2026-10-08 16:13 ` Jan-Gerd Tenberge [this message]
2026-10-08 17:13 ` [PATCH bpf 1/2] bpf: Revalidate iterator programs on link update bot+bpf-ci
2026-10-08 16:13 ` [PATCH bpf 2/2] selftests/bpf: Test iterator link target validation Jan-Gerd Tenberge
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=20261008161309.8179-2-janten@gmail.com \
--to=janten@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--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=jakub@cloudflare.com \
--cc=jiayuan.chen@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--cc=willemb@google.com \
--cc=yonghong.song@linux.dev \
/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