All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net 0/1] net: ipconfig: bound DHCP option construction
@ 2026-09-19  8:45 Yuqi Xu
  2026-09-19  8:45 ` [PATCH net 1/1] " Yuqi Xu
  2026-09-24  9:10 ` [PATCH net 0/1] " patchwork-bot+netdevbpf
  0 siblings, 2 replies; 5+ messages in thread
From: Yuqi Xu @ 2026-09-19  8:45 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, stable, Vega, Ren Wei,
	xuyq21

Hi Linux kernel maintainers,

We found and validated an issue in net/ipv4/ipconfig.c.  Triggering it
requires control over the kernel command line (the ip= and dhcpclass=
boot parameters), so it is not reachable by an unprivileged user; a
root-controlled boot or a malicious boot configuration can still turn
it into a boot-time panic.

We've tested it, and it should not affect any other functionality.

We will provide detailed information about the bug
in this email, along with a PoC to trigger it.

---- details below ----

Bug details:

ic_dhcp_init_options() builds the DHCP options for a request inside the
fixed 312-byte bootp_pkt.exten[] buffer.  It writes the hostname
(option 12) and the vendor-class (option 60) options by appending
type/length/value bytes without checking how much room is left; only the
client-ID (option 61) branch looked at the remaining space.

The hostname comes from the ip= boot parameter (up to 64 bytes) and the
vendor-class from dhcpclass= (up to 252 bytes after strscpy).  Together
with the 18 bytes already emitted (magic cookie, message type and
parameter request list) they need 18 + (2 + 64) + (2 + 252) = 338 bytes,
more than the 312 available even before the terminating END marker
(255).  The vendor-class memcpy() therefore writes past the end of
exten[].  CONFIG_FORTIFY_SOURCE reports this as a field-spanning write
and, with panic_on_warn=1, the warning becomes a panic during boot.

The patch routes the three optional options through a common helper that
checks that the option, its 2-byte header and the END marker all fit,
and drops an option that does not fit.  The helper also rejects a length
that does not fit in the one-byte DHCP option length field.  With short
options the emitted bytes are unchanged.

Reproducer:

Build a kernel with CONFIG_IP_PNP=y, CONFIG_IP_PNP_DHCP=y,
CONFIG_IP_PNP_BOOTP=y and CONFIG_FORTIFY_SOURCE=y, then boot it in QEMU
with one NIC (e1000) and the crafted command line below.
ip_auto_config() runs from a late_initcall and builds the DHCPDISCOVER
packet before any server reply is needed, so no DHCP server is required.

  HOST=$(printf 'h%.0s' {1..64})
  CLASS=$(printf 'v%.0s' {1..252})
  qemu-system-x86_64 -m 2048 -smp 2 -nographic -no-reboot \
    -kernel arch/x86/boot/bzImage -initrd initramfs.cpio.gz \
    -append "console=ttyS0 panic_on_warn=1 oops=panic panic=1 \
             ip=::::${HOST}::dhcp dhcpclass=${CLASS}" \
    -nic user,model=e1000

We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.  It is a
boot-time trigger: CONFIG_FORTIFY_SOURCE plus panic_on_warn=1 is what
turns the out-of-bounds write into the observed panic.

----BEGIN crash log----

