From: netdev-bot+sashiko@kernel.org
To: q.h.hack.winter@gmail.com
Cc: netdev@vger.kernel.org, steffen.klassert@secunet.com,
herbert@gondor.apana.org.au, stable@vger.kernel.org,
kuba@kernel.org
Subject: Re: [PATCH] xfrm: validate ihl in xfrm4_transport_output()
Date: Mon, 21 Sep 2026 07:52:12 +0000 [thread overview]
Message-ID: <178997713210.2160803.14018605703606783403@kernel.org> (raw)
In-Reply-To: <20260917072226.80788-1-q.h.hack.winter@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 (1) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] net/xfrm/xfrm_output.c:69-70, xfrm4_transport_output(): the new
guard validates the attacker-controlled IPv4 header length against…
Pre-existing issues:
- [Medium] net/xfrm/xfrm_output.c:66-81, xfrm4_transport_output(): only
an upper bound on ihl is added, so iph->ihl values 0..4 (ihl = 0..16…
--
xfrm: validate ihl in xfrm4_transport_output()
This adds a bounds check on the IPv4 header length read from the packet in
xfrm4_transport_output(), rejecting the packet with -EINVAL when ihl exceeds
skb->len, before the header relocation done by __skb_pull() and memmove().
The stated goal is to close a local denial of service reachable by injecting
frames into an xfrm interface, for example with AF_PACKET on an xfrmi device.
> diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c
> index e305ba32e356b..3abb21d113a45 100644
> --- a/net/xfrm/xfrm_output.c
> +++ b/net/xfrm/xfrm_output.c
> @@ -66,6 +66,9 @@ static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
> struct iphdr *iph = ip_hdr(skb);
> int ihl = iph->ihl * 4;
>
> + if (ihl > skb->len)
> + return -EINVAL;
> +
[Severity: High]
Is skb->len the right bound here? skb->len covers the paged fragment data
too, but the two operations further down in xfrm4_transport_output() need
the linear head length:
net/xfrm/xfrm_output.c:xfrm4_transport_output()
skb->transport_header = skb->network_header + ihl;
__skb_pull(skb, ihl);
memmove(skb_network_header(skb), iph, ihl);
include/linux/skbuff.h:__skb_pull()
skb->len -= len;
if (unlikely(skb->len < skb->data_len)) {
...
BUG();
}
So for any skb where skb_headlen(skb) < ihl <= skb->len, the new check
passes and __skb_pull() still drives skb->len below skb->data_len and
BUG()s, and the memmove() source range still runs past skb->tail.
Can the injection vector named in the changelog produce exactly such an
skb? In packet_snd() the linear length is attacker controlled:
net/packet/af_packet.c:packet_snd()
linear = __virtio16_to_cpu(vio_le(), vnet_hdr.hdr_len);
linear = max(linear, min_t(int, len, hard_header_len));
xfrmi_dev_setup() never calls ether_setup(), so hard_header_len is 0 and
linear stays at hdr_len, and packet_alloc_skb() keeps the rest paged:
net/packet/af_packet.c:packet_alloc_skb()
if (prepad + len < PAGE_SIZE || !linear)
linear = len;
...
skb_put(skb, linear);
skb->data_len = len - linear;
With hdr_len = 1 and len larger than PAGE_SIZE, skb_headlen(skb) is 1 and
the single linear byte is the attacker's version/ihl byte.
Does anything linearize the skb before the pull? xfrmi_dev_init() sets
NETIF_F_SG | NETIF_F_FRAGLIST, so skb_needs_linearize() is false, and
xfrm_output_one() only calls xfrm_skb_check_space() (pskb_expand_head(),
which does not linearize) before xfrm_outer_mode_output().
Would using the linear bound instead work, i.e.
if (ihl > skb_headlen(skb))
return -EINVAL;
or
if (!pskb_may_pull(skb, ihl))
return -EINVAL;
which also makes the memmove() source in-bounds?
[Severity: Medium]
This isn't a bug introduced by this patch, but since the check being added
here is the ihl validation, should it also enforce a lower bound? Only the
upper bound is checked, so iph->ihl values 0..4 (0..16 bytes, less than
sizeof(struct iphdr)) are still accepted.
The changelog points at the validation in ip_rcv_core(), which rejects both
directions:
net/ipv4/ip_input.c:ip_rcv_core()
if (iph->ihl < 5 || iph->version != 4)
goto inhdr_error;
and sibling xfrm4 code already assumes ihl >= 5:
net/xfrm/xfrm_inout.h:xfrm4_extract_header()
XFRM_MODE_SKB_CB(skb)->optlen = iph->ihl * 4 - sizeof(*iph);
Can a short ihl leak uninitialized headroom? In
xfrm4_transport_output() mac_header is placed at a fixed offset while only
ihl bytes are relocated:
skb->mac_header = skb->network_header +
offsetof(struct iphdr, protocol);
...
memmove(skb_network_header(skb), iph, ihl);
offsetof(struct iphdr, protocol) is 9, so for iph->ihl <= 2 the byte at
skb_mac_header() is never written, and esp_output() reads it:
net/ipv4/esp4.c:esp_output()
esp.proto = *skb_mac_header(skb);
*skb_mac_header(skb) = IPPROTO_ESP;
esp_output_head() then puts that value on the wire:
esp_output_fill_trailer(tail, esp->tfclen, esp->plen, esp->proto);
For iph->ihl == 2 the emitted outer header bytes covering check/saddr/daddr
are similarly never-initialized headroom, apart from tot_len and check
being rewritten by __ip_local_out()/ip_send_check().
On reachability, the IP_HDRINCL raw path cannot deliver this because
raw_send_hdrinc() rejects it:
net/ipv4/raw.c:raw_send_hdrinc()
if (iphlen > length || iphlen < sizeof(*iph))
goto error_free;
and for the AF_PACKET/xfrmi path the flow dissector bails out on short ihl:
net/core/flow_dissector.c:__skb_flow_dissect()
if (!iph || iph->ihl < 5) {
fdret = FLOW_DISSECT_RET_OUT_BAD;
which leaves decode_session4() with an all-zero flowi4, so a wildcard
(0.0.0.0) transport-mode policy and SA would additionally have to be
selected. I could not establish that such a state cannot be installed, so
would adding an ihl < sizeof(struct iphdr) rejection alongside the new
check be worthwhile?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917072226.80788-1-q.h.hack.winter%40gmail.com
next prev parent reply other threads:[~2026-09-21 7:52 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-17 7:22 [PATCH] xfrm: validate ihl in xfrm4_transport_output() Qihang
2026-09-21 7:52 ` netdev-bot+sashiko [this message]
2026-09-23 2:16 ` Qihang
2026-09-23 2:16 ` [PATCH net v2] " Qihang
2026-09-23 2:24 ` Herbert Xu
2026-09-23 3:28 ` Qihang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178997713210.2160803.14018605703606783403@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=herbert@gondor.apana.org.au \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=q.h.hack.winter@gmail.com \
--cc=stable@vger.kernel.org \
--cc=steffen.klassert@secunet.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.