See how zephyr compares to other vendors in security performance
The HTTP client's debug helper printheaderfield() in subsys/net/lib/http/httpclient.c formatted an httpparser data-callback slice with a %s conversion: snprintk(output, len + 1, "%s", str). The parser hands its callbacks a pointer and a length into the caller's receive buffer, and those bytes are not NUL terminated. The size argument of snprintk() bounds only the destination, while the %s conversion measures the source first — lib/os/cbprintfcomplete.c uses strlen() when no precision is given, and lib/os/cbprintfnano.c uses strlen() unconditionally — so the read walks past the end of the header field and past the end of the receive buffer until it finds a zero byte.
The slice reaches the helper from onurl(), onheaderfield() and onheadervalue(), which the parser invokes on bytes that httpwaitdata() read straight off the socket into the application-supplied recvbuf. A malicious or compromised HTTP server, or an attacker able to answer a plaintext request, therefore controls both the header field and the bytes that follow it in the buffer, and can guarantee that no zero byte appears before the buffer ends, forcing the over-read. The in-tree tests/net/lib/httpclient suite reproduces the over-read under CONFIGASAN=y on an ordinary GET.
The impact is bounded. The whole helper body is compiled only when the HTTP client log module is explicitly set to Debug (CONFIGNETHTTPLOGLEVELDBG=y, which the global CONFIGLOGDEFAULTLEVEL does not set), so default builds are unaffected. No out-of-bounds write occurs and none of the over-read bytes are emitted into the log, so there is no information disclosure; the consequence is an out-of-bounds read that can fault and panic the device where the buffer abuts an unmapped or MPU/MMU-protected region.
The fix copies exactly the len bytes the parser offered with memcpy() and terminates them, instead of relying on a %s conversion (a %.s precision would not have sufficed, since cbprintfnano.c measures the source in full before applying it).
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.
The RTIO submission-queue verifier rtiovrfysqe() in subsys/rtio/rtiosyscalls.c (subsys/rtio/rtiohandlers.c before v4.3.0) accumulated the results of KSYSCALLMEMORY() into its validsqe flag with &=. That macro returns nonzero on failure (it is built on KSYSCALLVERIFYMSG(), which yields !(expr)), so every buffer permission check contributed the opposite of its result. A submission whose tx.buf, rx.buf or txrx.txbuf/txrx.rxbuf pointer was not accessible to the calling thread was accepted, while a legitimate, thread-owned buffer caused the calling thread to be killed with a kernel oops.
The verifier runs on behalf of the rtiosqecopyingethandles() system call (also reached through the inline rtiosqecopyin() wrapper), so it is driven directly by untrusted user-mode input on any image built with CONFIGUSERSPACE. A user thread that has been granted an RTIO context and an rtioiodev object — the intended usage of RTIO userspace support, established via rtioaccessgrant() — can submit an SQE whose buffer pointer and length name arbitrary kernel memory; the object check on sqe->iodev is unaffected and still passes, and the crafted SQE is forwarded to the bus driver unchanged.
Downstream consumers dereference the pointer with supervisor privilege. The generic I2C and I3C RTIO fallback handlers (drivers/i2c/i2crtiodefault.c, drivers/i3c/i3crtiodefault.c) copy sqe.rx.buf/sqe.tx.buf into struct i2cmsg and call i2ctransfer(), and the vendor SPI/I2C RTIO drivers behave equivalently. An RTIOOPRX submission therefore has peripheral data written into kernel memory of the attacker's choosing (arbitrary kernel write), an RTIOOPTX submission clocks kernel memory out over the bus (kernel memory disclosure), and a pointer to unmapped memory faults the kernel. This is a full escape from the user-mode isolation boundary that CONFIGUSERSPACE exists to enforce.
The defect was masked for years because the in-tree user-mode RTIO tests only exercise RTIOOPNOP and mempool-backed reads, neither of which reaches the affected checks. The fix negates each KSYSCALLMEMORY() result and additionally validates the TXRX transmit buffer read-only, matching the SPI, I2C and I3C handlers.
Two RTIO syscall verifiers, in subsys/rtio/rtiosyscalls.c (subsys/rtio/rtiohandlers.c before v4.3.0), did not validate user-supplied pointers before the kernel wrote through them.
zvrfyrtiosqecancel() performed no validation at all. It passed the caller's struct rtiosqe pointer straight to zimplrtiosqecancel(), which converts it with CONTAINEROF to a struct rtioiodevsqe, ORs RTIOSQECANCELED (BIT(3)) into its 16-bit flags field in supervisor mode, then loads its next pointer and repeats until that pointer is NULL. The syscall takes no RTIO context argument, so it needs no object grant: any user-mode thread on a CONFIGUSERSPACE build can set that bit in a 16-bit word at an address of its choosing in kernel memory, and the kernel continues the write along whatever pointer chain it finds at the corresponding offset. Setting chosen bits in kernel data structures, such as object permission or thread state fields, can be used to escalate privilege from user mode.
zvrfyrtiosqecopyingethandles() validated its RTIO context and the input SQE array, but only NULL-checked the handle out-parameter before writing a kernel SQE pointer through it, with no KSYSCALLMEMORYWRITE check. A user thread that holds a granted RTIO context can therefore make the kernel store a pointer value at an arbitrary address.
The fixes validate the handle out-parameter as writable, bound the cancel handle to real submission-pool storage, and require permission on the RTIO context that owns the matching pool. Without that last check a bounded handle still let any user thread cancel submissions belonging to other contexts, silently aborting their I/O. Systems built without CONFIGUSERSPACE or without CONFIGRTIO are unaffected.
The HTTP/2 server in subsys/net/lib/http/httpserverhttp2.c clears the connection's stream binding at the start of every frame. handlehttpframeheader() executed client->currentstream = NULL unconditionally, but a CONTINUATION frame does not open a new stream and enterhttpframecontinuationstate() never re-bound one, so the rest of the header block was processed with client->currentstream unset. Two paths then write through the NULL pointer. A request for an unregistered resource, or a method the matched resource does not support, is answered by sendhttp2404() or sendhttp2405(), and both call sendheadersframe(), which unconditionally sets client->currentstream->headerssent = true. A POST, PUT or PATCH to a registered dynamic resource reaches handlehttp2dynamicresource(), which unlike its siblings (handlehttp2staticresource(), dynamicgetdeloptsreqv2(), dynamicpostputreqv2()) had no client->currentstream == NULL guard, and stores through the pointer with client->currentstream->currentdetail = dynamicdetail.
Any peer that can reach the server can trigger this without authentication or prior state. A single stray CONTINUATION frame with ENDHEADERS set, sent right after the client preface and requesting any unregistered path, is enough, because nothing rejected a CONTINUATION that no HEADERS frame had opened. A legitimate split header block (a HEADERS frame with ENDHEADERS clear whose payload ends on a header-field boundary, then a CONTINUATION with ENDHEADERS set) reaches the same state. The in-tree test testhttp2postheaderswithcontinuation() did not catch this because it splits the header block mid field, which takes the handleincompletehttpheader() shortcut that keeps the stream bound.
The result is a write to a fixed low address (a small offset into struct http2streamctx), which faults on mainstream targets (flash/vector-table region on Cortex-M, or a trap under MPU/MMU null-pointer detection), and the default fatal-error handler halts the system. Impact is an unauthenticated, remotely triggerable denial of service against any device running the HTTP/2 server: the 404 path needs no particular resource to be registered, and HTTP/2 is built into CONFIGHTTPSERVER with cleartext h2c accepted, so TLS does not prevent it. Neither the target address nor the written value is attacker-controlled, so there is no confidentiality or integrity impact. Applications built without CONFIGHTTPSERVER are not affected.
The HTTP/2 server in subsys/net/lib/http/httpserverhttp2.c clears the connection's stream binding at the start of every frame. handlehttpframeheader() executed client->currentstream = NULL unconditionally, but a CONTINUATION frame does not open a new stream and enterhttpframecontinuationstate() never re-bound one, so the rest of the header block was processed with client->currentstream unset. Two paths then write through the NULL pointer. A request for an unregistered resource, or a method the matched resource does not support, is answered by sendhttp2404() or sendhttp2405(), and both call sendheadersframe(), which unconditionally sets client->currentstream->headerssent = true. A POST, PUT or PATCH to a registered dynamic resource reaches handlehttp2dynamicresource(), which unlike its siblings (handlehttp2staticresource(), dynamicgetdeloptsreqv2(), dynamicpostputreqv2()) had no client->currentstream == NULL guard, and stores through the pointer with client->currentstream->currentdetail = dynamicdetail.
Any peer that can reach the server can trigger this without authentication or prior state. A single stray CONTINUATION frame with ENDHEADERS set, sent right after the client preface and requesting any unregistered path, is enough, because nothing rejected a CONTINUATION that no HEADERS frame had opened. A legitimate split header block (a HEADERS frame with ENDHEADERS clear whose payload ends on a header-field boundary, then a CONTINUATION with ENDHEADERS set) reaches the same state. The in-tree test testhttp2postheaderswithcontinuation() did not catch this because it splits the header block mid field, which takes the handleincompletehttpheader() shortcut that keeps the stream bound.
The result is a write to a fixed low address (a small offset into struct http2streamctx), which faults on mainstream targets (flash/vector-table region on Cortex-M, or a trap under MPU/MMU null-pointer detection), and the default fatal-error handler halts the system. Impact is an unauthenticated, remotely triggerable denial of service against any device running the HTTP/2 server: the 404 path needs no particular resource to be registered, and HTTP/2 is built into CONFIGHTTPSERVER with cleartext h2c accepted, so TLS does not prevent it. Neither the target address nor the written value is attacker-controlled, so there is no confidentiality or integrity impact. Applications built without CONFIGHTTPSERVER are not affected.
parsewriteop() in subsys/net/lib/lwm2m/lwm2mmessagehandling.c handles inbound CoAP WRITE/CREATE requests that carry a Block1 option. For the first block of a transfer it called initblockctx() and then immediately stored the peer-selected block size with blockctx->ctx.blocksize = blocksize before inspecting the return code. initblockctx() sets the caller's pointer to NULL and returns -ENOMEM when no entry of the static block1contexts[] pool is free or timed out, so that store dereferences a NULL pointer.
The pool holds CONFIGLWM2MNUMBLOCK1CONTEXT entries (default 3) and an entry is only reclaimed once its transfer completes, fails, or ages past 30 seconds. A peer that reaches the client's LwM2M socket can therefore start three block-wise writes on three distinct object paths with the CoAP More bit set and leave them incomplete, then send the first block of a fourth write on a new path to reach the unguarded dereference. Reachability is gated only by the connected UDP socket's source-address filter unless CONFIGLWM2MDTLSSUPPORT is enabled — which has no default — so in a NoSec deployment an on-path or address-spoofing attacker needs no credentials; the same sequence is also reachable from a bootstrap or lower-trust server, and can be hit accidentally by a legitimate server running four concurrent block transfers.
The write targets a fixed low address with a value between 0 and 7, so the consequence is a fatal memory fault (BusFault or corrupted low memory leading to a fault) rather than a usable memory-corruption primitive: the device crashes or resets. Confidentiality and integrity are not affected. The fix moves the store below the guard and validates the context pointer itself instead of the return code, so the context is only touched once it is known to be valid.
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.
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.
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.
The Zephyr ext2 file system validates the on-disk superblock in ext2verifydisksuperblock() (subsys/fs/ext2/ext2impl.c) before completing a mount. The validator checked the magic number, block size, revision and feature flags, but did not verify that the on-disk fields sblockspergroup and sinodespergroup are non-zero. Both fields are read directly from the image and are later used as divisors during mount-time initialization.
During mount, getngroups() divides and modulos sblockscount by sblockspergroup (reached via ext2fetchblockgroup() from ext2initfs()), and getitableentry() divides (ino - 1) by sinodespergroup when fetching the root inode (both in subsys/fs/ext2/ext2diskops.c). A superblock with either field set to zero therefore causes an integer division by zero during the mount sequence.
An attacker who can present a crafted ext2 image to a device that mounts ext2 — removable media such as an SD card or a USB mass-storage device — can trigger this. On ARMv7-M / ARMv8-M-mainline Cortex-M targets, divide-by-zero trapping is enabled (SCBCCRDIV0TRP), so the division raises a UsageFault that Zephyr treats as a fatal error, producing a denial of service. The impact is limited to availability; the malformed value is consumed only as a divisor.
The fix rejects a zero sblockspergroup or sinodespergroup in the superblock validator, returning -EINVAL so the mount fails before any block-group or inode I/O occurs.
tlsoptdtlspeerconnectionidvalueget() in subsys/net/lib/sockets/socketstls.c, which handles getsockopt(SOLTLS, TLSDTLSPEERCIDVALUE), passed the caller-supplied optval directly to mbedtlssslgetpeercid() without verifying the buffer was at least MBEDTLSSSLCIDOUTLENMAX (default 32) bytes. mbedtlssslgetpeercid() copies the peer-negotiated DTLS Connection ID (length 1..MBEDTLSSSLCIDOUTLENMAX) into that buffer without a destination-size parameter, so a caller-supplied optlen smaller than the CID causes a write of up to 31 bytes past the buffer end.
In CONFIGUSERSPACE builds the getsockopt syscall verifier (zvrfyzsockgetsockopt) bounce-buffers the user's optval into a kernel allocation of exactly optlen bytes (kusermodeallocfromcopy -> zthreadmalloc), so an unprivileged user thread that passes a small optlen on a connected DTLS socket with Connection ID enabled induces a kernel-heap buffer overflow, with the overflowing content being the remote peer's CID.
The defect requires CONFIGMBEDTLSSSLDTLSCONNECTIONID, an established DTLS session with a negotiated peer CID, and (for the kernel-crossing case) CONFIGUSERSPACE. Introduced when the TLSDTLSCID option was added (v3.5.0).
The fix rejects callers whose optlen is below MBEDTLSSSLCIDOUTLENMAX with -EINVAL.
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.
Zephyr's HTTP server (subsys/net/lib/http) provides a static-filesystem resource type (HTTPRESOURCETYPESTATICFS, available when CONFIGFILESYSTEM is enabled) that serves files from a configured root directory. Before this fix, both the HTTP/1 and HTTP/2 front-ends placed the raw, attacker-controlled request path into client->urlbuffer (assembled in onurl() for HTTP/1 and copied verbatim from the :path pseudo-header for HTTP/2) without resolving ./.. segments. The static-FS handler then built the on-disk filename by directly concatenating the configured root with that raw URL (snprintk(fname, ..., "%s%s", staticfsdetail->fspath, client->urlbuffer) at httpserverhttp1.c:603 and httpserverhttp2.c:490) and opened it with fsopen(fname, FSOREAD). Because the handler is reached via wildcard/leading-dir (fnmatch FNMLEADINGDIR) or fallback resource matching, a request such as GET /<prefix>/../../<file> is dispatched to the handler and, after the underlying filesystem (e.g. LittleFS/FAT) resolves the .. segments, escapes the configured web root, letting an unauthenticated remote client read arbitrary readable files on the mounted volume (information disclosure). The HTTP server requires no TLS or authentication to reach this path. The fix adds httpserverremovedotsegments(), which canonicalizes the path portion of the URL before resource lookup in both protocol handlers, neutralizing the traversal. Affects releases v4.0.0 through v4.4.0 for deployments that register a static-filesystem resource.
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.
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.
The UpdateHub firmware-over-the-air client parses the CoAP probe response with Zephyr's JSON library in zimplupdatehubprobe() (subsys/mgmt/updatehub/updatehub.c). The decoder treats every descriptor field as optional — jsonobjparse() returns a bitmask of the fields it actually decoded and never fails on a missing one — so a response that omits the "sha256sum" string leaves the corresponding const char in the zero-initialised struct respprobeanyboards / struct respprobesomeboards at NULL. Both metadata branches then computed sha256size = strlen(...objects.sha256sum) + 1 before any validation, dereferencing that NULL pointer; the SHA256HEXDIGESTSIZE test that follows operates on a value the bad read has already produced and cannot prevent it.
The metadata is supplied entirely by the network peer: probecb() accepts the datagram, runs coappacketparse() and copies the payload with no token matching and no cryptographic check. With CONFIGUPDATEHUBDTLS unset — the default — startcoapclient() uses plain CoAP over UDP on port 5683 with no peer authentication and resolves the server via unauthenticated DNS, so an on-path attacker, a DNS spoofer, or an off-path attacker who wins the race with a forged source address can deliver the malformed metadata. A probe response as simple as "objects":[[{}],[{}]] suffices, and no user interaction is required because updatehubautohandler() polls on CONFIGUPDATEHUBPOLLINTERVAL. When CONFIGUPDATEHUBDTLS=y the trigger narrows to a malicious or compromised update server.
The primitive is a read at address zero, not a write, so the impact is availability only. On targets where the low address range is unmapped or protected — MMU-based architectures, builds with CONFIGNULLPOINTEREXCEPTIONDETECTIONDWT/MPU/PMP, TrustZone non-secure images, and SoCs such as nRF54L whose 0x0–0x400 window is unmapped — the load faults and Zephyr's default fatal handler halts the system, giving a remotely re-triggerable denial of service on every poll. On classic Cortex-M targets where the vector table is readable at address zero the walk usually terminates immediately and the request is rejected by the length check, so the outcome is configuration- and SoC-dependent. There is no memory write, and the copied bytes are only compared against the locally computed image hash, so there is no information-disclosure channel.
The fix adds an explicit sha256sum == NULL test that returns UPDATEHUBMETADATAERROR ahead of the strlen() in both metadata branches. Debug builds (CONFIGUPDATEHUBLOGLEVEL=4) still format the same pointers with %s in a LOGDBG() loop that runs before the new checks, so that variant remains in debug configurations only.
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.
The SF32LB MPI QSPI NOR flash driver (drivers/flash/flashsf32lbmpiqspinor.c) validated the flash offset and length on its read and write paths with the test (offset + size) > data->size. Because offset is a signed offt while size is unsigned, a negative offset is converted to a large unsigned value and the addition can wrap to a small result that passes the check. The read path then performs memcpy(dst, (void )(data->base + offset), size) and the write path programs flash at offset and cache-invalidates data->base + offset, in both cases accessing memory outside the mapped flash window. The driver's erase path already rejected negative offsets, but read and write did not.
In builds with CONFIGUSERSPACE, flashread and flashwrite are syscalls whose verifiers validate the device object and the caller's buffer but deliberately delegate offset bounds checking to the driver. An unprivileged thread that has been granted access to this flash device can therefore call the syscall with a crafted negative offset and a buffer valid in its own memory domain, and reach the unchecked access.
The most direct impact is on the read path: by choosing a negative offset and matching size, an attacker slides the memcpy source below the flash base and copies arbitrary CPU-addressable memory into its own buffer, disclosing memory it is not authorized to read. The write path additionally allows programming flash at an out-of-range address and invalidating an attacker-chosen cache range, affecting integrity and availability. Reachability requires userspace to be enabled and the raw flash device object to be granted to an untrusted thread.
The fix replaces the check with qspinorrangeisvalid(), which rejects negative offsets and performs the bound comparison in overflow-safe 64-bit arithmetic on both paths, and additionally adds an SRAM DMA bounce buffer plus source/destination overlap rejection to prevent a separate DMA bus-hang condition.
The UpdateHub over-the-air update client's startcoapclient() in subsys/mgmt/updatehub/updatehub.c leaks the CoAP/DTLS socket descriptor on its connection-setup failure paths. The shared error: cleanup gated socket closing on a ret > 0 flag, but ret was set to -1 immediately after the socket was created, so when zsocksetsockopt() (DTLS) or zsockconnect() subsequently failed the gate was false and cleanupconnection() was never called. The open descriptor in the global ctx.sock was then overwritten by the next attempt, permanently leaking it from the socket / netcontext pool until reboot.
The failing setup path is reached every time the OTA client tries to contact the UpdateHub server and the connection cannot be established — driven automatically by the periodic autohandler() poll (and on demand via the updatehubprobe()/updatehubupdate() API or the updatehub run shell command). The DTLS handshake/connect outcome is influenceable by a network or on-path attacker who drops, resets, or otherwise disrupts traffic to the server, and also fails naturally whenever the server is unreachable.
Each failed attempt permanently leaks one descriptor; once the shared socket pool is exhausted, networking degrades device-wide until the device is rebooted, a denial-of-service condition. Severity is low because the leak rate is bounded by the configured OTA poll interval (default once per 24 hours), the effect is gradual and recovered by reboot, and only builds with the UpdateHub client enabled are affected. There is no memory-corruption, information-disclosure, or authentication impact.
The CONFIGUSERSPACE syscall verifier zvrfykpoll() in kernel/poll.c allocates a kernel-side copy of the user-supplied kpollevent[] via zthreadmalloc() and then validates each event's object handle. Before this fix, validation used KOOPS(KSYSCALLOBJ(...)) inline inside the loop, which kills the calling thread without freeing eventscopy.
A user thread can pass numevents >= 1 with a forged object handle to leak the allocation; because newly spawned user threads inherit the parent's resourcepool (kernel/thread.c), an attacker spawns sacrificial threads to repeat the leak until the shared kernel heap is exhausted. Once depleted, legitimate kernel allocations from that pool (kqueue alloc nodes, kmsgq buffers, future kpoll calls, etc.) fail, causing a system-level denial of service.
The fix replaces each inline KOOPS with a conditional goto oopsfree so the buffer is freed before the thread is killed. Affects Zephyr releases from v1.12.0 (when kpoll was first exposed to user mode) through v4.4.1.
The CONFIGUSERSPACE syscall verifier zvrfykpoll() in kernel/poll.c allocates a kernel-side copy of the user-supplied kpollevent[] via zthreadmalloc() and then validates each event's object handle. Before this fix, validation used KOOPS(KSYSCALLOBJ(...)) inline inside the loop, which kills the calling thread without freeing eventscopy.
A user thread can pass numevents >= 1 with a forged object handle to leak the allocation; because newly spawned user threads inherit the parent's resourcepool (kernel/thread.c), an attacker spawns sacrificial threads to repeat the leak until the shared kernel heap is exhausted. Once depleted, legitimate kernel allocations from that pool (kqueue alloc nodes, kmsgq buffers, future kpoll calls, etc.) fail, causing a system-level denial of service.
The fix replaces each inline KOOPS with a conditional goto oopsfree so the buffer is freed before the thread is killed. Affects Zephyr releases from v1.12.0 (when kpoll was first exposed to user mode) through v4.4.1.
Zephyr's DNS resolver (subsys/net/lib/dns) parses resource records from DNS responses in dnsunpackanswer(), which validated only the fixed RR header (type, class, TTL, rdlength) and accepted any attacker-declared rdlength, including one extending past the end of the received datagram. The TXT and SRV consumers in dnsvalidaterecord() (resolve.c) then read up to rdlength bytes (clamped only to a record-type maximum such as DNSMAXTEXTSIZE, default 64, not to the packet) from the receive buffer via memcpy without their own bounds check, and pass the result to the application's resolve callback. A malicious or spoofed DNS server, an on-path attacker forging UDP DNS replies, or (with mDNS/LLMNR enabled) any LAN node can craft a truncated TXT or SRV response that causes an out-of-bounds read of adjacent receive-pool memory; the disclosed stale bytes (residual contents of prior DNS packets / uninitialized pool memory) are returned to the application as TXT/SRV record contents, an information leak, and may in some configurations cross the allocation boundary and fault, causing a denial of service. The read is bounded (~64 bytes for TXT, ~6 for SRV) and read-only (no write). The fix rejects any record whose declared rdata extends past dnsmsg->msgsize at the single chokepoint in dnsunpackanswer(). Affected: v4.3.0 and v4.4.0.
Zephyr's DNS resolver detects mDNS (.local) queries in dnsresolvenameinternal() (subsys/net/lib/dns/resolve.c) with memcmp(strrchr(query, '.'), ".local", 7), which always reads a fixed 7 bytes from the suffix pointer. When the resolved hostname's final label is shorter than 7 bytes (e.g. names ending in .org, .com, .net, .io, or a trailing dot), the comparison reads 1-2 bytes past the string's NUL terminator.
The hostname (query) is the caller-supplied name passed through the standard getaddrinfo()/dnsgetaddrinfo()/dnsresolvename() path and is influenceable by operators or remote inputs (server names from configuration, parsed URLs, or app-facing interfaces).
On a tightly-sized buffer with no slack (for example a userspace getaddrinfo call where the hostname is copied with kusermodestringalloccopy to exactly strlen+1 bytes), the over-read crosses the allocation boundary; if that boundary is unmapped (guard page, memory-domain boundary under MPU, or an address sanitizer) the over-read faults, causing a denial of service. The over-read bytes are never returned, so there is no information disclosure.
The flaw is compiled only when CONFIGMDNSRESOLVER is enabled, exists since v1.10.0, and is fixed by replacing the fixed-length memcmp with a NUL-safe strcmp(ptr, ".local").
Zephyr's DNS resolver detects mDNS (.local) queries in dnsresolvenameinternal() (subsys/net/lib/dns/resolve.c) with memcmp(strrchr(query, '.'), ".local", 7), which always reads a fixed 7 bytes from the suffix pointer. When the resolved hostname's final label is shorter than 7 bytes (e.g. names ending in .org, .com, .net, .io, or a trailing dot), the comparison reads 1-2 bytes past the string's NUL terminator.
The hostname (query) is the caller-supplied name passed through the standard getaddrinfo()/dnsgetaddrinfo()/dnsresolvename() path and is influenceable by operators or remote inputs (server names from configuration, parsed URLs, or app-facing interfaces).
On a tightly-sized buffer with no slack (for example a userspace getaddrinfo call where the hostname is copied with kusermodestringalloccopy to exactly strlen+1 bytes), the over-read crosses the allocation boundary; if that boundary is unmapped (guard page, memory-domain boundary under MPU, or an address sanitizer) the over-read faults, causing a denial of service. The over-read bytes are never returned, so there is no information disclosure.
The flaw is compiled only when CONFIGMDNSRESOLVER is enabled, exists since v1.10.0, and is fixed by replacing the fixed-length memcmp with a NUL-safe strcmp(ptr, ".local").
Zephyr's dynamic kernel-object tracking (kernel/userspace/userspace.c, formerly kernel/userspace.c) maintains a doubly-linked list (objlist) of dynamically allocated kernel objects. Iteration over this list in kobjectwordlistforeach() was performed under listslock using the SAFE iterator (which caches the next node), but list removal and freeing of nodes was performed under different, disjoint spinlocks: objfreelock in kobjectfree() and objlock in unrefcheck(). On an SMP system, while one CPU iterated objlist under listslock, another CPU could unlink and kfree() the dynobj node that the iterator had cached as its next pointer, causing the iterator to dereference freed kernel memory (use-after-free / dangling list traversal). All of the racing operations are reachable from unprivileged user-mode threads via system calls: kobjectalloc/kobjectallocsize and kobjectrelease drive removals through unrefcheck() (under objlock), while kthreadabort and thread creation drive the iteration through kthreadpermsallclear()/kthreadpermsinherit() (under listslock). A deprivileged user thread on a CONFIGSMP + CONFIGUSERSPACE build can therefore corrupt the kernel's object-tracking structures across the userspace security boundary, yielding kernel memory corruption (potential privilege escalation) or a kernel crash (denial of service). The fix removes objfreelock and serializes every objlist modification under listslock, including holding it across find+remove in kobjectfree() and around unrefcheck() in kthreadpermsclear(). Affects CONFIGSMP+CONFIGUSERSPACE+CONFIGDYNAMICOBJECTS configurations; the defect dates to the 2019 spinlockification (commit 8a3d57b6cc6, first released in v1.14.0) and shipped through v4.4.0.
The canaddrxfiltermsgq() system call installs a CAN RX filter whose callback (canmsgqput() in drivers/can/cancommon.c) is handed the caller's struct kmsgq as userdata. The controller driver stores that raw pointer in its filter table and dereferences it from the RX path every time a matching frame arrives. The system call verifier zvrfycanaddrxfiltermsgq() in drivers/can/canhandlers.c validated the queue only with KSYSCALLOBJ(), which is a point-in-time check of type, initialization and caller permission and says nothing about the object's lifetime.
On a build with CONFIGUSERSPACE=y and CONFIGDYNAMICOBJECTS=y, an unprivileged user-mode thread that has been granted access to a CAN device can obtain a message queue whose lifetime it fully controls: kobjectalloc(KOBJMSGQ) followed by kmsgqallocinit(). The lifetime of such an object is driven solely by its permission bitmask, and a pointer retained by the kernel is not represented there. After registering the filter, the thread calls kobjectrelease() — or simply terminates — and unrefcheck() frees the struct kmsgq storage while the CAN driver still holds the pointer.
The next matching CAN frame causes kmsgqput() to run in supervisor context against freed memory that the attacker can have groomed with another kernel allocation: the frame payload is memcpy()d through the reused block's writeptr, and the queue's waitq (and pollevents under CONFIGPOLL) list nodes are manipulated. This yields kernel-heap corruption reachable from a user-mode thread, defeating the isolation CONFIGUSERSPACE is intended to provide; the same dangling pointer also arises benignly when an application thread exits without calling canremoverxfilter().
The fix validates the queue with KSYSCALLOBJSTATIC(), which additionally rejects objects carrying KOBJFLAGALLOC, so only statically defined queues (CANMSGQDEFINE()) may be registered from user mode. Supervisor-mode callers bypass the verifier and must themselves keep the queue valid until the filter is removed.
The hawkBit device management client in subsys/mgmt/hawkbit accumulates the body of an HTTP response from the update server into a heap buffer in responsejsoncb() (subsys/mgmt/hawkbit/hawkbit.c). The buffer is sized to hold the received body bytes but reserves no space for a terminating NUL. When the full response has arrived, the code writes responsedata[downloadedsize] = '\0' — and whenever the accumulated body length equals the allocation, that terminator lands one byte past the end of the heap object (a heap-based out-of-bounds write, CWE-122 / CWE-787).
The body length and fragmentation are taken directly from the parsed HTTP response (rsp->bodyfragstart / rsp->bodyfraglen) and are fully controlled by the remote hawkBit server, which chooses its own response length. The precise trigger depends on how the buffer grows, and both forms are remotely reachable. Since v4.0.0 the reallocation is sized to exactly downloadedsize + bodylen, so any response body larger than the 1100-byte initial buffer makes the out-of-bounds write deterministic; such response sizes are normal for hawkBit deployment metadata. Before v4.0.0 the buffer grew by doubling and the growth check ((downloadedsize + bodylen) > responsebuffersize) is false at equality, so a response body whose length is exactly the current allocation — 1100 bytes with the default initial buffer — skips the reallocation entirely and writes the terminator at responsedata[1100] of an 1100-byte object. The HTTP length-mismatch check does not catch this, because the declared and received lengths genuinely agree. Either form is reachable by a malicious, compromised, or man-in-the-middle update server (TLS is optional and, when enabled, does not protect against a hostile server), with no authentication of response content and no client-side length cap protecting the write.
The out-of-bounds write is a fixed single NUL byte immediately following the allocation, corrupting adjacent allocator metadata or the next allocation. The practical impact is heap corruption leading to denial of service (fault on a subsequent allocation or free), with the bounded, allocator-dependent possibility of further corruption. The fix sizes the buffer to the body length plus one and copies with memcpy, ensuring the terminator always lands within the allocation.
In Zephyr's experimental USB host stack (CONFIGUSBHOSTSTACK), usbhdevicedisconnect() (subsys/usb/host/usbhdevice.c) freed the root usbdevice slab object without clearing the cached pointer ctx->root. The bus removal handler devremovedhandler() (subsys/usb/host/usbhcore.c) decides what to tear down solely from ctx->root, checking only that it is non-NULL.
Because UHC controller drivers (e.g. uhcmax3421e, uhcmcuxcommon) synthesize UHCEVTDEVREMOVED directly from physical bus line state with no debounce or state guard, an attacker with physical USB access (or a rogue device that bounces its connection) can deliver a second device-removed event after a root device disconnect. The handler then re-enters usbhdevicedisconnect() with the dangling pointer, locking a mutex inside the freed object (use-after-free), removing the freed node from the device list, and calling kmemslabfree() on the already-freed block (double-free). If the slab block has been reissued to a newly attached device in between, this corrupts a live object.
Impact is denial of service (crash) and memory corruption; the attack vector is physical/local. The flaw was introduced in v4.4.0 by the connect/disconnect refactor and is fixed by clearing ctx->root in usbhdevicedisconnect() before freeing.
The userspace verifier zvrfylogfilterset() for the logfilterset syscall in subsys/logging/logmgmt.c performed a signed comparison against the int16t srcid parameter: srcid < (int16t)logsrccntget(domainid). Any negative value for srcid (e.g. -1) trivially satisfied this check and was forwarded into zimpllogfilterset, where it propagated to filterset() and ultimately to getdynamicfilter(), which uses sourceid as an unsigned index into the linker-section array &TYPESECTIONSTART(logdynamic)[sourceid].filters.
After implicit conversion through uint32t, an int16t -1 becomes 0xFFFFFFFF, indexing logdynamic far out of bounds and causing the kernel to perform an OOB read and an OOB read-modify-write (LOGFILTERSLOTGET/SET) against memory adjacent to the logdynamic section.
The written value is a constrained 3-bit log level slot within the targeted 32-bit word, but the target address is attacker-chosen (a small negative offset from logdynamic) and the write occurs in supervisor mode following a syscall from an unprivileged user thread, providing a kernel memory-corruption / privilege-escalation primitive.
The defect is reachable on any build with CONFIGUSERSPACE=y and CONFIGLOGRUNTIMEFILTERING=y. Present from Zephyr v3.3.0 through v4.4.1. The fix replaces the signed bound check with an unsigned comparison: (uint32t)srcid < logsrccntget(domainid), which correctly rejects negative inputs.