* [PATCH net v2] calipso: keep BIG TCP payload_len in calipso_skbuff_setattr()
@ 2026-10-09 15:59 Joas Antonio dos Santos
2026-10-09 16:13 ` sashiko-bot
2026-10-10 9:05 ` Wang Zhan
0 siblings, 2 replies; 4+ messages in thread
From: Joas Antonio dos Santos @ 2026-10-09 15:59 UTC (permalink / raw)
To: Paul Moore
Cc: Eric Dumazet, Wang Zhan, David S. Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, netdev, linux-security-module,
Alice Mikityanska
calipso_skbuff_setattr() adjusts payload_len by the length of the
option it inserts or resizes:
payload = ntohs(ip6_hdr->payload_len);
ip6_hdr->payload_len = htons(payload + len_delta);
GRO can build BIG TCP packets larger than IPV6_MAXPLEN, and stores
payload_len == 0 for them; ipv6_payload_len() then falls back to
skb->len. For such a packet forwarded to a destination mapped to
CALIPSO, the code above writes len_delta (or 65536 + len_delta when
shrinking) into payload_len, and ipv6_payload_len() no longer falls
back to skb->len. nf_tables, conntrack and sch_cake rely on it.
The same arithmetic also wraps when a packet grows past IPV6_MAXPLEN,
and a BIG TCP packet that shrinks below IPV6_MAXPLEN should get its
real length back.
Set payload_len from the skb length with ipv6_set_payload_len(), which
stores 0 above IPV6_MAXPLEN and the real length otherwise, as
ipv6_gro_complete() does. The network header was just reset to
skb->data, so skb->len - sizeof(*ip6_hdr) is the payload length.
Tested on one kernel (arm64, QEMU) with SELinux permissive, a minimal
mdp-generated policy (network_peer_controls=1), "netlabelctl calipso
add pass doi:16" and a NetLabel mapping of fd00:2::/64 to CALIPSO
DOI 16. calipso_skbuff_setattr() was called through
netlbl_skbuff_setattr() from a test-only debugfs hook (not part of
this patch) adding a level-only label; payload_len after the call:
case before fix after fix real length
1000, unlabeled 1016 1016 1016
70000 GSO, payload_len 0 16 0 70016
65530 GSO, grows past MAXPLEN 10 0 65546
65540 GSO with a 24 byte hop
header, shrinks below MAXPLEN 65528 65532 65532
1000 with a 24 byte hop header 992 992 992
(0 means ipv6_payload_len() returns the real length.) On the forward
path, with a client injecting unlabeled UDP through a router to a
server, the router adds the CALIPSO option and the server delivers all
datagrams (Udp6InDatagrams 5, Ip6InTruncatedPkts 0) with this patch.
Fixes: 81fbc812132c ("ipv6/gro: insert temporary HBH/jumbo header")
Suggested-by: Eric Dumazet <edumazet@kernel.org>
Suggested-by: Wang Zhan <wang.zhan@smartx.com>
Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
Assisted-by: Claude:claude-opus-5-5
---
Changes in v2:
- Use ipv6_set_payload_len(ip6_hdr, skb->len - sizeof(*ip6_hdr)) instead
of skipping payload_len == 0, so that growing past IPV6_MAXPLEN and
shrinking below it are handled too (Wang Zhan).
- Test the boundary cases; table in the changelog.
Test-only debugfs hook used above (built in with obj-y in
net/netlabel/Makefile, triggered with
"echo 1 > /sys/kernel/debug/calipso_selftest"):
// SPDX-License-Identifier: GPL-2.0
/* TEST ONLY, not for submission: drive calipso_skbuff_setattr() through
* netlbl_skbuff_setattr() around IPV6_MAXPLEN. Needs a NetLabel mapping
* of fd00:2::/64 to CALIPSO (netlabelctl).
* Trigger: echo 1 > /sys/kernel/debug/calipso_selftest
*/
#include <linux/debugfs.h>
#include <linux/ipv6.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <net/ipv6.h>
#include <net/netlabel.h>
/* hop-by-hop header carrying a CALIPSO option with 8 category bytes,
* 24 bytes in total, so that a level-only label shrinks it by 8 */
static const u8 big_hop[24] = {
IPPROTO_TCP, 2, /* next header, 3 x 8 bytes */
0x07, 16, 0, 0, 0, 16, 2, 0, 0, 0, /* CALIPSO, DOI 16, 2 cat words */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1, 2, 0, 0, /* PadN */
};
static void run_one(const char *name, unsigned int payload, bool labeled,
bool gso)
{
struct netlbl_lsm_secattr secattr;
struct ipv6hdr *ip6;
struct sk_buff *skb;
int rc;
skb = alloc_skb(LL_MAX_HEADER + 512 + sizeof(*ip6) + payload, GFP_KERNEL);
if (!skb)
return;
skb_reserve(skb, LL_MAX_HEADER + 512);
skb_reset_network_header(skb);
ip6 = skb_put_zero(skb, sizeof(*ip6) + payload);
ip6->version = 6;
ip6->hop_limit = 64;
if (labeled) {
ip6->nexthdr = NEXTHDR_HOP;
memcpy(ip6 + 1, big_hop, sizeof(big_hop));
} else {
ip6->nexthdr = IPPROTO_TCP;
}
ipv6_set_payload_len(ip6, payload);
ipv6_addr_set(&ip6->saddr, htonl(0xfd000001), 0, 0, htonl(2));
ipv6_addr_set(&ip6->daddr, htonl(0xfd000002), 0, 0, htonl(2));
skb->protocol = htons(ETH_P_IPV6);
if (gso) {
skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
skb_shinfo(skb)->gso_size = 1400;
}
netlbl_secattr_init(&secattr);
secattr.flags = NETLBL_SECATTR_MLS_LVL;
secattr.attr.mls.lvl = 0;
pr_info("CALSELFTEST %-12s before: payload_len=%u ipv6_payload_len=%u\n",
name, ntohs(ipv6_hdr(skb)->payload_len),
ipv6_payload_len(skb, ipv6_hdr(skb)));
rc = netlbl_skbuff_setattr(skb, AF_INET6, &secattr);
pr_info("CALSELFTEST %-12s after: rc=%d payload_len=%u ipv6_payload_len=%u real=%u\n",
name, rc, ntohs(ipv6_hdr(skb)->payload_len),
ipv6_payload_len(skb, ipv6_hdr(skb)),
skb->len - (unsigned int)sizeof(struct ipv6hdr));
netlbl_secattr_destroy(&secattr);
kfree_skb(skb);
}
static ssize_t trigger(struct file *f, const char __user *buf, size_t len,
loff_t *pos)
{
run_one("grow-normal", 1000, false, false); /* 1000 -> 1016 */
run_one("grow-bigtcp", 70000, false, true); /* 70000 -> 70016 */
run_one("grow-past", 65530, false, true); /* 65530 -> 65546 */
run_one("shrink-below", 65540, true, true); /* 65540 -> 65532 */
run_one("shrink-normal", 1000, true, false); /* 1000 -> 992 */
return len;
}
static const struct file_operations fops = { .write = trigger };
static int __init calipso_selftest_init(void)
{
debugfs_create_file("calipso_selftest", 0200, NULL, NULL, &fops);
return 0;
}
late_initcall(calipso_selftest_init);
net/ipv6/calipso.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/net/ipv6/calipso.c b/net/ipv6/calipso.c
index c6a34334e..a0192b4fe 100644
--- a/net/ipv6/calipso.c
+++ b/net/ipv6/calipso.c
@@ -1318,7 +1318,7 @@ static int calipso_skbuff_setattr(struct sk_buff *skb,
struct ipv6hdr *ip6_hdr;
struct ipv6_opt_hdr *hop;
unsigned char buf[CALIPSO_MAX_BUFFER];
- int len_delta, new_end, pad, payload;
+ int len_delta, new_end, pad;
unsigned int start, end;
ip6_hdr = ipv6_hdr(skb);
@@ -1357,8 +1357,8 @@ static int calipso_skbuff_setattr(struct sk_buff *skb,
sizeof(*ip6_hdr) + start);
skb_reset_network_header(skb);
ip6_hdr = ipv6_hdr(skb);
- payload = ntohs(ip6_hdr->payload_len);
- ip6_hdr->payload_len = htons(payload + len_delta);
+ /* 0 above IPV6_MAXPLEN (BIG TCP), the real length otherwise */
+ ipv6_set_payload_len(ip6_hdr, skb->len - sizeof(*ip6_hdr));
}
hop = (struct ipv6_opt_hdr *)(ip6_hdr + 1);
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH net v2] calipso: keep BIG TCP payload_len in calipso_skbuff_setattr()
2026-10-09 15:59 [PATCH net v2] calipso: keep BIG TCP payload_len in calipso_skbuff_setattr() Joas Antonio dos Santos
@ 2026-10-09 16:13 ` sashiko-bot
2026-10-10 9:05 ` Wang Zhan
1 sibling, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-10-09 16:13 UTC (permalink / raw)
Cc: linux-security-module
> calipso_skbuff_setattr() adjusts payload_len by the length of the
> option it inserts or resizes:
>
> payload = ntohs(ip6_hdr->payload_len);
> ip6_hdr->payload_len = htons(payload + len_delta);
>
> GRO can build BIG TCP packets larger than IPV6_MAXPLEN, and stores
> payload_len == 0 for them; ipv6_payload_len() then falls back to
> skb->len. For such a packet forwarded to a destination mapped to
> CALIPSO, the code above writes len_delta (or 65536 + len_delta when
> shrinking) into payload_len, and ipv6_payload_len() no longer falls
> back to skb->len. nf_tables, conntrack and sch_cake rely on it.
>
> The same arithmetic also wraps when a packet grows past IPV6_MAXPLEN,
> and a BIG TCP packet that shrinks below IPV6_MAXPLEN should get its
> [ ... ]
>
> Fixes: 81fbc812132c ("ipv6/gro: insert temporary HBH/jumbo header")
> Suggested-by: Eric Dumazet <edumazet@kernel.org>
> Suggested-by: Wang Zhan <wang.zhan@smartx.com>
> Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/179156155963.36253.400481753322266658@gmail.com?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH net v2] calipso: keep BIG TCP payload_len in calipso_skbuff_setattr()
2026-10-09 15:59 [PATCH net v2] calipso: keep BIG TCP payload_len in calipso_skbuff_setattr() Joas Antonio dos Santos
2026-10-09 16:13 ` sashiko-bot
@ 2026-10-10 9:05 ` Wang Zhan
2026-10-14 14:07 ` Paul Moore
1 sibling, 1 reply; 4+ messages in thread
From: Wang Zhan @ 2026-10-10 9:05 UTC (permalink / raw)
To: netdev, Joas Antonio dos Santos
Cc: Wang Zhan, Paul Moore, Eric Dumazet, David S. Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-security-module,
Alice Mikityanska
On Fri, 09 Oct 2026 12:59:19 -0300 Joas Antonio dos Santos wrote:
> calipso_skbuff_setattr() adjusts payload_len by the length of the
> option it inserts or resizes:
>
> payload = ntohs(ip6_hdr->payload_len);
> ip6_hdr->payload_len = htons(payload + len_delta);
>
> GRO can build BIG TCP packets larger than IPV6_MAXPLEN, and stores
> payload_len == 0 for them; ipv6_payload_len() then falls back to
> skb->len. For such a packet forwarded to a destination mapped to
> CALIPSO, the code above writes len_delta (or 65536 + len_delta when
> shrinking) into payload_len, and ipv6_payload_len() no longer falls
> back to skb->len. nf_tables, conntrack and sch_cake rely on it.
>
> The same arithmetic also wraps when a packet grows past IPV6_MAXPLEN,
> and a BIG TCP packet that shrinks below IPV6_MAXPLEN should get its
> real length back.
>
> Set payload_len from the skb length with ipv6_set_payload_len(), which
> stores 0 above IPV6_MAXPLEN and the real length otherwise, as
> ipv6_gro_complete() does. The network header was just reset to
> skb->data, so skb->len - sizeof(*ip6_hdr) is the payload length.
>
> Tested on one kernel (arm64, QEMU) with SELinux permissive, a minimal
> mdp-generated policy (network_peer_controls=1), "netlabelctl calipso
> add pass doi:16" and a NetLabel mapping of fd00:2::/64 to CALIPSO
> DOI 16. calipso_skbuff_setattr() was called through
> netlbl_skbuff_setattr() from a test-only debugfs hook (not part of
> this patch) adding a level-only label; payload_len after the call:
>
> case before fix after fix real length
> 1000, unlabeled 1016 1016 1016
> 70000 GSO, payload_len 0 16 0 70016
> 65530 GSO, grows past MAXPLEN 10 0 65546
> 65540 GSO with a 24 byte hop
> header, shrinks below MAXPLEN 65528 65532 65532
> 1000 with a 24 byte hop header 992 992 992
>
> (0 means ipv6_payload_len() returns the real length.) On the forward
> path, with a client injecting unlabeled UDP through a router to a
> server, the router adds the CALIPSO option and the server delivers all
> datagrams (Udp6InDatagrams 5, Ip6InTruncatedPkts 0) with this patch.
>
> Fixes: 81fbc812132c ("ipv6/gro: insert temporary HBH/jumbo header")
> Suggested-by: Eric Dumazet <edumazet@kernel.org>
> Suggested-by: Wang Zhan <wang.zhan@smartx.com>
> Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
> Assisted-by: Claude:claude-opus-5-5
Reviewed-by: Wang Zhan <wang.zhan@smartx.com>
> @@ -1357,8 +1357,8 @@ static int calipso_skbuff_setattr(struct sk_buff *skb,
> sizeof(*ip6_hdr) + start);
> skb_reset_network_header(skb);
> ip6_hdr = ipv6_hdr(skb);
> - payload = ntohs(ip6_hdr->payload_len);
> - ip6_hdr->payload_len = htons(payload + len_delta);
> + /* 0 above IPV6_MAXPLEN (BIG TCP), the real length otherwise */
nit: consider dropping the comment, it just repeats what
ipv6_set_payload_len() does.
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH net v2] calipso: keep BIG TCP payload_len in calipso_skbuff_setattr()
2026-10-10 9:05 ` Wang Zhan
@ 2026-10-14 14:07 ` Paul Moore
0 siblings, 0 replies; 4+ messages in thread
From: Paul Moore @ 2026-10-14 14:07 UTC (permalink / raw)
To: Wang Zhan
Cc: netdev, Joas Antonio dos Santos, Eric Dumazet, David S. Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-security-module,
Alice Mikityanska
On Sat, Oct 10, 2026 at 5:06 AM Wang Zhan <wang.zhan@smartx.com> wrote:
> On Fri, 09 Oct 2026 12:59:19 -0300 Joas Antonio dos Santos wrote:
> > calipso_skbuff_setattr() adjusts payload_len by the length of the
> > option it inserts or resizes:
> >
> > payload = ntohs(ip6_hdr->payload_len);
> > ip6_hdr->payload_len = htons(payload + len_delta);
> >
> > GRO can build BIG TCP packets larger than IPV6_MAXPLEN, and stores
> > payload_len == 0 for them; ipv6_payload_len() then falls back to
> > skb->len. For such a packet forwarded to a destination mapped to
> > CALIPSO, the code above writes len_delta (or 65536 + len_delta when
> > shrinking) into payload_len, and ipv6_payload_len() no longer falls
> > back to skb->len. nf_tables, conntrack and sch_cake rely on it.
> >
> > The same arithmetic also wraps when a packet grows past IPV6_MAXPLEN,
> > and a BIG TCP packet that shrinks below IPV6_MAXPLEN should get its
> > real length back.
> >
> > Set payload_len from the skb length with ipv6_set_payload_len(), which
> > stores 0 above IPV6_MAXPLEN and the real length otherwise, as
> > ipv6_gro_complete() does. The network header was just reset to
> > skb->data, so skb->len - sizeof(*ip6_hdr) is the payload length.
> >
> > Tested on one kernel (arm64, QEMU) with SELinux permissive, a minimal
> > mdp-generated policy (network_peer_controls=1), "netlabelctl calipso
> > add pass doi:16" and a NetLabel mapping of fd00:2::/64 to CALIPSO
> > DOI 16. calipso_skbuff_setattr() was called through
> > netlbl_skbuff_setattr() from a test-only debugfs hook (not part of
> > this patch) adding a level-only label; payload_len after the call:
> >
> > case before fix after fix real length
> > 1000, unlabeled 1016 1016 1016
> > 70000 GSO, payload_len 0 16 0 70016
> > 65530 GSO, grows past MAXPLEN 10 0 65546
> > 65540 GSO with a 24 byte hop
> > header, shrinks below MAXPLEN 65528 65532 65532
> > 1000 with a 24 byte hop header 992 992 992
> >
> > (0 means ipv6_payload_len() returns the real length.) On the forward
> > path, with a client injecting unlabeled UDP through a router to a
> > server, the router adds the CALIPSO option and the server delivers all
> > datagrams (Udp6InDatagrams 5, Ip6InTruncatedPkts 0) with this patch.
> >
> > Fixes: 81fbc812132c ("ipv6/gro: insert temporary HBH/jumbo header")
> > Suggested-by: Eric Dumazet <edumazet@kernel.org>
> > Suggested-by: Wang Zhan <wang.zhan@smartx.com>
> > Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
> > Assisted-by: Claude:claude-opus-5-5
>
> Reviewed-by: Wang Zhan <wang.zhan@smartx.com>
>
> > @@ -1357,8 +1357,8 @@ static int calipso_skbuff_setattr(struct sk_buff *skb,
> > sizeof(*ip6_hdr) + start);
> > skb_reset_network_header(skb);
> > ip6_hdr = ipv6_hdr(skb);
> > - payload = ntohs(ip6_hdr->payload_len);
> > - ip6_hdr->payload_len = htons(payload + len_delta);
> > + /* 0 above IPV6_MAXPLEN (BIG TCP), the real length otherwise */
>
> nit: consider dropping the comment, it just repeats what
> ipv6_set_payload_len() does.
Agree with the nit, but otherwise this looks good to me. Thanks to
Joas Antonio dos Santos for identifying the problem and working on the
fix as well as everyone else who took the time to review and comment;
the help is always appreciated!
Acked-by: Paul Moore <paul@paul-moore.com>
--
paul-moore.com
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-14 14:07 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09 15:59 [PATCH net v2] calipso: keep BIG TCP payload_len in calipso_skbuff_setattr() Joas Antonio dos Santos
2026-10-09 16:13 ` sashiko-bot
2026-10-10 9:05 ` Wang Zhan
2026-10-14 14:07 ` Paul Moore
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.