Where
-Infinity
0
Severity
6.8
Integer Underflow
AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H

The MCUmgr SMP-over-console transport decodes a base64 frame, reads a 16-bit packet length from it, verifies a CRC and then unconditionally strips the trailing CRC with rxctxt->nb->len -= 2U; in mcumgrserialprocessfrag() (subsys/mgmt/mcumgr/transport/src/serialutil.c). mcumgrserialextractlen() accepted any declared length, including 0 and 1, and a packet declaring length 0 passes the checksum test for free because crc16itut() over zero bytes returns the zero seed. Since netbuf::len is a uint16t, the subtraction underflows and the buffer is handed to SMP claiming roughly 65 KB of payload while its data area is only CONFIGMCUMGRTRANSPORTNETBUFSIZE bytes (default 384).

The trigger is a single unauthenticated 7-byte line on the management console — the 0x06 0x09 packet marker followed by the base64 group AAA= and a newline — delivered to any transport built on this helper: CONFIGMCUMGRTRANSPORTUART (smpuart.c) or CONFIGMCUMGRTRANSPORTSHELL (smpshell.c), both of which select MCUMGRTRANSPORTSERIALHASSMPOVERCONSOLE. No prior session state, fragmentation or credentials are required to trigger the underflow, and the malformed frame is mishandled before any command handler or command-level access control runs. The attacker only needs write access to that console, which on many boards is a USB CDC-ACM port rather than a bare UART header.

With the inflated length, smpprocessrequestpacket() in subsys/mgmt/mcumgr/smp/src/smp.c loses its bound: cbornbreaderinit() gives the CBOR decoder a ~65 KB window into a 384-byte buffer, and each request header's nhlen is checked only against the inflated length. On its own the 7-byte frame re-parses whatever stale bytes the reused pool buffer still holds, typically a replay of the previously received request followed by a parse error, without leaving the buffer. Because the transport is unauthenticated, though, the attacker also controls the frames sent before the trigger, and can stage buffer contents so that a request succeeds with an nhlen larger than the buffer; netbufpull(), guarded only by ASSERTNOMSG, then moves the parse cursor out of bounds and the loop reads further headers and CBOR from adjacent memory. The consequence is an out-of-bounds read that can fault the MCUmgr thread (denial of service); memory disclosure is also possible, since the default-enabled os echo handler (CONFIGMCUMGRGRPOSECHO) decodes its string inside that window and copies it into its response. There is no integrity gain beyond what the unauthenticated transport already permits.

The fix rejects any declared packet length of two bytes or fewer in mcumgrserialextractlen(), so the CRC-strip subtraction can no longer underflow. The identical pattern remains in the test-only loopback transport subsys/mgmt/mcumgr/transport/src/smpdummy.c (CONFIGMCUMGRTRANSPORTDUMMY), which has no external input path and therefore carries no practical exposure.

First published (updated )
Severity
6.8
Integer Underflow
AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H

The MCUmgr SMP-over-console transport decodes a base64 frame, reads a 16-bit packet length from it, verifies a CRC and then unconditionally strips the trailing CRC with rxctxt->nb->len -= 2U; in mcumgrserialprocessfrag() (subsys/mgmt/mcumgr/transport/src/serialutil.c). mcumgrserialextractlen() accepted any declared length, including 0 and 1, and a packet declaring length 0 passes the checksum test for free because crc16itut() over zero bytes returns the zero seed. Since netbuf::len is a uint16t, the subtraction underflows and the buffer is handed to SMP claiming roughly 65 KB of payload while its data area is only CONFIGMCUMGRTRANSPORTNETBUFSIZE bytes (default 384).

The trigger is a single unauthenticated 7-byte line on the management console — the 0x06 0x09 packet marker followed by the base64 group AAA= and a newline — delivered to any transport built on this helper: CONFIGMCUMGRTRANSPORTUART (smpuart.c) or CONFIGMCUMGRTRANSPORTSHELL (smpshell.c), both of which select MCUMGRTRANSPORTSERIALHASSMPOVERCONSOLE. No prior session state, fragmentation or credentials are required to trigger the underflow, and the malformed frame is mishandled before any command handler or command-level access control runs. The attacker only needs write access to that console, which on many boards is a USB CDC-ACM port rather than a bare UART header.

With the inflated length, smpprocessrequestpacket() in subsys/mgmt/mcumgr/smp/src/smp.c loses its bound: cbornbreaderinit() gives the CBOR decoder a ~65 KB window into a 384-byte buffer, and each request header's nhlen is checked only against the inflated length. On its own the 7-byte frame re-parses whatever stale bytes the reused pool buffer still holds, typically a replay of the previously received request followed by a parse error, without leaving the buffer. Because the transport is unauthenticated, though, the attacker also controls the frames sent before the trigger, and can stage buffer contents so that a request succeeds with an nhlen larger than the buffer; netbufpull(), guarded only by ASSERTNOMSG, then moves the parse cursor out of bounds and the loop reads further headers and CBOR from adjacent memory. The consequence is an out-of-bounds read that can fault the MCUmgr thread (denial of service); memory disclosure is also possible, since the default-enabled os echo handler (CONFIGMCUMGRGRPOSECHO) decodes its string inside that window and copies it into its response. There is no integrity gain beyond what the unauthenticated transport already permits.

The fix rejects any declared packet length of two bytes or fewer in mcumgrserialextractlen(), so the CRC-strip subtraction can no longer underflow. The identical pattern remains in the test-only loopback transport subsys/mgmt/mcumgr/transport/src/smpdummy.c (CONFIGMCUMGRTRANSPORTDUMMY), which has no external input path and therefore carries no practical exposure.

First published (updated )
Severity
6.5
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

The native BSD-socket layer recorded a pending asynchronous socket error by type-punning it into struct netcontext's void userdata field (ctx->userdata = INTTOPOINTER(-status) in zsockacceptedcb(), zsockreceivedcb(), zsockconnectedcb() and zsockclosectx() in subsys/net/lib/sockets/socketsinet.c), reading it back with POINTERTOINT(). That same field is owned by the network stack for listening TCP contexts: nettcpaccept() stores the parent context pointer there and the TCP core passes it back to the registered accept callback. A failed accept therefore left a small integer (an errno value) where the stack expected a struct netcontext .

When the network interface carrying a listening TCP socket goes down, closetcpconn() in subsys/net/ip/tcp.c invokes the accept callback with -ENETDOWN and the context's userdata. In v4.3.0 the callback was not disarmed afterwards, so a second interface-down event forwarded the previously stored errno to zsockacceptedcb(), which dereferenced it as the parent context and performed several stores through it (sockseterror()'s read-modify-write of socketdata, kfifocancelwait(&parent->recvq)) — the crash described in the fix's commit message. v4.3.1 and v4.4.x carry a later change clearing conn->acceptcb after the error callback (269cb8823d3 on the v4.3 branch, 913fae5169425550f2364655298fceb79b320066 on main), which closes that repeat path; on those releases the poisoned cookie remains reachable only by a narrower race, a handshake completing alongside the interface-down still passing the stale cookie to kfifoput(&parent->acceptq, ...), and by getsockopt(SOERROR), which reads the field back unconditionally.

On v4.3.0 an application that keeps a listening TCP socket open across repeated link-down events is sufficient to reach the defect; the triggering condition is a network-interface state change, not attacker-supplied packet data, so the practical attacker is one able to force the link down repeatedly (for example an adjacent attacker disrupting a wireless link) or one with local/physical access. Because both the faulting address and the stored data are fixed small constants derived from the errno value, the outcome is a wild-pointer access leading to a kernel fatal error — a denial of service (device crash or reset) rather than an attacker-directed memory corruption.