[    2.349975] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
[    2.383902] Sending DHCP requests .
[    2.384513] DHCP: sending class identifier "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"
[    2.385144] ------------[ cut here ]------------
[    2.385241] memcpy: detected field-spanning write (size 252) of single field "e" at net/ipv4/ipconfig.c:736 (size 226)
[    2.385345] WARNING: net/ipv4/ipconfig.c:736 at ip_auto_config+0x924/0x1120, CPU#0: swapper/0/1
[    2.387029] Modules linked in:
[    2.387710] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.3.0-rc3-00339-g6c096bb08de9 #1 PREEMPT(lazy) 
[    2.388052] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-10.fc44 06/10/2025
[    2.388442] RIP: 0010:ip_auto_config+0x938/0x1120
[    2.388743] Code: 8d 4e 02 49 39 c7 73 35 4c 89 8d 50 ff ff ff 48 8d 3d bc 67 ac ff 48 c7 c2 a0 c0 4a 85 4c 89 f9 48 89 c6 48 89 85 58 ff ff ff <67> 48 0f b9 3a 4c 8b 8d 50 ff ff ff 48 8b 85 58 ff ff ff 48 c7 c6
[    2.389258] RSP: 0018:ffffb79f40013dd0 EFLAGS: 00010293
[    2.389478] RAX: 00000000000000fc RBX: ffff971101181000 RCX: 00000000000000e2
[    2.389664] RDX: ffffffff854ac0a0 RSI: 00000000000000fc RDI: ffffffff85913240
[    2.389847] RBP: ffffb79f40013e80 R08: 0000000000000000 R09: ffff971101dd316e
[    2.390026] R10: ffffb79f40013bc0 R11: ffffb79f40013bb8 R12: ffff971101d56000
[    2.390235] R13: ffff971101dd3010 R14: ffff971101dd316c R15: 00000000000000e2
[    2.390542] FS:  0000000000000000(0000) GS:ffff9711f7b6f000(0000) knlGS:0000000000000000
[    2.390755] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    2.390910] CR2: ffff97111d555000 CR3: 000000001ca30000 CR4: 00000000000006f0
[    2.391206] Call Trace:
[    2.391913]  <TASK>
[    2.392196]  ? __pfx_ip_auto_config+0x10/0x10
[    2.392453]  ? do_one_initcall+0x84/0x3c0
[    2.392580]  do_one_initcall+0x84/0x3c0
[    2.392800]  kernel_init_freeable+0x202/0x260
[    2.392955]  ? __pfx_kernel_init+0x10/0x10
[    2.393086]  kernel_init+0x1a/0x130
[    2.393204]  ret_from_fork+0x177/0x240
[    2.393328]  ? __pfx_kernel_init+0x10/0x10
[    2.393551]  ret_from_fork_asm+0x1a/0x30
[    2.393711]  </TASK>

-----END crash log-----

With the patch applied the same command line boots without the
field-spanning warning and without a panic; a normal configuration with
short options still sends the class identifier as before.

Best regards,
Yuqi Xu


Yuqi Xu (1):
  net: ipconfig: bound DHCP option construction

 net/ipv4/ipconfig.c | 45 ++++++++++++++++++++++++++-------------------
 1 file changed, 26 insertions(+), 19 deletions(-)


base-commit: 6c096bb08de97cdca051fecddad22cac6a1fd275
-- 
2.55.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net 1/1] net: ipconfig: bound DHCP option construction
  2026-09-19  8:45 [PATCH net 0/1] net: ipconfig: bound DHCP option construction Yuqi Xu
