[PATCH net v2 4/4] netlabel: validate CIPSO option against skb tail in netlbl_skbuff_getattr
From: Qi Tang <hidden>
Date: 2026-05-24 04:15:12
Also in:
netdev, stable
Subsystem:
networking [general], networking [labeled] (netlabel, labeled ipsec, secmark), the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Paul Moore, Linus Torvalds
netlbl_skbuff_getattr() locates the CIPSO option in the IPv4 IP header
via cipso_v4_optptr() and hands the bare pointer to cipso_v4_getattr().
The consumer re-reads cipso[1] (option length), cipso[6] (tag type),
and then cipso_v4_parsetag_*() re-reads further bytes from the skb.
__ip_options_compile() validates these bytes only at parse time. An
nftables LOCAL_IN payload write reachable from an unprivileged user
namespace can rewrite them after parse and before the SELinux/Smack
peer-label consume path (selinux_sock_rcv_skb_compat ->
selinux_netlbl_sock_rcv_skb -> netlbl_skbuff_getattr). This is the
IPv4 analogue of the CALIPSO IPv6 trust-after-modification fixed in
the previous patch: the tag parsers walk the option using attacker-
controlled length bytes, producing slab-out-of-bounds reads whose
contents feed into the MLS access decision.
Validate the option fits within skb_tail_pointer(skb) before invoking
cipso_v4_getattr(). The pre-tag-walk guard "ptr + 8 > tail" covers
the CIPSO option header (type + length + DOI = 6 bytes) plus the
first tag header (type + length = 2 bytes), which are the bytes
cipso_v4_getattr() reads to dispatch on the tag. When the bounds
check fails the packet has been mutated after parse, so return
-EINVAL rather than fall through to the unlabeled path.
Runtime confirmation (Smack peer-label policy + nft LOCAL_IN
mutation of tag_len): UdpInDatagrams increments to 1 and recvfrom
returns the payload, showing netlbl_skbuff_getattr ->
cipso_v4_getattr -> cipso_v4_parsetag_rbm -> netlbl_bitmap_walk runs
end-to-end past the option's true bound; with this patch the
consume path returns -EINVAL at the bounds check and the counter
stays 0.
Cc: stable@vger.kernel.org
Reported-by: Qi Tang <redacted>
Reported-by: Tong Liu <redacted>
Fixes: 04f81f0154e4 ("cipso: don't use IPCB() to locate the CIPSO IP option")
Signed-off-by: Qi Tang <redacted>
---
net/netlabel/netlabel_kapi.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/net/netlabel/netlabel_kapi.c b/net/netlabel/netlabel_kapi.c
index d0d6220b8d59d..c2d3ea751f4e1 100644
--- a/net/netlabel/netlabel_kapi.c
+++ b/net/netlabel/netlabel_kapi.c@@ -1393,11 +1393,24 @@ int netlbl_skbuff_getattr(const struct sk_buff *skb, unsigned char *ptr; switch (family) { - case AF_INET: + case AF_INET: { + const unsigned char *tail = skb_tail_pointer(skb); + u8 opt_len, tag_len; + ptr = cipso_v4_optptr(skb); - if (ptr && cipso_v4_getattr(ptr, secattr) == 0) + if (!ptr) + break; + /* CIPSO header (type+len+DOI = 6) + first tag header (type+len = 2) */ + if (ptr + 8 > tail) + return -EINVAL; + opt_len = ptr[1]; /* total CIPSO option length */ + tag_len = ptr[7]; /* first tag length */ + if (ptr + opt_len > tail || ptr + 6 + tag_len > tail) + return -EINVAL; + if (cipso_v4_getattr(ptr, secattr) == 0) return 0; break; + } #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: { const unsigned char *tail = skb_tail_pointer(skb);
--
2.47.3