The fix stores the pending error in a dedicated netcontext.sockerror field and converts every producer and consumer to sockseterror()/sockgeterror(), leaving userdata untouched. As a side effect it also stops getsockopt(SOERROR) — which is evaluated unconditionally — from returning the kernel address held in userdata to a userspace application.

First published (updated )
Severity
6.5
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

The native BSD-socket layer recorded a pending asynchronous socket error by type-punning it into struct netcontext's void userdata field (ctx->userdata = INTTOPOINTER(-status) in zsockacceptedcb(), zsockreceivedcb(), zsockconnectedcb() and zsockclosectx() in subsys/net/lib/sockets/socketsinet.c), reading it back with POINTERTOINT(). That same field is owned by the network stack for listening TCP contexts: nettcpaccept() stores the parent context pointer there and the TCP core passes it back to the registered accept callback. A failed accept therefore left a small integer (an errno value) where the stack expected a struct netcontext .

When the network interface carrying a listening TCP socket goes down, closetcpconn() in subsys/net/ip/tcp.c invokes the accept callback with -ENETDOWN and the context's userdata. In v4.3.0 the callback was not disarmed afterwards, so a second interface-down event forwarded the previously stored errno to zsockacceptedcb(), which dereferenced it as the parent context and performed several stores through it (sockseterror()'s read-modify-write of socketdata, kfifocancelwait(&parent->recvq)) — the crash described in the fix's commit message. v4.3.1 and v4.4.x carry a later change clearing conn->acceptcb after the error callback (269cb8823d3 on the v4.3 branch, 913fae5169425550f2364655298fceb79b320066 on main), which closes that repeat path; on those releases the poisoned cookie remains reachable only by a narrower race, a handshake completing alongside the interface-down still passing the stale cookie to kfifoput(&parent->acceptq, ...), and by getsockopt(SOERROR), which reads the field back unconditionally.

On v4.3.0 an application that keeps a listening TCP socket open across repeated link-down events is sufficient to reach the defect; the triggering condition is a network-interface state change, not attacker-supplied packet data, so the practical attacker is one able to force the link down repeatedly (for example an adjacent attacker disrupting a wireless link) or one with local/physical access. Because both the faulting address and the stored data are fixed small constants derived from the errno value, the outcome is a wild-pointer access leading to a kernel fatal error — a denial of service (device crash or reset) rather than an attacker-directed memory corruption.

The fix stores the pending error in a dedicated netcontext.sockerror field and converts every producer and consumer to sockseterror()/sockgeterror(), leaving userdata untouched. As a side effect it also stops getsockopt(SOERROR) — which is evaluated unconditionally — from returning the kernel address held in userdata to a userspace application.

First published (updated )
Severity
7.8
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

The GPIO syscall handlers in drivers/gpio/gpiohandlers.c did not validate the pin argument supplied by a user-mode caller. zvrfygpiopinconfigure(), zvrfygpiopingetconfig() and zvrfygpiopininterruptconfigure() verified only the device object (KSYSCALLDRIVERGPIO() / KSYSCALLOBJ()) and then passed the caller-supplied gpiopint straight through to zimplgpiopin() and on to the driver. The only pin check in include/zephyr/drivers/gpio.h was ASSERT(gpioportpinissupported(cfg->portpinmask, pin), ...), which compiles to nothing unless CONFIGASSERT is enabled, so in a production build no check existed at all.

GPIO drivers use pin as a shift count and as an array index on the documented assumption that it is a pin the controller supports. On STM32, for example, gpiostm32pininterruptconfigure() turns pin into an EXTI line and stm32gpiointcsetirqcallback() writes data->cb[linenum].cb and data->cb[linenum].data into a NUMEXTILINES-entry array (default 16) in drivers/interruptcontroller/intcgpiostm32.c, guarded only by a compiled-out ASSERTNOMSG(); the same function then reads extiirqtable[linenum] and calls irqenable() on the result. Other controllers index fixed-size per-pin configuration tables the same way — drivers/gpio/gpioiteit8xxx2v2.c reads an MMIO base pointer out of wucbase[8] with the caller's pin and performs a read-modify-write through it.

The issue is reachable only in builds with CONFIGUSERSPACE=y (the file is compiled under that symbol), by a user-mode thread that has been granted access to a GPIO device object. Such a grant is intended to authorize control of that port's pins; the missing validation instead lets the thread drive a supervisor-mode out-of-bounds write of a function pointer and an associated void past a kernel .bss array, and out-of-bounds reads that feed irqenable() and MMIO accesses — an escape from the user-mode sandbox that can lead to kernel memory corruption, arbitrary code execution, or a crash.

The fix validates pin against the controller's gpiodriverconfig.portpinmask with gpioportpinissupported() in every pin-taking zvrfy handler and returns -EINVAL otherwise, complemented by an unconditional pin >= GPIOMAXPINSPERPORT range check added to the shared zimplgpiopin() inlines.

First published (updated )
Severity
7.8
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

The GPIO syscall handlers in drivers/gpio/gpiohandlers.c did not validate the pin argument supplied by a user-mode caller. zvrfygpiopinconfigure(), zvrfygpiopingetconfig() and zvrfygpiopininterruptconfigure() verified only the device object (KSYSCALLDRIVERGPIO() / KSYSCALLOBJ()) and then passed the caller-supplied gpiopint straight through to zimplgpiopin() and on to the driver. The only pin check in include/zephyr/drivers/gpio.h was ASSERT(gpioportpinissupported(cfg->portpinmask, pin), ...), which compiles to nothing unless CONFIGASSERT is enabled, so in a production build no check existed at all.

GPIO drivers use pin as a shift count and as an array index on the documented assumption that it is a pin the controller supports. On STM32, for example, gpiostm32pininterruptconfigure() turns pin into an EXTI line and stm32gpiointcsetirqcallback() writes data->cb[linenum].cb and data->cb[linenum].data into a NUMEXTILINES-entry array (default 16) in drivers/interruptcontroller/intcgpiostm32.c, guarded only by a compiled-out ASSERTNOMSG(); the same function then reads extiirqtable[linenum] and calls irqenable() on the result. Other controllers index fixed-size per-pin configuration tables the same way — drivers/gpio/gpioiteit8xxx2v2.c reads an MMIO base pointer out of wucbase[8] with the caller's pin and performs a read-modify-write through it.

The issue is reachable only in builds with CONFIGUSERSPACE=y (the file is compiled under that symbol), by a user-mode thread that has been granted access to a GPIO device object. Such a grant is intended to authorize control of that port's pins; the missing validation instead lets the thread drive a supervisor-mode out-of-bounds write of a function pointer and an associated void past a kernel .bss array, and out-of-bounds reads that feed irqenable() and MMIO accesses — an escape from the user-mode sandbox that can lead to kernel memory corruption, arbitrary code execution, or a crash.

The fix validates pin against the controller's gpiodriverconfig.portpinmask with gpioportpinissupported() in every pin-taking zvrfy handler and returns -EINVAL otherwise, complemented by an unconditional pin >= GPIOMAXPINSPERPORT range check added to the shared zimplgpiopin() inlines.

First published (updated )
Severity
6.5
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H