@ 2026-09-19  8:45 ` Yuqi Xu
  2026-09-20  7:25   ` Yuqi Xu
  2026-09-24  9:10 ` [PATCH net 0/1] " patchwork-bot+netdevbpf
  1 sibling, 1 reply; 5+ messages in thread
From: Yuqi Xu @ 2026-09-19  8:45 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, stable, Vega, Ren Wei,
	xuyq21

ic_dhcp_init_options() appends the hostname (option 12), vendor-class
(option 60) and client-ID (option 61) options into the fixed 312-byte
bootp_pkt.exten[] buffer.  Only the client-ID branch checked the
remaining space; the hostname and vendor-class writes were unbounded.

A 64-byte hostname together with the maximum 252-byte dhcpclass=
identifier needs 18 + (2 + 64) + (2 + 252) = 338 of the 312 available
bytes even before the terminating END marker, so the vendor-class memcpy
runs past the end of exten[].  With CONFIG_FORTIFY_SOURCE this is
reported as a field-spanning write and, when the kernel is booted with
panic_on_warn=1, aborts boot with a panic.

Route the optional options through a common helper that makes sure the
option, its 2-byte header and the END marker all fit and drops an option
that would not.  Configurations with short options keep sending exactly
the same bytes as before.

Fixes: 130c0f47fdf9 ("ipconfig: send host-name in DHCP requests")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Signed-off-by: Yuqi Xu <xuyuqiabc@gmail.com>
Reviewed-by: Ren Wei <weir@nebusec.ai>
---
 net/ipv4/ipconfig.c | 45 ++++++++++++++++++++++++++-------------------
 1 file changed, 26 insertions(+), 19 deletions(-)

diff --git a/net/ipv4/ipconfig.c b/net/ipv4/ipconfig.c
index 155db067eaec..1b8585404a41 100644
--- a/net/ipv4/ipconfig.c
+++ b/net/ipv4/ipconfig.c
@@ -676,6 +676,24 @@ static const u8 ic_bootp_cookie[4] = { 99, 130, 83, 99 };
 
 #ifdef IPCONFIG_DHCP
 
+static bool __init
+ic_dhcp_add_option(u8 **options, const u8 *end, u8 type, const void *value,
+		   int len)
+{
+	u8 *e = *options;
+
+	/* leave room for the option header and the END marker */
+	if (len > U8_MAX || end - e < len + 3)
+		return false;
+
+	*e++ = type;
+	*e++ = len;
+	memcpy(e, value, len);
+	*options = e + len;
+
+	return true;
+}
+
 static void __init
 ic_dhcp_init_options(u8 *options, struct ic_device *d)
 {
@@ -691,6 +709,7 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d)
 		42,	/* NTP servers */
 	};
 	u8 mt = (ic_servaddr == NONE) ? DHCPDISCOVER : DHCPREQUEST;
+	u8 *end = options + sizeof(((struct bootp_pkt *)0)->exten);
 	u8 *e = options;
 	int len;
 
@@ -721,31 +740,19 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d)
 	e += sizeof(ic_req_params);
 
 	if (ic_host_name_set) {
-		*e++ = 12;	/* host-name */
 		len = strlen(utsname()->nodename);
-		*e++ = len;
-		memcpy(e, utsname()->nodename, len);
-		e += len;
+		ic_dhcp_add_option(&e, end, 12, utsname()->nodename, len);
 	}
 	if (*vendor_class_identifier) {
-		pr_info("DHCP: sending class identifier \"%s\"\n",
-			vendor_class_identifier);
-		*e++ = 60;	/* Class-identifier */
 		len = strlen(vendor_class_identifier);
-		*e++ = len;
-		memcpy(e, vendor_class_identifier, len);
-		e += len;
+		if (ic_dhcp_add_option(&e, end, 60, vendor_class_identifier, len))
+			pr_info("DHCP: sending class identifier \"%s\"\n",
+				vendor_class_identifier);
 	}
 	len = strlen(dhcp_client_identifier + 1);
-	/* the minimum length of identifier is 2, include 1 byte type,
-	 * and can not be larger than the length of options
-	 */
-	if (len >= 1 && len < 312 - (e - options) - 1) {
-		*e++ = 61;
-		*e++ = len + 1;
-		memcpy(e, dhcp_client_identifier, len + 1);
-		e += len + 1;
-	}
+	/* the minimum length of identifier is 2, include 1 byte type */
+	if (len >= 1)
+		ic_dhcp_add_option(&e, end, 61, dhcp_client_identifier, len + 1);
 
 	*e++ = 255;	/* End of the list */
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH net 1/1] net: ipconfig: bound DHCP option construction
  2026-09-19  8:45 ` [PATCH net 1/1] " Yuqi Xu
@ 2026-09-20  7:25   ` Yuqi Xu
  2026-09-23 14:54     ` Simon Horman
  0 siblings, 1 reply; 5+ messages in thread
From: Yuqi Xu @ 2026-09-20  7:25 UTC (permalink / raw)
  To: netdev
  Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, stable, Vega, Ren Wei,
	xuyq21

Sashiko reported the following finding on the patchset page; it has not
been posted to lore:

> Blind copies in BOOTP extension parsing (`ic_do_bootp_ext`) cause
> out-of-bounds reads if an attacker provides a truncated option length.
> location: net/ipv4/ipconfig.c

This finding is about the receive path, not the code this patch changes.
It is a real, pre-existing issue; this series does not introduce it.

This series only touches the transmit side, ic_dhcp_init_options()
(net/ipv4/ipconfig.c:698) and the new ic_dhcp_add_option() helper
(net/ipv4/ipconfig.c:680). The function named in the finding,
ic_do_bootp_ext() (net/ipv4/ipconfig.c:919), is reached only from
ic_bootp_recv() (net/ipv4/ipconfig.c:1156) and is not modified here; the
diff contains no receive-path hunks, so this series neither introduces
nor worsens the issue.

The receive loop is:

	u8 *opt = ext++;
	if (*opt == 0)
		continue;
	ext += *ext + 1;
	if (ext < end)
		ic_do_bootp_ext(opt);

with end = (u8 *)b + ntohs(b->iph.tot_len) (net/ipv4/ipconfig.c:1078).
An option whose length byte overruns the remaining space drives ext to
or past end and is skipped. That is not the case the finding describes.

The truncated length in the finding is a short option length, not a
length greater than the remaining space. After switch (*ext++), ext
points at the length byte; the copies ignore it and memcpy a fixed-size
value at ext+1. Option 1 and 3 always memcpy 4 bytes
(net/ipv4/ipconfig.c:935 and :939); option 26 always memcpy 2 bytes
(:968). When that option still satisfies ext < end, those copies can
read past the declared option and past end. Options 1 and 3 do so even
at length 2; option 26 only at length 0 with 3 bytes remaining. With
skb->len == tot_len that is a real KASAN out-of-bounds read. It
predates this patch.

