From 5aa2066aca2ad95c5ed204c50dfd69379c9a8d32 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Thu, 4 Jun 2026 04:08:37 +0300 Subject: net: airoha_eth: fix mt7531 mdio related initialization bug Private data isn't ready during bind time. The call of dev_get_priv() function will return NULL. Thus we can't save mdio device pointer and use it later during probe. To solve an issue, we will move mt7531 mdio device binding to the probing function of 'airoha-eth' driver. All GDM ports (except of GDM1) are connected directly to their PHYs, so corresponding mdio bus will be automatically probed during PHY setup. GDM1 ports differ from other GDM ports. It connected to the airoha switch device. The mt7531 mdio bus is used to manage link state of airoha switch device ports (LAN ports 1-4 corresponds to PHYs 0x09-0x0C). Therefore, manual probing of mt7531 mdio bus is required to be able set/query states of corresponding LAN ports. Fixes: 96d9e7c46425 ("net: airoha: use mt7531 mdio for GDM1") Signed-off-by: Mikhail Kshevetskiy --- drivers/net/airoha_eth.c | 78 +++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c index 84ee9b2ad76..e5d39b95cc5 100644 --- a/drivers/net/airoha_eth.c +++ b/drivers/net/airoha_eth.c @@ -845,11 +845,45 @@ static int airoha_alloc_gdm_port(struct udevice *dev, ofnode node) (ulong)eth, node, &gdm_dev); } +static struct udevice *airoha_switch_mdio_init(struct udevice *dev) +{ + struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev); + ofnode switch_node, mdio_node; + struct udevice *mdio_dev; + int ret; + + if (!CONFIG_IS_ENABLED(MDIO_MT7531_MMIO)) + return NULL; + + switch_node = ofnode_by_compatible(ofnode_null(), + data->switch_compatible); + if (!ofnode_valid(switch_node)) { + debug("Warning: missing airoha switch node\n"); + return ERR_PTR(-EINVAL); + } + + mdio_node = ofnode_find_subnode(switch_node, "mdio"); + if (!ofnode_valid(mdio_node)) { + debug("Warning: missing airoha switch mdio subnode\n"); + return ERR_PTR(-EINVAL); + } + + ret = device_bind_driver_to_node(dev, "mt7531-mdio-mmio", "mt7531-mdio", + mdio_node, &mdio_dev); + if (ret) { + debug("Warning: failed to bind airoha switch mdio\n"); + return ERR_PTR(ret); + } + + return mdio_dev; +} + static int airoha_eth_probe(struct udevice *dev) { struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev); struct airoha_eth *eth = dev_get_priv(dev); struct regmap *scu_regmap; + struct udevice *mdio_dev; ofnode node; int i, ret; @@ -908,10 +942,10 @@ static int airoha_eth_probe(struct udevice *dev) if (ret) return ret; - if (eth->switch_mdio_dev) { - if (!device_probe(eth->switch_mdio_dev)) - debug("Warning: failed to probe airoha switch mdio\n"); - } + /* Airoha switch mdio PHYs maybe used by several GDM devices */ + mdio_dev = airoha_switch_mdio_init(dev); + if (!IS_ERR_OR_NULL(mdio_dev)) + eth->switch_mdio_dev = mdio_dev; ofnode_for_each_subnode(node, dev_ofnode(dev)) { if (!ofnode_device_is_compatible(node, "airoha,eth-mac")) @@ -957,6 +991,16 @@ static int airoha_eth_port_probe(struct udevice *dev) #else return -EINVAL; #endif + } else { + /* + * GDM1 device connected to airoha switch. Probe airoha switch + * mdio to be able set/query states of corresponding LAN ports. + */ + ret = device_probe(eth->switch_mdio_dev); + if (ret) { + debug("Warning: failed to probe airoha switch mdio\n"); + eth->switch_mdio_dev = NULL; + } } return 0; @@ -1202,38 +1246,12 @@ static int arht_eth_write_hwaddr(struct udevice *dev) static int airoha_eth_bind(struct udevice *dev) { - struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev); - struct airoha_eth *eth = dev_get_priv(dev); - ofnode switch_node, mdio_node; - int ret; - /* * Force Probe as we set the Main ETH driver as misc * to register multiple eth port for each GDM */ dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND); - if (!CONFIG_IS_ENABLED(MDIO_MT7531_MMIO)) - return 0; - - switch_node = ofnode_by_compatible(ofnode_null(), - data->switch_compatible); - if (!ofnode_valid(switch_node)) { - debug("Warning: missing switch node\n"); - return 0; - } - - mdio_node = ofnode_find_subnode(switch_node, "mdio"); - if (!ofnode_valid(mdio_node)) { - debug("Warning: missing mdio node\n"); - return 0; - } - - ret = device_bind_driver_to_node(dev, "mt7531-mdio-mmio", "mt7531-mdio", - mdio_node, ð->switch_mdio_dev); - if (ret) - debug("Warning: failed to bind mdio controller\n"); - return 0; } -- cgit v1.3.1 From b1aec609bb5e0d08c25c888c91935287ab4ee5fa Mon Sep 17 00:00:00 2001 From: Mateusz Furdyna Date: Wed, 10 Jun 2026 16:25:33 +0200 Subject: net: clear IP defragmentation state after returning a complete packet During the IP defragmentation process, after the reassembly is finished with the last packet arriving with MF=0, the reassembly state wrt. static counters is not cleared. In case this last arriving packet with MF=0 gets duplicated, payload bytes are mistakenly treated as hole data. A malicious actor who can deliver fragmented IP traffic to a U-Boot instance with CONFIG_IP_DEFRAG=y can corrupt memory via out-of-bound writes and redirect control flow into attacker-supplied payload bytes that already sit in `pkt_buff[]`. Publicly available AI models are able to generate a reproducer based on the provided information. Fix: once the assembled packet has been handed back to the caller, mark the reassembly state empty so that any further fragment (duplicate, replay, or a brand-new datagram that happens to reuse the `ip_id`) goes through the normal re-init path and rebuilds a clean hole list instead of dereferencing payload bytes as struct hole. Fixes: 5cfaa4e54d0e ("net: defragment IP packets") Reported-by: Mariusz Madej Reviewed-by: Simon Glass Acked-by: Alessandro Rubini Signed-off-by: Mateusz Furdyna --- net/net.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/net/net.c b/net/net.c index ae3b977781f..61c5a6ef6c4 100644 --- a/net/net.c +++ b/net/net.c @@ -1103,6 +1103,15 @@ static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp) *lenp = total_len + IP_HDR_SIZE; localip->ip_len = htons(*lenp); + + /* + * Mark the reassembly state empty so that any further + * fragment goes through the normal re-init path and + * rebuilds a clean hole list + */ + total_len = 0; + first_hole = 0; + return localip; } -- cgit v1.3.1 From 8382c2ad5284fdfffb6848fb429c40fdf169bf3a Mon Sep 17 00:00:00 2001 From: Mateusz Furdyna Date: Wed, 10 Jun 2026 16:25:34 +0200 Subject: test: net: add IP defragmentation duplicate-fragment regression test Add a unit test for the IP datagram reassembler (CONFIG_IP_DEFRAG) that covers the duplicate-last-fragment scenario. Without the fix the last fragment will re-trigger datagram delivery, increasing udp_rx_count to 2 and effectively failing the test; with it applied the test passes with udp_rx_count == 1. Signed-off-by: Mateusz Furdyna Reviewed-by: Simon Glass --- test/dm/Makefile | 1 + test/dm/net_defrag.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 test/dm/net_defrag.c diff --git a/test/dm/Makefile b/test/dm/Makefile index d69b0e08d66..0e3c63568dd 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_MULTIPLEXER) += mux-emul.o obj-$(CONFIG_MUX_MMIO) += mux-mmio.o obj-y += fdtdec.o obj-$(CONFIG_MTD_RAW_NAND) += nand.o +obj-$(CONFIG_IP_DEFRAG) += net_defrag.o obj-$(CONFIG_UT_DM) += nop.o obj-y += ofnode.o obj-y += ofread.o diff --git a/test/dm/net_defrag.c b/test/dm/net_defrag.c new file mode 100644 index 00000000000..3fd40de90cd --- /dev/null +++ b/test/dm/net_defrag.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Regression test for IP fragment reassembly. + * + * The test drives the real RX path via net_process_received_packet(). Final IP + * fragment (MF=0) is duplicated, crafted payload triggers redelivery of the datagram, + * which fails the test for the unfixed code. + */ + +#include +#include +#include +#include + +#define FRAG_LEN (8) +#define PAYLOAD_OFFSET (ETHER_HDR_SIZE + IP_HDR_SIZE) +#define FRAME_LEN (PAYLOAD_OFFSET + FRAG_LEN) + +static int udp_rx_count; + +static void defrag_udp_handler(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + udp_rx_count++; +} + +static int build_frag(uchar *buf, u16 off_flags, const u16 *payload) +{ + struct ethernet_hdr *et = (struct ethernet_hdr *)buf; + struct ip_udp_hdr *ip = (struct ip_udp_hdr *)(buf + ETHER_HDR_SIZE); + + memset(buf, 0, FRAME_LEN); + et->et_protlen = htons(PROT_IP); + + ip->ip_hl_v = 0x45; + ip->ip_len = htons(IP_HDR_SIZE + FRAG_LEN); + ip->ip_id = htons(0x4321); + ip->ip_off = htons(off_flags); + ip->ip_ttl = 64; + ip->ip_p = IPPROTO_UDP; + /* Broadcast destination is accepted regardless of net_ip. */ + ip->ip_dst.s_addr = 0xffffffff; + ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE); + + memcpy(buf + PAYLOAD_OFFSET, payload, FRAG_LEN); + + return FRAME_LEN; +} + +static int dm_test_net_ip_defrag_dup_last(struct unit_test_state *uts) +{ + rxhand_f *saved_handler = net_get_udp_handler(); + uchar frame[FRAME_LEN]; + /* UDP header, carried by first fragment. */ + u16 udp_hdr[4] = { htons(5000), htons(5001), + htons(UDP_HDR_SIZE + FRAG_LEN), 0 }; + /* + * Second fragment's payload doubles as a fake hole + * {last_byte >= FRAG_LEN, next_hole = 0, prev_hole = 0}, so that the + * buggy code re-reading it on a duplicate re-delivers the datagram. + */ + u16 frag_b[4] = { 2 * FRAG_LEN, 0, 0, 0 }; + + udp_rx_count = 0; + net_set_udp_handler(defrag_udp_handler); + + /* UDP header, offset 0, MF=1; then data, offset 1, MF=0 */ + net_process_received_packet(frame, build_frag(frame, IP_FLAGS_MFRAG, udp_hdr)); + net_process_received_packet(frame, build_frag(frame, 1, frag_b)); + ut_asserteq(1, udp_rx_count); + + /* Duplicate the final fragment: UDP datagram must not be delivered again. */ + net_process_received_packet(frame, build_frag(frame, 1, frag_b)); + ut_asserteq(1, udp_rx_count); + + net_set_udp_handler(saved_handler); + + return 0; +} + +DM_TEST(dm_test_net_ip_defrag_dup_last, 0); -- cgit v1.3.1 From 747720eb73642fcab9693833f8943f3f37bdb881 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 11 Jun 2026 18:36:08 -0500 Subject: net: lwip: wget: fix error handling in wget_do_request() Split wget_do_request() into two functions to make error handling less error-prone. After a successful call to net_lwip_new_netif(), net_lwip_remove_netif() must always be called to prevent leaks. This was missed in the CACERT section of the code where we returned on error without cleaning up. Instead of adding more calls to net_lwip_remove_netif(), refactor the code into two functions. The outer function handles managing the netif lifecycle. The inner function no longer has to worry about cleaning up before returning on error. To keep things simple, the `path` local variable is removed during the refactoring. Instead, ctx.path is used directly everywhere. Fixes: 3c656c928bd7 ("net: lwip: add wget command") Reviewed-by: Jerome Forissier Acked-by: Ilias Apalodimas Signed-off-by: David Lechner --- net/lwip/wget.c | 88 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/net/lwip/wget.c b/net/lwip/wget.c index 502c0faebb2..f89a6580b41 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -292,45 +292,17 @@ static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, struct #if CONFIG_IS_ENABLED(WGET_CACERT) #endif -int wget_do_request(ulong dst_addr, char *uri) +static int wget_handle_request(struct wget_ctx *ctx, bool is_https, + struct udevice *udev, struct netif *netif) { #if CONFIG_IS_ENABLED(WGET_HTTPS) altcp_allocator_t tls_allocator; #endif httpc_connection_t conn; httpc_state_t *state; - struct udevice *udev; - struct netif *netif; - struct wget_ctx ctx; - char *path; - bool is_https; - - ctx.daddr = dst_addr; - ctx.saved_daddr = dst_addr; - ctx.done = NOT_DONE; - ctx.size = 0; - ctx.prevsize = 0; - ctx.start_time = 0; - ctx.content_len = 0; - ctx.hash_count = 0; - - if (parse_url(uri, ctx.server_name, &ctx.port, &path, &is_https)) - return CMD_RET_USAGE; - - if (net_lwip_eth_start() < 0) - return CMD_RET_FAILURE; - - if (!wget_info) - wget_info = &default_wget_info; - - udev = eth_get_dev(); - - netif = net_lwip_new_netif(udev); - if (!netif) - return -1; /* if URL with hostname init dns */ - if (!ipaddr_aton(ctx.server_name, NULL) && net_lwip_dns_init()) + if (!ipaddr_aton(ctx->server_name, NULL) && net_lwip_dns_init()) return CMD_RET_FAILURE; memset(&conn, 0, sizeof(conn)); @@ -374,11 +346,10 @@ int wget_do_request(ulong dst_addr, char *uri) tls_allocator.alloc = &altcp_tls_alloc; tls_allocator.arg = altcp_tls_create_config_client(ca, ca_sz, - ctx.server_name); + ctx->server_name); if (!tls_allocator.arg) { log_err("error: Cannot create a TLS connection\n"); - net_lwip_remove_netif(netif); return -1; } @@ -388,24 +359,20 @@ int wget_do_request(ulong dst_addr, char *uri) conn.result_fn = httpc_result_cb; conn.headers_done_fn = httpc_headers_done_cb; - ctx.path = path; - if (httpc_get_file_dns(ctx.server_name, ctx.port, path, &conn, httpc_recv_cb, - &ctx, &state)) { - net_lwip_remove_netif(netif); + if (httpc_get_file_dns(ctx->server_name, ctx->port, ctx->path, &conn, + httpc_recv_cb, ctx, &state)) { return CMD_RET_FAILURE; } errno = 0; - while (!ctx.done) { + while (!ctx->done) { net_lwip_rx(udev, netif); if (ctrlc()) break; } - net_lwip_remove_netif(netif); - - if (ctx.done == SUCCESS) + if (ctx->done == SUCCESS) return 0; if (errno == EPERM && !wget_info->silent) @@ -414,6 +381,45 @@ int wget_do_request(ulong dst_addr, char *uri) return -1; } +int wget_do_request(ulong dst_addr, char *uri) +{ + struct udevice *udev; + struct wget_ctx ctx; + struct netif *netif; + bool is_https; + int ret; + + ctx.daddr = dst_addr; + ctx.saved_daddr = dst_addr; + ctx.done = NOT_DONE; + ctx.size = 0; + ctx.prevsize = 0; + ctx.start_time = 0; + ctx.content_len = 0; + ctx.hash_count = 0; + + if (parse_url(uri, ctx.server_name, &ctx.port, &ctx.path, &is_https)) + return CMD_RET_USAGE; + + if (net_lwip_eth_start() < 0) + return CMD_RET_FAILURE; + + if (!wget_info) + wget_info = &default_wget_info; + + udev = eth_get_dev(); + + netif = net_lwip_new_netif(udev); + if (!netif) + return -1; + + ret = wget_handle_request(&ctx, is_https, udev, netif); + + net_lwip_remove_netif(netif); + + return ret; +} + /** * wget_validate_uri() - validate the uri for wget * -- cgit v1.3.1 From 91911aa0c76e641e673999111fc0aec1fe75b6d2 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 11 Jun 2026 18:36:09 -0500 Subject: net: lwip: wget: return errno codes from wget_do_request() Change the return values of the lwip implementation of wget_do_request() to be errno codes instead of command return codes. wget_do_request() is not a command, so it does not make sense to return command return codes from it. Also, the legacy network implementation of wget_do_request() already returns errno codes so it is logical for the lwip implementation to do the same. This fixes a bug in try_load_from_uri_path() in efi_manager.c where it checks that the return value of wget_do_request() is < 0. Before this change, CMD_RET_FAILURE would not be considered an error since it has a value of 1. The value of ENODEV is used in places where there could actually be a number of different causes of failure and it isn't possible to discriminate (i.e. failing function returns NULL for all errors). Since all callers of wget_do_request() don't propagate the error code, it doesn't matter so much that this is not ideal, at least at this point in time. Fixes: 3c656c928bd7 ("net: lwip: add wget command") Reviewed-by: Jerome Forissier Signed-off-by: David Lechner --- net/lwip/wget.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/net/lwip/wget.c b/net/lwip/wget.c index f89a6580b41..3a0b0dca145 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -300,10 +300,14 @@ static int wget_handle_request(struct wget_ctx *ctx, bool is_https, #endif httpc_connection_t conn; httpc_state_t *state; + int ret; /* if URL with hostname init dns */ - if (!ipaddr_aton(ctx->server_name, NULL) && net_lwip_dns_init()) - return CMD_RET_FAILURE; + if (!ipaddr_aton(ctx->server_name, NULL)) { + ret = net_lwip_dns_init(); + if (ret) + return ret; + } memset(&conn, 0, sizeof(conn)); #if CONFIG_IS_ENABLED(WGET_HTTPS) @@ -325,7 +329,7 @@ static int wget_handle_request(struct wget_ctx *ctx, bool is_https, printf("Error: cacert authentication " "mode is 'required' but no CA " "certificates given\n"); - return CMD_RET_FAILURE; + return -EINVAL; } } else if (cacert_auth_mode == AUTH_NONE) { ca = NULL; @@ -350,7 +354,7 @@ static int wget_handle_request(struct wget_ctx *ctx, bool is_https, if (!tls_allocator.arg) { log_err("error: Cannot create a TLS connection\n"); - return -1; + return -ENODEV; } conn.altcp_allocator = &tls_allocator; @@ -361,7 +365,7 @@ static int wget_handle_request(struct wget_ctx *ctx, bool is_https, conn.headers_done_fn = httpc_headers_done_cb; if (httpc_get_file_dns(ctx->server_name, ctx->port, ctx->path, &conn, httpc_recv_cb, ctx, &state)) { - return CMD_RET_FAILURE; + return -ENODEV; } errno = 0; @@ -378,7 +382,7 @@ static int wget_handle_request(struct wget_ctx *ctx, bool is_https, if (errno == EPERM && !wget_info->silent) printf("Certificate verification failed\n"); - return -1; + return -errno ?: -EIO; } int wget_do_request(ulong dst_addr, char *uri) @@ -398,11 +402,13 @@ int wget_do_request(ulong dst_addr, char *uri) ctx.content_len = 0; ctx.hash_count = 0; - if (parse_url(uri, ctx.server_name, &ctx.port, &ctx.path, &is_https)) - return CMD_RET_USAGE; + ret = parse_url(uri, ctx.server_name, &ctx.port, &ctx.path, &is_https); + if (ret) + return ret; - if (net_lwip_eth_start() < 0) - return CMD_RET_FAILURE; + ret = net_lwip_eth_start(); + if (ret) + return ret; if (!wget_info) wget_info = &default_wget_info; @@ -411,7 +417,7 @@ int wget_do_request(ulong dst_addr, char *uri) netif = net_lwip_new_netif(udev); if (!netif) - return -1; + return -ENODEV; ret = wget_handle_request(&ctx, is_https, udev, netif); -- cgit v1.3.1 From 9f7906a58cf194fc43484f5be4b65e368fa59040 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 11 Jun 2026 18:36:10 -0500 Subject: net: lwip: introduce net_lwip_eth_stop() function Add a introduce net_lwip_eth_stop() function and use that to stop the network interface after each command that uses the network. This makes the behavior the same as the legacy net code and avoids potential issues with the network interface being left in an active state after a command finishes. The start/stop is reference-counted since there is at least one command (dhcp) that calls another command (tftp) to avoid starting and stopping the network interface multiple times in a single command. Signed-off-by: David Lechner Reviewed-by: Jerome Forissier --- cmd/lwip/ping.c | 19 +++++++++++-------- cmd/lwip/sntp.c | 10 ++++++++-- include/net-lwip.h | 1 + net/lwip/dhcp.c | 15 +++++++++++---- net/lwip/dns.c | 7 ++++++- net/lwip/net-lwip.c | 16 ++++++++++++++++ net/lwip/nfs.c | 4 ++++ net/lwip/tftp.c | 4 ++++ net/lwip/wget.c | 5 ++++- 9 files changed, 65 insertions(+), 16 deletions(-) diff --git a/cmd/lwip/ping.c b/cmd/lwip/ping.c index fc4cf7bde5f..98fa8e22bce 100644 --- a/cmd/lwip/ping.c +++ b/cmd/lwip/ping.c @@ -163,6 +163,7 @@ static int ping_loop(struct udevice *udev, const ip_addr_t *addr) int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { ip_addr_t addr; + int ret; if (argc < 2) return CMD_RET_USAGE; @@ -171,13 +172,15 @@ int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) return CMD_RET_USAGE; net_try_count = 1; -restart: - if (net_lwip_eth_start() < 0 || ping_loop(eth_get_dev(), &addr) < 0) { - if (net_start_again() == 0) - goto restart; - else - return CMD_RET_FAILURE; - } - return CMD_RET_SUCCESS; + do { + if (net_lwip_eth_start() == 0) { + ret = ping_loop(eth_get_dev(), &addr); + net_lwip_eth_stop(); + if (ret == 0) + return CMD_RET_SUCCESS; + } + } while (net_start_again() == 0); + + return CMD_RET_FAILURE; } diff --git a/cmd/lwip/sntp.c b/cmd/lwip/sntp.c index 608345c873b..5fa400b104a 100644 --- a/cmd/lwip/sntp.c +++ b/cmd/lwip/sntp.c @@ -101,6 +101,7 @@ int do_sntp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) ip_addr_t *srvip; char *server; ip_addr_t ipaddr; + int ret = CMD_RET_FAILURE; switch (argc) { case 1: @@ -127,7 +128,12 @@ int do_sntp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) return CMD_RET_FAILURE; if (sntp_loop(eth_get_dev(), srvip) < 0) - return CMD_RET_FAILURE; + goto out; + + ret = CMD_RET_SUCCESS; + +out: + net_lwip_eth_stop(); - return CMD_RET_SUCCESS; + return ret; } diff --git a/include/net-lwip.h b/include/net-lwip.h index 20cb0992cce..5d0627eb271 100644 --- a/include/net-lwip.h +++ b/include/net-lwip.h @@ -35,6 +35,7 @@ int eth_init_state_only(void); /* Set active state */ int net_lwip_dns_init(void); int net_lwip_eth_start(void); +void net_lwip_eth_stop(void); struct netif *net_lwip_new_netif(struct udevice *udev); struct netif *net_lwip_new_netif_noip(struct udevice *udev); void net_lwip_remove_netif(struct netif *netif); diff --git a/net/lwip/dhcp.c b/net/lwip/dhcp.c index acdf601d7eb..18dc36ae7ca 100644 --- a/net/lwip/dhcp.c +++ b/net/lwip/dhcp.c @@ -138,18 +138,25 @@ int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) dev = eth_get_dev(); if (!dev) { log_err("No network device\n"); - return CMD_RET_FAILURE; + ret = CMD_RET_FAILURE; + goto out; } ret = dhcp_loop(dev); if (ret) - return ret; + goto out; if (argc > 1) { struct cmd_tbl cmdtp = {}; - return do_tftpb(&cmdtp, 0, argc, argv); + ret = do_tftpb(&cmdtp, 0, argc, argv); + goto out; } - return CMD_RET_SUCCESS; + ret = CMD_RET_SUCCESS; + +out: + net_lwip_eth_stop(); + + return ret; } diff --git a/net/lwip/dns.c b/net/lwip/dns.c index 8b7b3b7f970..b620b0611d6 100644 --- a/net/lwip/dns.c +++ b/net/lwip/dns.c @@ -91,6 +91,7 @@ int do_dns(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { char *name; char *var = NULL; + int ret; if (argc == 1 || argc > 3) return CMD_RET_USAGE; @@ -103,5 +104,9 @@ int do_dns(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) if (net_lwip_eth_start() < 0) return CMD_RET_FAILURE; - return dns_loop(eth_get_dev(), name, var); + ret = dns_loop(eth_get_dev(), name, var); + + net_lwip_eth_stop(); + + return ret; } diff --git a/net/lwip/net-lwip.c b/net/lwip/net-lwip.c index 0c83c004cab..cfe5a6a640d 100644 --- a/net/lwip/net-lwip.c +++ b/net/lwip/net-lwip.c @@ -31,6 +31,7 @@ void (*push_packet)(void *, int len) = 0; int net_try_count; static int net_restarted; int net_restart_wrap; +static int net_lwip_eth_started; static uchar net_pkt_buf[(PKTBUFSRX) * PKTSIZE_ALIGN + PKTALIGN] __aligned(PKTALIGN); const u8 net_bcast_ethaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; @@ -180,11 +181,15 @@ int net_lwip_eth_start(void) { int ret; + if (net_lwip_eth_started++ > 0) + return 0; + net_init(); eth_halt(); eth_set_current(); ret = eth_init(); if (ret < 0) { + net_lwip_eth_started--; eth_halt(); return ret; } @@ -192,6 +197,17 @@ int net_lwip_eth_start(void) return 0; } +void net_lwip_eth_stop(void) +{ + if (!net_lwip_eth_started) + return; + + if (--net_lwip_eth_started) + return; + + eth_halt(); +} + static struct netif *new_netif(struct udevice *udev, bool with_ip) { unsigned char enetaddr[ARP_HLEN]; diff --git a/net/lwip/nfs.c b/net/lwip/nfs.c index 9e6b801e465..4cc36373fdd 100644 --- a/net/lwip/nfs.c +++ b/net/lwip/nfs.c @@ -187,6 +187,7 @@ static int nfs_loop(struct udevice *udev, ulong addr, char *fname, int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { int ret = CMD_RET_SUCCESS; + bool started = false; char *arg = NULL; char *words[2] = { }; char *fname = NULL; @@ -281,10 +282,13 @@ int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) ret = CMD_RET_FAILURE; goto out; } + started = true; if (nfs_loop(eth_get_dev(), laddr, fname, srvip) < 0) ret = CMD_RET_FAILURE; out: + if (started) + net_lwip_eth_stop(); if (arg != net_boot_file_name) free(arg); return ret; diff --git a/net/lwip/tftp.c b/net/lwip/tftp.c index 7f3b28b8507..571c38172f9 100644 --- a/net/lwip/tftp.c +++ b/net/lwip/tftp.c @@ -261,6 +261,7 @@ static int tftp_loop(struct udevice *udev, ulong addr, char *fname, int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { int ret = CMD_RET_SUCCESS; + bool started = false; char *arg = NULL; char *words[3] = { }; char *fname = NULL; @@ -365,12 +366,15 @@ int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) ret = CMD_RET_FAILURE; goto out; } + started = true; if (tftp_loop(eth_get_dev(), laddr, fname, srvip, port) < 0) ret = CMD_RET_FAILURE; else image_load_addr = laddr; out: + if (started) + net_lwip_eth_stop(); if (arg != net_boot_file_name) free(arg); return ret; diff --git a/net/lwip/wget.c b/net/lwip/wget.c index 3a0b0dca145..247ece18e2b 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -416,12 +416,15 @@ int wget_do_request(ulong dst_addr, char *uri) udev = eth_get_dev(); netif = net_lwip_new_netif(udev); - if (!netif) + if (!netif) { + net_lwip_eth_stop(); return -ENODEV; + } ret = wget_handle_request(&ctx, is_https, udev, netif); net_lwip_remove_netif(netif); + net_lwip_eth_stop(); return ret; } -- cgit v1.3.1 From 91d5e0ee3e857c76bb05f0c69c21f3d4a150a2e6 Mon Sep 17 00:00:00 2001 From: Piyush Paliwal Date: Fri, 12 Jun 2026 13:17:30 +0530 Subject: net: cdp: reject CDP TLVs with a length below the 4-byte header cdp_receive() reads a 16-bit TLV length (tlen) from the packet and only checks that it does not exceed the remaining buffer (tlen > len). It then unconditionally does "tlen -= 4" to skip the TLV header. As tlen is a u16, a crafted TLV with a length of 0..3 underflows tlen to ~65532-65535. For a CDP_APPLIANCE_VLAN_TLV the underflowed length then drives the inner "while (tlen > 0)" loop, which walks ~64KB past the receive buffer reading *ss each step -> out-of-bounds read (crash / info-influence). A length of 0 additionally fails to advance pkt/len, hanging the parse loop. Reject any TLV whose declared length is smaller than its own 4-byte header. This is the same class of bug as the recent bootp/dhcpv6/sntp/nfs fixes (unchecked length field), in a sibling LAN parser that was missed. Verified with a standalone AddressSanitizer harness using the verbatim cdp_receive()/cdp_compute_csum() routines: a 16-byte CDP frame with an appliance-VLAN TLV of length 3 triggers a heap-buffer-overflow READ that the check eliminates. Fixes: f575ae1f7d39 ("net: Move CDP out of net.c") Cc: stable@vger.kernel.org Signed-off-by: Piyush Paliwal Reviewed-by: Jerome Forissier --- net/cdp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/cdp.c b/net/cdp.c index 6e404981d4a..300b3d5c409 100644 --- a/net/cdp.c +++ b/net/cdp.c @@ -276,7 +276,13 @@ void cdp_receive(const uchar *pkt, unsigned len) ss = (const ushort *)pkt; type = ntohs(ss[0]); tlen = ntohs(ss[1]); - if (tlen > len) + /* + * tlen includes the 4-byte TLV header, so it must be at + * least 4. Without this check a crafted tlen < 4 makes the + * "tlen -= 4" below underflow (tlen is a ushort), and a tlen + * of 0 also fails to advance pkt/len, hanging the loop. + */ + if (tlen < 4 || tlen > len) goto pkt_short; pkt += tlen; -- cgit v1.3.1