See how zephyr project compares to other vendors in security performance
A remote, unauthenticated attacker can trigger memory corruption in Zephyr's HTTP server WebSocket upgrade path by sending a crafted Sec-WebSocket-Key header. The HTTP/1 header parser copies the header into a fixed-size buffer using a bounded copy that does not guarantee NUL termination when the input length reaches the buffer size. During upgrade handling the buffer is copied to a local stack buffer and passed to strlen(); if no NUL exists in-bounds, strlen() reads beyond the stack buffer and subsequent concatenation with the WebSocket magic string can write out of bounds. This leads to out-of-bounds read and write on stack memory, resulting in crash (denial of service) and potentially code execution. The path is reachable when CONFIGHTTPSERVERWEBSOCKET is enabled.
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 OCPP 1.6 client in subsys/net/lib/ocpp/ocppj.c contains a stack buffer overflow in parsegetconfigmsg(). When handling a GetConfiguration request from the central system, the handler copied the attacker-controlled JSON "key" string into the caller's fixed 50-byte stack buffer (skey[CISTR50], declared in subsys/net/lib/ocpp/ocpp.c) using an unbounded strcpy(). The parsed key value points directly into the receive buffer, so its length is bounded only by the message size (CONFIGOCPPRECVBUFFERSIZE, default 2048).
The GetConfiguration message is delivered over the WebSocket connection that the charge point opens to its configured central system. The reader thread ocppwsreader() reads the message into ui->recvbuf and dispatches it to parsegetconfigmsg() via the PDU function table. An attacker who controls the central system endpoint, or a man-in-the-middle on an unencrypted connection, can send a GetConfiguration request whose "key" field exceeds 50 bytes and overflow the reader thread's stack with attacker-chosen bytes.
The consequence is a remotely triggerable stack smash on the OCPP reader thread: at minimum a denial of service, and plausibly remote code execution depending on build-time hardening such as stack canaries and MPU configuration. The fix replaces the strcpy() with a bounded strncpy(key, payload.key[0], CISTR50 - 1) followed by explicit NUL termination, matching the bounded copies already used by the sibling handlers.
dnsunpackname() caches the buffer tailroom once and reuses it while appending DNS labels. As the buffer grows, the cached size becomes incorrect, and the final null terminator can be written past the buffer. With assertions disabled (default), a malicious DNS response can trigger an out-of-bounds write when CONFIGDNSRESOLVER is enabled.
A potential out-of-bounds write/read exists in the TLS socket connect path of the network sockets subsystem (subsys/net/lib/sockets/socketstls.c). When the TLS session cache is enabled, tlssessionstore() and tlssessionrestore() memcpy the caller-supplied address into a fixed-size buffer using the caller-controlled addrlen value without validating it against the destination size. struct netsockaddr is an opaque type, so an application can pass an addrlen larger than sizeof(struct netsockaddr) (for example 128 bytes into a 24-byte stack buffer), causing the memcpy to read and write past the end of the address memory used by the TLS session cache. This out-of-bounds write can lead to a crash and denial of service, and potentially to arbitrary code execution.
A remote, unauthenticated BLE peer can trigger a 2-byte out-of-bounds write in the Bluetooth host during L2CAP LE CoC SDU reassembly. When the application enables segmentation (via chanops.allocbuf) and the chosen RX pool has a userdatasize smaller than 2 bytes, the segmentation counter stored in the netbuf userdata area is written out of bounds in l2capchanlerecvseg (subsys/bluetooth/host/l2cap.c). The observed effects are an AddressSanitizer abort and, without ASan, heap corruption / fatal error.
Zephyr's dynamic kernel-object disposal path unrefcheck() in kernel/userspace/userspace.c frees an object's storage (kfree(dyn->data)) once its reference count reaches zero, after running a per-object-type cleanup. The cleanup switch handled only KOBJMSGQ and KOBJSTACK; there was no KOBJTIMER case. A dynamically-allocated, initialized, and armed ktimer keeps its embedded struct timeout dnode linked in the global timeout queue (timeoutq), so freeing the timer storage without cancelling the timeout leaves a dangling node in that queue.
When the timer next expires, the timeout machinery walks timeoutq and invokes ztimerexpirationhandler() on the freed node, dereferencing and writing freed (and reusable) kernel heap in kernel/ISR context. This is a deterministic use-after-free that does not depend on SMP: the queued node is simply never unlinked at free time.
The disposal is reachable from an unprivileged user thread under CONFIGUSERSPACE + CONFIGDYNAMICOBJECTS: a thread that holds the last permission on such a timer drops it via the kobjectrelease() syscall (or by exiting, through kthreadpermsallclear()), and can arm the timer itself via the ktimerstart() syscall. The free and the expiration handler run at kernel privilege while the actor is a user thread, so the bug is a sandbox-escape memory-corruption primitive usable for privilege escalation. The fix adds ktimercleanup() (cancel the timeout and wait for any in-flight handler) and calls it for KOBJTIMER before freeing.
dynamicobjectcreate() in kernel/userspace/userspace.c computed the backing allocation for a dynamically allocated kernel object as objsizeget(otype) + size, and for thread stack elements as STACKELEMENTDATASIZE(size) (a round-up plus fixed overhead), without checking either expression for unsigned wrap-around. A size close to SIZEMAX makes the computed total wrap to a very small value, so the heap chunk handed out is a few bytes while the object descriptor is still tagged with the full requested type and registered in the kernel object table.
The size argument reaches that arithmetic directly from user mode. kobjectallocsize() is declared syscall in include/zephyr/sys/kobject.h, its verifier zvrfykobjectallocsize() in kernel/userspace/userspacehandler.c is a bare pass-through, and zobjectalloc() only range-checks otype — nothing bounds size. The stack-element branch is additionally reachable through the kthreadstackalloc() syscall via kernel/dynamic.c. Because subsequent kernel-object validation checks only the object's type and initialization state, the undersized handle passes KSYSCALLOBJINIT()/KSYSCALLOBJNEVERINIT(), and the matching init syscall (for example kmutexinit(), kseminit(), or kthreadcreate()) then writes a complete object over the truncated allocation.
An unprivileged user-mode thread can therefore trigger a supervisor-mode out-of-bounds write into the kernel resource-pool heap, of a size and content it substantially controls, corrupting sysheap chunk metadata and adjacent kernel objects. Under CONFIGGENPRIVSTACKS the thread-stack branch additionally stores an attacker-influenced wild pointer as a user thread's privileged stack base. The practical result is escape from the CONFIGUSERSPACE sandbox — kernel-level code execution or at minimum kernel memory corruption and system compromise.
Exploitation requires CONFIGUSERSPACE together with CONFIGDYNAMICOBJECTS (also selected by CONFIGDYNAMICTHREAD under userspace), and a calling thread with an assigned resource pool. The fix rejects both overflowing computations and frees the partially built descriptor.
The LE Audio Broadcast Sink in subsys/bluetooth/audio/bapbroadcastsink.c copies subgroup metadata from a received Basic Audio Announcement (BASE) into the static Broadcast Audio Scan Service parameter structure modsrcparam without any bounds check. In basesubgroupmetacb() the destination element was selected as modsrcparam.subgroups[modsrcparam.numsubgroups] with no test against ARRAYSIZE(modsrcparam.subgroups) (sized by CONFIGBTBAPBASSMAXSUBGROUPS, default 1), and the metadata was copied with memcpy() using the raw on-air length returned by btbapbasegetsubgroupcodecmeta() into a metadata array sized by CONFIGBTAUDIOCODECCFGMAXMETADATASIZE (default 4). The BASE validator btbapbasegetbasefromad() only checks structural consistency and permits up to ~24 subgroups and metadata LTVs of ~240 octets.
The defect is reached from the periodic advertising receive callback: parecv() → btdataparse() → padecodebase() → updaterecvstatebase() → btbapbaseforeachsubgroup() → basesubgroupmetacb(). Every broadcast sink registers a scan-delegator receive state at creation (btbapbroadcastsinkcreate() calls broadcastsinkaddsrc()), and CONFIGBTBAPBROADCASTSINK depends on CONFIGBTBAPSCANDELEGATOR, so the path is active in every broadcast-sink build once the device is periodic-advertising-synced. An attacker in radio range who operates a broadcast source the device syncs to — or who impersonates the advertiser address and SID of one already in use, periodic advertising data being unauthenticated — can change the BASE at will; each new BASE is re-parsed.
A crafted BASE therefore writes attacker-chosen bytes past the end of a fixed static object in .bss: up to roughly 236 bytes for an oversized metadata LTV, plus whole struct btbapbasssubgroup records for each subgroup beyond CONFIGBTBAPBASSMAXSUBGROUPS. This is memory corruption of adjacent Bluetooth-audio state reachable with no pairing, bonding or GATT connection, with a potential for remote code execution in the Bluetooth RX thread; in addition, the unvalidated metadatalen is forwarded to btbapscandelegatormodsrc(), which neither clamps it nor rejects it, leading to a further copy into the receive state and to out-of-bounds memory being disclosed in the BASS receive-state notification sent to a connected Broadcast Assistant.
The fix rejects a BASE carrying more subgroups than the receive state can hold (discarding the update entirely) and omits metadata that does not fit rather than copying it, and additionally honours the previously-ignored error return of the subgroup decode pass.
The Bluetooth HCI USB transport class of the new USB device stack, subsys/usb/devicenext/class/bthci.c, copies every controller-to-host HCI packet into a USB transfer buffer before enqueuing it on an endpoint. bthcitxsyncin() allocated that buffer from bthcieppool, a fixed pool whose slots are only USBDMAXBULKMPS bytes (64 on full-speed-only builds), and then performed netbufaddmem(buf, btbuf->data, btbuf->len) with no length check. HCI event and ACL buffers are sized by CONFIGBTBUFEVTRXSIZE and CONFIGBTBUFACLRXSIZE and can be far larger, so the copy runs past the end of the 64-byte slot into the adjacent slots and into whatever follows the statically allocated pool.
The device runs the local Bluetooth controller in raw HCI mode (CONFIGBTHCIRAW), so the oversized packets originate over the air. With the HCI buffer sizes recommended for full-featured controllers, an unauthenticated attacker within Bluetooth range can supply the overflowing bytes: a connected peer that negotiates Data Length Extension can send ACL fragments up to CONFIGBTCTLRDATALENGTHMAX bytes, and an attacker that merely broadcasts extended advertising data reaches the same path through LE Extended Advertising Report events, which the controller fragments only to CONFIGBTBUFEVTRXSIZE. In the stock configuration the overflow is already reached without any radio traffic: the mandatory Read Supported Commands command-complete event is 70 bytes and overruns the 64-byte slot by six bytes on every host initialization.
Because netbufaddmem() validates tailroom only through an assertion unless CONFIGNETBUFHARDENING is enabled, builds with CONFIGASSERT=n — the usual production setting — silently corrupt memory adjacent to the USB buffer pool with attacker-chosen contents, which on a target without memory protection between subsystems can lead to arbitrary code execution. Builds with assertions enabled fail the assertion instead and panic, giving a remotely triggerable denial of service. The fix introduces a separate bthcitxpool sized to MAX(BTBUFACLRXSIZE, BTBUFEVTRXSIZE) for the controller-to-host direction while leaving the host-to-device pool at the endpoint packet size, which is already bounds-checked during reassembly.
The Bluetooth HCI USB transport class of the new USB device stack, subsys/usb/devicenext/class/bthci.c, copies every controller-to-host HCI packet into a USB transfer buffer before enqueuing it on an endpoint. bthcitxsyncin() allocated that buffer from bthcieppool, a fixed pool whose slots are only USBDMAXBULKMPS bytes (64 on full-speed-only builds), and then performed netbufaddmem(buf, btbuf->data, btbuf->len) with no length check. HCI event and ACL buffers are sized by CONFIGBTBUFEVTRXSIZE and CONFIGBTBUFACLRXSIZE and can be far larger, so the copy runs past the end of the 64-byte slot into the adjacent slots and into whatever follows the statically allocated pool.
The device runs the local Bluetooth controller in raw HCI mode (CONFIGBTHCIRAW), so the oversized packets originate over the air. With the HCI buffer sizes recommended for full-featured controllers, an unauthenticated attacker within Bluetooth range can supply the overflowing bytes: a connected peer that negotiates Data Length Extension can send ACL fragments up to CONFIGBTCTLRDATALENGTHMAX bytes, and an attacker that merely broadcasts extended advertising data reaches the same path through LE Extended Advertising Report events, which the controller fragments only to CONFIGBTBUFEVTRXSIZE. In the stock configuration the overflow is already reached without any radio traffic: the mandatory Read Supported Commands command-complete event is 70 bytes and overruns the 64-byte slot by six bytes on every host initialization.
Because netbufaddmem() validates tailroom only through an assertion unless CONFIGNETBUFHARDENING is enabled, builds with CONFIGASSERT=n — the usual production setting — silently corrupt memory adjacent to the USB buffer pool with attacker-chosen contents, which on a target without memory protection between subsystems can lead to arbitrary code execution. Builds with assertions enabled fail the assertion instead and panic, giving a remotely triggerable denial of service. The fix introduces a separate bthcitxpool sized to MAX(BTBUFACLRXSIZE, BTBUFEVTRXSIZE) for the controller-to-host direction while leaving the host-to-device pool at the endpoint packet size, which is already bounds-checked during reassembly.
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.
Zephyr's dynamic kernel-object disposal path unrefcheck() in kernel/userspace/userspace.c frees an object's storage (kfree(dyn->data)) once its reference count reaches zero, after running a per-object-type cleanup. The cleanup switch handled only KOBJMSGQ and KOBJSTACK; there was no KOBJTIMER case. A dynamically-allocated, initialized, and armed ktimer keeps its embedded struct timeout dnode linked in the global timeout queue (timeoutq), so freeing the timer storage without cancelling the timeout leaves a dangling node in that queue.
When the timer next expires, the timeout machinery walks timeoutq and invokes ztimerexpirationhandler() on the freed node, dereferencing and writing freed (and reusable) kernel heap in kernel/ISR context. This is a deterministic use-after-free that does not depend on SMP: the queued node is simply never unlinked at free time.
The disposal is reachable from an unprivileged user thread under CONFIGUSERSPACE + CONFIGDYNAMICOBJECTS: a thread that holds the last permission on such a timer drops it via the kobjectrelease() syscall (or by exiting, through kthreadpermsallclear()), and can arm the timer itself via the ktimerstart() syscall. The free and the expiration handler run at kernel privilege while the actor is a user thread, so the bug is a sandbox-escape memory-corruption primitive usable for privilege escalation. The fix adds ktimercleanup() (cancel the timeout and wait for any in-flight handler) and calls it for KOBJTIMER before freeing.
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.
dynamicobjectcreate() in kernel/userspace/userspace.c computed the backing allocation for a dynamically allocated kernel object as objsizeget(otype) + size, and for thread stack elements as STACKELEMENTDATASIZE(size) (a round-up plus fixed overhead), without checking either expression for unsigned wrap-around. A size close to SIZEMAX makes the computed total wrap to a very small value, so the heap chunk handed out is a few bytes while the object descriptor is still tagged with the full requested type and registered in the kernel object table.
The size argument reaches that arithmetic directly from user mode. kobjectallocsize() is declared syscall in include/zephyr/sys/kobject.h, its verifier zvrfykobjectallocsize() in kernel/userspace/userspacehandler.c is a bare pass-through, and zobjectalloc() only range-checks otype — nothing bounds size. The stack-element branch is additionally reachable through the kthreadstackalloc() syscall via kernel/dynamic.c. Because subsequent kernel-object validation checks only the object's type and initialization state, the undersized handle passes KSYSCALLOBJINIT()/KSYSCALLOBJNEVERINIT(), and the matching init syscall (for example kmutexinit(), kseminit(), or kthreadcreate()) then writes a complete object over the truncated allocation.
An unprivileged user-mode thread can therefore trigger a supervisor-mode out-of-bounds write into the kernel resource-pool heap, of a size and content it substantially controls, corrupting sysheap chunk metadata and adjacent kernel objects. Under CONFIGGENPRIVSTACKS the thread-stack branch additionally stores an attacker-influenced wild pointer as a user thread's privileged stack base. The practical result is escape from the CONFIGUSERSPACE sandbox — kernel-level code execution or at minimum kernel memory corruption and system compromise.
Exploitation requires CONFIGUSERSPACE together with CONFIGDYNAMICOBJECTS (also selected by CONFIGDYNAMICTHREAD under userspace), and a calling thread with an assigned resource pool. The fix rejects both overflowing computations and frees the partially built descriptor.
The LE Audio Broadcast Sink in subsys/bluetooth/audio/bapbroadcastsink.c copies subgroup metadata from a received Basic Audio Announcement (BASE) into the static Broadcast Audio Scan Service parameter structure modsrcparam without any bounds check. In basesubgroupmetacb() the destination element was selected as modsrcparam.subgroups[modsrcparam.numsubgroups] with no test against ARRAYSIZE(modsrcparam.subgroups) (sized by CONFIGBTBAPBASSMAXSUBGROUPS, default 1), and the metadata was copied with memcpy() using the raw on-air length returned by btbapbasegetsubgroupcodecmeta() into a metadata array sized by CONFIGBTAUDIOCODECCFGMAXMETADATASIZE (default 4). The BASE validator btbapbasegetbasefromad() only checks structural consistency and permits up to ~24 subgroups and metadata LTVs of ~240 octets.
The defect is reached from the periodic advertising receive callback: parecv() → btdataparse() → padecodebase() → updaterecvstatebase() → btbapbaseforeachsubgroup() → basesubgroupmetacb(). Every broadcast sink registers a scan-delegator receive state at creation (btbapbroadcastsinkcreate() calls broadcastsinkaddsrc()), and CONFIGBTBAPBROADCASTSINK depends on CONFIGBTBAPSCANDELEGATOR, so the path is active in every broadcast-sink build once the device is periodic-advertising-synced. An attacker in radio range who operates a broadcast source the device syncs to — or who impersonates the advertiser address and SID of one already in use, periodic advertising data being unauthenticated — can change the BASE at will; each new BASE is re-parsed.
A crafted BASE therefore writes attacker-chosen bytes past the end of a fixed static object in .bss: up to roughly 236 bytes for an oversized metadata LTV, plus whole struct btbapbasssubgroup records for each subgroup beyond CONFIGBTBAPBASSMAXSUBGROUPS. This is memory corruption of adjacent Bluetooth-audio state reachable with no pairing, bonding or GATT connection, with a potential for remote code execution in the Bluetooth RX thread; in addition, the unvalidated metadatalen is forwarded to btbapscandelegatormodsrc(), which neither clamps it nor rejects it, leading to a further copy into the receive state and to out-of-bounds memory being disclosed in the BASS receive-state notification sent to a connected Broadcast Assistant.
The fix rejects a BASE carrying more subgroups than the receive state can hold (discarding the update entirely) and omits metadata that does not fit rather than copying it, and additionally honours the previously-ignored error return of the subgroup decode pass.
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 flashcopy() system call is verified by zvrfyflashcopy() in drivers/flash/flashutil.c. On builds with CONFIGUSERSPACE enabled, this handler is the kernel-side trust boundary for a user-mode caller. Prior to the fix it validated only the output buffer (KSYSCALLMEMORYWRITE) and passed the two struct device arguments, srcdev and dstdev, directly into the implementation without any object validation — unlike every sibling flash syscall, which guards its device pointer with KSYSCALLDRIVERFLASH.
A user-mode thread fully controls the values of srcdev/dstdev and the contents of its own address space. The implementation zimplflashcopy() dereferences these pointers and calls through their driver-API function tables (e.g. api->getparameters(dstdev), flashread(srcdev, ...), flashwrite(dstdev, ...)). By supplying a pointer to a forged struct device whose api table contains attacker-chosen function pointers, an unprivileged thread can cause the kernel to call arbitrary code in supervisor mode; passing any arbitrary or invalid address otherwise yields a kernel crash or out-of-bounds read.
The result is a local privilege escalation out of the userspace sandbox (with kernel denial-of-service and information disclosure as lesser outcomes). The fix adds KSYSCALLDRIVERFLASH(srcdev, read) and KSYSCALLDRIVERFLASH(dstdev, write) to zvrfyflashcopy(), which verify each device is a registered flash-driver kernel object the calling thread is permitted to use before any dereference, closing the path completely.
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.
Zephyr OS IP packet handling does not properly drop IP packets arriving on an external interface with a source address equal to 127.0.01 or the destination address.
The OCPP 1.6 client in subsys/net/lib/ocpp parsed inbound WAMP RPC frames in parserpcmsg() (subsys/net/lib/ocpp/ocppj.c) using a hand-rolled helper, extractstringfield(), that copied the message's uid and action fields with strncpy(outbuf, token + 1, outlen - 1) and then scanned the result with strchr(outbuf, '"'). Because strncpy does not NUL-terminate the destination when the source is at least outlen - 1 (127) bytes long, the subsequent strchr reads past the 128-byte destination buffer into adjacent stack memory; if a " byte is found beyond the buffer, a one-byte out-of-bounds NUL write also occurs. A related defect in extractpayload() runs strchr/strrchr over the receive buffer, which may not be NUL-terminated when a maximal-length frame fills it.
The parsed bytes come directly from the OCPP central-system server over a websocket: the reader thread fills recvbuf via websocketrecvmsg() and calls parserpcmsg() on each inbound DATA frame (subsys/net/lib/ocpp/ocpp.c). A malicious or compromised central server, or an on-path attacker (OCPP is commonly deployed over plain ws://), can send an RPC frame whose uid or action field is 127+ bytes with no closing quote, triggering the out-of-bounds access.
The primary impact is a remotely triggerable denial of service: the unbounded scan can fault on an unmapped page, and the stray NUL write can corrupt adjacent stack state. The over-read data is not reflected to the peer, so disclosure is limited. The feature is EXPERIMENTAL and must be explicitly enabled (CONFIGOCPP). The fix replaces the manual parser with the bounds-respecting jsonmixedarrparse() and copies the extracted uid with an explicitly NUL-terminated buffer, eliminating both over-reads.
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 nRF link-layer receive paths of the open-source Bluetooth LE Controller did not validate the on-air LENGTH field of a received PDU against the maximum receive length that was used to program the radio. isrrxpdu() in subsys/bluetooth/controller/llsw/nordic/lll/lllconn.c enqueued any non-empty data PDU after only the sequence-number and (for encrypted links) MIC checks, and the corresponding isrrx() handlers in lllcentraliso.c, lllperipheraliso.c, lllsynciso.c and lllscanaux.c did the same for ISO and extended-advertising PDUs. The nRF radio stores the received header verbatim and truncates only the payload at PCNF1.MAXLEN, so a frame declaring more octets than were configured leaves a node buffer whose len field describes more data than the buffer holds.
An attacker within radio range that can emit a hand-crafted link-layer PDU — for a connection, any peer that establishes an unencrypted LE connection to a connectable advertiser; for CONFIGBTCTLRSYNCISO, any broadcaster the device is synchronised to — can set the length byte up to 255 while the controller has programmed a much smaller maximum (lll->dle.eff.maxrxoctets, which is 27 until a Data Length Update, or the negotiated cislll->rx.maxpdu). No pairing, authentication or user interaction is required. On encrypted links the CCM MIC check normally rejects such a frame first, so the exposure is on unencrypted traffic, which is why the added check is placed after decryption.
The unchecked length reaches hciaclencode() in subsys/bluetooth/controller/hci/hci.c, which performs netbufadd(buf, pdudata->len) followed by a memcpy() of that many bytes out of the noderx pool element. Because CONFIGBTCTLRDATALENGTHMAX and CONFIGBTBUFACLRXSIZE both default to 27, this is an out-of-bounds read of up to roughly 228 bytes past the received PDU and a corresponding out-of-bounds write past the HCI ACL buffer into neighbouring buffers of the same pool; the tailroom test in netbufsimpleadd() is only enforced when CONFIGNETBUFHARDENING (or CONFIGASSERT) is enabled. On the ISO paths the same unchecked length makes isoalrxappendtosdu() read past the ISO node buffer and emit adjacent controller RAM to the host as ISO data. The practical result is remote memory corruption and controller crashes, plus disclosure of controller memory into the host data path.
The fix rejects any received PDU whose length exceeds the value that was used to configure the radio reception. Note that lllsync.c (periodic advertising sync reception) was not changed and still enqueues received PDUs without an equivalent bound.
The nRF link-layer receive paths of the open-source Bluetooth LE Controller did not validate the on-air LENGTH field of a received PDU against the maximum receive length that was used to program the radio. isrrxpdu() in subsys/bluetooth/controller/llsw/nordic/lll/lllconn.c enqueued any non-empty data PDU after only the sequence-number and (for encrypted links) MIC checks, and the corresponding isrrx() handlers in lllcentraliso.c, lllperipheraliso.c, lllsynciso.c and lllscanaux.c did the same for ISO and extended-advertising PDUs. The nRF radio stores the received header verbatim and truncates only the payload at PCNF1.MAXLEN, so a frame declaring more octets than were configured leaves a node buffer whose len field describes more data than the buffer holds.
An attacker within radio range that can emit a hand-crafted link-layer PDU — for a connection, any peer that establishes an unencrypted LE connection to a connectable advertiser; for CONFIGBTCTLRSYNCISO, any broadcaster the device is synchronised to — can set the length byte up to 255 while the controller has programmed a much smaller maximum (lll->dle.eff.maxrxoctets, which is 27 until a Data Length Update, or the negotiated cislll->rx.maxpdu). No pairing, authentication or user interaction is required. On encrypted links the CCM MIC check normally rejects such a frame first, so the exposure is on unencrypted traffic, which is why the added check is placed after decryption.
The unchecked length reaches hciaclencode() in subsys/bluetooth/controller/hci/hci.c, which performs netbufadd(buf, pdudata->len) followed by a memcpy() of that many bytes out of the noderx pool element. Because CONFIGBTCTLRDATALENGTHMAX and CONFIGBTBUFACLRXSIZE both default to 27, this is an out-of-bounds read of up to roughly 228 bytes past the received PDU and a corresponding out-of-bounds write past the HCI ACL buffer into neighbouring buffers of the same pool; the tailroom test in netbufsimpleadd() is only enforced when CONFIGNETBUFHARDENING (or CONFIGASSERT) is enabled. On the ISO paths the same unchecked length makes isoalrxappendtosdu() read past the ISO node buffer and emit adjacent controller RAM to the host as ISO data. The practical result is remote memory corruption and controller crashes, plus disclosure of controller memory into the host data path.
The fix rejects any received PDU whose length exceeds the value that was used to configure the radio reception. Note that lllsync.c (periodic advertising sync reception) was not changed and still enqueues received PDUs without an equivalent bound.
The Zephyr ADIN2111/ADIN1110 10BASE-T1S/T1L Ethernet driver (drivers/ethernet/ethadin2111.c) reassembles received Ethernet frames in OPEN Alliance (OA) SPI mode by copying device-supplied 64-byte data chunks into a fixed static buffer ctx->buf of size CONFIGETHADIN2111BUFFERSIZE (default 1524 bytes). In ethadin2111oadataread(), each valid chunk was memcpy'd into ctx->buf[ctx->scur] and the write cursor scur advanced, with no check that scur + len stayed within the buffer. The number of chunks (up to 255, from the BUFSTS RCA field) and the per-chunk length are taken entirely from the frame data received off the wire; the cursor is only reset on a start-of-frame chunk. An attacker on the single-pair Ethernet segment can therefore send a frame whose reassembled size exceeds the configured buffer, causing the driver's RX offload thread to write attacker-controlled frame bytes past the end of the static buffer into adjacent driver/kernel memory (up to roughly 14.8 KB in the worst case). This is a remotely/adjacently reachable out-of-bounds write (CWE-787) that can corrupt memory and cause denial of service or potentially code execution. The defect was introduced when OA SPI support was added (commit 0ca8b0756b1) and shipped in releases v3.7.0 through v4.4.0. The fix adds a bounds check that drops the oversized frame and resets the cursor before the copy.
The WireGuard VPN service in subsys/net/lib/wireguard/wg.c updated the peer endpoint from a message it could not authenticate. In handletransportdata(), when the 32-bit receiver index of an incoming transport-data message matched no session, the code both scheduled a new handshake and copied the datagram's source address into peer->endpoint with memcpy(&peer->endpoint, peeraddr, sizeof(peer->endpoint)). Because the receiver index is unknown, there is no key with which such a message could be verified, so the address written into peer->endpoint is entirely attacker-supplied. Every other endpoint update in the implementation happens only after authentication — after the Noise handshake in handlehandshakeinit(), or after AEAD decryption plus the anti-replay check in wgprocessdatamessage().
The WireGuard listener is registered with netudpregister() using no remote address or port filter and defaults to UDP port 51820 (CONFIGWIREGUARDPORT), and wgctrlrecv() resolves the peer by interface rather than by any cryptographic identity. Any host able to send a UDP datagram to that port can therefore reach the defect off-path, with no need to observe legitimate traffic: a 16-byte datagram carrying the transport-data type, zeroed reserved bytes and an arbitrary receiver index is sufficient. The vulnerable branch runs whenever the peer has neither a valid handshake nor a current keypair — the state at boot and after a session expires.
A single spoofed datagram redirects peer->endpoint to an address of the attacker's choosing, and all subsequent handshake initiations, keepalives and encrypted data packets are sent there. The legitimate peer never receives the initiations and so never re-authenticates, and nothing resets the endpoint afterwards, leaving the tunnel persistently unusable on devices whose peer does not initiate handshakes itself. The attacker can also relay the redirected initiations to the real peer to place itself in the path of the tunnel, observing ciphertext and traffic metadata and selectively dropping packets. WireGuard's end-to-end cryptography still prevents decryption or forgery of tunnel content, so the impact is loss of availability plus limited confidentiality, not key compromise.
The fix removes the endpoint learning from this path entirely: the stale-session recovery is kept, but the handshake is sent only to an endpoint learnt from an authenticated message, falling back to the configured cfgendpoint, and the message is dropped when neither is known. CONFIGWIREGUARD is marked experimental and is not enabled by default.
The WireGuard VPN service in subsys/net/lib/wireguard/wg.c updated the peer endpoint from a message it could not authenticate. In handletransportdata(), when the 32-bit receiver index of an incoming transport-data message matched no session, the code both scheduled a new handshake and copied the datagram's source address into peer->endpoint with memcpy(&peer->endpoint, peeraddr, sizeof(peer->endpoint)). Because the receiver index is unknown, there is no key with which such a message could be verified, so the address written into peer->endpoint is entirely attacker-supplied. Every other endpoint update in the implementation happens only after authentication — after the Noise handshake in handlehandshakeinit(), or after AEAD decryption plus the anti-replay check in wgprocessdatamessage().
The WireGuard listener is registered with netudpregister() using no remote address or port filter and defaults to UDP port 51820 (CONFIGWIREGUARDPORT), and wgctrlrecv() resolves the peer by interface rather than by any cryptographic identity. Any host able to send a UDP datagram to that port can therefore reach the defect off-path, with no need to observe legitimate traffic: a 16-byte datagram carrying the transport-data type, zeroed reserved bytes and an arbitrary receiver index is sufficient. The vulnerable branch runs whenever the peer has neither a valid handshake nor a current keypair — the state at boot and after a session expires.
A single spoofed datagram redirects peer->endpoint to an address of the attacker's choosing, and all subsequent handshake initiations, keepalives and encrypted data packets are sent there. The legitimate peer never receives the initiations and so never re-authenticates, and nothing resets the endpoint afterwards, leaving the tunnel persistently unusable on devices whose peer does not initiate handshakes itself. The attacker can also relay the redirected initiations to the real peer to place itself in the path of the tunnel, observing ciphertext and traffic metadata and selectively dropping packets. WireGuard's end-to-end cryptography still prevents decryption or forgery of tunnel content, so the impact is loss of availability plus limited confidentiality, not key compromise.
The fix removes the endpoint learning from this path entirely: the stale-session recovery is kept, but the handshake is sent only to an endpoint learnt from an authenticated message, falling back to the configured cfgendpoint, and the message is dropped when neither is known. CONFIGWIREGUARD is marked experimental and is not enabled by default.
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.
subsys/net/lib/lwm2m/lwm2mpullcontext.c copied the firmware-update Package URI into a fixed static buffer (context.uri, size CONFIGLWM2MSWMGMTPACKAGEURILEN, default 128) with memcpy(context.uri, uri, LWM2MPACKAGEURILEN), copying exactly the destination size with no length validation. The Firmware-Update object stores the server-supplied Package URI (/5/0/1) in a 255-byte buffer, so a LwM2M management server (or an on-path attacker on a session lacking strong DTLS) can WRITE a URI of 128-254 characters; only the first 128 bytes are then copied into context.uri with no NUL terminator. That buffer is subsequently consumed as a C string by httpparserparseurl(context.uri, strlen(context.uri), ...), strlen-based CoAP URI-path/PROXY-URI option appends, and lwm2mparsepeerinfo(), causing an out-of-bounds read of adjacent static memory. The over-read bytes are appended to outbound CoAP requests (information disclosure of adjacent device memory to the server/proxy) and can crash the device (denial of service). The vulnerable copy was introduced by the pull-context refactor (first released in v3.0.0) and is present through v4.4.0; the default-on CONFIGLWM2MFIRMWAREUPDATEPULLSUPPORT path is affected. The fix adds a strlen(uri) >= sizeof(context.uri) check returning -ENOMEM and switches to strcpy(), guaranteeing a bounded, NUL-terminated buffer.
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.