ethernetfillheader() in subsys/net/l2/ethernet/ethernet.c can prepend the Ethernet header in place, by pushing it into the headroom of the packet's first fragment, instead of allocating a separate header fragment. Before this fix the in-place path was selected purely from the build-time value of CONFIGNETL2ETHERNETRESERVEHEADER, with no check that the packet actually has that headroom, and then called netbufpush(pkt->buffer, hdrlen). Only Ethernet TX allocations reserve that space: netpktallocbuffer() invokes the L2 alloc hook (ethernetl2alloc()) only when ispkttx() is true and the packet's interface is the Ethernet interface.

A packet that was allocated by a different L2 and then handed to Ethernet for transmission therefore has too little (usually zero) headroom. Such packets are produced by ordinary routing and tunnelling paths: netroutepacketif() re-targets a received packet to another interface when CONFIGNETIPV4FORWARDING / CONFIGNETIPV6FORWARDING are enabled (for example an 802.15.4/OpenThread or PPP interface forwarding to Wi-Fi/Ethernet), netrouteipv6mcastforwardpacket() forwards an RX-slab clone that carries no reserve, and the IPIP tunnel interfacesend() prepends an outer IP header allocated on the virtual interface. In these configurations a remote or adjacent attacker triggers the condition simply by sending traffic that the device forwards.

Because netbufsimplepush() only asserts on the headroom bound, a build without CONFIGASSERT (and without the newer CONFIGNETBUFHARDENING) moves the fragment data pointer below the start of its data block and writes 14 bytes — 18 with VLAN — of Ethernet header out of bounds. With the default fixed-size buffer pools this overwrites the tail of the neighbouring buffer's payload or memory preceding the pool array; with variable-size pools it overwrites the buffer reference count and the heap chunk header. The attacker controls neither the written bytes nor the offset, so the practical impact is memory corruption leading to a crash, packet corruption, or heap inconsistency rather than controlled code execution. The fix takes the in-place path only when netbufheadroom(pkt->buffer) is large enough and otherwise falls back to a separate header fragment.

First published (updated )
Severity
7.4
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H

In Zephyr's WireGuard subsystem (subsys/net/lib/wireguard), wgprocessdatamessage() in wgcrypto.c linearizes an inbound transport-data payload into a fixed pool buffer of CONFIGWIREGUARDBUFLEN bytes before decryption. The call netbuflinearize(buf->data, datalen, pkt->buffer, ..., datalen) passed the attacker-derived datalen as both the destination capacity and the copy length, defeating the function's internal len = min(len, dstlen) bound. datalen is derived from the received UDP datagram length and is only lower-bounded by wgctrlrecv() (no upper bound). When datalen exceeds CONFIGWIREGUARDBUFLEN — e.g. when the buffer length is lowered below the link MTU, on links with MTU above the buffer size, or via reassembled IPv4/IPv6 fragments that exceed it — the underlying memcpy writes past the end of the pool buffer, an out-of-bounds write (CWE-787). The overflow occurs before the Poly1305 authentication check, so it requires only a valid receiver session index rather than a valid authenticator, and is reachable by a malicious or compromised peer (or an on-path attacker driving an established session) over the network, yielding remote memory corruption and at minimum a reliable denial of service. The defect was present in the WireGuard implementation shipped in Zephyr 4.4.0. The fix adds an explicit datalen > CONFIGWIREGUARDBUFLEN rejection and corrects the linearize call to pass netbufmaxlen(buf) as the destination capacity.

First published (updated )
Severity
5.3
Use After Free
AV:A/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H

Zephyr's native TCP stack iterates the global connection list in nettcpforeach() (subsys/net/ip/tcp.c) using the SYSSLISTFOREACHCONTAINERSAFE macro, which caches a pointer to the next list node. Prior to this fix the function released tcplock while invoking the per-connection callback and re-acquired it afterwards.

During that window a concurrent tcpconnrelease(), running on the dedicated TCP work-queue thread when a connection's reference count drops to zero (e.g. a remote peer closing or resetting the connection), can remove and kmemslabfree() the cached next connection. When the iterator advances it dereferences the freed (and possibly reallocated) slab memory — a use-after-free that can crash the system (denial of service) and, if the slot has been reused, cause the callback to operate on an attacker-influenced object (potential information disclosure or further fault).

nettcpforeach() is reached in production via the net conn network shell command and via nettcpcloseallforiface() on interface-down; the freeing side is driven by ordinary TCP traffic.

The fix moves the connection/context teardown in tcpconnrelease() inside the tcplock critical section and keeps tcplock held across the callback in nettcpforeach(). The defect was introduced with the modern (TCP2) stack in 2020 and affects releases up to and including v4.4.0.

First published (updated )
Severity
9.8
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

parseipv4() in subsys/net/ip/utils.c (reached via netipaddrparse() for strings of the form "a.b.c.d:port") copies the port substring into a fixed 17-byte stack buffer (char ipaddr[NETIPV4ADDRLEN + 1]) using a length of strlen - end - 1, where strlen is the full, unbounded input length and end is only the (<=15-byte) offset of the ':' delimiter. Because the destination size is never consulted, a crafted address string with a long suffix after the colon (e.g. "1.2.3.4:" followed by hundreds of bytes) causes an out-of-bounds stack write whose length and contents are fully attacker-controlled (memcpy of the suffix plus a trailing NUL), enabling memory corruption and at minimum a denial of service, and potentially control-flow hijack. The parser is reached from the standard socket API (zsockgetaddrinfo / literal-address resolution), DNS server-string configuration, and the eswifi Wi-Fi co-processor DNS-response path, so an application that resolves a network-influenced address string is exposed. The bug was introduced when the parser was added (Zephyr v1.9.0) and shipped in all releases through v4.4.0. The fix removes the unbounded copy and validates the port length before copying into a small dedicated buffer. Note: the equivalent IPv6 "[addr]:port" path in parseipv6() retains the same unbounded copy at this commit and remains a separate, still-reachable instance of the defect.

First published (updated )
Severity
9.4
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H

dnsunpackname() caches the buffer tailroom once and reuses it while appending DNS labels. As the buffer grows, the cached size becomes incorrect, and the final null terminator can be written past the buffer. With assertions disabled (default), a malicious DNS response can trigger an out-of-bounds write when CONFIGDNSRESOLVER is enabled.

First published (updated )
Severity
6.5
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

The Mbed TLS crypto shim driver in drivers/crypto/cryptombedtlsshim.c drives PSA multipart cipher and AEAD operations. In mbedtlscbc() and mbedtlsaead(), every error path after a successful psacipherencryptsetup() / psaaeaddecryptsetup() returned directly without calling psacipherabort() / psaaeadabort(). Because the abort is the only thing that reaches mbedtlscipherfree(), each failed operation permanently leaked the heap-allocated mbedtlsaescontext (roughly 288 bytes) that setup had allocated from Mbed TLS's fixed static heap (mbedtlsheap[CONFIGMBEDTLSHEAPSIZE], default 512 bytes). With CONFIGPSACRYPTOPROVIDERTFM the leaked object is instead a finite secure-partition multipart-operation slot.

The failure is chosen by an unauthenticated attacker. With CONFIGNETL2IEEE802154SECURITY backed by this driver, ieee802154decryptauth() in subsys/net/l2/ieee802154/ieee802154security.c invokes cipherccmop() on every received secured frame, with no authentication ahead of the MIC check. A device in radio range can transmit a stream of syntactically valid 802.15.4 frames carrying a bogus MIC; each one makes psaaeadverify() fail and leaks a block. The CBC path is reachable the same way through the OSDP secure channel, where CONFIGOSDPSCENABLED selects this shim and osdpencrypt() / osdpdecrypt() leave outbufmax unset, so every operation fails inside psacipherupdate().

