* [PATCH RESEND 3/4] usb: eth: lan7x: Account for the whole rx command header
2026-10-29 6:06 [PATCH RESEND 0/4] usb: eth: Check rx header lengths in r8152, asix88179 and lan7x Ana Bobillo
2026-10-29 6:06 ` [PATCH RESEND 1/4] usb: eth: r8152: Check the rx descriptor fits in the transfer Ana Bobillo
2026-10-29 6:06 ` [PATCH RESEND 2/4] usb: eth: asix88179: Bound the rx packet headers and packets Ana Bobillo
@ 2026-10-29 6:06 ` Ana Bobillo
2026-10-29 6:07 ` [PATCH RESEND 4/4] test: dm: Add tests for the USB Ethernet rx header checks Ana Bobillo
3 siblings, 0 replies; 5+ messages in thread
From: Ana Bobillo @ 2026-10-29 6:06 UTC (permalink / raw)
To: Marek Vasut; +Cc: Tom Rini, Simon Glass, Jonas Karlman, u-boot
From: Ana Bobillo <anabobillo3@gmail.com>
lan7x_eth_recv() returns the frame that follows rx command A, B and C,
10 bytes into the transfer, but checks only that the transfer holds
command A and that the frame fits in what follows command A. A transfer
of 4 to 9 bytes, or a frame length up to 6 bytes longer than what
follows the header, hands the network stack a frame that runs past the
received data.
On LAN75xx, command A and B are followed by 2 bytes of padding rather
than command C, and the length in command A counts them, as Linux
smsc75xx_rx_fixup() has it: the frame handed up was 2 bytes longer than
the one received, running past the transfer. Take the padding out of the
length there, so that the frame can be bounded by what follows the
header on both.
lan7x_free_pkt() also steps over only 4 bytes of header, so the tail of
a frame is parsed as the header of another. Step over the 10 bytes of
header, the frame and the padding that aligns the next header to
4 bytes, as Linux lan78xx_rx() and smsc75xx_rx_fixup() do.
Signed-off-by: Ana Bobillo <anabobillo3@gmail.com>
---
drivers/usb/eth/lan7x.c | 31 ++++++++++++++++++++-----------
drivers/usb/eth/lan7x.h | 4 ++++
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/drivers/usb/eth/lan7x.c b/drivers/usb/eth/lan7x.c
index 0a283619ae5..14c3ace4cc9 100644
--- a/drivers/usb/eth/lan7x.c
+++ b/drivers/usb/eth/lan7x.c
@@ -446,8 +446,8 @@ int lan7x_eth_recv(struct udevice *dev, int flags, uchar **packetp)
* 1st 4 bytes contain the length of the actual data plus error info.
* Extract data length.
*/
- if (len < sizeof(packet_len)) {
- debug("Rx: incomplete packet length\n");
+ if (len < RX_CMD_LEN) {
+ debug("Rx: incomplete packet header\n");
goto err;
}
memcpy(&rx_cmd_a, ptr, sizeof(rx_cmd_a));
@@ -458,18 +458,26 @@ int lan7x_eth_recv(struct udevice *dev, int flags, uchar **packetp)
}
packet_len = (u16) (rx_cmd_a & RX_CMD_A_LEN_MASK);
- if (packet_len > len - sizeof(packet_len)) {
- debug("Rx: too large packet: %d\n", packet_len);
- goto err;
- }
-
/*
* For LAN7x, the length in command A does not
* include command A, B, and C length.
- * So use it as is.
+ * On LAN75xx, command A and B are followed by 2 bytes of
+ * padding instead, which the length does include.
*/
+ if (priv->chipid == ID_REV_CHIP_ID_7500) {
+ if (packet_len < RX_LAN75XX_PADDING) {
+ debug("Rx: too small packet: %d\n", packet_len);
+ goto err;
+ }
+ packet_len -= RX_LAN75XX_PADDING;
+ }
+
+ if (packet_len > len - RX_CMD_LEN) {
+ debug("Rx: too large packet: %d\n", packet_len);
+ goto err;
+ }
- *packetp = ptr + 10;
+ *packetp = ptr + RX_CMD_LEN;
return packet_len;
err:
@@ -481,8 +489,9 @@ int lan7x_free_pkt(struct udevice *dev, uchar *packet, int packet_len)
{
struct lan7x_private *priv = dev_get_priv(dev);
- packet_len = ALIGN(packet_len, 4);
- usb_ether_advance_rxbuf(&priv->ueth, sizeof(u32) + packet_len);
+ /* Skip command A, B and C, the frame and its padding to 4 bytes */
+ packet_len = ALIGN(RX_CMD_LEN + packet_len, 4);
+ usb_ether_advance_rxbuf(&priv->ueth, packet_len);
return 0;
}
diff --git a/drivers/usb/eth/lan7x.h b/drivers/usb/eth/lan7x.h
index 9480f4f6d15..29f06c076c5 100644
--- a/drivers/usb/eth/lan7x.h
+++ b/drivers/usb/eth/lan7x.h
@@ -23,6 +23,10 @@
#define RX_CMD_A_RXE BIT(18)
#define RX_CMD_A_LEN_MASK 0x00003FFF
+/* Rx Command A, B and C (or padding) in front of each received frame */
+#define RX_CMD_LEN 10
+#define RX_LAN75XX_PADDING 2
+
/* SCSRs */
#define ID_REV 0x00
#define ID_REV_CHIP_ID_MASK 0xFFFF0000
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH RESEND 4/4] test: dm: Add tests for the USB Ethernet rx header checks
2026-10-29 6:06 [PATCH RESEND 0/4] usb: eth: Check rx header lengths in r8152, asix88179 and lan7x Ana Bobillo
` (2 preceding siblings ...)
2026-10-29 6:06 ` [PATCH RESEND 3/4] usb: eth: lan7x: Account for the whole rx command header Ana Bobillo
@ 2026-10-29 6:07 ` Ana Bobillo
3 siblings, 0 replies; 5+ messages in thread
From: Ana Bobillo @ 2026-10-29 6:07 UTC (permalink / raw)
To: Marek Vasut; +Cc: Tom Rini, Simon Glass, Jonas Karlman, u-boot
From: Ana Bobillo <anabobillo3@gmail.com>
Add DM tests that bind the r8152, ax88179, lan75xx and lan78xx drivers
without probing them and hand their recv() and free_pkt() prepared
transfers in the receive buffer: well-formed ones, which must come back
as the packets they hold, and ones whose header or lengths run past the
transfer, which must be dropped.
Enable CONFIG_USB_HOST_ETHER and these drivers in sandbox_defconfig so
that they are built and tested under sandbox.
Signed-off-by: Ana Bobillo <anabobillo3@gmail.com>
---
configs/sandbox_defconfig | 5 +
test/dm/Makefile | 1 +
test/dm/usb_eth.c | 375 ++++++++++++++++++++++++++++++++++++++
3 files changed, 381 insertions(+)
create mode 100644 test/dm/usb_eth.c
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index a3fa5246082..1d409891580 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -359,6 +359,11 @@ CONFIG_USB=y
CONFIG_DM_USB_GADGET=y
CONFIG_USB_EMUL=y
CONFIG_USB_KEYBOARD=y
+CONFIG_USB_HOST_ETHER=y
+CONFIG_USB_ETHER_ASIX88179=y
+CONFIG_USB_ETHER_LAN75XX=y
+CONFIG_USB_ETHER_LAN78XX=y
+CONFIG_USB_ETHER_RTL8152=y
CONFIG_USB_GADGET=y
CONFIG_USB_GADGET_DOWNLOAD=y
CONFIG_USB_ETHER=y
diff --git a/test/dm/Makefile b/test/dm/Makefile
index 1b3a20aa932..e22cbf2c2ba 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -150,6 +150,7 @@ obj-$(CONFIG_TEE) += tee.o
obj-$(CONFIG_TIMER) += timer.o
obj-$(CONFIG_TPM_V2) += tpm.o
obj-$(CONFIG_DM_USB) += usb.o
+obj-$(CONFIG_USB_HOST_ETHER) += usb_eth.o
obj-$(CONFIG_VIDEO_SANDBOX_SDL) += video.o
ifeq ($(CONFIG_VIRTIO_SANDBOX),y)
obj-y += virtio.o
diff --git a/test/dm/usb_eth.c b/test/dm/usb_eth.c
new file mode 100644
index 00000000000..2f8903b71b6
--- /dev/null
+++ b/test/dm/usb_eth.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for the receive header checks of the USB Ethernet drivers
+ *
+ * Each driver is bound without being probed, so that no USB device sits
+ * behind it, and its recv() is handed a prepared transfer in the receive
+ * buffer.
+ */
+
+#include <dm.h>
+#include <net.h>
+#include <usb_ether.h>
+#include <asm/unaligned.h>
+#include <dm/device-internal.h>
+#include <dm/root.h>
+#include <dm/test.h>
+#include <test/ut.h>
+#include "../../drivers/usb/eth/lan7x.h"
+#include "../../drivers/usb/eth/r8152.h"
+
+static u8 rxbuf[256] __maybe_unused;
+
+/*
+ * Bind @drv to a new device and present the first @len bytes of rxbuf to it
+ * as a received transfer. device_of_to_plat() allocates the private data
+ * without probing the device; @ueth_offset is where the driver keeps its
+ * struct ueth_data in there.
+ */
+static int __maybe_unused usb_eth_test_bind(struct unit_test_state *uts,
+ struct driver *drv,
+ size_t ueth_offset, int len,
+ struct udevice **devp,
+ struct ueth_data **uethp)
+{
+ struct ueth_data *ueth;
+
+ ut_assertok(device_bind(dm_root(), drv, drv->name, NULL, ofnode_null(),
+ devp));
+ ut_assertok(device_of_to_plat(*devp));
+ ueth = dev_get_priv(*devp) + ueth_offset;
+ ueth->rxbuf = rxbuf;
+ ueth->rxsize = sizeof(rxbuf);
+ ueth->rxlen = len;
+ ueth->rxptr = 0;
+ *uethp = ueth;
+
+ return 0;
+}
+
+static int __maybe_unused usb_eth_test_recv(struct udevice *dev,
+ uchar **packetp)
+{
+ return eth_get_ops(dev)->recv(dev, 0, packetp);
+}
+
+static int __maybe_unused usb_eth_test_free_pkt(struct udevice *dev,
+ uchar *packet, int len)
+{
+ return eth_get_ops(dev)->free_pkt(dev, packet, len);
+}
+
+#if IS_ENABLED(CONFIG_USB_ETHER_RTL8152)
+static int r8152_test_bind(struct unit_test_state *uts, int len,
+ struct udevice **devp, struct ueth_data **uethp)
+{
+ return usb_eth_test_bind(uts, DM_DRIVER_GET(r8152_eth),
+ offsetof(struct r8152, ueth), len, devp,
+ uethp);
+}
+
+/* r8152_eth_recv() returns each packet of an aggregated transfer */
+static int dm_test_r8152_eth_recv(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ /* A packet of 60 bytes, then one of 42 at the next 8-byte boundary */
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(60 + CRC_SIZE, rxbuf);
+ put_unaligned_le32(42 + CRC_SIZE, rxbuf + 88);
+ ut_assertok(r8152_test_bind(uts, 88 + sizeof(struct rx_desc) + 42 +
+ CRC_SIZE, &dev, &ueth));
+
+ ut_asserteq(60, usb_eth_test_recv(dev, &packet));
+ ut_asserteq_ptr(rxbuf + sizeof(struct rx_desc), packet);
+ ut_assertok(usb_eth_test_free_pkt(dev, packet, 60));
+ ut_asserteq(42, usb_eth_test_recv(dev, &packet));
+ ut_asserteq_ptr(rxbuf + 88 + sizeof(struct rx_desc), packet);
+ ut_assertok(usb_eth_test_free_pkt(dev, packet, 42));
+ ut_asserteq(-EAGAIN, usb_eth_test_recv(dev, &packet));
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_r8152_eth_recv, 0);
+
+/* r8152_eth_recv() rejects a transfer too short for the rx descriptor */
+static int dm_test_r8152_eth_recv_short(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(CRC_SIZE + 2, rxbuf);
+ ut_assertok(r8152_test_bind(uts, 8, &dev, &ueth));
+
+ ut_asserteq(-ENOSPC, usb_eth_test_recv(dev, &packet));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_r8152_eth_recv_short, 0);
+
+/* r8152_eth_recv() rejects a descriptor length shorter than the CRC */
+static int dm_test_r8152_eth_recv_crc(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(CRC_SIZE - 2, rxbuf);
+ ut_assertok(r8152_test_bind(uts, sizeof(struct rx_desc) + 64, &dev,
+ &ueth));
+
+ ut_asserteq(-ENOSPC, usb_eth_test_recv(dev, &packet));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_r8152_eth_recv_crc, 0);
+#endif
+
+#if IS_ENABLED(CONFIG_USB_ETHER_ASIX88179)
+/*
+ * struct asix_private is local to the driver; its struct ueth_data comes
+ * first
+ */
+static int ax88179_test_bind(struct unit_test_state *uts, int len,
+ struct udevice **devp, struct ueth_data **uethp)
+{
+ return usb_eth_test_bind(uts, DM_DRIVER_GET(ax88179_eth), 0, len, devp,
+ uethp);
+}
+
+/*
+ * Prepare a transfer of 64 bytes of packet data, two packet headers, the
+ * first giving @pkt_len, and the rx header giving @pkt_cnt of them
+ */
+static int ax88179_test_rx(int pkt_cnt, int pkt_len)
+{
+ const int hdr_off = 64;
+
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(pkt_len << 16, rxbuf + hdr_off);
+ put_unaligned_le32(hdr_off << 16 | pkt_cnt, rxbuf + hdr_off + 8);
+
+ return hdr_off + 12;
+}
+
+/* ax88179_eth_recv() returns each packet of the transfer */
+static int dm_test_ax88179_eth_recv(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+ int len;
+
+ /* A packet of 22 bytes padded to 24, then one of 40 up to the headers */
+ len = ax88179_test_rx(2, 22);
+ put_unaligned_le32(40 << 16, rxbuf + 68);
+ ut_assertok(ax88179_test_bind(uts, len, &dev, &ueth));
+
+ ut_asserteq(22, usb_eth_test_recv(dev, &packet));
+ ut_asserteq_ptr(rxbuf + 2, packet);
+ ut_assertok(usb_eth_test_free_pkt(dev, packet, 22));
+ ut_asserteq(40, usb_eth_test_recv(dev, &packet));
+ ut_asserteq_ptr(rxbuf + 24 + 2, packet);
+ ut_assertok(usb_eth_test_free_pkt(dev, packet, 40));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_ax88179_eth_recv, 0);
+
+/* ax88179_eth_recv() rejects packet headers running past the rx header */
+static int dm_test_ax88179_eth_recv_pkt_cnt(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+ int len;
+
+ len = ax88179_test_rx(3, 22);
+ ut_assertok(ax88179_test_bind(uts, len, &dev, &ueth));
+
+ ut_asserteq(-EIO, usb_eth_test_recv(dev, &packet));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_ax88179_eth_recv_pkt_cnt, 0);
+
+/* ax88179_eth_recv() rejects a packet running past the packet headers */
+static int dm_test_ax88179_eth_recv_pkt_len(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+ int len;
+
+ len = ax88179_test_rx(1, 65);
+ ut_assertok(ax88179_test_bind(uts, len, &dev, &ueth));
+
+ ut_asserteq(-EIO, usb_eth_test_recv(dev, &packet));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_ax88179_eth_recv_pkt_len, 0);
+#endif
+
+#if IS_ENABLED(CONFIG_USB_ETHER_LAN75XX) || IS_ENABLED(CONFIG_USB_ETHER_LAN78XX)
+static int lan7x_test_bind(struct unit_test_state *uts, struct driver *drv,
+ u32 chipid, int len, struct udevice **devp,
+ struct ueth_data **uethp)
+{
+ struct lan7x_private *priv;
+
+ ut_assertok(usb_eth_test_bind(uts, drv,
+ offsetof(struct lan7x_private, ueth), len,
+ devp, uethp));
+ priv = dev_get_priv(*devp);
+ priv->chipid = chipid;
+
+ return 0;
+}
+#endif
+
+#if IS_ENABLED(CONFIG_USB_ETHER_LAN78XX)
+static int lan78xx_test_bind(struct unit_test_state *uts, int len,
+ struct udevice **devp, struct ueth_data **uethp)
+{
+ return lan7x_test_bind(uts, DM_DRIVER_GET(lan78xx_eth),
+ ID_REV_CHIP_ID_7800, len, devp, uethp);
+}
+
+/* lan7x_eth_recv() returns the frame behind command A, B and C */
+static int dm_test_lan7x_eth_recv(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(60, rxbuf);
+ ut_assertok(lan78xx_test_bind(uts, 10 + 60, &dev, &ueth));
+
+ ut_asserteq(60, usb_eth_test_recv(dev, &packet));
+ ut_asserteq_ptr(rxbuf + 10, packet);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_lan7x_eth_recv, 0);
+
+/* lan7x_eth_recv() rejects a transfer too short for command A, B and C */
+static int dm_test_lan7x_eth_recv_short(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(2, rxbuf);
+ ut_assertok(lan78xx_test_bind(uts, 6, &dev, &ueth));
+
+ ut_asserteq(-EINVAL, usb_eth_test_recv(dev, &packet));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_lan7x_eth_recv_short, 0);
+
+/* lan7x_eth_recv() rejects a frame running past the transfer */
+static int dm_test_lan7x_eth_recv_len(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(64, rxbuf);
+ ut_assertok(lan78xx_test_bind(uts, 4 + 64, &dev, &ueth));
+
+ ut_asserteq(-EINVAL, usb_eth_test_recv(dev, &packet));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_lan7x_eth_recv_len, 0);
+
+/* lan7x_free_pkt() steps over the header, the frame and its padding */
+static int dm_test_lan7x_free_pkt(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ /* A frame of 61 bytes, then one of 60 at the next 4-byte boundary */
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(61, rxbuf);
+ put_unaligned_le32(60, rxbuf + 72);
+ ut_assertok(lan78xx_test_bind(uts, 72 + 10 + 60, &dev, &ueth));
+
+ ut_asserteq(61, usb_eth_test_recv(dev, &packet));
+ ut_assertok(usb_eth_test_free_pkt(dev, packet, 61));
+ ut_asserteq(60, usb_eth_test_recv(dev, &packet));
+ ut_asserteq_ptr(rxbuf + 72 + 10, packet);
+ ut_assertok(usb_eth_test_free_pkt(dev, packet, 60));
+ ut_asserteq(0, ueth->rxlen);
+ ut_asserteq(-EAGAIN, usb_eth_test_recv(dev, &packet));
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_lan7x_free_pkt, 0);
+#endif
+
+#if IS_ENABLED(CONFIG_USB_ETHER_LAN75XX)
+/*
+ * lan7x_eth_recv() returns the LAN75xx frame behind command A, B and the
+ * padding, which its length counts
+ */
+static int dm_test_lan7x_eth_recv_lan75xx(struct unit_test_state *uts)
+{
+ struct ueth_data *ueth;
+ struct udevice *dev;
+ uchar *packet;
+
+ memset(rxbuf, 0, sizeof(rxbuf));
+ put_unaligned_le32(2 + 60, rxbuf);
+ ut_assertok(lan7x_test_bind(uts, DM_DRIVER_GET(lan75xx_eth),
+ ID_REV_CHIP_ID_7500, 10 + 60, &dev, &ueth));
+
+ ut_asserteq(60, usb_eth_test_recv(dev, &packet));
+ ut_asserteq_ptr(rxbuf + 10, packet);
+ ut_assertok(usb_eth_test_free_pkt(dev, packet, 60));
+ ut_asserteq(0, ueth->rxlen);
+
+ ut_assertok(device_unbind(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_lan7x_eth_recv_lan75xx, 0);
+#endif
^ permalink raw reply related [flat|nested] 5+ messages in thread