Conversation
First NXP platform support for wolfIP. Adds a bare-metal port for the LPCXpresso54S018M development board (Cortex-M4F, 96 MHz) with DHCP, ICMP ping, and TCP echo server over 100 Mbps Ethernet (LAN8720A PHY). The Ethernet driver is split into a shared lpc_enet/ component (Synopsys DesignWare Ethernet QoS with enhanced descriptors) and board-specific code in lpc54s018/, matching the existing stm32/stm32h563 structure for easy reuse across other NXP LPC boards. Tested: DHCP lease acquisition, ICMP ping (<1 ms), TCP echo on port 7.
5455fc1 to
d49de56
Compare
- Makefile: Add cppcheck comparePointers suppressions for lpc54s018 startup.c/syscalls.c - main.c: Fix non-atomic 64-bit tick_ms reads with get_tick_ms() (disable/enable interrupts) - startup.c: Restore __libc_init_array() call (matches other ports) - lpc_enet.c: Add RX_BUF_SIZE bounds check in eth_poll to prevent buffer over-read - fix_checksum.py: Auto-detect vector table offset (0x0 for RAM build, 0x200 for flash) - target_ram.ld: Clarify SRAM2 is 32KB on J4M package - flash.sh: Add flash helper script (matches N6 port's flash.sh)
d49de56 to
0a91639
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #96
Scan targets checked: wolfip-bugs, wolfip-compliance, wolfip-defaults, wolfip-mutation, wolfip-proptest, wolfip-src
Findings: 6
6 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
|
|
||
| static int hw_reset(void) | ||
| { | ||
| uint32_t t = 1000000U; | ||
| ETH_DMAMR |= DMAMR_SWR; | ||
| while ((ETH_DMAMR & DMAMR_SWR) && --t) { } | ||
| return t ? 0 : -1; | ||
| } | ||
|
|
||
| static void config_mac(const uint8_t mac[6]) | ||
| { |
There was a problem hiding this comment.
🔶 [Medium] config_speed_duplex uses PHY capability bits instead of negotiated link parameters
Category: Logic errors
config_speed_duplex() reads the PHY BSR register and uses bits 11-14 (BSR_10_HALF, BSR_10_FULL, BSR_100_HALF, BSR_100_FULL) to configure the MAC speed and duplex mode. Per IEEE 802.3, BSR bits 11-14 indicate the PHY's capability (what speeds/modes it can support), not the actual negotiated link parameters. This means the MAC will always be configured for the highest mode the PHY supports (typically 100 Mbps full-duplex for the LAN8720A), regardless of what was actually negotiated with the link partner. If the link partner only supports 10 Mbps or half-duplex, the MAC will be misconfigured, causing communication failure. The correct approach is to read the Auto-Negotiation Link Partner Ability Register (ANLPAR, reg 5), AND it with the local ANAR (reg 4), and derive the highest common mode — or read a PHY vendor-specific status register (e.g., LAN8720A reg 0x1F, Special Modes register, or reg 0x1E for PHY Special Control/Status).
static void config_speed_duplex(void)
{
uint32_t maccr;
uint16_t bsr;
if (phy_addr < 0) return;
maccr = ETH_MACCR & ~(MACCR_FES | MACCR_DM);
bsr = mdio_read((uint32_t)phy_addr, PHY_BSR);
bsr |= mdio_read((uint32_t)phy_addr, PHY_BSR);
if (bsr & BSR_100_FULL) maccr |= MACCR_FES | MACCR_DM;
else if (bsr & BSR_100_HALF) maccr |= MACCR_FES;
else if (bsr & BSR_10_FULL) maccr |= MACCR_DM;
ETH_MACCR = maccr;
}Recommendation: Read ANLPAR (register 5) and AND with ANAR (register 4) to determine the highest common capability, then configure the MAC accordingly. For example:
uint16_t anar = mdio_read(phy_addr, PHY_ANAR);
uint16_t anlpar = mdio_read(phy_addr, 0x05); // ANLPAR
uint16_t common = anar & anlpar;
if (common & 0x0100) maccr |= MACCR_FES | MACCR_DM; // 100F
else if (common & 0x0080) maccr |= MACCR_FES; // 100H
else if (common & 0x0040) maccr |= MACCR_DM; // 10F| ETH_MACWTR = 0; | ||
| ETH_MACQ0TXFCR = (1U << 7); |
There was a problem hiding this comment.
🔹 [Low] DMACRXCR buffer size configured using RX descriptor packet-length mask
Category: Incorrect sizeof/type usage
The DMA RX control register buffer size field is masked with RDES3_PL_MASK (a mask defined for the packet length field in RX descriptors). While both fields happen to be 14 bits wide (0x3FFF), using a descriptor-specific mask for a DMA control register field is semantically incorrect and fragile — if RDES3_PL_MASK were ever changed to match a different descriptor revision, this DMA configuration would silently break. The Synopsys DW Ethernet QoS RBSZ field in CH0_RX_CONTROL uses bits [14:1], and should use its own named mask constant.
ETH_DMACRXCR = ((RX_BUF_SIZE & RDES3_PL_MASK) << DMACRXCR_RBSZ_SHIFT) |
DMACRXCR_RPBL(DMA_PBL);Recommendation: Define a dedicated mask for the RBSZ field, e.g.:
#define DMACRXCR_RBSZ_MASK 0x3FFFUand use it:
ETH_DMACRXCR = ((RX_BUF_SIZE & DMACRXCR_RBSZ_MASK) << DMACRXCR_RBSZ_SHIFT) | ...| lfsr ^= lfsr >> 17; | ||
| lfsr ^= lfsr << 5; | ||
| return lfsr; | ||
| } | ||
|
|
||
| static void delay_ms(uint32_t ms) | ||
| { | ||
| uint64_t target = get_tick_ms() + ms; | ||
| while (get_tick_ms() < target) { } | ||
| } | ||
|
|
||
| static void clock_init(void) | ||
| { |
There was a problem hiding this comment.
Category: Unsafe protocol defaults
wolfIP_getrandom() uses a 32-bit xorshift LFSR seeded from get_tick_ms(). This function is called by the wolfIP stack for generating TCP initial sequence numbers (ISNs) and other protocol randomness. The seed is the system uptime in milliseconds at the time of the first call, which is highly predictable — an attacker who can estimate device boot time (or simply observe a few packets) can predict the LFSR state and thus predict future TCP ISNs. The fallback seed 0x1A2B3C4DU when tick_ms == 0 is a hardcoded constant, making the sequence fully deterministic if the first call happens before SysTick fires. Predictable TCP ISNs enable TCP session hijacking, RST injection, and data injection attacks.
uint32_t wolfIP_getrandom(void)
{
static uint32_t lfsr;
static int seeded = 0;
if (!seeded) {
lfsr = (uint32_t)get_tick_ms();
if (lfsr == 0U) lfsr = 0x1A2B3C4DU;
seeded = 1;
}
lfsr ^= lfsr << 13;
lfsr ^= lfsr >> 17;
lfsr ^= lfsr << 5;
return lfsr;
}Recommendation: Use a hardware entropy source if available on the LPC54S018 (e.g., the on-chip True Random Number Generator peripheral, or combine multiple entropy sources such as ADC LSBs, uninitialized SRAM values at boot, and timing jitter). At minimum, mix in additional entropy beyond the tick counter (e.g., PHY register values, MAC address, free-running timer LSBs) to make the seed less predictable. Document the limitation if no hardware RNG is available.
|
|
||
| /* TCP echo server on port 7 */ | ||
| { | ||
| struct wolfIP_sockaddr_in addr; | ||
| memset(&addr, 0, sizeof(addr)); | ||
| addr.sin_family = AF_INET; | ||
| addr.sin_port = ee16(ECHO_PORT); | ||
| addr.sin_addr.s_addr = 0; | ||
| listen_fd = wolfIP_sock_socket(IPStack, AF_INET, IPSTACK_SOCK_STREAM, 0); | ||
| wolfIP_register_callback(IPStack, listen_fd, echo_cb, IPStack); | ||
| (void)wolfIP_sock_bind(IPStack, listen_fd, |
There was a problem hiding this comment.
🔶 [Medium] Echo server ignores socket and bind/listen return values
Category: Silent error swallowing
The TCP echo server setup calls wolfIP_sock_socket(), wolfIP_sock_bind(), and wolfIP_sock_listen() but casts the return values of bind and listen to (void) and does not check the return value of wolfIP_sock_socket() before passing listen_fd to wolfIP_register_callback() and wolfIP_sock_bind(). If socket creation fails (returns a negative value), listen_fd will be negative, and subsequent calls to wolfIP_register_callback, wolfIP_sock_bind, and wolfIP_sock_listen with an invalid fd could cause undefined behavior in the wolfIP stack, or silently fail leaving the echo server callback registered on an invalid descriptor. Similarly, if bind fails, listen proceeds anyway, potentially leaving a half-configured socket.
listen_fd = wolfIP_sock_socket(IPStack, AF_INET, IPSTACK_SOCK_STREAM, 0);
wolfIP_register_callback(IPStack, listen_fd, echo_cb, IPStack);
(void)wolfIP_sock_bind(IPStack, listen_fd,
(struct wolfIP_sockaddr *)&addr, sizeof(addr));
(void)wolfIP_sock_listen(IPStack, listen_fd, 1);Recommendation: Check the return value of wolfIP_sock_socket(). If it returns a negative value, do not proceed with bind/listen/register. Also check wolfIP_sock_bind() and wolfIP_sock_listen() return values and handle errors (at minimum, log the failure).
| uint16_t id1, bsr; | ||
|
|
||
| if (!ll) return -1; | ||
|
|
||
| if (!mac) { | ||
| local_mac[0] = 0x02; local_mac[1] = 0x11; |
There was a problem hiding this comment.
🔹 [Low] Hardcoded fallback MAC address shared across all devices
Category: Dangerous default configurations
When mac is NULL (as called from main.c line 261: lpc54s018_eth_init(ll, NULL)), lpc_enet_init uses a hardcoded locally-administered MAC address 02:11:54:18:00:01. Every device flashed with this firmware will share the same MAC address. On a shared network, duplicate MAC addresses cause ARP table conflicts, intermittent connectivity, and can enable an attacker to impersonate or intercept traffic from another device running the same firmware. While the 0x02 prefix correctly marks it as locally administered, the static nature means no uniqueness guarantee.
if (!mac) {
local_mac[0] = 0x02; local_mac[1] = 0x11;
local_mac[2] = 0x54; local_mac[3] = 0x18;
local_mac[4] = 0x00; local_mac[5] = 0x01;
mac = local_mac;
}Recommendation: Document prominently that the default MAC is for single-device development only. Consider deriving a unique MAC from a device-unique identifier (e.g., LPC54S018 UID registers at 0x40000100) or require the caller to always provide a MAC address.
| systick_init(); | ||
| led_init(); | ||
|
|
||
| printf("\r\n=== wolfIP LPC54S018M-EVK ===\r\n"); | ||
|
|
||
| eth_gpio_init(); | ||
| phy_reset(); | ||
| eth_clk_init(); | ||
|
|
||
| wolfIP_init_static(&IPStack); | ||
|
|
||
| ll = wolfIP_getdev(IPStack); | ||
| ret = lpc54s018_eth_init(ll, NULL); |
There was a problem hiding this comment.
🔹 [Low] Echo server does not close socket on receive error
Category: Resource leaks
In echo_cb(), when wolfIP_sock_recvfrom() returns a negative value (error), the function neither closes the socket nor resets client_fd. The ret > 0 branch echoes data, the ret == 0 branch closes the socket (clean close), but ret < 0 falls through with no action. While the CB_EVENT_CLOSED handler below may eventually fire, if the error condition persists without triggering a close event, client_fd remains set to the errored socket, preventing new connections from being accepted (since accept is gated on client_fd == -1). On a single-connection echo server, this effectively becomes a denial of service for the echo service until the device is rebooted.
if ((fd == client_fd) && (event & CB_EVENT_READABLE)) {
ret = wolfIP_sock_recvfrom(s, client_fd, rx_buf, sizeof(rx_buf),
0, NULL, NULL);
if (ret > 0) {
(void)wolfIP_sock_sendto(s, client_fd, rx_buf, (uint32_t)ret,
0, NULL, 0);
} else if (ret == 0) {
wolfIP_sock_close(s, client_fd);
client_fd = -1;
}
}Recommendation: Handle ret < 0 by closing the client socket and resetting client_fd: add else { wolfIP_sock_close(s, client_fd); client_fd = -1; } after the ret == 0 block.
Description
src/port/lpc_enet/for Synopsys DW Ethernet QoS MAC.github/workflows/lpc54s018.yml