Exhausting the Mbed TLS heap — which the default configuration reaches after a couple of failed operations, and any realistic configuration reaches in well under a second of frames — makes all subsequent mbedtlscalloc() calls fail. Every later cryptographic operation in the image then fails permanently, including link-layer security and any TLS/DTLS sharing the same heap, until the device is rebooted. There is no memory corruption or information disclosure: frames with an invalid MIC are still rejected, so the impact is limited to availability.

The fix routes all post-setup failures in mbedtlscbc() and mbedtlsaead() through a common error: label that calls the matching PSA abort before returning. Setup failures need no change, since the PSA core aborts the operation itself when setup fails.

First published (updated )
Severity
4.8
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N

The entropy-device random number generator in subsys/random/randomentropydevice.c returned early with -ENODEV from randget() as soon as deviceisready() failed, before reaching the non-cryptographic system-timer fallback further down the same function. Because zimplsysrandget() returns void and discards that error — and the public sysrandget() syscall is itself declared void, so it cannot report failure — the caller's output buffer was left untouched and its previous contents were handed back as random data. The thin wrappers make this a plain uninitialized read: sysrand32get() declares uint32t ret;, calls sysrandget(&ret, sizeof(ret)) and returns ret.

The "not ready" state is a normal runtime condition rather than a fault. Entropy drivers initialise at PREKERNEL1 with CONFIGENTROPYINITPRIORITY (default 50), so any earlier initialiser — kernel objects run at priority 30 — observes an unready device; the sibling generator subsys/random/randomxoshiro128.c and zearlyrandget() in kernel/init.c both already carried an explicit timer fallback for exactly this case. The state is also permanent for the boot when an entropy driver's init hook fails, which several in-tree drivers can do on real hardware (entropystm32 on an RNG clock configuration error, entropypsacrypto when psacryptoinit() fails, entropynrfcracen, entropyesp32, entropyvirtio).

When this occurs, every non-cryptographic random value in the system is either a constant or stale memory. coapnexttoken() in subsys/net/lib/coap/coap.c fills a static buffer, so every CoAP token becomes the same .bss constant; tcpinitisn() in subsys/net/ip/tcp.c returns an uninitialized stack word as the TCP initial sequence number; DHCPv4/DHCPv6 transaction IDs, WebSocket masking keys and RTP SSRCs are similarly degraded, and arch/arm64/core/pac.c derives per-thread Pointer Authentication keys from the same source. Where the bytes are transmitted directly — SSH packet padding, the netmidi2 session nonce, a DTLS connection ID on builds without CONFIGCSPRNGENABLED — stale caller stack or heap contents are disclosed on the wire.

The fix initialises ret to -ENODEV and only calls entropygetentropy() when the device is ready, so the not-ready case now falls through to the timer fallback and the buffer is always written. The cryptographically secure path is unchanged: syscsrandget() still propagates the error to the caller and never silently degrades. An attacker cannot induce the unready state, but can observe and exploit the resulting non-random values remotely on an affected device.

First published (updated )
Severity
6.7
AV:A/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:H

The Bluetooth host's GATT client passed the value of a received Handle Value Notification, Handle Value Indication or Multiple Handle Value Notification to btgattsubscribeparams::notify callbacks using only the length derived from the received ATT PDU. btattrecv() in subsys/bluetooth/host/att.c validates only a minimum PDU length and does not enforce the negotiated ATT MTU, and the fixed ATT L2CAP channel in subsys/bluetooth/host/l2cap.c applies no rx.mtu check either, so the only ceiling was the ACL reassembly buffer CONFIGBTBUFACLRXSIZE. Values longer than the 512-octet maximum attribute length defined by the Bluetooth Core Specification (Vol 3, Part F, Section 3.2.9) could therefore be handed to subscribers that size their buffers to that documented limit.

A remote peer that a device has connected and subscribed to — a malicious, spoofed or compromised GATT server within radio range — triggers the flaw by sending a notification or indication whose value exceeds 512 octets on a subscribed handle. In the LE Audio unicast client this reaches a concrete out-of-bounds write: unicastclientepnotify() in subsys/bluetooth/audio/bapunicastclient.c copies the notification into the 512-byte attbuf of struct unicastclient with netbufsimpleaddmem() and no tailroom check whenever length equals btaudiogetmaxntfsize(), i.e. the negotiated ATT MTU minus three. notifyhandler() in subsys/bluetooth/audio/bapbroadcastassistant.c has the same shape. Reaching the overflow requires a build whose ATT MTU exceeds 515 octets (CONFIGBTL2CAPTXMTU and CONFIGBTBUFACLRXSIZE set for full-length attribute support); the shipped defaults are far smaller.

The overflow writes peer-controlled bytes past attbuf directly over the adjacent struct netbufsimple netbuf — its data, buf, len and size fields — which the subsequent long-read accumulation then uses as its destination, turning a small linear overflow into a controlled write. netbufsimpleadd() only asserts on insufficient tailroom unless CONFIGNETBUFHARDENING is enabled, and that option is disabled by default, so ordinary production builds perform the copy. Beyond the in-tree LE Audio clients, any application subscriber that sized its receive buffer to the specification's 512-octet limit was exposed to the same overflow.

The fix rejects notification, indication and multiple-notification values longer than BTATTMAXATTRIBUTELEN in callnotifycbandmaybeunsubscribe() in subsys/bluetooth/host/gatt.c before any subscriber callback is invoked, so no subscriber can be handed more than the specified maximum. Remaining tuples of a multiple handle value notification are still delivered and oversized indications are still confirmed.

First published (updated )
Severity
7
AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

The user-mode verification handler for the i3ctransfer() system call, zvrfyi3ctransfer() in drivers/i3c/i3chandlers.c, validated the caller-supplied struct i3cdevicedesc in place instead of snapshotting it. It confirmed only that the descriptor was readable by the calling thread (KSYSCALLMEMORYREAD()), read target->bus once to check it with KSYSCALLOBJ(), and then passed the caller's live pointer down. The implementation zimpli3ctransfer() re-reads target->bus from that user memory and dispatches through it as DEVICEAPIGET(i3c, target->bus)->i3cxfers(...), so the bus that was validated is not necessarily the bus that is used. DEVICEAPIGET() performs no runtime type check outside CONFIGDEVICEAPIASSERT builds.

On a build with CONFIGUSERSPACE and an I3C controller driver, a user thread that has been granted an I3C device object can exploit this with a second thread in the same memory domain: one thread calls i3ctransfer() while the other rewrites the bus field of the descriptor. The window between the check and the use spans the nummsgs validation, the message-array access check, a stack VLA allocation and a memcpy() of up to 32 struct i3cmsg, so the race is repeatable and can simply be retried until it lands.

When the substitution succeeds, the kernel dereferences an attacker-chosen pointer as a struct device, loads a function pointer from the attacker-controlled api table and calls it in supervisor mode with attacker-influenced arguments. This yields kernel-mode code execution from an unprivileged user thread — a complete escape of the userspace memory-protection sandbox; lesser outcomes are an arbitrary two-level kernel read or a kernel fault. Builds without CONFIGUSERSPACE, or without an I3C driver, are unaffected.

The fix copies the descriptor into a kernel-stack local with kusermodefromcopy(), validates the bus in that copy, and dispatches on the copy, so the validated value is provably the value used.

First published (updated )
Severity
5.7
AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

