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(), a selective variant of skb_ext_reset(). It deletes every extension except SKB_EXT_BPF. Scrubbing is safe when the extension slab is shared with clones: deleting an extension only clears the per-skb active_extensions bit, and the shared slab payload is released lazily by __skb_ext_put() once the last reference goes away. Replace the skb_ext_reset() call in skb_scrub_packet() with skb_ext_scrub() and also switch udp_try_make_stateless() to skb_ext_scrub() as well, so the BPF metadata survives queueing onto a UDP socket receive queue and stays readable there (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. Signed-off-by: Jakub Sitnicki --- include/linux/skbuff.h | 2 ++ net/core/skbuff.c | 24 ++++++++++++++++++++++-- net/ipv4/udp.c | 6 ++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 0202bcb9338d..509d447179e1 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -5091,6 +5091,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) { @@ -5167,6 +5168,7 @@ static inline bool skb_ext_shared(const 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 9c03cd7c63af..a479e25de564 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -83,6 +83,7 @@ #include #include #include +#include #include #include @@ -6290,7 +6291,8 @@ EXPORT_SYMBOL(skb_try_coalesce); * operations. * skb_scrub_packet can also be used to clean a skb before injecting it in * another namespace (@xnet == true). We have to clear all information in the - * skb that could impact namespace isolation. + * skb that could impact namespace isolation. Note that BPF skb extension is + * meant to carry information across namespaces by design. */ void skb_scrub_packet(struct sk_buff *skb, bool xnet) { @@ -6298,7 +6300,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); @@ -7307,6 +7309,24 @@ void __skb_ext_put(struct skb_ext *ext) kmem_cache_free(skbuff_ext_cache, ext); } EXPORT_SYMBOL(__skb_ext_put); + +void skb_ext_scrub(struct sk_buff *skb) +{ + unsigned int id; + + if (likely(!skb->active_extensions)) + return; + + for (id = 0; id < SKB_EXT_NUM; id++) { +#if IS_ENABLED(CONFIG_BPF_SKB_EXT) + if (id == SKB_EXT_BPF) + continue; +#endif + skb_ext_del(skb, id); + } +} +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 b3887c42adfd..cf093b0d66c5 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -1540,8 +1540,6 @@ 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. */ static bool udp_try_make_stateless(struct sk_buff *skb) { @@ -1549,8 +1547,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