[PATCH net-next 03/14] bpf: Make BPF skb extension survive packet scrubbing
Jakub Sitnicki <[email protected]>
| Newsgroups | org.kernel.vger.bpf,org.kernel.vger.netdev |
|---|---|
| Message-ID | <20260814-bpf-meta-inside-skb-ext-v1-3-767edd862656@cloudflare.com> |
skb_scrub_packet() drops all skb extensions unconditionally via skb_ext_reset(). It runs on tunnel encap/decap (ip_tunnel_rcv, vxlan_rcv, etc.) and cross-netns forwarding (dev_forward_skb). This makes it impossible for a BPF program to pass metadata via bpf_skb_ext through a tunnel or across a netns boundary. The extension is always lost at the scrub point. Introduce skb_ext_scrub() which consults each active extension before discarding it. Extensions that request preservation are kept while the rest are torn down. When the extension slab is shared with clones, COW ensures isolation. Replace the skb_ext_reset() call in skb_scrub_packet() with skb_ext_scrub(). Switch udp_try_make_stateless() to skb_ext_scrub() as well, so that the metadata stays readable until the packet is queued on a UDP socket receive queue (e.g. for a sockmap verdict program). Only mark the skb stateless when no extension survives the scrub. Otherwise skb_consume_udp() would take the __consume_stateless_skb() fast path, which skips skb_release_head_state(), and leak the extension slab. Finally, make BPF skb extension survive skb scrubbing. Signed-off-by: Jakub Sitnicki <[email protected]> --- include/linux/skbuff.h | 2 ++ net/core/skbuff.c | 78 +++++++++++++++++++++++++++++++++++++++++++------- net/ipv4/udp.c | 9 ++++-- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index a4f88107c2d5..286f2e4cdbb1 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -5061,6 +5061,7 @@ void *__skb_ext_set(struct sk_buff *skb, enum skb_ext_id id, void *skb_ext_add(struct sk_buff *skb, enum skb_ext_id id); void __skb_ext_del(struct sk_buff *skb, enum skb_ext_id id); void __skb_ext_put(struct skb_ext *ext); +void skb_ext_scrub(struct sk_buff *skb); static inline void skb_ext_put(struct sk_buff *skb) { @@ -5130,6 +5131,7 @@ static inline bool skb_has_extensions(struct sk_buff *skb) static inline void __skb_ext_put(struct skb_ext *ext) {} static inline void skb_ext_put(struct sk_buff *skb) {} static inline void skb_ext_reset(struct sk_buff *skb) {} +static inline void skb_ext_scrub(struct sk_buff *skb) {} static inline void skb_ext_del(struct sk_buff *skb, int unused) {} static inline void __skb_ext_copy(struct sk_buff *d, const struct sk_buff *s) {} static inline void skb_ext_copy(struct sk_buff *dst, const struct sk_buff *s) {} diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 9947d2ed6635..700bee045b5c 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -83,6 +83,7 @@ #include <net/page_pool/helpers.h> #include <net/psp/types.h> #include <net/dropreason.h> +#include <linux/bpf.h> #include <net/xdp_sock.h> #include <linux/uaccess.h> @@ -6284,7 +6285,7 @@ void skb_scrub_packet(struct sk_buff *skb, bool xnet) skb->skb_iif = 0; skb->ignore_df = 0; skb_dst_drop(skb); - skb_ext_reset(skb); + skb_ext_scrub(skb); nf_reset_ct(skb); nf_reset_trace(skb); @@ -7258,6 +7259,20 @@ void __skb_ext_del(struct sk_buff *skb, enum skb_ext_id id) } EXPORT_SYMBOL(__skb_ext_del); +static void skb_ext_put_each(struct skb_ext *ext, unsigned int skip) +{ +#ifdef CONFIG_XFRM + if (!(skip & (1 << SKB_EXT_SEC_PATH)) && + __skb_ext_exist(ext, SKB_EXT_SEC_PATH)) + skb_ext_put_sp(skb_ext_get_ptr(ext, SKB_EXT_SEC_PATH)); +#endif +#ifdef CONFIG_MCTP_FLOWS + if (!(skip & (1 << SKB_EXT_MCTP)) && + __skb_ext_exist(ext, SKB_EXT_MCTP)) + skb_ext_put_mctp(skb_ext_get_ptr(ext, SKB_EXT_MCTP)); +#endif +} + void __skb_ext_put(struct skb_ext *ext) { /* If this is last clone, nothing can increment @@ -7269,18 +7284,61 @@ void __skb_ext_put(struct skb_ext *ext) if (!refcount_dec_and_test(&ext->refcnt)) return; free_now: -#ifdef CONFIG_XFRM - if (__skb_ext_exist(ext, SKB_EXT_SEC_PATH)) - skb_ext_put_sp(skb_ext_get_ptr(ext, SKB_EXT_SEC_PATH)); -#endif -#ifdef CONFIG_MCTP_FLOWS - if (__skb_ext_exist(ext, SKB_EXT_MCTP)) - skb_ext_put_mctp(skb_ext_get_ptr(ext, SKB_EXT_MCTP)); -#endif - + skb_ext_put_each(ext, 0); kmem_cache_free(skbuff_ext_cache, ext); } EXPORT_SYMBOL(__skb_ext_put); + +static unsigned int skb_ext_no_scrub(struct skb_ext *ext) +{ + unsigned int keep = 0; + +#if IS_ENABLED(CONFIG_BPF_SKB_EXT) + if (__skb_ext_exist(ext, SKB_EXT_BPF)) + keep |= (1 << SKB_EXT_BPF); +#endif + return keep; +} + +static int __skb_ext_scrub(struct sk_buff *skb, unsigned int keep) +{ + struct skb_ext *old = skb->extensions; + struct skb_ext *ext; + int i; + + if (refcount_read(&old->refcnt) == 1) { + skb_ext_put_each(old, keep); + ext = old; + } else { + ext = skb_ext_maybe_cow(old, keep); + if (!ext) + return -ENOMEM; + skb->extensions = ext; + } + + for (i = 0; i < SKB_EXT_NUM; i++) { + if (!(keep & (1 << i))) + ext->offset[i] = 0; + } + skb->active_extensions = keep; + return 0; +} + +void skb_ext_scrub(struct sk_buff *skb) +{ + unsigned int keep; + + if (likely(!skb->active_extensions)) + return; + + keep = skb_ext_no_scrub(skb->extensions); + if (keep && !__skb_ext_scrub(skb, keep)) + return; + + skb_ext_reset(skb); +} +EXPORT_SYMBOL(skb_ext_scrub); + #endif /* CONFIG_SKB_EXTENSIONS */ static void kfree_skb_napi_cache(struct sk_buff *skb) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index af9603217444..865655db2185 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -1530,7 +1530,10 @@ void udp_splice_eof(struct socket *sock) * We need to preserve secpath, if present, to eventually process * IP_CMSG_PASSSEC at recvmsg() time. * - * Other extensions can be cleared. + * Other extensions get scrubbed. If any extension survives scrubbing + * (e.g. SKB_EXT_BPF), the skb must stay stateful, so that + * skb_consume_udp() releases it via skb_release_head_state() instead of + * leaking the extension slab on the __consume_stateless_skb() path. */ static bool udp_try_make_stateless(struct sk_buff *skb) { @@ -1538,8 +1541,8 @@ static bool udp_try_make_stateless(struct sk_buff *skb) return true; if (!secpath_exists(skb)) { - skb_ext_reset(skb); - return true; + skb_ext_scrub(skb); + return !skb_has_extensions(skb); } return false; -- 2.43.0