The Bluetooth Classic AVDTP layer defers releasing a session's stream endpoints: btavdtpl2capdisconnected() in subsys/bluetooth/host/classic/avdtp.c ends with kworksubmit(&session->releasework), placing the embedded struct kwork on the system work queue's pending list. When the same struct btavdtp object is immediately reused for a new signalling connection, the session memory is re-initialized — a2dpgetconnection() in subsys/bluetooth/host/classic/a2dp.c memset()s the whole struct bta2dp, and btavdtpl2capaccept() / btavdtpconnect() call kworkinit() on session->releasework — while that work item is still linked into ksysworkq.pending. Zeroing a linked syssnodet truncates the list while its tail pointer keeps pointing past the truncation.

The reuse window is deterministic rather than a race. All host RX processing runs on the Bluetooth RX work queue at a cooperative priority (CONFIGBTRXPRIO), while releasework is submitted to the lower-priority system work queue, so the release handler cannot run until the RX thread blocks. Because l2capbrrecv() processes every signalling command contained in a single L2CAP signalling PDU back-to-back, a peer can send one PDU holding an L2CAP Disconnection Request for the AVDTP signalling channel followed by a Connection Request for PSM 0x0019; the second command reaches the session-reuse path with the release work provably still queued. The peer must already hold an encrypted AVDTP signalling channel, i.e. be a paired/bonded BR/EDR device.

The result is corruption of the global system work queue's pending list: either the queue silently stops executing any work at all (subsequent submissions are appended to a chain the head can no longer reach, and the orphaned items keep their KWORKQUEUED flag forever) or, in the tail variant, the list acquires a self-referencing node that the queue thread spins on. The written values are pointers to other struct kwork objects and are not attacker-controlled, so the impact is a device-wide, reboot-required denial of service rather than a controllable write primitive. Only builds with CONFIGBTCLASSIC and CONFIGBTA2DP are affected.

The fix is a two-commit unit, and neither commit closes the path on its own. ec822ceed83b adds a pending-release check to btavdtpconnect() and btavdtpl2capaccept(), but the A2DP upper layer's a2dpgetconnection() memset()s the session — including the queued work item's list node and flags — before those checks run, so kworkbusyget() would observe an already-cleared state. 2bd5ea88c69b closes that by making a2dpgetconnection() itself skip the reuse and return NULL while the release work is still pending, which both callers already handle by rejecting the connection attempt. Apply both commits together; either one alone leaves the device vulnerable.

First published (updated )
Severity
5.7
AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

The Bluetooth Classic AVDTP layer defers releasing a session's stream endpoints: btavdtpl2capdisconnected() in subsys/bluetooth/host/classic/avdtp.c ends with kworksubmit(&session->releasework), placing the embedded struct kwork on the system work queue's pending list. When the same struct btavdtp object is immediately reused for a new signalling connection, the session memory is re-initialized — a2dpgetconnection() in subsys/bluetooth/host/classic/a2dp.c memset()s the whole struct bta2dp, and btavdtpl2capaccept() / btavdtpconnect() call kworkinit() on session->releasework — while that work item is still linked into ksysworkq.pending. Zeroing a linked syssnodet truncates the list while its tail pointer keeps pointing past the truncation.

The reuse window is deterministic rather than a race. All host RX processing runs on the Bluetooth RX work queue at a cooperative priority (CONFIGBTRXPRIO), while releasework is submitted to the lower-priority system work queue, so the release handler cannot run until the RX thread blocks. Because l2capbrrecv() processes every signalling command contained in a single L2CAP signalling PDU back-to-back, a peer can send one PDU holding an L2CAP Disconnection Request for the AVDTP signalling channel followed by a Connection Request for PSM 0x0019; the second command reaches the session-reuse path with the release work provably still queued. The peer must already hold an encrypted AVDTP signalling channel, i.e. be a paired/bonded BR/EDR device.

The result is corruption of the global system work queue's pending list: either the queue silently stops executing any work at all (subsequent submissions are appended to a chain the head can no longer reach, and the orphaned items keep their KWORKQUEUED flag forever) or, in the tail variant, the list acquires a self-referencing node that the queue thread spins on. The written values are pointers to other struct kwork objects and are not attacker-controlled, so the impact is a device-wide, reboot-required denial of service rather than a controllable write primitive. Only builds with CONFIGBTCLASSIC and CONFIGBTA2DP are affected.

The fix is a two-commit unit, and neither commit closes the path on its own. ec822ceed83b adds a pending-release check to btavdtpconnect() and btavdtpl2capaccept(), but the A2DP upper layer's a2dpgetconnection() memset()s the session — including the queued work item's list node and flags — before those checks run, so kworkbusyget() would observe an already-cleared state. 2bd5ea88c69b closes that by making a2dpgetconnection() itself skip the reuse and return NULL while the release work is still pending, which both callers already handle by rejecting the connection attempt. Apply both commits together; either one alone leaves the device vulnerable.

First published (updated )
Severity
7
AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

The user-mode verification handler for the i3ctransfer() system call, zvrfyi3ctransfer() in drivers/i3c/i3chandlers.c, validated the caller-supplied struct i3cdevicedesc in place instead of snapshotting it. It confirmed only that the descriptor was readable by the calling thread (KSYSCALLMEMORYREAD()), read target->bus once to check it with KSYSCALLOBJ(), and then passed the caller's live pointer down. The implementation zimpli3ctransfer() re-reads target->bus from that user memory and dispatches through it as DEVICEAPIGET(i3c, target->bus)->i3cxfers(...), so the bus that was validated is not necessarily the bus that is used. DEVICEAPIGET() performs no runtime type check outside CONFIGDEVICEAPIASSERT builds.

On a build with CONFIGUSERSPACE and an I3C controller driver, a user thread that has been granted an I3C device object can exploit this with a second thread in the same memory domain: one thread calls i3ctransfer() while the other rewrites the bus field of the descriptor. The window between the check and the use spans the nummsgs validation, the message-array access check, a stack VLA allocation and a memcpy() of up to 32 struct i3cmsg, so the race is repeatable and can simply be retried until it lands.

When the substitution succeeds, the kernel dereferences an attacker-chosen pointer as a struct device, loads a function pointer from the attacker-controlled api table and calls it in supervisor mode with attacker-influenced arguments. This yields kernel-mode code execution from an unprivileged user thread — a complete escape of the userspace memory-protection sandbox; lesser outcomes are an arbitrary two-level kernel read or a kernel fault. Builds without CONFIGUSERSPACE, or without an I3C driver, are unaffected.

The fix copies the descriptor into a kernel-stack local with kusermodefromcopy(), validates the bus in that copy, and dispatches on the copy, so the validated value is provably the value used.

First published (updated )
Severity
4.6
Use After Free
AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L

The AVRCP Target role in subsys/bluetooth/host/classic/avrcp.c keeps its vendor-dependent response transmit state in a per-connection object selected by connection index (getavrcptg()), with the per-transfer context (tid, sentlen, and the BTAVRCPTGFLAGTXONGOING flag) stored in the userdata of the queued response netbuf. The AVCTP teardown handler avrcpdisconnected() did not cancel tg->vdrsptxwork and did not release the buffers still queued on tg->vdrsptxpending, so a link drop in the middle of a fragmented response left the buffer on the list with TXONGOING still set.

A remote AVRCP Controller can trigger this deterministically: it issues a vendor-dependent command whose response exceeds one AVRCP fragment (for example GetElementAttributes or GetCapabilities), lets btavrcptgvendortxwork() emit the START fragment, and then drops the ACL link instead of sending REQUESTCONTINUINGRESPONSE. The AVCTP control channel requires an encrypted BR/EDR link (BTSECURITYL2), so the attacker must be a paired peer within radio range; no user interaction is needed.