Since the receive-path issue is independent of the send-side overflow
addressed here, we have kept it out of this series.

Best regards,
Yuqi Xu

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net 1/1] net: ipconfig: bound DHCP option construction
  2026-09-20  7:25   ` Yuqi Xu
@ 2026-09-23 14:54     ` Simon Horman
  0 siblings, 0 replies; 5+ messages in thread
From: Simon Horman @ 2026-09-23 14:54 UTC (permalink / raw)
  To: Yuqi Xu
  Cc: netdev, David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, stable, Vega, Ren Wei, xuyq21

On Sun, Sep 20, 2026 at 03:25:03PM +0800, Yuqi Xu wrote:
> Sashiko reported the following finding on the patchset page; it has not
> been posted to lore:
> 
> > Blind copies in BOOTP extension parsing (`ic_do_bootp_ext`) cause
> > out-of-bounds reads if an attacker provides a truncated option length.
> > location: net/ipv4/ipconfig.c
> 
> This finding is about the receive path, not the code this patch changes.
> It is a real, pre-existing issue; this series does not introduce it.
> 
> This series only touches the transmit side, ic_dhcp_init_options()
> (net/ipv4/ipconfig.c:698) and the new ic_dhcp_add_option() helper
> (net/ipv4/ipconfig.c:680). The function named in the finding,
> ic_do_bootp_ext() (net/ipv4/ipconfig.c:919), is reached only from
> ic_bootp_recv() (net/ipv4/ipconfig.c:1156) and is not modified here; the
> diff contains no receive-path hunks, so this series neither introduces
> nor worsens the issue.
> 
> The receive loop is:
> 
> 	u8 *opt = ext++;
> 	if (*opt == 0)
> 		continue;
> 	ext += *ext + 1;
> 	if (ext < end)
> 		ic_do_bootp_ext(opt);
> 
> with end = (u8 *)b + ntohs(b->iph.tot_len) (net/ipv4/ipconfig.c:1078).
> An option whose length byte overruns the remaining space drives ext to
> or past end and is skipped. That is not the case the finding describes.
> 
> The truncated length in the finding is a short option length, not a
> length greater than the remaining space. After switch (*ext++), ext
> points at the length byte; the copies ignore it and memcpy a fixed-size
> value at ext+1. Option 1 and 3 always memcpy 4 bytes
> (net/ipv4/ipconfig.c:935 and :939); option 26 always memcpy 2 bytes
> (:968). When that option still satisfies ext < end, those copies can
> read past the declared option and past end. Options 1 and 3 do so even
> at length 2; option 26 only at length 0 with 3 bytes remaining. With
> skb->len == tot_len that is a real KASAN out-of-bounds read. It
> predates this patch.
> 
> Since the receive-path issue is independent of the send-side overflow
> addressed here, we have kept it out of this series.

Yes, agreed.

Reviewed-by: Simon Horman <horms@kernel.org>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net 0/1] net: ipconfig: bound DHCP option construction
  2026-09-19  8:45 [PATCH net 0/1] net: ipconfig: bound DHCP option construction Yuqi Xu
  2026-09-19  8:45 ` [PATCH net 1/1] " Yuqi Xu
@ 2026-09-24  9:10 ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 5+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-24  9:10 UTC (permalink / raw)
  To: Yuqi Xu
  Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	stable, vega, weir, xuyq21

Hello:

This patch was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Sat, 19 Sep 2026 16:45:26 +0800 you wrote:
> Hi Linux kernel maintainers,
> 
> We found and validated an issue in net/ipv4/ipconfig.c.  Triggering it
> requires control over the kernel command line (the ip= and dhcpclass=
> boot parameters), so it is not reachable by an unprivileged user; a
> root-controlled boot or a malicious boot configuration can still turn
> it into a boot-time panic.
> 
> [...]

Here is the summary with links:
  - [net,1/1] net: ipconfig: bound DHCP option construction
    https://git.kernel.org/netdev/net/c/e47a1958e12a

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-24  9:11 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-19  8:45 [PATCH net 0/1] net: ipconfig: bound DHCP option construction Yuqi Xu
2026-09-19  8:45 ` [PATCH net 1/1] " Yuqi Xu
2026-09-20  7:25   ` Yuqi Xu
2026-09-23 14:54     ` Simon Horman
2026-09-24  9:10 ` [PATCH net 0/1] " patchwork-bot+netdevbpf

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.