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 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.
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.
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.
Zephyr's IP socket recvmsg() implementation (subsys/net/lib/sockets/socketsinet.c, insertpktinfo()) validated the user-supplied ancillary (msgcontrol) buffer using only the payload length (msg->msgcontrollen < pktinfolen) before writing a full control message consisting of an aligned cmsg header plus the payload. Because the check omitted the cmsg header size, a control buffer whose length falls in the under-checked window (e.g. 16-27 bytes for IPv4 IPPKTINFO on a 64-bit target, where a single element actually occupies 28 bytes) passes the guard yet causes a fixed-size out-of-bounds write of up to one cmsg header (~12 bytes) past the end of the buffer.
Under CONFIGUSERSPACE the recvmsg verifier allocates a kernel-heap copy of the control buffer sized to msgcontrollen and runs the implementation against it, so the overflow corrupts kernel heap memory and is triggerable from an unprivileged userspace thread; in supervisor mode it corrupts the caller's buffer.
The path is reachable on a UDP/IP socket with IPPKTINFO/IPV6RECVPKTINFO (or hoplimit/timestamping) enabled when the application calls recvmsg() with an undersized control buffer and a datagram is received; part of the overwritten bytes (the destination IP in ipiaddr) is influenced by the received packet.
The fix makes the capacity check use NETCMSGSPACE(pktinfolen) (aligned header + aligned data) and returns -ENOMEM when the buffer is too small. Affected: v3.6.0 through v4.4.0.
The NXP GAU ADC driver (drivers/adc/adcmcuxgauadc.c) validated the caller-supplied sequence->buffersize, which is expressed in bytes, against the number of active channels, which is a sample count. It then stored that byte count directly in data->resultslength and used it in mcuxgauadcreadsamples() as the number of uint16t slots available. Because each conversion result occupies sizeof(uint16t) bytes, a buffer that was accepted as "large enough" could be written with up to twice its size in bytes, so every sample past the buffer's midpoint was written out of bounds.
adcread() and adcreadasync() are Zephyr system calls. The syscall verifier in drivers/adc/adchandlers.c only confirms that the caller owns buffersize writable bytes (KSYSCALLMEMORYWRITE); deciding whether that size is sufficient for the requested channels and extrasamplings is delegated entirely to the driver. On a build with CONFIGUSERSPACE=y, a user-mode thread that has been granted the ADC device object could therefore submit a deliberately half-sized buffer and cause the driver's work-queue handler — which runs in supervisor mode, outside the caller's MPU restrictions — to write ADC conversion results past the end of that buffer, at an address and for a length of the caller's choosing.
The overrun is bounded by the requested sequence: with sequence->options->extrasamplings set, the sampling loop walks the buffer pointer forward across every sampling, so the total overrun can reach the full size of the supplied buffer (kilobytes for a large extrasamplings). The written words are 16-bit ADC conversion results, so the content is only partially attacker-influenced (via the selected analog input, gain and resolution), but the destination and length are fully controlled — sufficient for kernel memory corruption, a crash, or a userspace-to-kernel privilege escalation. Builds without CONFIGUSERSPACE, or on SoCs other than NXP RW61x with the GAU ADC node enabled, are not exposed to the privilege boundary; there the same defect only causes a silent overflow when the application itself passes an undersized buffer.
The fix replaces the ad-hoc check with the shared adcsequencevalidatebuffer() helper (validating against numchannels sizeof(uint16t)), stores buffersize / sizeof(uint16t) in resultslength, and corrects the loop bound to a post-decrement so exactly the available number of slots may be written.
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.
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 MCTP-over-I2C+GPIO target binding in Zephyr (subsys/pmci/mctp/mctpi2cgpiotarget.c) processes pseudo-register writes from an I2C bus master byte-by-byte in mctpi2cgpiotargetwritereceived() without validating the order or the receive buffer. In the affected versions the MCTPI2CGPIORXMSGADDR (data) handler dereferences and writes through b->rxpkt without checking that the receive buffer was allocated: a controller that selects the data register and writes a byte without first sending the length register (which is what allocates the buffer) causes a write of an attacker-chosen byte through a NULL/unallocated mctppktbuf pointer (i.e. into a small attacker-advanceable offset above address 0), producing memory corruption or a hard fault.
The same handler also performs a write-then-check bounds test, allowing a one-byte heap overflow at data[255] when more than 255 data bytes are sent.
Because the I2C target callback is invoked with raw bytes supplied by whatever device is the bus master and the binding performs no authentication, a malicious or malfunctioning controller on the bus can trigger these without any prior protocol state, leading to memory corruption and/or denial of service on the target device.
The vulnerable code was introduced when the I2C+GPIO target binding was added and shipped in Zephyr v4.3.0 and v4.4.0. The fix defers allocation to the first data byte with a NULL check, treats a missing length as a zero-sized packet rejected by libmctp, and moves the bounds check before the store.
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.
The Time-aware GPIO syscall verification handler zvrfytgpiopinreadtsec() in drivers/timeawaregpio/timeawaregpiohandlers.c validated only the port device object and passed the caller-supplied timestamp and eventcount output pointers to the driver without a KSYSCALLMEMORYWRITE() check. The other handlers in the same file (zvrfytgpioportgettime(), zvrfytgpioportgetcyclespersecond()) already performed that check, so the omission left one syscall unguarded.
tgpiopinreadtsec() is declared syscall, so with CONFIGUSERSPACE=y an unprivileged user-mode thread that has been granted access to the TGPIO device object can invoke it with arbitrary pointer values. tgpiointelreadtsec() in drivers/timeawaregpio/timeawaregpiointel.c bounds-checks only the pin index and then unconditionally performs timestamp = ... and eventcount = ..., executing two 8-byte stores in supervisor mode at addresses chosen by the user-mode caller.
The result is a write-what-where primitive that crosses the userspace/kernel boundary: the target address is fully attacker-chosen and the stored values are the hardware time-capture and event-counter register contents. Corrupting kernel data structures this way can escalate the calling thread to supervisor privilege or crash the system; the device-object permission required is a narrow capability that is not intended to confer any kernel-memory access. The fix adds the two missing KSYSCALLMEMORYWRITE() validations before the driver call.
Exposure is narrow in practice. Only builds with CONFIGUSERSPACE=y and CONFIGTIMEAWAREGPIO=y compile the affected file, and from v3.6.0 onward the file additionally referenced a relocated header (<zephyr/syscallhandler.h>) and removed ZSYSCALL macros, so such a configuration failed to build until those were repaired after v4.4.0. Downstream trees that locally corrected that breakage, and v3.5.0 builds where it did not exist, are the exposed population.
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.
Zephyr's OpenThread integration defined CONFIGOPENTHREADJOINERPSKD with default "J01NME" in modules/openthread/Kconfig.thread (previously subsys/net/l2/openthread/Kconfig, then subsys/net/l2/openthread/Kconfig.thread). J01NME is the example Joining Device Credential used throughout the public OpenThread and Thread commissioning documentation, including Zephyr's own samples/net/sockets/echoclient/README.rst, so it is not a secret. A firmware image built with CONFIGOPENTHREADJOINERAUTOSTART=y that never overrode the PSKd therefore shipped with a publicly known joining credential compiled in, with no build-time or run-time warning.
The PSKd is the only value that authenticates Thread commissioning: joiner and commissioner run a DTLS-PSK handshake keyed on it, after which the commissioner supplies the full operational dataset. In otconfigureinstance() (modules/openthread/openthread.c), a device with no stored dataset calls otJoinerStart() with the configured PSKd, and otjoinerstarthandler() calls otThreadSetEnabled() on success. An unauthenticated attacker within 802.15.4 radio range of an uncommissioned device (factory-fresh, after a factory reset, or after a settings erase) can run any stock Thread commissioner registered with J01NME, complete the handshake, and hand the device an attacker-controlled operational dataset.
The device then joins and enables Thread on the attacker's mesh. The attacker becomes its router, seeing, dropping, injecting and spoofing all of the device's IPv6 traffic and impersonating the peers the application expects, while the device is cut off from the network it was meant to join and stays mis-provisioned until it is reset. Only application-layer security with independently provisioned credentials survives. Exposure is limited to builds that enable joiner autostart and leave the PSKd unset; the option is not enabled by default and no in-tree sample uses the vulnerable combination.
The fix removes the default from CONFIGOPENTHREADJOINERPSKD and adds a BUILDASSERT() in modules/openthread/openthread.c, active only under CONFIGOPENTHREADJOINERAUTOSTART, that fails the build unless a 6-to-32-character PSKd is configured. Products should provision a per-device PSKd from factory data and call otJoinerStart() from the application rather than relying on a credential compiled identically into every unit.
Zephyr's IPv6 Neighbor Discovery send paths (netipv6sendna, netipv6sendns, netipv6sendrs in subsys/net/ip/ipv6nbr.c) updated the per-interface ICMP-sent statistics by calling netpktiface(pkt) after netsenddata(pkt) had already returned successfully. On the success path the network stack owns and releases the packet's reference (the L2/driver send unrefs it, e.g. ethernetsend -> netpktunref), so for a freshly allocated packet with refcount 1 the netpkt slab block can be freed before the statistics line runs (synchronously when no TX queue thread is configured, or via a concurrent TX thread otherwise).
The subsequent netpktiface(pkt) reads pkt->iface from the freed slab block, and with CONFIGNETSTATISTICSPERINTERFACE enabled that loaded pointer is dereferenced to increment iface->stats.icmp.sent, a use-after-free (CWE-416). If the slab block was reallocated in the meantime the read/increment targets unrelated or attacker-influenced memory, yielding corrupted statistics, a fault/crash (denial of service), or potential limited memory corruption.
The vulnerable Neighbor Advertisement path is reachable by any unauthenticated on-link node simply by sending ICMPv6 Neighbor Solicitations to a Zephyr node with native IPv6 enabled (handlensinput -> netipv6sendna).
Affected from v3.3.0 through v4.4.0; the fix uses the already-available iface argument instead of touching the sent packet. Configurations without per-interface statistics dereference only a global counter and are not affected by the memory-safety aspect.
ethernetrecv() in subsys/net/l2/ethernet/ethernet.c validates only that the first netbuf of a received frame holds a plain 14-byte struct netethhdr. When the ethertype is 0x8100 and VLAN is enabled on the interface, the same buffer is reinterpreted as the 18-byte struct netethvlanhdr without re-checking the length: the VLAN TCI and the inner ethertype are read from offsets 14..17, past pkt->buffer->len, and hdrlen is raised to 18 so the subsequent netbufpull() removes more bytes than the buffer holds.
A single unauthenticated frame of 14 to 17 bytes carrying the VLAN ethertype triggers this. No knowledge of the node's VLAN configuration is needed: netethgetvlaniface() returns the base interface for the priority tag 0, and a broadcast destination address satisfies the "frame is for me" test, so a 16-byte broadcast frame with ethertype 0x8100 and TCI 0x0000 reaches the pull. Nothing in the receive path enforces the 60-byte Ethernet minimum — the USB CDC ECM and CDC NCM gadget classes in particular hand host-supplied frames of any non-zero length straight to netrecvdata().
With assertions enabled, netbufsimplepull() asserts and the node panics. In a production build (assertions and CONFIGNETBUFHARDENING off) the uint16t buffer length underflows to 65534 and buf->data is advanced past the end of the fragment's storage, so every later parser reads far outside the buffer and any netbufadd() on that fragment computes tailroom from the underflowed length. The result is a reliable remote denial of service and out-of-bounds access with information-disclosure and corruption potential.
Only configurations built with CONFIGNETVLAN=y that have a VLAN enabled on the receiving Ethernet interface, and whose MAC does not strip VLAN tags in hardware (ETHERNETHWVLANTAGSTRIP), are affected. The fix rejects the frame before the tag is read, so hdrlen is thereafter either 14 or 18 with both sizes validated against pkt->buffer->len.
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.
ethernetrecv() in subsys/net/l2/ethernet/ethernet.c validates only that the first netbuf of a received frame holds a plain 14-byte struct netethhdr. When the ethertype is 0x8100 and VLAN is enabled on the interface, the same buffer is reinterpreted as the 18-byte struct netethvlanhdr without re-checking the length: the VLAN TCI and the inner ethertype are read from offsets 14..17, past pkt->buffer->len, and hdrlen is raised to 18 so the subsequent netbufpull() removes more bytes than the buffer holds.
A single unauthenticated frame of 14 to 17 bytes carrying the VLAN ethertype triggers this. No knowledge of the node's VLAN configuration is needed: netethgetvlaniface() returns the base interface for the priority tag 0, and a broadcast destination address satisfies the "frame is for me" test, so a 16-byte broadcast frame with ethertype 0x8100 and TCI 0x0000 reaches the pull. Nothing in the receive path enforces the 60-byte Ethernet minimum — the USB CDC ECM and CDC NCM gadget classes in particular hand host-supplied frames of any non-zero length straight to netrecvdata().
With assertions enabled, netbufsimplepull() asserts and the node panics. In a production build (assertions and CONFIGNETBUFHARDENING off) the uint16t buffer length underflows to 65534 and buf->data is advanced past the end of the fragment's storage, so every later parser reads far outside the buffer and any netbufadd() on that fragment computes tailroom from the underflowed length. The result is a reliable remote denial of service and out-of-bounds access with information-disclosure and corruption potential.
Only configurations built with CONFIGNETVLAN=y that have a VLAN enabled on the receiving Ethernet interface, and whose MAC does not strip VLAN tags in hardware (ETHERNETHWVLANTAGSTRIP), are affected. The fix rejects the frame before the tag is read, so hdrlen is thereafter either 14 or 18 with both sizes validated against pkt->buffer->len.
btsdpparseattribute() in subsys/bluetooth/host/classic/sdp.c validated only that the SDP record buffer held the type-marker byte plus the 2-byte attribute ID (a check of buf->len < 3) but then read a fourth byte, the data-element descriptor (type), via netbufsimplepullu8(). Because netbufsimplepullu8() dereferences buf->data[0] before its only bounds guard (an ASSERTNOMSG that compiles out when CONFIGASSERT is disabled, the production default), a record of exactly three bytes (0x09 followed by a 2-byte attribute ID) causes a one-byte read past the end of the logical buffer. The parser is reachable from inbound, remote-controlled data: a Bluetooth BR/EDR peer acting as an SDP server returns discovery-response records that are stored verbatim in the client receive buffer and parsed via the public btsdpgetattr()/btsdphasattr()/btsdprecordparse() helpers. The over-read is bounded to a single byte that is used only as an internal length selector and is never leaked to the attacker; subsequent length checks then reject the malformed record. Realistic impact is therefore limited to an edge-case denial of service (a fault only if the record ends exactly at a mapped-memory boundary, or a deterministic assert panic when CONFIGASSERT=y). Affects Zephyr v4.3.0 and v4.4.0; fixed by adding sizeof(type) to the length check.
Malformed ATAES132A responses with an oversized length field overflow a 52-byte stack buffer in the Zephyr crypto driver, allowing a compromised device or bus attacker to corrupt kernel memory and potentially hijack execution.
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.
The ITE IT8xxx2 SHI host-command backend (subsys/mgmt/echostcmd/backends/echostcmdbackendshiite.c) copied the 8-byte host-command request header from the SPI Rx FIFO directly into the shared receive buffer data->inmsg and only afterwards checked the protocol version and the derived packet length. The interrupt handler also accepted a chip-select assertion and an Rx-valid-length (RVLI) interrupt in any driver state other than SHISTATEDISABLED, so a new header could be parsed while the host-command thread was still processing the previous request out of the very same buffer.
The host processor is the SPI controller and drives both chip select and the clock. After sending a well-formed request it can immediately de-assert chip select — which returns the driver to the ready state and re-enables the FIFO — and start a second transaction carrying a header with datalen = 0xFFFF. Those eight bytes are written into inmsg before the oversized length is rejected, so they land in a buffer whose contents verifyrx() in subsys/mgmt/echostcmd/echostcmdhandler.c has already validated. If this lands in the window before the host-command thread executes args.inputbufsize = rxheader->datalen, the framework hands the registered command handler a 65535-byte input length over a 256-byte buffer.
The result is an out-of-bounds read of up to roughly 64 KiB beyond the request buffer: command handlers that copy or echo inputbufsize bytes disclose adjacent embedded-controller memory back to the host or overflow the response buffer, and a read past the end of SRAM faults the controller. The same race also allows cmdid and cmdver to be swapped after checksum verification and after handler lookup. Exploitation requires the ability to drive the inter-processor SHI bus (a compromised host OS or physical access to the SPI lines) and winning a timing race, which the SPI controller can retry indefinitely.
The fix parses the header into a local struct echostcmdrequestheader and copies it into inmsg only after the length has been bounded by sizeof(data->inmsg), and ignores chip-select and RVLI interrupts outside SHISTATEREADYTORECV/SHISTATERECEIVING. A residual, bounded race remains: an end-of-transaction interrupt still resets the state to ready while the host-command thread owns the buffer, so a valid second request can still overwrite the in-flight request's contents, unlike the NPCX backend which parks in SHISTATECNLRESPNOTRDY while the buffer is in use.
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.
The Zephyr Bluetooth controller ISO Adaptation Layer (subsys/bluetooth/controller/llsw/isoal.c) fails to validate the length field of a framed ISO PDU start segment. Per the Bluetooth specification a start segment (sc=0) always carries a 3-byte timeoffset, so its segment-header len must be at least PDUISOSEGTIMEOFFSETSIZE (3). isoalchecksegheader() accepted start segments with len < 3 as valid, and isoalrxframedconsume() then computed length = seghdr->len - 3 in a uint8t, underflowing to 253-255 when len is 0-2. That oversized length is passed to isoalrxappendtosdu(), whose copy is clamped only against the destination SDU buffer size, not the source PDU length, so up to ~255 bytes of controller memory beyond the received PDU are copied (via sinksduwritehci()/netbufaddmem) into an HCI ISO data packet and delivered to the host. The PDU and its segment headers are entirely attacker-controlled and arrive over the air, reachable through both the CIS and BIS-sync HCI data paths (hcidriver.c) and the vendor data path (ulliso.c), so a remote CIS peer or a broadcaster the device is synced to can trigger an out-of-bounds read causing information disclosure to the host and potential denial of service (faults or malformed oversized HCI ISO packets). The flaw affects all Zephyr releases since framed ISO reception was introduced in v3.0.0. The fix rejects sc=0 segments with len < 3 in isoalchecksegheader() and adds a guard before the subtraction in isoalrxframedconsume().
netifipv6calcreachabletime() in subsys/net/ip/netif.c derives a randomized ND reachable time from ipv6->basereachabletime as minreachable + sysrand32get() % (maxreachable - minreachable), where minreachable = base/2 and maxreachable = 3base/2 using integer division. When basereachabletime is 1, both minreachable and the modulus collapse so the function returns 0, and netifipv6setreachabletime() stores that 0 into ipv6->reachabletime.
The basereachabletime is attacker-controlled: handlerainput() in subsys/net/ip/ipv6nbr.c accepts the Reachable Time field of an incoming Router Advertisement whenever it is nonzero and <= MAXREACHABLETIME, so a single unauthenticated, link-local RA carrying a Reachable Time of 1 drives the computed reachable time to 0. Router Advertisements are unauthenticated by default and require only adjacency to the target link.
When a neighbor is subsequently confirmed reachable, netipv6nbrsetreachabletimer() reads the value and executes NETASSERT(time, "Zero reachable timeout!"). On builds with CONFIGASSERT enabled this triggers a fatal kernel assertion — a remote denial of service; on builds without assertions the reachable timer is armed with KMSEC(0) and fires immediately, forcing reachable neighbors into perpetual re-solicitation (STALE), degrading Neighbor Discovery. The impact is limited to availability; there is no memory-safety, confidentiality, or integrity consequence.
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.
mcumgrserialprocessfrag() in subsys/mgmt/mcumgr/transport/src/serialutil.c calls netbufreset() on the result of smppacketalloc() before checking it for NULL. smppacketalloc() uses netbufalloc(KNOWAIT) against the shared MCUmgr packet pool (CONFIGMCUMGRTRANSPORTNETBUFCOUNT, default 4), which returns NULL when the pool is exhausted. In default builds the ASSERTNOMSG in netbufreset is a no-op, so netbufsimplereset writes through the NULL pointer (buf->len = 0; buf->data = buf->buf), causing a fault/crash.
The fragment data reaches this code from attacker-controlled bytes on the MCUmgr serial/UART/shell-console transports (smpuart.c, smprawuart.c, smpshell.c), and a fresh buffer is allocated at the start of essentially every new packet. An attacker on the serial/console link can flood the transport to drive the 4-entry buffer pool to exhaustion and induce the NULL dereference, crashing the device (denial of service).
The defect was introduced after the original MCUmgr rework and shipped in Zephyr v4.4.0. The fix moves the NULL check ahead of netbufreset.
mcumgrserialprocessfrag() in subsys/mgmt/mcumgr/transport/src/serialutil.c calls netbufreset() on the result of smppacketalloc() before checking it for NULL. smppacketalloc() uses netbufalloc(KNOWAIT) against the shared MCUmgr packet pool (CONFIGMCUMGRTRANSPORTNETBUFCOUNT, default 4), which returns NULL when the pool is exhausted. In default builds the ASSERTNOMSG in netbufreset is a no-op, so netbufsimplereset writes through the NULL pointer (buf->len = 0; buf->data = buf->buf), causing a fault/crash.
The fragment data reaches this code from attacker-controlled bytes on the MCUmgr serial/UART/shell-console transports (smpuart.c, smprawuart.c, smpshell.c), and a fresh buffer is allocated at the start of essentially every new packet. An attacker on the serial/console link can flood the transport to drive the 4-entry buffer pool to exhaustion and induce the NULL dereference, crashing the device (denial of service).
The defect was introduced after the original MCUmgr rework and shipped in Zephyr v4.4.0. The fix moves the NULL check ahead of netbufreset.
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 OCPP 1.6 client in subsys/net/lib/ocpp/ocpp.c reconstructs a session handle and PDU id from the uid field of a CALLRESULT message. In ocppprocessservermsg() the code calls atoi(strtokr(uid, "-", &tmp)) without checking the strtokr return value. When the server-supplied uid is empty or contains no - delimiter, strtokr() returns NULL and atoi(NULL) dereferences a NULL pointer, which is undefined behaviour.
The uid originates from network data: parserpcmsg() in subsys/net/lib/ocpp/ocppj.c JSON-parses a frame received from the OCPP central system over TCP/WebSocket and copies the server-controlled string into the local buffer. A malicious or compromised central system, or a man-in-the-middle on a non-TLS ws:// connection, can return a malformed uid to reach the defect. No authentication beyond the existing server connection (or MITM position) is required, and the reconstructed pointer is membership-validated by ocppsessionisvalid(), so the impact is limited to the NULL dereference rather than arbitrary pointer use.
On Zephyr targets that trap access to address 0 (MMU/MPU platforms or CONFIGNULLPOINTEREXCEPTIONDETECTION), the dereference faults inside the OCPP reader thread and invokes the fatal handler, producing a remote denial of service of the charge point; on bare targets where address 0 is readable the call returns 0 and is benign, so the impact is availability-only and platform-conditional.
The applied fix guards only the first atoi(); the second strtokr(NULL, "-", &tmp) followed by pdu = atoi(buf) in the same function remains unguarded and the identical NULL dereference is still reachable from the same network input when the uid has a first token but no second --delimited token. A complete fix should validate the second token as well.
subsys/net/ip/ipv6mld.c:mldsend() read the packet interface via netpktiface(pkt) after netsenddata(pkt) returned successfully. Per the network stack's ownership contract (include/zephyr/net/netcore.h, and the explicit warning in subsys/net/ip/netcore.c:453-460 'do not use pkt after that call'), a successful send transfers ownership of the netpkt and the L2 driver frees it (e.g. ethernetsend() unrefs the packet on success, subsys/net/l2/ethernet/ethernet.c:790), returning it to its kmemslab.
The subsequent netpktiface(pkt) is therefore a read of a freed object; the recovered interface pointer is then dereferenced and incremented by the per-interface statistics path (netstats.h UPDATESTAT/SETSTAT) when CONFIGNETSTATISTICSPERINTERFACE is enabled. If the freed slot is concurrently reallocated, pkt->iface may read back as NULL (NULL-pointer dereference / crash) or as a stale/garbage pointer (stray increment write / memory corruption).
The path is reachable remotely on the local link without authentication: handlemldquery() (registered for NETICMPV6MLDQUERY) responds to a valid MLDv2 General Query (unspecified multicast address, hop limit 1) by calling sendmldreport() -> mldsend().
The result is a remotely triggerable denial of service of the networking stack, with a narrow possibility of memory corruption. The fix caches the interface in a local before sending and no longer touches the packet after netsenddata(). The IPv4/IGMP sibling (igmpsend) already used the corrected pattern.