Because the Target objects are static and reused, the next connection landing on the same connection index finds the stale buffer at the head of vdrsptxpending. Every run of the TX work sees TXONGOING and returns immediately, so all further vendor-dependent responses queue behind the stale head and are never sent — a persistent loss of AVRCP Target function that no timeout clears and only a reboot recovers. Each cycle also leaks one response buffer from a shallow application pool, and a new peer that sends REQUESTCONTINUINGRESPONSE clears the stale TXONGOING via btavrcptgprocesscontinuingcmd(), causing the un-sent remainder of the previous peer's response to be transmitted to it.

The fix cancels tg->vdrsptxwork and unrefs every buffer on tg->vdrsptxpending in avrcpdisconnected(); since the TX context lives in the buffer userdata, releasing the buffers also resets the TX state machine. There is no memory corruption: the pre-fix buffer is leaked with a live reference, so no use-after-free arises.

First published (updated )
Severity
4.6
Use After Free
AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L

The AVRCP Target role in subsys/bluetooth/host/classic/avrcp.c keeps its vendor-dependent response transmit state in a per-connection object selected by connection index (getavrcptg()), with the per-transfer context (tid, sentlen, and the BTAVRCPTGFLAGTXONGOING flag) stored in the userdata of the queued response netbuf. The AVCTP teardown handler avrcpdisconnected() did not cancel tg->vdrsptxwork and did not release the buffers still queued on tg->vdrsptxpending, so a link drop in the middle of a fragmented response left the buffer on the list with TXONGOING still set.

A remote AVRCP Controller can trigger this deterministically: it issues a vendor-dependent command whose response exceeds one AVRCP fragment (for example GetElementAttributes or GetCapabilities), lets btavrcptgvendortxwork() emit the START fragment, and then drops the ACL link instead of sending REQUESTCONTINUINGRESPONSE. The AVCTP control channel requires an encrypted BR/EDR link (BTSECURITYL2), so the attacker must be a paired peer within radio range; no user interaction is needed.

Because the Target objects are static and reused, the next connection landing on the same connection index finds the stale buffer at the head of vdrsptxpending. Every run of the TX work sees TXONGOING and returns immediately, so all further vendor-dependent responses queue behind the stale head and are never sent — a persistent loss of AVRCP Target function that no timeout clears and only a reboot recovers. Each cycle also leaks one response buffer from a shallow application pool, and a new peer that sends REQUESTCONTINUINGRESPONSE clears the stale TXONGOING via btavrcptgprocesscontinuingcmd(), causing the un-sent remainder of the previous peer's response to be transmitted to it.

The fix cancels tg->vdrsptxwork and unrefs every buffer on tg->vdrsptxpending in avrcpdisconnected(); since the TX context lives in the buffer userdata, releasing the buffers also resets the TX state machine. There is no memory corruption: the pre-fix buffer is leaked with a live reference, so no use-after-free arises.

First published (updated )
Severity
8.2
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H

The WireGuard VPN service in subsys/net/lib/wireguard/wg.c updated the peer endpoint from a message it could not authenticate. In handletransportdata(), when the 32-bit receiver index of an incoming transport-data message matched no session, the code both scheduled a new handshake and copied the datagram's source address into peer->endpoint with memcpy(&peer->endpoint, peeraddr, sizeof(peer->endpoint)). Because the receiver index is unknown, there is no key with which such a message could be verified, so the address written into peer->endpoint is entirely attacker-supplied. Every other endpoint update in the implementation happens only after authentication — after the Noise handshake in handlehandshakeinit(), or after AEAD decryption plus the anti-replay check in wgprocessdatamessage().

The WireGuard listener is registered with netudpregister() using no remote address or port filter and defaults to UDP port 51820 (CONFIGWIREGUARDPORT), and wgctrlrecv() resolves the peer by interface rather than by any cryptographic identity. Any host able to send a UDP datagram to that port can therefore reach the defect off-path, with no need to observe legitimate traffic: a 16-byte datagram carrying the transport-data type, zeroed reserved bytes and an arbitrary receiver index is sufficient. The vulnerable branch runs whenever the peer has neither a valid handshake nor a current keypair — the state at boot and after a session expires.

A single spoofed datagram redirects peer->endpoint to an address of the attacker's choosing, and all subsequent handshake initiations, keepalives and encrypted data packets are sent there. The legitimate peer never receives the initiations and so never re-authenticates, and nothing resets the endpoint afterwards, leaving the tunnel persistently unusable on devices whose peer does not initiate handshakes itself. The attacker can also relay the redirected initiations to the real peer to place itself in the path of the tunnel, observing ciphertext and traffic metadata and selectively dropping packets. WireGuard's end-to-end cryptography still prevents decryption or forgery of tunnel content, so the impact is loss of availability plus limited confidentiality, not key compromise.

The fix removes the endpoint learning from this path entirely: the stale-session recovery is kept, but the handshake is sent only to an endpoint learnt from an authenticated message, falling back to the configured cfgendpoint, and the message is dropped when neither is known. CONFIGWIREGUARD is marked experimental and is not enabled by default.

First published (updated )
Severity
3.6
AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L

The RTIO streaming completion callback adxl345processfifosamplescb() in drivers/sensor/adi/adxl345/adxl345stream.c clamps the amount of FIFO data it will report (readlen = MIN(fifobytes, bufavail)) to the space actually returned by rtiosqerxbuf(), but then queues one 6-byte bus read per hardware FIFO entry at consecutive offsets from readbuf, bounded by the device-reported entry count rather than by the clamp. When the allocated buffer is smaller than a full batch, the driver writes up to 186 bytes past the end of the allocation; a companion defect left data->fifototalbytes un-reset at batch start, so an aborted or errored chain could leave a stale write offset that pushes the next batch's writes out of bounds as well.

A short buffer is an expected, first-class outcome of the RTIO allocator: rtioblockpoolalloc() walks the requested size down one block at a time until a contiguous run is available, and the driver only demands sizeof(struct adxl345fifodata) + 6 bytes as its minimum. Any streaming configuration whose mempool block granularity is smaller than a full FIFO batch (204 bytes) — for example the sensor shell's RTIODEFINEWITHMEMPOOL(sensorreadrtio, 8, 8, 32, 64, 4) used by sensor stream <dev> on fifowm incl, or the 16-byte-block streaming example in the documentation — takes this path whenever the pool is fragmented or the consuming thread falls behind.

The out-of-bounds writes are performed by the I2C/SPI RTIO backend (DMA on many SoCs) into the shared RTIO block pool, corrupting neighbouring live buffers or, for a pool smaller than one batch, memory beyond the pool itself. The written bytes are accelerometer samples and the destination follows the allocation, so there is no controllable corruption primitive: the realistic impact is silent corruption of other RTIO consumers' data or a crash. Reaching it requires a board with an interrupt-wired ADXL345 built with CONFIGADXL345STREAM, plus buffer-pool starvation, which a co-resident local workload can induce but which also occurs spontaneously under load; there is no network, USB or radio path to this code.

First published (updated )
Severity
8.8
AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

