From nobody Mon May 25 05:54:19 2026 Received: from mail-qk1-f180.google.com (mail-qk1-f180.google.com [209.85.222.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7762A3164AA for ; Sun, 17 May 2026 23:50:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779061805; cv=none; b=ohWVqERfA94F4hFlC2JLkcaRm9pVI2lU7NLlI9yX1Kp1sDQsIHTaPsKp6wW4tccCV2Kepu+xQDBTvTrIQhI+zo2BIXPedd0PJdr4asNxKnxcGCKpd2kweeViougAHEeCu4dcIv/sZ9rHkjT3AV4Vm/6ynsfDGmLiyvam92tQ0cU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779061805; c=relaxed/simple; bh=CnEPR4WvuGalhEU1d6bA9LwifKrg2bPP2TSmKSwG/Dw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MwT1L0pnlR3TS3bg9+dcwWM3HGXwuLusP5oto69psh5U8nJrrJx8RsUFXSqniA13DRJssuYapDlvckM8zpbYmNHWdup6i+pxbrD6sRcvH8u6t8lrhon5HEwREvx85OGU+JrfBUhLUdBisIr110NJIBUxo/7HCZBp9X1iG8ndPuQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Zxdie/AY; arc=none smtp.client-ip=209.85.222.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Zxdie/AY" Received: by mail-qk1-f180.google.com with SMTP id af79cd13be357-9106ea78cd8so329842485a.3 for ; Sun, 17 May 2026 16:50:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779061803; x=1779666603; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=yIXRslS3bbrBf0TjW6A+hsOey38qddDJZpNKTIIzNw8=; b=Zxdie/AY4Vmp5j2w8tMmn0cHPGkJyKimlUUR6lcBSsBMfWfhchYr9Nof+fjdn0ARZL 2zF8BmcXlAc2aOWQlvvUG9dNScqbYyYGhfdSoTQ01/YIRmrmfvn4J0GBb8n/dBOmGtUQ F4+/ZuGKF/8KvptY059e4PE3SnrpY1cvgq1YY6s6VY0kXp+QdcjZhBBBiUEbH+MK94SB Ct7+Pj42CKjTLZN3oKorq/89Ngc+v4NArlBuP6mDnJhsAKkDPlBajZf29Btlk9orrTgc NJsO1DxAlo0FUj8NYP/SnoXKTJbYVbPi40EPexqRpbh/RLkrnxvrDT1pohamG/9iO7Ay huBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779061803; x=1779666603; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=yIXRslS3bbrBf0TjW6A+hsOey38qddDJZpNKTIIzNw8=; b=p512yyV9JIyhVlyouezO/2gCM3hXf/ytXMQD/cIF/SHsdoeXih9P1varM0OhffrIIS 8Ij6zUnAAyUc2XR0akY1ujIQXhFqjAHdxFWczFdd5hpwHXlqS2C/hZf9NZkreMhESSOB AMhY1BsE4/2lKSG7+WVvvinjzyZs3V2yx+wVLTBm+cTuSCI4H7wB5ccWCprPyFGtaXIC 5SmABSRtGbzk5GRnEjhemTV+aqGR7D3K9U16QuPCbs/p05EgFqEhk5Vg7c301mR7PFHK dyzPJdKIpSTJUV/nF2bJctJo0QOPrGs8/4RLLB3g/G9LtK8h+R/ksPY+OD+u4XJFL1pz +BVw== X-Forwarded-Encrypted: i=1; AFNElJ81e7dDsySl1BRC+yZs7FuP5feEmZXHnl54IPQrX+A6vFFKFKzrV0Day3sWQk7H/jAUbVj44s3jFou6zBs=@vger.kernel.org X-Gm-Message-State: AOJu0YzFwOeSFzRSfKKcSVXTzke6mMC7upmfcp2N0HzuT93lRovxQASN iAIlSbJ0/z2V7YrlSm4ci7Jyt3HsKS3FSAYrprrgjAlwQho+nBCHgDlL X-Gm-Gg: Acq92OFWHBXSkvl1Kb5QDhzxSU10ohVAANnGMYV5VQCIJIS30Hz+WdRbBu7Xx9Y9ePY Qor0zW0NrU4o3wQLXgQ0QEGhCsSiTfd4pom+f4pjtepGsq3oocqGW9g8ETXvw1Ay5/Q3XAEddGS Oh6ay1g6aGOYBT06mk8jpz+pW+O4dZ/iMi5YGgyorrQpMooQasDoWVN19aigmExp4SBwiJ+K1Jn zbUzQzZ6J6Lc5h/PkKM0efYdxpNDmsLNx14ZDRP9tPUYjjoOU4zu/Gx1Nnx11HArKCaMGKNVw2p BrBUH/876UDHzByWZ2nhcaor8JjFwRF5+fuEq6hHG+zEXHk5YYLtZDBF3bZRrb//KqLr6oDfzKv hry/Hf+Z5HuO/aFTyjPYQefLCilZUaNpbga7XUOkKqB8hwIIAMzqEOOgvv1N8iQdOYBj/5PPFqk jelttCyz+4aXR/q5k2/axxEqXLqDiL1ZdRn0LRw91Zko6x36cTBYYO2IwPn5lAnh3jvKMiR+1iK hJS6cgVn8KD5Zref7+PHllXwA7X6V2X3KO4byP6jcQ= X-Received: by 2002:a05:620a:46a9:b0:911:6136:281a with SMTP id af79cd13be357-911cc4bc1b8mr1891279485a.17.1779061803265; Sun, 17 May 2026 16:50:03 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id af79cd13be357-9138a830c89sm502230585a.6.2026.05.17.16.50.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 May 2026 16:50:02 -0700 (PDT) From: Michael Bommarito To: Herbert Xu , Steffen Klassert , netdev@vger.kernel.org Cc: davem@davemloft.net, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Pablo Neira Ayuso , Florian Westphal , netfilter-devel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH net] xfrm: validate IPv4 header length before output transforms Date: Sun, 17 May 2026 19:49:55 -0400 Message-ID: <20260517234955.1276828-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The IPv4 output path validates ihl before handing packets to xfrm, and raw_send_hdrinc() now rejects IP_HDRINCL packets with ihl < 5. xfrm can still see a malformed IPv4 skb after that point, for example if a netfilter rule rewrites the packet between the normal IPv4 checks and the xfrm output transform. Do not let xfrm output consumers be the first code to discover that malformed header. xfrm4_transport_output() consumes iph->ihl before AH gets control, and the BEET/tunnel path records IPv4 option length from iph->ihl before constructing the outer header. Validate IPv4 skbs before xfrm output handles offload/GSO and again before each software outer-mode transform. Warn once for ihl < 5, since that means a malformed IPv4 packet was reinjected after the normal IP stack checks, and reject the packet before transform code can consume the bogus header length. A QEMU regression with an nft payload rule on the IPv4 output hook rewriting byte 0 to 0x40 now reaches the WARN_ON_ONCE, drops before AH, leaves xfrm packet counters at zero, and exits without a panic. A valid AH transport regression with normal ihl=3D5 UDP still succeeds: five sends complete and the xfrm state accounts 75 bytes and five packets. Suggested-by: Herbert Xu Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito --- Posting this as a follow-up to Herbert's requests in the existing thread. Patch 1/2 from the original series landed as 915fab69823a1; the AH-only hardening did not. I read the patch-2 comment as asking for the defensive guard to live in common xfrm output code rather than per-consumer, and this patch is my attempt at that first block. Happy to revise if you'd prefer the validator placed differently (only at xfrm_outer_mode_output, only at xfrm_output, gated under a debug option, or moved further out to __ip_local_out / xfrm4_extract_output) or the WARN_ON_ONCE swapped for a silent counter. net/xfrm/xfrm_output.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index cc35c2fcbbe09..02f38eaa68ff6 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -27,6 +27,31 @@ static int xfrm_output2(struct net *net, struct sock *sk, struct sk_buff *= skb); static int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff = *skb); =20 +static int xfrm_output_validate_iphdr(struct sk_buff *skb) +{ + struct iphdr *iph; + unsigned int ihl; + + if (skb->protocol !=3D htons(ETH_P_IP)) + return 0; + + if (unlikely(!pskb_network_may_pull(skb, sizeof(struct iphdr)))) + return -EINVAL; + + iph =3D ip_hdr(skb); + if (unlikely(iph->version !=3D 4)) + return -EINVAL; + + if (WARN_ON_ONCE(iph->ihl < 5)) + return -EINVAL; + + ihl =3D ip_hdrlen(skb); + if (unlikely(!pskb_network_may_pull(skb, ihl))) + return -EINVAL; + + return 0; +} + static int xfrm_skb_check_space(struct sk_buff *skb) { struct dst_entry *dst =3D skb_dst(skb); @@ -459,6 +484,12 @@ static int xfrm6_prepare_output(struct xfrm_state *x, = struct sk_buff *skb) =20 static int xfrm_outer_mode_output(struct xfrm_state *x, struct sk_buff *sk= b) { + int err; + + err =3D xfrm_output_validate_iphdr(skb); + if (err) + return err; + switch (x->props.mode) { case XFRM_MODE_BEET: case XFRM_MODE_TUNNEL: @@ -769,6 +800,13 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) break; } =20 + err =3D xfrm_output_validate_iphdr(skb); + if (err) { + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; + } + if (x->xso.type =3D=3D XFRM_DEV_OFFLOAD_PACKET) { if (!xfrm_dev_offload_ok(skb, x)) { XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); base-commit: aaec7096f9961eb223b5b149abe9495525c205d9 --=20 2.53.0