See how zephyr compares to other vendors in security performance
Unchecked length coming from user input in settings shell
Possible buffer overflow in ismountpoint
The Bluetooth Mesh On-Demand Private Proxy solicitation handler in subsys/bluetooth/mesh/solicitation.c copies a received Solicitation PDU into a fixed 17-byte stack buffer without bounding the source length. In solpdudecrypt(), out is allocated as NETBUFSIMPLE(17) and then filled with netbufsimpleaddmem(out, in->data, in->len); netbufsimpleadd() guards its tailroom only with ASSERTNOMSG, which is compiled out in production builds, so when in->len > 17 the underlying memcpy writes attacker-controlled bytes past the 17-byte stack buffer. The copy occurs before any decryption or authentication, so no key material is required to trigger it.
The oversized length arises because the mesh scan callback in subsys/bluetooth/mesh/adv.c calls netbufsimplerestore() before dispatching to btmeshsolrecv(), leaving buf->len covering the entire remaining advertising payload rather than just the Solicitation Service Data. After the parser locates the Service Data AD and consumes the Identification Type byte, the remaining buf->len is the 17-octet Network PDU plus any trailing advertising bytes, and prior to this fix there was no maximum-length check (only a minimum). An attacker can therefore append extra AD structures or padding after the Solicitation Service Data to make buf->len exceed 17.
btmeshscancb() is registered directly as the BLE scan callback, so buf is raw, unauthenticated advertising data received over the air. Any device in radio range can send a non-connectable advertisement carrying a crafted mesh Proxy Solicitation to a node that has CONFIGBTMESHODPRIVPROXYSRV enabled and is currently eligible to be solicited (GATT proxy disabled, On-Demand Private Proxy enabled), with no pairing, bonding, or provisioning. The result is an attacker-controlled stack overwrite — plausibly leading to remote code execution and at minimum a reliable remote denial of service. The fix trims buf->len to the spec-fixed 17 octets (dropping the PDU if fewer remain) before decryption.
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.
The HL7800 cellular modem driver's +CGCONTRDP: response handler oncmdatcmdinfoipaddr() in drivers/modem/vendorstandalone/hl7800.c parses the PDP-context dynamic parameters (local address, subnet mask, gateway, and DNS servers) that the cellular network assigns to the device. The response is linearized into a 256-byte stack buffer, after which each address field length is computed from comma/. delimiter positions in the network-supplied data and used directly as the length argument to strncpy() into the fixed 64-byte stack buffer tempaddrstr (and the 16-byte ifacectx.dnsv4string).
Because the field length is derived from attacker-controlled delimiter positions and was not bounded against the destination buffer, a single field can be far larger than 64 bytes. A malicious or impersonated cellular network (for example a rogue base station) can return a crafted +CGCONTRDP response with an overlong address field, causing strncpy() to write past tempaddrstr on the modem worker thread's stack, plus an out-of-bounds NUL write at tempaddrstr[addrlen].
No device-side privileges or user interaction are required: the device itself issues the AT+CGCONTRDP=1 query during normal network attach and parses whatever the network returns. The overflow corrupts adjacent stack memory in supervisor context, yielding at minimum a remotely triggerable crash and potentially control-flow hijacking on targets without stack protection.
The fix bounds every field length against its destination buffer (tempaddrstr and dnsv4string) before each copy, rejecting overlong fields.
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 Zephyr virtio driver does not validate the descriptor-chain head id that the virtio device writes into the used ring. In virtioisr() (drivers/virtio/virtiocommon.c), the device-written vq->used->ring[idx].id is used directly as an index into vq->recvcbs[] and vq->desc[], which are both allocated with exactly vq->num entries. recvcbs[] holds {cb, opaque} callback entries, and the indexed callback pointer is then invoked as cbe.cb(cbe.opaque, usedlen).
Because the id is consumed as a 16-bit value with no bound check, a malicious or compromised virtio backend (an untrusted hypervisor, or an untrusted hardware/peer-processor virtio device on a PCI or MMIO transport) can supply an id far beyond vq->num. This causes an out-of-bounds read of a {function pointer, argument} pair from heap memory beyond recvcbs[], after which the driver calls that attacker-shaped pointer in the guest's interrupt context. No guest privileges or user interaction are required; the backend triggers it by writing the shared used ring and raising the queue interrupt.
The result is an arbitrary / attacker-influenced function-pointer call in the Zephyr guest, i.e. a control-flow-hijack primitive that can lead to code execution or, at minimum, a reliable crash. The fix rejects any used-ring id >= vq->num before indexing recvcbs[]/desc[] or invoking the callback. This affects builds using CONFIGVIRTIO with the PCI or MMIO transport.
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.
The user-space system-call verifier zvrfyzlogmsgstaticcreate() in subsys/logging/logmsg.c was a pure pass-through: it forwarded the caller-supplied source, desc, package, and data arguments directly to the kernel-mode implementation zimplzlogmsgstaticcreate() without performing any of the mandatory KSYSCALL checks. Because zlogmsgstaticcreate() is declared syscall, under CONFIGUSERSPACE any unprivileged user-mode thread can invoke it directly with fully attacker-controlled arguments.
The kernel-mode handler dereferences each of these untrusted values: frontendruntimefiltering() reads through the source pointer as a struct logsourcedynamicdata, cbprintfpackagecopy() reads desc.packagelen bytes from the package pointer, and zlogmsgfinalize() performs a memcpy() of desc.datalen bytes from the data pointer. With no verification, a user thread can supply arbitrary kernel addresses and arbitrary lengths, and the kernel will read from them.
The impact is a kernel-mode denial of service (the kernel faults dereferencing an attacker-chosen pointer) and, where a log backend output is observable to the attacker, disclosure of arbitrary kernel memory copied into the emitted log message — a confidentiality breach across the user/kernel boundary that the userspace sandbox is meant to enforce. The reads do not corrupt kernel memory, so there is no out-of-bounds write primitive.
The fix adds the required validation to the verifier: it bounds desc.packagelen against ZLOGMSGMAXPACKAGE, rejects non-NULL/length mismatches, and applies KSYSCALLMEMORYREAD() to package, data, and (when runtime filtering with a frontend is enabled) source, so any out-of-bounds or kernel pointer now raises KOOPS instead of being honored.
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 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.
The Zephyr Project Manager plugin for WordPress is vulnerable to limited privilege escalation in all versions up to, and including, 3.3.101. This is due to the plugin not properly checking a users capabilities before allowing them to enable access to the plugin's settings through the updateuseraccess() function. This makes it possible for authenticated attackers, with subscriber-level access and above, to grant themselves full access to the plugin's settings.
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.
On Xtensa SoCs built with CONFIGXTENSAMPU and CONFIGUSERSPACE, archbuffervalidate() in arch/xtensa/core/mpu.c — the architecture hook that verifies a user-mode-supplied buffer is accessible to the calling user thread with the requested permission — defaulted its return value to 0 (access permitted) and only set a denial result inside its per-MPU-region probe loop. When the rounded extent of the buffer wraps the 32-bit address space (size + alignment offset near SIZEMAX, or ROUNDUP(size + offset) overflowing to 0), the loop executes zero iterations and the function returns 0 = permitted without probing any MPU region.
The syscall-layer pre-checks (KSYSCALLMEMORYSIZECHECK / ZDETECTPOINTEROVERFLOW) only catch a raw addr+size wrap and do not cover the ROUNDUP-induced wrap, and the string path (archuserstringnlen -> archbuffervalidate) has no syscall-layer guard at all.
An unprivileged user-mode thread can therefore pass a crafted (addr, size) to any syscall that validates user buffers via kusermodefromcopy/tocopy or kusermodestringcopy and have validation succeed for memory it must not access; the kernel then reads from (disclosure) or, with write=1, writes to (corruption) attacker-chosen kernel or other-partition memory on the thread's behalf, enabling information disclosure, memory corruption, privilege escalation, and denial of service.
Affected from v3.7.0 (when Xtensa MPU userspace support was added) through v4.4.0. The fix changes the default to -EINVAL (deny by default), adds an explicit sizeaddoverflow check, and sets the success value only after the full range has been validated.
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 NXP MCUX TRNG entropy driver in drivers/entropy/entropymcuxtrng.c passed the caller's byte count straight to the vendor SDK routine TRNGGetRandomData(). On i.MX RT5xx and RT6xx parts the SDK compiles its TRNGSWHEALTHTESTS variant, which always copies whole 32-bit words and draws entropy rounded up to a multiple of 128 bytes. Its "caller buffer is full" guard tests dataSize == 0, so a request whose length is not a multiple of four makes dataSize underflow past zero and the SDK keeps writing into the caller's buffer for the entire extraction: a 1-byte request results in 128 bytes written, and any non-word-multiple length overflows by up to 127 bytes.
entropygetentropy() is a syscall, and its verifier in drivers/entropy/entropyhandlers.c validates only the requested length via KSYSCALLMEMORYWRITE(). With CONFIGUSERSPACE enabled, an unprivileged user-mode thread that has merely been granted the entropy device can therefore choose both the destination address and a length such as 1, and cause the kernel to write up to 127 bytes beyond the region it proved it owns. On these Cortex-M33 targets there is no MMU, so user partitions and kernel data share one SRAM and the overflow can land in adjacent kernel state. The same defect is reached from kernel mode by any caller requesting a non-word-multiple length, including getentropy() and, in builds where syscsrandget()/sysrandget() resolve to the hardware generator, sysrand8get() and sysrand16get().
Impact is memory corruption of up to 127 bytes immediately following the supplied buffer — typically the caller's stack in kernel-mode use, or memory outside the caller's partition when driven through the syscall. The overflow offset is fully determined by the requested length and is therefore deterministic, while the written content is uncontrolled TRNG output; the practical consequences range from crashes and unpredictable state corruption to opportunistic escalation when kernel bookkeeping such as object permission bitmaps is overwritten. The affected devices are those where the MCUX SDK enables TRNGSWHEALTHTESTS (MIMXRT595S, MIMXRT555S, MIMXRT533S, MIMXRT685S, MIMXRT633S), on which the TRNG is the zephyr,entropy chosen node; other SoCs using this driver take the SDK path that clamps the copy size and are unaffected.
The fix routes any unaligned prefix and any sub-word tail through a local bounce word and hands the SDK only word-multiple sizes, so the SDK's word-granular writes can no longer pass the end of the caller's buffer.
The userspace verifier zvrfyrtiosqecopyingethandles() in subsys/rtio/rtiosyscalls.c (subsys/rtio/rtiohandlers.c before v4.3.0) validated the RTIO object handle and the sqes input array, but not the handle out-parameter. On the first loop iteration it executed handle = sqe, storing the kernel address of the newly acquired submission-queue entry through a pointer taken verbatim from user mode, with no KSYSCALLMEMORYWRITE check in front of it.
Any user-mode thread that has been granted a struct rtio kernel object can invoke the syscall with an arbitrary address in handle. That is the ordinary way an unprivileged thread uses the RTIO API, for example via sensorreadasyncmempool() or the async ADC helpers, which call rtiosqecopyingethandles() internally. The store happens in supervisor mode before any submission-entry validation, so it fires regardless of whether the SQE contents are subsequently rejected. Only builds with CONFIGUSERSPACE and CONFIGRTIO are affected; without CONFIGUSERSPACE the verifier is not compiled and the caller is already privileged.
The write address is fully attacker-chosen and the written value is a pointer into the caller's own RTIO ring, whose contents the caller controls (the following sqe = sqes[i] copies an attacker-supplied struct rtiosqe into that slot). This yields a write-what-where primitive placing a pointer to attacker-controlled data at any kernel address, sufficient to corrupt kernel function pointers, thread structures, or memory-domain partition tables, and thus to escalate from user mode to kernel mode, defeating the isolation boundary CONFIGUSERSPACE is meant to enforce. At minimum it is a reliable kernel memory-corruption and crash primitive. The reporter reproduced the write on qemux86: a KUSER thread changed a supervisor global from NULL to a live kernel SQE pointer.
The fix adds KSYSCALLMEMORYWRITE(handle, sizeof(handle)) (guarded by the existing optional-NULL semantics) before the loop, so the destination must lie in the calling thread's writable memory domain or the thread is terminated by KOOPS. The neighbouring verifier zvrfyrtiocqegetmempoolbuffer(), which checked its buff/bufflen out-parameters only for read although the implementation writes through them, was hardened separately by bea93400138 ("rtio: syscalls: validate output params as writable"); that residual was materially weaker, since a read check still confines the target to the caller's own memory domain.
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.
The system-call verifier for i3cdoccc() in drivers/i3c/i3chandlers.c validated the outer struct i3ccccpayload, the broadcast ccc.data buffer and the targets.payloads[] array, but did not validate the per-target data buffers those array elements point at. Each struct i3cccctargetpayload carries its own data pointer and datalen, and neither was passed through KSYSCALLMEMORY() before the payload was handed to zimpli3cdoccc() and on to the controller driver. The verifier also operated on the caller's live structure rather than a snapshot, so validated fields could be changed by a second user thread between the check and the driver's use — unlike the sibling zvrfyi3ctransfer(), which has always copied its message array first.
The defect is only present in CONFIGUSERSPACE builds, where drivers/i3c/i3chandlers.c is compiled. An unprivileged user-mode thread that has been granted access to the I3C controller device object — the ordinary way an application lets a user thread talk to I3C peripherals — can issue a direct CCC whose target payload data pointer names an arbitrary kernel address. Controller drivers dereference that pointer directly (for example drivers/i3c/i3cmcux.c, drivers/i3c/i3ccdns.c, drivers/i3c/i3cstm32.c, drivers/i3c/i3cnpcx.c), using rnw to decide direction.
A read CCC therefore causes the kernel-mode driver to write bus-received bytes into an attacker-chosen kernel address for an attacker-chosen length, and a write CCC transmits kernel memory out onto the I3C bus. The result is an out-of-bounds kernel write plus a kernel memory disclosure, i.e. escalation from a user-mode thread to supervisor privilege, defeating the isolation CONFIGUSERSPACE is meant to provide.
The fix introduces copycccanddo(), which snapshots the payload, copies the target array into kernel memory with kusermodeallocfromcopy() (bounding numtargets to fewer than 32), validates each per-target buffer with KSYSCALLMEMORY() according to rnw, and copies the driver-written numxfer and err fields back to the caller.
The user-mode syscall verifiers zvrfyfuelgaugegetprops() and zvrfyfuelgaugesetprops() in drivers/fuelgauge/fuelgaugesyscallhandlers.c declared two variable-length arrays, union fuelgaugepropval kvals[len] and fuelgaugepropt kprops[len], sized directly by the caller-supplied len argument. len is an unvalidated sizet taken straight from the syscall ABI, and the VLAs were allocated before any check at all — including before the KSYSCALLDRIVERFUELGAUGE() object-permission check. The subsequent kusermodefromcopy() calls validated only that the user source buffer was readable; the kernel destination was never bounds-checked, since it was sized by the same attacker-chosen len.
Any thread running in user mode with CONFIGUSERSPACE enabled can invoke fuelgaugegetprops() or fuelgaugesetprops() with a large len. This first displaces the supervisor stack pointer by an arbitrary attacker-chosen amount — Zephyr does not build with stack-clash probing, so the displacement itself does not fault, and the nested calls made by the verifier then write frames below the privileged stack. If the caller has been granted access to a fuel-gauge device object, the memcpy inside kusermodefromcopy() additionally writes len sizeof(union fuelgaugepropval) bytes of fully attacker-controlled data starting well below the stack base. CONFIGPRIVILEGEDSTACKSIZE defaults to 1024 bytes, so a len of roughly 170 already exhausts it.
The result is an out-of-bounds write in supervisor mode with attacker-controlled length and, on the permitted path, attacker-controlled content — a break out of the user-mode sandbox into kernel memory, leading to kernel code execution or a system crash. A stack guard region does not contain it, because the copy begins below the guard and walks upward, corrupting unprotected memory before the guard is reached. The fix removes the kernel-side copies entirely and validates the caller's arrays in place with KSYSCALLMEMORYARRAYREAD() / KSYSCALLMEMORYARRAYWRITE(), which also handle the len size multiplication overflow; this is safe because neither fuelgaugepropt nor union fuelgaugepropval contains embedded pointers.
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.
The Classic (BR/EDR) L2CAP signaling handlers l2capbrconfreq() and l2capbrconfrsp() in subsys/bluetooth/host/classic/l2capbr.c validated the minimum command size against buf->len (the bytes remaining in the whole received PDU) instead of len (the per-command data length from the L2CAP signaling header). Because multiple signaling commands can be packed into one PDU, buf->len may exceed a command's len. An attacker can send a CONFREQ command with a header length smaller than the configuration-request structure (e.g. 0), followed by another command so that buf->len still satisfies the check. The check then passes incorrectly and optlen = len - sizeof(req) underflows the uint16t to a near-0xFFFF value. The configuration-option loop, which lacks an optlen-versus-buf->len guard, then walks far past the end of the pooled ACL receive buffer using netbuf pull primitives that perform no runtime bounds check, producing an out-of-bounds read of host memory and, when the out-of-bounds option bytes encode an MTU or flush-timeout option, an out-of-bounds write. The BR/EDR signaling channel is processed before pairing/encryption and an L2CAP channel to an L0 service such as SDP can be opened without pairing, so an unauthenticated peer within radio range that can establish an ACL connection can trigger the flaw, leading to memory corruption and denial of service (host/device crash). The defect is present in released versions including v4.4.0. The fix validates against len instead of buf->len in both handlers.
subsys/net/ip/icmpv6.c reads the network interface from a netpkt after that packet has been handed to nettrysenddata(). In icmpv6handleechorequest() and neticmpv6senderror(), the post-send statistics update calls netpktiface(reply)/netpktiface(pkt) on the just-sent packet.
The send path (nettrysenddata -> netiftx) unreferences and may free the packet back to its memory slab before returning — synchronously in the RX thread when no TX queue is configured (CONFIGNETTCTXCOUNT == 0), and asynchronously the driver/L2 may already have freed it otherwise. netpktiface() therefore dereferences a freed (and possibly reused) netpkt; with CONFIGNETSTATISTICSPERINTERFACE the stale iface pointer is further dereferenced and written through (iface->stats.icmp.sent++), turning the use-after-free read into a write through an attacker-influenceable pointer.
The core stack already documents this hazard in netcore.c ("do not use pkt after that call") and caches iface before sending; the ICMPv6 callers did not.
An unauthenticated remote attacker triggers the flaw simply by sending an ICMPv6 Echo Request (ping) or an IPv6 packet that elicits an ICMPv6 error (unknown next header, fragment reassembly timeout, destination unreachable), leading to denial of service via crash and potential memory corruption. Affected: Zephyr networking with CONFIGNETNATIVEIPV6, roughly v4.2.0 through v4.4.0.
The fix caches the interface pointer before sending and uses it for all statistics updates; the sibling commit 86e21665d46 fixes the identical bug in ICMPv4.
The UpdateHub firmware-update agent's probe handler (zimplupdatehubprobe() in subsys/mgmt/updatehub/updatehub.c) parses the JSON metadata returned by the update server into a fixed two-level nested-array struct. After parsing it validates only the outer array length (objectslen != 2) and then dereferences objects[1].objects[0].objects.sha256sum via strlen() without checking that the inner object array of element [1] is non-empty.
The metadata is attacker-influenceable network input: the agent fetches it over CoAP from the configured UpdateHub server during its routine OTA probe. A malicious or compromised update server (or, when DTLS is disabled, a network man-in-the-middle) can return a response whose second outer object array is empty. Because the parse target is zero-initialised, the corresponding objects[1].objects[0].objects.sha256sum pointer is NULL, and the subsequent strlen() dereferences address zero. The same defect exists in both the 'any boards' and 'some boards' metadata layouts.
The resulting CPU fault is fatal under Zephyr's default error handling, halting or resetting the device, so the flaw is a remotely triggerable denial of service. Impact is limited to availability; it is a read from NULL with no out-of-bounds write, memory corruption, or information disclosure. The fix rejects metadata whose inner object array is empty before any dereference, on both layouts.
The MQTT-SN client keepalive handler processping() in subsys/net/lib/mqttsn/mqttsn.c removes the gateway record after PINGREQ retries are exhausted. It invoked SYSSLISTPEEKHEADCONTAINER(&client->gateways, gw, next) but discarded the result. That macro is a pure expression that does not assign to gw, so gw retained its NULL initializer regardless of the list contents.
The code then dereferences the NULL gw (gw->gwid) and passes it to mqttsngwdestroy(), reaching kmemslabfree(&gateways, NULL). With CONFIGMEMSLABPOINTERVALIDATE enabled this triggers kpanic(); in the default configuration it performs a write through the NULL pointer ((char )mem = slab->freelist;) and corrupts the slab free list. The outcome is a crash/kernel panic or, on targets where address 0 is writable, silent memory-allocator corruption.
The vulnerable branch runs whenever the connected MQTT-SN gateway fails to answer keepalive PINGREQs for the configured number of retries. This condition is controlled by the remote peer: a malicious or compromised gateway, or an on-path/adjacent attacker that advertises itself as a gateway and then stops responding (or blackholes the real gateway's PINGRESPs), forces the client into the defect. MQTT-SN runs over UDP and no authentication is required.
The impact is a remotely triggerable denial of service (availability) of the affected MQTT-SN client; there is no attacker-controlled data written. The sibling remover processadvertise() uses SYSSLISTFOREACHCONTAINERSAFE and is not affected. The fix assigns the macro's return value to gw.