The Bluetooth HCI USB transport class of the new USB device stack, subsys/usb/devicenext/class/bthci.c, copies every controller-to-host HCI packet into a USB transfer buffer before enqueuing it on an endpoint. bthcitxsyncin() allocated that buffer from bthcieppool, a fixed pool whose slots are only USBDMAXBULKMPS bytes (64 on full-speed-only builds), and then performed netbufaddmem(buf, btbuf->data, btbuf->len) with no length check. HCI event and ACL buffers are sized by CONFIGBTBUFEVTRXSIZE and CONFIGBTBUFACLRXSIZE and can be far larger, so the copy runs past the end of the 64-byte slot into the adjacent slots and into whatever follows the statically allocated pool.

The device runs the local Bluetooth controller in raw HCI mode (CONFIGBTHCIRAW), so the oversized packets originate over the air. With the HCI buffer sizes recommended for full-featured controllers, an unauthenticated attacker within Bluetooth range can supply the overflowing bytes: a connected peer that negotiates Data Length Extension can send ACL fragments up to CONFIGBTCTLRDATALENGTHMAX bytes, and an attacker that merely broadcasts extended advertising data reaches the same path through LE Extended Advertising Report events, which the controller fragments only to CONFIGBTBUFEVTRXSIZE. In the stock configuration the overflow is already reached without any radio traffic: the mandatory Read Supported Commands command-complete event is 70 bytes and overruns the 64-byte slot by six bytes on every host initialization.

Because netbufaddmem() validates tailroom only through an assertion unless CONFIGNETBUFHARDENING is enabled, builds with CONFIGASSERT=n — the usual production setting — silently corrupt memory adjacent to the USB buffer pool with attacker-chosen contents, which on a target without memory protection between subsystems can lead to arbitrary code execution. Builds with assertions enabled fail the assertion instead and panic, giving a remotely triggerable denial of service. The fix introduces a separate bthcitxpool sized to MAX(BTBUFACLRXSIZE, BTBUFEVTRXSIZE) for the controller-to-host direction while leaving the host-to-device pool at the endpoint packet size, which is already bounds-checked during reassembly.

First published (updated )
Severity
5.7
AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

The Bluetooth Classic Hands-Free Profile HF role in subsys/bluetooth/host/classic/hfphf.c does not release the AT command buffers that are still queued on hf->txpending when the RFCOMM DLC goes down. hfphfdisconnected() only cancelled the work items and cleared hf->acl, and no other code path ever dequeues those buffers, so each one is permanently lost from the fixed hfpool (CONFIGBTMAXCONN + 1 buffers, i.e. two in the default configuration). The same function also left BTHFPHFFLAGCONNECTED set, and hfphfcreate() wiped the object with memset() as soon as hf->acl was NULL, without checking whether hf->work, hf->slcwork or hf->deferredwork was still pending or running.

The remote Audio Gateway peer controls the trigger end to end and needs no application involvement: an unsolicited +BCS: 1 response makes bcshandle() call bthfphfselectcodec() and queue an AT+BCS buffer, which cannot be transmitted while BTHFPHFFLAGRXONGOING is set, and an immediately following RFCOMM DISC strands that buffer. Repeating the connect / +BCS / disconnect cycle a couple of times empties hfpool for good.

With the pool exhausted, HFP command sending fails permanently, and because hfphfsendcmd() allocates with KFOREVER (btrfcommcreatepdu() → btconncreatepdu()) from the Bluetooth RX context — where that timeout is not downgraded to KNOWAIT — one further +BCS from the peer parks the Bluetooth RX thread inside netbufalloc() with no possible waker, halting all HCI event processing until the device is rebooted. Separately, reusing the HF slot while a work handler was still queued or running allowed memset() to wipe live kwork items out from under the system workqueue, corrupting shared kernel workqueue state. Exploitation requires an established HFP session (for SSP devices L2CAP enforces authentication before the RFCOMM channel is reachable) and a build with CONFIGBTHFPHF enabled.

First published (updated )
Severity
7.1
Null Pointer Dereference
AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H

The MCTP-over-I2C+GPIO target binding in subsys/pmci/mctp/mctpi2cgpiotarget.c emulates a small pseudo-register file, where the bus controller first writes a register number and then writes or reads payload bytes. The selected register (b->regaddr) and the data-phase flag (b->rxtx) were never cleared when the controller issued an I2C STOP, so after a completed transfer the target still had a data register selected while the packet buffer that register refers to had already been consumed and set to NULL by mctpi2cgpiotargetstop().

A controller on the bus can re-enter the same branch of mctpi2cgpiotargetstop() with a NULL buffer using two ordinary transactions and no authentication: first a normal packet write to MCTPI2CGPIORXMSGADDR followed by STOP (which delivers and frees b->rxpkt), then a bare read transaction followed by STOP. The failing readrequested() call leaves the stale state untouched and the I2C driver still delivers the STOP callback, which then calls mctpbusrx() with a NULL packet. The symmetric case exists on the transmit side, where the stale MCTPI2CGPIOTXMSGADDR selection dereferences b->txpkt->end after b->txpkt has been cleared and additionally signals b->txcomplete a second time, causing the next outbound packet to be dropped.

The dereference happens in the I2C target interrupt handler, so on any target that traps accesses to address 0 the result is a fatal exception and a device reset or halt — a denial of service inflicted by any device able to act as controller on the shared management bus (for example compromised firmware on a peer MCTP endpoint or on the BMC). On parts where address 0 is readable, the impact degrades to a malformed packet buffer being handed to the MCTP stack and lost transmit packets. No attacker-controlled value is written and no confidentiality or integrity boundary is crossed.

The fix resets b->regaddr to MCTPI2CGPIOINVALIDADDR and clears b->rxtx at every STOP, so a new register must be selected before any data phase can run again. Only builds that opt into the experimental CONFIGMCTP subsystem with CONFIGMCTPI2CGPIOTARGET=y are affected.

First published (updated )
Severity
8.8
AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

The OSDP Control Panel reply parser cpdecoderesponse() in subsys/mgmt/osdp/src/osdpcp.c decodes replies from a Peripheral Device into a stack-allocated struct osdpevent. For the REPLYKEYPPAD and REPLYRAW reply types, the payload length is taken from the received frame and the only validation performed is that the declared length is self-consistent with the number of payload bytes present in the frame. Because the attacker controls both the length field and the frame length, that check imposes no bound, and the subsequent memcpy() writes into the fixed 64-byte data arrays of struct osdpeventkeypress / struct osdpeventcardread (OSDPEVENTMAXDATALEN). The sibling REPLYFMT handler in the same switch already carried the missing > OSDPEVENTMAXDATALEN bound.

The defect is reached from cpprocessreply(), which reads bytes from the OSDP RS-485 channel and passes them through osdpphycheckpacket() and osdpphydecodepacket() before dispatching on the reply ID. Triggering it requires CONFIGOSDP with CONFIGOSDPMODECP and an application event callback registered (the normal Control Panel integration), plus a frame carrying a correct SOM, a valid CRC-16, and a matching 2-bit sequence number — all of which are computable or observable by anyone on the bus. No check ties the reply ID to the outstanding command, so an oversized REPLYKEYPPAD or REPLYRAW is accepted as the answer to a routine CMDPOLL. A malicious or compromised card reader, or anyone with access to the RS-485 wiring, can supply such a frame; CONFIGOSDPSCENABLED only helps while a secure channel is active, and a single unauthenticated frame carrying a bad MAC causes scdeactivate(), after which plaintext replies are again accepted.

With the default CONFIGOSDPUARTBUFFERLENGTH of 256, roughly 180 bytes of fully attacker-chosen data can be written past the end of the stack-allocated event object, over saved registers and the return address on typical Cortex-M and RISC-V frame layouts (Zephyr does not enable stack canaries by default). The impact ranges from a hard crash of the access-control panel to potential control-flow hijack in the context of the OSDP receive thread. The fix adds an OSDPEVENTMAXDATALEN bound to both handlers, capping each memcpy() at the size of its destination.

