bpf, sockmap: reject a packet-modifying SK_SKB stream parser
Subsystems (7)
v5 cover bpf, sockmap: reject a packet-modifying SK_SKB stream parser
A BPF_PROG_TYPE_SK_SKB stream parser runs on strparser's message head, which can chain skbs through frag_list. A parser that resizes the skb frees the frag_list segments that strparser still tracks through skb_nextp, leading to a use-after-free.
A stream parser is only meant to measure the next message, not to modify the packet, so reject a packet-modifying parser at attach time.
v5:
- target bpf-next instead of bpf
- add Reviewed-by tag (Jiayuan Chen)
v4:
- https://lore.kernel.org/all/[email protected]/
v3:
- https://lore.kernel.org/all/[email protected]/
v2:
- https://lore.kernel.org/all/[email protected]/
v1:
- https://lore.kernel.org/all/[email protected]/
Sechang Lim (3):
selftests/bpf: don't modify the skb in the strparser parser prog bpf, sockmap: reject a packet-modifying SK_SKB stream parser selftests/bpf: test rejection of a packet-modifying SK_SKB stream
parser
net/core/sock_map.c | 20 ++++++++++++ .../selftests/bpf/prog_tests/sockmap_strp.c | 31 +++++++++++++++++++ .../selftests/bpf/progs/sockmap_parse_prog.c | 22 ------------- .../selftests/bpf/progs/test_sockmap_strp.c | 7 +++++ 4 files changed, 58 insertions(+), 22 deletions(-)
Signature · 1 line
2.43.0
PATCH v5 1/3 selftests/bpf: don't modify the skb in the strparser parser prog
sockmap_parse_prog.c is attached as an SK_SKB stream parser and modifies the skb: it calls bpf_skb_pull_data() and writes a byte into the packet. A stream parser runs on strparser's message head and must not modify it. A resize frees the frag_list segments strparser still tracks, leading to a use-after-free.
Make the parser read-only. It only needs to return the message length, which keeps it attaching once packet-modifying parsers are rejected.
--- .../selftests/bpf/progs/sockmap_parse_prog.c | 22 ------------------- 1 file changed, 22 deletions(-)
tools/testing/selftests/bpf/progs/sockmap_parse_prog.c +0 -22
@@ -5,28 +5,6 @@ SEC("sk_skb1")int bpf_prog1 (struct __sk_buff *skb) { -void *data_end = (void *)(long ) skb->data_end; -void *data = (void *)(long ) skb->data; -__u8 *d = data; -int err; - -if (data +10 > data_end) { - err =bpf_skb_pull_data (skb,10 ); -if (err) -return SK_DROP ; - - data_end = (void *)(long )skb->data_end; - data = (void *)(long )skb->data; -if (data +10 > data_end) -return SK_DROP ; - } - -/* This write/read is a bit pointless but tests the verifier and -* strparser handler for read/write pkt data and access into sk -* fields. -*/ - d = data; - d[7 ] =1 ;return skb->len; }
Signature · 1 line
2.43.0
PATCH v5 2/3 bpf, sockmap: reject a packet-modifying SK_SKB stream parser
In reply to Sechang Limsk_psock_strp_parse() runs the BPF_PROG_TYPE_SK_SKB stream-parser program to find the length of the next message. strparser assembles a message out of several received skbs by chaining them onto the head's frag_list and recording where to append the next one in strp->skb_nextp:
*strp->skb_nextp = skb; strp->skb_nextp = &skb->next;
and then calls the parser on the head:
len = (*strp->cb.parse_msg)(strp, head);
The parser is only meant to inspect the skb, but the program may call bpf_skb_change_tail() -- or the sibling bpf_skb_pull_data(), bpf_skb_change_head(), bpf_skb_adjust_room(), all allowed for SK_SKB. Once the head carries a frag_list these go
... -> skb_ensure_writable -> pskb_may_pull -> __pskb_pull_tail
and __pskb_pull_tail() frees the frag_list skbs that strparser still tracks through skb_nextp:
while ((list = skb_shinfo(skb)->frag_list) != insp) {
skb_shinfo(skb)->frag_list = list->next;
consume_skb(list);
}strp->skb_nextp now points into a freed sk_buff. The next segment of the same message arrives in __strp_recv(), which links it with *strp->skb_nextp = skb, an 8-byte write into the freed skb. The free and the write happen in different __strp_recv() calls, so the message has to span at least three segments before it triggers.
BUG: KASAN: slab-use-after-free in __strp_recv+0x447/0xda0 Write of size 8 at addr ffff88810db86140 by task repro/349 Call Trace: <IRQ> __strp_recv+0x447/0xda0 __tcp_read_sock+0x13d/0x590 tcp_bpf_strp_read_sock+0x195/0x320 strp_data_ready+0x267/0x340 sk_psock_strp_data_ready+0x1ce/0x350 tcp_data_queue+0x1364/0x2fd0 tcp_rcv_established+0xe07/0x1640 [...] Allocated by task 349: skb_clone+0x17b/0x210 __strp_recv+0x2c3/0xda0 __tcp_read_sock+0x13d/0x590 [...] Freed by task 349: kmem_cache_free+0x150/0x570 __pskb_pull_tail+0x57b/0xc20 skb_ensure_writable+0x236/0x260 __bpf_skb_change_tail+0x1d4/0x590 sk_skb_change_tail+0x2a/0x40 bpf_prog_1b285dcd6c41373e+0x27/0x30 bpf_prog_run_pin_on_cpu+0xf3/0x260 sk_psock_strp_parse+0x118/0x1e0 __strp_recv+0x4f6/0xda0 [...]
The same resize also leaves the head's length inconsistent with its frags, so a later __pskb_pull_tail() can instead hit the BUG_ON(skb_copy_bits(...)) in net/core/skbuff.c.
A stream parser is only meant to measure the next message, not to modify the packet. Reject a parser whose program can change packet data (prog->aux->changes_pkt_data) at attach time. The check is shared by sock_map_prog_update() and sock_map_link_update_prog(), which between them cover prog attach, link create and link update. Verdict programs are unaffected and may still modify the skb.
--- net/core/sock_map.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
net/core/sock_map.c +20 -0
@@ -1515,6 +1515,17 @@ static int sock_map_prog_link_lookup(struct bpf_map *map, struct bpf_prog ***pprreturn 0 ; } +static int sock_map_prog_attach_check (enum bpf_attach_type attach_type, +struct bpf_prog *prog) +{ +/* A stream parser must not modify the skb, only measure it. */ +if (prog && attach_type ==BPF_SK_SKB_STREAM_PARSER && + prog->aux->changes_pkt_data) +return -EINVAL ; + +return 0 ; +} +/* Handle the following four cases: * prog_attach: prog != NULL, old == NULL, link == NULL * prog_detach: prog == NULL, old != NULL, link == NULL
@@ -1533,6 +1544,10 @@ static int sock_map_prog_update(struct bpf_map *map, struct bpf_prog *prog,if (ret)return ret; + ret =sock_map_prog_attach_check (which, prog); +if (ret) +return ret; +/* for prog_attach/prog_detach/link_attach, return error if a bpf_link * exists for that prog. */
@@ -1776,6 +1791,11 @@ static int sock_map_link_update_prog(struct bpf_link *link, ret = -EINVAL ; goto out; } + + ret =sock_map_prog_attach_check (link->attach_type, prog); +if (ret) + goto out; +if (!sockmap_link->map) { ret = -ENOLINK ; goto out;
Signature · 1 line
2.43.0
[email protected]
AI reviewThank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The fix for the stream parser slab-use-after-free can be bypassed because BPF_FUNC_dynptr_write is missing from bpf_helper_changes_pkt_data().
- [High] Lockless access to link->prog in bpf_link_show_fdinfo and bpf_link_get_info_by_fd can lead to a Use-After-Free if the program is updated concurrently. --
commit bb301227c425ce89eb44f853ff40b55c28c66bcd
Author: Sechang Lim <[email protected]>
bpf, sockmap: reject a packet-modifying SK_SKB stream parser
The patch prevents a slab-use-after-free by rejecting SK_SKB stream parser programs that modify the packet. It does this by checking if prog->aux->changes_pkt_data is set at attach time.
Quoted 5 lines
net/core/sock_map.c +0
-0
net/core/sock_map.c static int sock_map_prog_link_lookup(struct bpf_map *map, struct bpf_prog ***ppr · line 1524
Quoted 10 lines
net/core/sock_map.c +7
-0@@ -1515,6 +1515,17 @@ static int sock_map_prog_link_lookup(struct bpf_map *map, struct bpf_prog ***pprreturn 0 ; } +static int sock_map_prog_attach_check (enum bpf_attach_type attach_type, +struct bpf_prog *prog) +{ +/* A stream parser must not modify the skb, only measure it. */ +if (prog && attach_type ==BPF_SK_SKB_STREAM_PARSER && + prog->aux->changes_pkt_data) +return -EINVAL ;
[Severity: High]
This isn't a bug introduced by this patch, but does this check completely prevent packet modifications?
Looking at bpf_helper_changes_pkt_data() in net/core/filter.c, it seems BPF_FUNC_dynptr_write is missing from the list. If a program acquires a writable dynptr via bpf_dynptr_from_skb() and calls bpf_dynptr_write(), it internally calls __bpf_skb_store_bytes() which can trigger pskb_may_pull() and cause the exact same slab-use-after-free this patch is trying to fix.
Quoted 4 lines
net/core/sock_map.c +4
-0+ +return 0 ; +} +
[ ... ]
net/core/sock_map.c static int sock_map_link_update_prog(struct bpf_link *link, · line 1798
Quoted 9 lines
net/core/sock_map.c +5
-0@@ -1776,6 +1791,11 @@ static int sock_map_link_update_prog(struct bpf_link *link, ret = -EINVAL ; goto out; } + + ret =sock_map_prog_attach_check (link->attach_type, prog); +if (ret) + goto out; +
[Severity: High]
This isn't a bug introduced by this patch, but does the later program exchange in this function race with lockless readers?
Later in sock_map_link_update_prog(), the program is updated using xchg(&link->prog, prog) and the old program is freed via bpf_prog_put(). In kernel/bpf/syscall.c, bpf_link_show_fdinfo() and bpf_link_get_info_by_fd() access link->prog locklessly without holding rcu_read_lock().
If a reader is preempted after fetching link->prog, could an RCU grace period elapse and bpf_prog_put() free the old program? When the reader resumes, dereferencing the freed memory (e.g., prog->tag or prog->aux->id) could lead to a use-after-free.
Quoted 3 lines
net/core/sock_map.c +0
-0if (!sockmap_link->map) { ret = -ENOLINK ; goto out;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
PATCH v5 3/3 selftests/bpf: test rejection of a packet-modifying SK_SKB stream parser
In reply to Sechang LimVerify that attaching an SK_SKB stream parser that can modify the packet is rejected, while a read-only parser still attaches.
--- .../selftests/bpf/prog_tests/sockmap_strp.c | 31 +++++++++++++++++++ .../selftests/bpf/progs/test_sockmap_strp.c | 7 +++++ 2 files changed, 38 insertions(+)
tools/testing/selftests/bpf/prog_tests/sockmap_strp.c +31 -0
@@ -431,6 +431,35 @@ static void test_sockmap_strp_verdict(int family, int sotype)test_sockmap_strp__destroy (strp); } +static void test_sockmap_strp_parser_reject (void ) +{ +struct test_sockmap_strp *strp =NULL ; +int parser_mod, parser_ro, link; +int err, map; + + strp =test_sockmap_strp__open_and_load (); +if (!ASSERT_OK_PTR (strp,"test_sockmap_strp__open_and_load" )) +return ; + + map =bpf_map__fd (strp->maps.sock_map); + parser_mod =bpf_program__fd (strp->progs.prog_skb_parser_resize); + parser_ro =bpf_program__fd (strp->progs.prog_skb_parser); + + err =bpf_prog_attach (parser_mod, map,BPF_SK_SKB_STREAM_PARSER ,0 ); +ASSERT_ERR (err,"bpf_prog_attach parser_mod" ); + + link =bpf_link_create (parser_ro, map,BPF_SK_SKB_STREAM_PARSER ,NULL ); +if (!ASSERT_GE (link,0 ,"bpf_link_create parser_ro" )) + goto out; + + err =bpf_link_update (link, parser_mod,NULL ); +ASSERT_ERR (err,"bpf_link_update parser_mod" ); +out: +if (link >=0 ) +close (link); +test_sockmap_strp__destroy (strp); +} +void test_sockmap_strp (void ) {if (test__start_subtest ("sockmap strp tcp pass" ))
@@ -451,4 +480,6 @@ void test_sockmap_strp(void)test_sockmap_strp_multiple_pkt (AF_INET ,SOCK_STREAM );if (test__start_subtest ("sockmap strp tcp dispatch" ))test_sockmap_strp_dispatch_pkt (AF_INET ,SOCK_STREAM ); +if (test__start_subtest ("sockmap strp parser reject pkt mod" )) +test_sockmap_strp_parser_reject (); }
tools/testing/selftests/bpf/progs/test_sockmap_strp.c +7 -0
@@ -50,4 +50,11 @@ int prog_skb_parser_partial(struct __sk_buff *skb)return 10 ; } +SEC("sk_skb/stream_parser") +int prog_skb_parser_resize (struct __sk_buff *skb) +{ +bpf_skb_change_tail (skb, skb->len,0 ); +return skb->len; +} + char _license[] SEC("license") = "GPL";
Signature · 1 line
2.43.0
This series was applied to bpf/bpf.git (master) by Alexei Starovoitov <[email protected]>:
Hello:
This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <[email protected]>:
Quoted 9 lines · On Sat, 20 Jun 2026 02:44:15 +0000 you wrote:
A
BPF_PROG_TYPE_SK_SKBstream parser runs on strparser's message head, which can chain skbs throughfrag_list. A parser that resizes the skb frees thefrag_listsegments that strparser still tracks throughskb_nextp, leading to a use-after-free.A stream parser is only meant to measure the next message, not to modify the packet, so reject a packet-modifying parser at attach time.
[...]
Here is the summary with links:
- [bpf-next,v5,1/3] selftests/bpf: don't modify the skb in the strparser parser prog
https://git.kernel.org/bpf/bpf/c/22a0cc10dacb - [bpf-next,v5,2/3] bpf, sockmap: reject a packet-modifying SK_SKB stream parser https://git.kernel.org/bpf/bpf/c/31e2f36d3821 - [bpf-next,v5,3/3] selftests/bpf: test rejection of a packet-modifying SK_SKB stream parser https://git.kernel.org/bpf/bpf/c/05fb34384d20
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html
Ihor Solodrai
reviewerQuoted 21 lines · Sechang Lim
A
BPF_PROG_TYPE_SK_SKBstream parser runs on strparser's message head, which can chain skbs throughfrag_list. A parser that resizes the skb frees thefrag_listsegments that strparser still tracks throughskb_nextp, leading to a use-after-free.A stream parser is only meant to measure the next message, not to modify the packet, so reject a packet-modifying parser at attach time.
v5:
- target bpf-next instead of bpf
- add Reviewed-by tag (Jiayuan Chen)v4:
- https://lore.kernel.org/all/[email protected]/
Quoted 8 lines
v1:
- https://lore.kernel.org/all/[email protected]/Sechang Lim (3):
selftests/bpf: don't modify the skb in the strparser parser prog bpf, sockmap: reject a packet-modifyingSK_SKBstream parser selftests/bpf: test rejection of a packet-modifyingSK_SKBstream
parser
Hi Sechang, all,
This series broke test_maps (test_sockmap subtest) on the bpf tree. Currently on BPF CI the test fails on bpf, but passes on bpf-next (it doesn't have the series yet).
test_maps fails with:
+ taskset 0xF ./test_maps
[ 8.352378] clocksource: Watchdog remote CPU 2 read timed out
Failed sockmap unexpected timeout
See test_maps.c:995 in test_sockmap(): the 30s select() times out and test_maps exits 1. Note there is no "Failed stream parser bpf prog attach" message, the parser attaches fine.
The series was merged into bpf on 2026-06-26 00:42 UTC
CI runs:
last good (pre-merge, 06-25):
https://github.com/kernel-patches/bpf/actions/runs/28158326456
first bad (post-merge, 06-26):
https://github.com/kernel-patches/bpf/actions/runs/28210181858
recent bad (06-30):
https://github.com/kernel-patches/bpf/actions/runs/28475936023
Confirmed locally reverting the 3 commits and rebuilding makes test_sockmap pass again.
Could you please help investigate?
Thanks!
Quoted 7 lines
net/core/sock_map.c | 20 ++++++++++++ .../selftests/bpf/prog_tests/sockmap_strp.c | 31 +++++++++++++++++++ .../selftests/bpf/progs/sockmap_parse_prog.c | 22 ------------- .../selftests/bpf/progs/test_sockmap_strp.c | 7 +++++ 4 files changed, 58 insertions(+), 22 deletions(-)
Jiayuan Chen
maintainerQuoted 55 lines · Ihor Solodrai
Quoted 21 lines · Sechang Lim
A
BPF_PROG_TYPE_SK_SKBstream parser runs on strparser's message head, which can chain skbs throughfrag_list. A parser that resizes the skb frees thefrag_listsegments that strparser still tracks throughskb_nextp, leading to a use-after-free.A stream parser is only meant to measure the next message, not to modify the packet, so reject a packet-modifying parser at attach time.
v5:
- target bpf-next instead of bpf
- add Reviewed-by tag (Jiayuan Chen)v4:
- https://lore.kernel.org/all/[email protected]/Quoted 8 lines
v1:
- https://lore.kernel.org/all/[email protected]/Sechang Lim (3):
selftests/bpf: don't modify the skb in the strparser parser prog bpf, sockmap: reject a packet-modifyingSK_SKBstream parser selftests/bpf: test rejection of a packet-modifyingSK_SKBstream parserHi Sechang, all,
This series broke
test_maps(test_sockmapsubtest) on the bpf tree. Currently on BPF CI the test fails on bpf, but passes on bpf-next (it doesn't have the series yet).
test_mapsfails with:+ taskset
0xF./test_maps
[ 8.352378] clocksource: Watchdog remote CPU 2 read timed out Failed sockmap unexpected timeoutSee test_maps.c:995 in
test_sockmap(): the 30sselect()times out andtest_mapsexits 1. Note there is no "Failed stream parser bpf prog attach" message, the parser attaches fine.The series was merged into bpf on 2026-06-26 00:42 UTC
CI runs:
last good (pre-merge, 06-25): https://github.com/kernel-patches/bpf/actions/runs/28158326456
first bad (post-merge, 06-26): https://github.com/kernel-patches/bpf/actions/runs/28210181858
recent bad (06-30): https://github.com/kernel-patches/bpf/actions/runs/28475936023
Quoted 4 lines
Confirmed locally reverting the 3 commits and rebuilding makes
test_sockmappass again.Could you please help investigate?
I'll work on this.