First published (updated )
Severity
8.1
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H

The IPv6 Neighbor Discovery handlers in subsys/net/ip/ipv6nbr.c (handlerainput, handlensinput, handlenainput) used an incorrect boolean expression that combined the RFC 4861 validity checks with the ICMPv6 code check using the wrong operator precedence: the form was ((length/hop/source/target checks) && (icmphdr->code != 0)). Because every legitimate ND message carries ICMPv6 code 0, an attacker setting code == 0 (the normal value) caused the entire predicate to evaluate false, so the packet was never dropped and all of the other checks were silently skipped. The bypassed checks include the mandatory Hop Limit == 255 verification (which proves an ND packet originated on-link and was not forwarded) and, for Router Advertisements, the requirement that the source be a link-local address, as well as multicast-target sanity checks. As a result, an adjacent on-link attacker — and, because the Hop-Limit-255 guard is bypassed, potentially a remote/off-link attacker whose packets would otherwise be rejected — can have forged Router Advertisement, Neighbor Solicitation, and Neighbor Advertisement messages accepted. A forged RA lets the attacker reconfigure the victim's default router, on-link prefixes (SLAAC), MTU, reachable/retransmit timers, and (with CONFIGNETIPV6RARDNSS) DNS servers, while forged NS/NA enable neighbor-cache poisoning, enabling man-in-the-middle, traffic redirection, and denial of service. The flaw is an input-validation/authentication weakness rather than a memory-safety issue: the underlying packet-parsing primitives (netpktgetdata, netpktread, netpktskip) are independently bounds-safe and the validated length is the true buffer length, so skipping the length check causes no out-of-bounds access. The defect has existed since the logic was introduced in 2018 and shipped in all releases through v4.4.0; it is fixed by splitting the condition so any failing check drops the packet.

First published (updated )
Severity
6.8
AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H

The CC1200 sub-GHz IEEE 802.15.4 driver takes the PHY packet length from the first byte of the radio's RX FIFO in getpacketlength() and validates it in verifyrxfifovalidity() (drivers/ieee802154/ieee802154cc1200.c). That validation only enforced a lower bound (pktlen < 3); its second clause rejects a FIFO that holds more bytes than declared and therefore never bounds large values. Because the driver configures the radio for variable packet length with PKTLEN = 0xFF, the length byte is an over-the-air value of 0-255 that reached netpktrxallocwithbuffer() and the subsequent SPI burst read unclamped.

An unauthenticated attacker within radio range can transmit a frame that presents a length byte above the IEEE 802.15.4 maximum of 127 while the chip's 128-byte RX FIFO has not overflowed, so the driver's RXFIFOERROR pre-check does not fire. Reaching this state depends on RF framing and timing, since the RX thread is driven off the PKTSYNCRXTX signal and may sample the FIFO before a packet is complete; no pairing, authentication, or user interaction is required.

With the default CONFIGNETBUFFIXEDDATASIZE and CONFIGNETBUFDATASIZE=128, an allocation larger than one fragment is satisfied by a chain of fragments, but readrxfifocontent() performs a single SPI burst of pktlen bytes into the first fragment's buf->data and then calls netbufadd() with the same length. A length above 128 therefore writes up to 127 bytes past the end of a fragment in the contiguous rxbufs pool and leaves a netbuf whose len exceeds its capacity. The overflowing bytes are FIFO-underflow output rather than attacker-chosen data, so the practical impact is network-buffer-pool corruption and crashes rather than a controlled write. Only builds that enable CONFIGIEEE802154CC1200 with an attached CC1200 radio are affected.

First published (updated )
Severity
5.9
Integer Underflow
AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H

The receive path of the NXP KW41Z IEEE 802.15.4 driver, kw41zrx() in drivers/ieee802154/ieee802154kw41z.c, computed the payload size as pktlen = len - KW41ZFCSLENGTH in a uint8t, where len is the frame length reported by the radio. The interrupt handler kw41zisr() obtained that length from the RXFRAMELENGTH field of the ZLL IRQSTS register — a copy of the PHY header length byte of the frame received over the air — and only rejected the value zero. A reported length of one therefore underflowed to pktlen == 255.

Because the packet is allocated with NETAFUNSPEC on a non-Ethernet L2, netpktrxallocwithbuffer() does not clamp the request to the interface MTU; with the default fixed-size buffer pool (CONFIGNETBUFFIXEDDATASIZE, CONFIGNETBUFDATASIZE=128) the allocation succeeded as a chain of two fragments. The subsequent register-to-buffer copy loops write into the first fragment only, so 255 bytes were written into a 128-byte netbuf data block, overrunning it by up to 127 bytes into adjacent network-buffer-pool memory, and reading up to 128 bytes past the end of the radio's PKTBUFFERRX register array.

The length field originates from an unauthenticated frame transmitted by any device within 802.15.4 radio range, and is captured by the hardware at PHY-header time — before frame filtering has completed — while the handler samples it after clearing the interrupt status, so an attacker flooding frames with a length byte of one can drive the underflow. The impact is memory corruption in the shared receive buffer pool from interrupt context, most plausibly a crash or corruption of other packets' data; the overflowing bytes are radio register contents rather than attacker-authored data, which bounds the integrity impact.

The fix rejects any length below KW41ZFCSLENGTH and additionally verifies netbuftailroom() of the first fragment before the copy loops, which also covers the CONFIGNETL2OPENTHREAD variant of the size computation.

First published (updated )
Severity
6.5
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

The Bluetooth Link Layer Control Procedure (LLCP) implementation for Connected Isochronous Stream (CIS) creation retains an RX node (ctx->noderef.rx, marked NODERXTYPERETAIN) so it can later be reused as the host notification — on the peripheral while awaiting the Host's reply to an LLCISREQ, and on the central for the whole duration of a locally initiated CIS Create. In subsys/bluetooth/controller/llsw/ullllcpcc.c, the "invalid PDU received" paths of llcprpccrx() and llcplpccrx() terminated the connection and completed the procedure without releasing that retained node, breaking the invariant checked in llcplrcheckdone() and llcprrcheckdone() and orphaning the node's memory.

A peer device within radio range can reach this with a single extra LL Control PDU on an unauthenticated, unencrypted ACL link. Against a peripheral, the attacker sends a valid LLCISREQ and then, before the Host replies, any unrelated LL Control PDU (for example LLVERSIONIND), which ullcprx() routes into the active remote procedure. Against a central performing a CIS Create, a malicious peripheral answers with LLUNKNOWNRSP for CISREQ, which is dispatched into the active local procedure. No pairing, encryption or user interaction is required; the code is compiled in when CONFIGBTCTLRPERIPHERALISO or CONFIGBTCTLRCENTRALISO is enabled.

In default builds (CONFIGBTCTLRASSERTDEBUG is default y) the retained-node assertion fires immediately, producing a controller fatal error and, typically, a system reset from one injected PDU. With the development assertions disabled, each attempt permanently loses one node from the controller's small LL notification pool (LLPDURXCNT, 2 CONFIGBTCTLRLLCPCONN) together with its memqlinkt; repeating the connect-attack-reconnect cycle exhausts the pool, after which notification allocation always fails, RX flow control stalls, and the non-disableable LLASSERTERR() in llcplpccflush() faults. The impact is limited to availability — the leaked node is orphaned, never reused or double-freed — and recovery requires a reboot.

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203