The Mbed TLS crypto shim driver in drivers/crypto/cryptombedtlsshim.c drives PSA multipart cipher and AEAD operations. In mbedtlscbc() and mbedtlsaead(), every error path after a successful psacipherencryptsetup() / psaaeaddecryptsetup() returned directly without calling psacipherabort() / psaaeadabort(). Because the abort is the only thing that reaches mbedtlscipherfree(), each failed operation permanently leaked the heap-allocated mbedtlsaescontext (roughly 288 bytes) that setup had allocated from Mbed TLS's fixed static heap (mbedtlsheap[CONFIGMBEDTLSHEAPSIZE], default 512 bytes). With CONFIGPSACRYPTOPROVIDERTFM the leaked object is instead a finite secure-partition multipart-operation slot.
The failure is chosen by an unauthenticated attacker. With CONFIGNETL2IEEE802154SECURITY backed by this driver, ieee802154decryptauth() in subsys/net/l2/ieee802154/ieee802154security.c invokes cipherccmop() on every received secured frame, with no authentication ahead of the MIC check. A device in radio range can transmit a stream of syntactically valid 802.15.4 frames carrying a bogus MIC; each one makes psaaeadverify() fail and leaks a block. The CBC path is reachable the same way through the OSDP secure channel, where CONFIGOSDPSCENABLED selects this shim and osdpencrypt() / osdpdecrypt() leave outbufmax unset, so every operation fails inside psacipherupdate().
Exhausting the Mbed TLS heap — which the default configuration reaches after a couple of failed operations, and any realistic configuration reaches in well under a second of frames — makes all subsequent mbedtlscalloc() calls fail. Every later cryptographic operation in the image then fails permanently, including link-layer security and any TLS/DTLS sharing the same heap, until the device is rebooted. There is no memory corruption or information disclosure: frames with an invalid MIC are still rejected, so the impact is limited to availability.
The fix routes all post-setup failures in mbedtlscbc() and mbedtlsaead() through a common error: label that calls the matching PSA abort before returning. Setup failures need no change, since the PSA core aborts the operation itself when setup fails.
The Object Transfer Service server builds its Object Action Control Point (OACP) response indication in oacpindsend() (subsys/bluetooth/services/ots/otsoacp.c). Before this fix the four-byte Checksum Value field was appended whenever the requested op code was BTGATTOTSOACPPROCCHECKSUMCALC, regardless of the result code. The response payload is a bare, uninitialized four-byte stack buffer declared with NETBUFSIMPLEDEFINE(respparam, sizeof(uint32t)) in btgattotsoacpwrite(), and it is filled only on the success path of oacpchecksumprocvalidate(). On every failure path the buffer is still empty, so netbufsimplepullle32() was invoked on a zero-length buffer.
The OACP characteristic and its client characteristic configuration descriptor are declared with BTGATTPERMWRITE and BTGATTPERMREAD | BTGATTPERMWRITE, so no pairing, encryption, or authentication is required. Any peer within radio range that can connect to a device running the OTS server can enable OACP indications and write a nine-byte Calculate Checksum command that fails validation — for example by issuing it with no object selected, which returns BTGATTOTSOACPRESINVOBJ. Because oacpcommanddecode() accepts the Calculate Checksum op code unconditionally (it is not guarded by CONFIGBTOTSOACPCHECKSUMSUPPORT), builds without checksum support are reachable too: they answer BTGATTOTSOACPRESOPCODENOTSUP and take the same bad path.
On builds with CONFIGASSERT enabled, the zero-length pull trips the assertion inside netbufsimplepullmem() and panics the device, giving a remotely triggered denial of service. On builds with assertions disabled there is no check: the pull returns the untouched stack array and four bytes of uninitialized stack memory are copied into the indication and transmitted to the peer, repeatable on every request. The disclosure is bounded to four bytes per request and the write stays inside the seven-byte res indication buffer, so there is no memory corruption; the impact is uninitialized-memory disclosure plus an availability loss in assertion-enabled configurations.
The Object Transfer Service server builds its Object Action Control Point (OACP) response indication in oacpindsend() (subsys/bluetooth/services/ots/otsoacp.c). Before this fix the four-byte Checksum Value field was appended whenever the requested op code was BTGATTOTSOACPPROCCHECKSUMCALC, regardless of the result code. The response payload is a bare, uninitialized four-byte stack buffer declared with NETBUFSIMPLEDEFINE(respparam, sizeof(uint32t)) in btgattotsoacpwrite(), and it is filled only on the success path of oacpchecksumprocvalidate(). On every failure path the buffer is still empty, so netbufsimplepullle32() was invoked on a zero-length buffer.
The OACP characteristic and its client characteristic configuration descriptor are declared with BTGATTPERMWRITE and BTGATTPERMREAD | BTGATTPERMWRITE, so no pairing, encryption, or authentication is required. Any peer within radio range that can connect to a device running the OTS server can enable OACP indications and write a nine-byte Calculate Checksum command that fails validation — for example by issuing it with no object selected, which returns BTGATTOTSOACPRESINVOBJ. Because oacpcommanddecode() accepts the Calculate Checksum op code unconditionally (it is not guarded by CONFIGBTOTSOACPCHECKSUMSUPPORT), builds without checksum support are reachable too: they answer BTGATTOTSOACPRESOPCODENOTSUP and take the same bad path.
On builds with CONFIGASSERT enabled, the zero-length pull trips the assertion inside netbufsimplepullmem() and panics the device, giving a remotely triggered denial of service. On builds with assertions disabled there is no check: the pull returns the untouched stack array and four bytes of uninitialized stack memory are copied into the indication and transmitted to the peer, repeatable on every request. The disclosure is bounded to four bytes per request and the write stays inside the seven-byte res indication buffer, so there is no memory corruption; the impact is uninitialized-memory disclosure plus an availability loss in assertion-enabled configurations.
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 Infineon PSOC 6 BLESS HCI driver in drivers/bluetooth/hci/hciinfineonpsoc6bless.c stored the outgoing HCI packet descriptor in a file-scope variable, static cystcblehcitxpacketinfot hcitxpkt. psoc6blesssend() cleared and populated that descriptor's packetType, dataLength and data fields before acquiring psoc6blessoperationsem, so the driver's serialising semaphore protected only the call into CyBLESoftHciSendAppPkt() and not the shared state that call reads.
The Bluetooth host does not guarantee a single HCI sender. Most transmissions are funnelled through the cooperative TX-processor work queue, but bthcicmdsend() deliberately bypasses btdev.cmdtxqueue for BTHCIOPHOSTNUMCOMPLETEDPACKETS and calls btsend() directly from the caller's thread; that caller is bthcihostnumcompletedpackets(), the aclinpool destroy callback, which runs on whichever thread drops the last reference to a received ACL buffer. CONFIGBTHCIACLFLOWCONTROL defaults to y on this driver's boards, and an application that returns -EINPROGRESS from its L2CAP recv callback and later calls btl2capchanrecvcomplete() performs that unref from its own preemptible thread. A remote peer that generates Bluetooth traffic concurrently with the device's own transmissions raises the collision rate.
When two senders interleave, the semaphore holder can hand the controller a descriptor whose dataLength comes from one netbuf and whose data pointer comes from another — or one whose buffer has already been returned to its pool. The controller then copies and transmits up to a full HCI/ACL payload starting at a shorter or recycled buffer, disclosing adjacent RAM contents to the connected peer; other interleavings duplicate one HCI packet and silently drop another, disrupting the link. The driver only reads through the shared descriptor, so no memory corruption or write primitive results, and the disclosed bytes are not attacker-chosen. The fix makes the descriptor a local variable in psoc6blesssend(), removing the shared state; CyBLESoftHciSendAppPkt() copies the packet before returning, so it need not outlive the call.
The Bluetooth Classic Hands-Free Profile HF role in subsys/bluetooth/host/classic/hfphf.c does not release the AT command buffers that are still queued on hf->txpending when the RFCOMM DLC goes down. hfphfdisconnected() only cancelled the work items and cleared hf->acl, and no other code path ever dequeues those buffers, so each one is permanently lost from the fixed hfpool (CONFIGBTMAXCONN + 1 buffers, i.e. two in the default configuration). The same function also left BTHFPHFFLAGCONNECTED set, and hfphfcreate() wiped the object with memset() as soon as hf->acl was NULL, without checking whether hf->work, hf->slcwork or hf->deferredwork was still pending or running.
The remote Audio Gateway peer controls the trigger end to end and needs no application involvement: an unsolicited +BCS: 1 response makes bcshandle() call bthfphfselectcodec() and queue an AT+BCS buffer, which cannot be transmitted while BTHFPHFFLAGRXONGOING is set, and an immediately following RFCOMM DISC strands that buffer. Repeating the connect / +BCS / disconnect cycle a couple of times empties hfpool for good.
With the pool exhausted, HFP command sending fails permanently, and because hfphfsendcmd() allocates with KFOREVER (btrfcommcreatepdu() → btconncreatepdu()) from the Bluetooth RX context — where that timeout is not downgraded to KNOWAIT — one further +BCS from the peer parks the Bluetooth RX thread inside netbufalloc() with no possible waker, halting all HCI event processing until the device is rebooted. Separately, reusing the HF slot while a work handler was still queued or running allowed memset() to wipe live kwork items out from under the system workqueue, corrupting shared kernel workqueue state. Exploitation requires an established HFP session (for SSP devices L2CAP enforces authentication before the RFCOMM channel is reachable) and a build with CONFIGBTHFPHF enabled.
The STM32WBA IEEE 802.15.4 driver in drivers/ieee802154/ieee802154stm32wba.c computed the packet length of a received frame as pktlen = rxframe->length - IEEE802154FCSLENGTH without first checking that the radio-reported length could hold an FCS. pktlen is a uint8t, so a reported length of 0 or 1 wrapped to 254 or 255. The only bound on the resulting value was ASSERTNOMSG(pktlen <= CONFIGNETBUFDATASIZE), which compiles to nothing in the default production configuration (CONFIGASSERT=n).
The length originates in the ST link-layer receive-done metadata (pmetadata->length, stored by stm32wba802154receivedone()), and ultimately in the PHY header of a frame received over the air, so it is influenced by any attacker within radio range of the device. Reaching the underflow requires the vendor link layer to surface a frame shorter than the FCS with no error; the driver performed no validation of its own on that value.
When the underflow occurs, stm32wba802154rxthread() calls netpktwrite(pkt, rxframe->psdu, pktlen) with a length of about 254 bytes against a receive buffer that holds at most one PHY packet (IEEE802154MAXPHYPACKETSIZE, 127 bytes). This reads roughly 128 bytes past the end of the radio receive buffer and copies that adjacent memory into a network packet that is submitted to the networking stack, giving an out-of-bounds read with a potential disclosure of adjacent RAM plus needless oversized packet allocation in the RX thread.
The fix rejects a frame whose reported length is smaller than IEEE802154FCSLENGTH before the subtraction, and replaces the compiled-out assertion with a runtime length check that drops oversized frames — matching the runtime check already present in drivers/ieee802154/ieee802154nrf5.c.
The receive path of the NXP KW41Z IEEE 802.15.4 driver, kw41zrx() in drivers/ieee802154/ieee802154kw41z.c, computed the payload size as pktlen = len - KW41ZFCSLENGTH in a uint8t, where len is the frame length reported by the radio. The interrupt handler kw41zisr() obtained that length from the RXFRAMELENGTH field of the ZLL IRQSTS register — a copy of the PHY header length byte of the frame received over the air — and only rejected the value zero. A reported length of one therefore underflowed to pktlen == 255.
Because the packet is allocated with NETAFUNSPEC on a non-Ethernet L2, netpktrxallocwithbuffer() does not clamp the request to the interface MTU; with the default fixed-size buffer pool (CONFIGNETBUFFIXEDDATASIZE, CONFIGNETBUFDATASIZE=128) the allocation succeeded as a chain of two fragments. The subsequent register-to-buffer copy loops write into the first fragment only, so 255 bytes were written into a 128-byte netbuf data block, overrunning it by up to 127 bytes into adjacent network-buffer-pool memory, and reading up to 128 bytes past the end of the radio's PKTBUFFERRX register array.
The length field originates from an unauthenticated frame transmitted by any device within 802.15.4 radio range, and is captured by the hardware at PHY-header time — before frame filtering has completed — while the handler samples it after clearing the interrupt status, so an attacker flooding frames with a length byte of one can drive the underflow. The impact is memory corruption in the shared receive buffer pool from interrupt context, most plausibly a crash or corruption of other packets' data; the overflowing bytes are radio register contents rather than attacker-authored data, which bounds the integrity impact.
The fix rejects any length below KW41ZFCSLENGTH and additionally verifies netbuftailroom() of the first fragment before the copy loops, which also covers the CONFIGNETL2OPENTHREAD variant of the size computation.
The Nuvoton NuMaker HSUSBD USB device-controller driver (drivers/usb/udc/udcnumaker.c) armed the control Data IN stage unconditionally (base->CEPTXCNT = len in numakerhsusbdeptrigger). Because the HSUSBD hardware cannot disarm a control Data IN already armed for a previous transfer, a USB host that cancels an in-flight control transfer (timeout) and then issues a new SETUP packet can drive the driver out of sync: stale data may be transmitted in the new transfer and the control endpoint can become permanently stuck NAK'ing every subsequent control transfer.
A malicious or buggy host (physical/adjacent attacker driving the bus) can repeatedly cancel-and-re-SETUP to wedge the device's USB control endpoint, denying service to the device's USB function (the device stops enumerating/responding on the control pipe) until a USB reset or re-plug. The flaw is an availability-only denial of service; the FIFO copy loops (bounded by netbuf length and the hardware BUFFULL flag) and the netbuf lifecycle are independent of the arming desync, so there is no out-of-bounds access, use-after-free, or information leak.
The fix monitors the IN-token and new-SETUP events (kevent) and only arms control Data IN when an IN token is present and no new SETUP has arrived, cancelling the current transfer on a new SETUP. Affects boards using the Nuvoton NuMaker HSUSBD controller (CONFIGUDCNUMAKER with DTHASNUVOTONNUMAKERHSUSBDENABLED); shipped in v4.4.0.
The CONFIGUSERSPACE verification handler for the kthreadnamecopy() system call (zvrfykthreadnamecopy() in kernel/thread.c) calls kobjectfind() on the caller-supplied thread pointer and then dereferences the returned struct kobject without checking it for NULL. kobjectfind() returns NULL whenever the supplied pointer is not a registered (static or dynamic) kernel object.
The pre-fix guard tested thread == NULL instead of ko == NULL, so an unprivileged user-mode thread that invokes kthreadnamecopy() with any non-NULL but unregistered pointer (e.g. an arbitrary address) passes the NULL test, after which the verifier reads ko->type through a NULL pointer.
Because the syscall verifier runs in supervisor mode, this NULL dereference is a kernel-mode fault that halts or reboots the system, allowing untrusted user code to crash the kernel across the userspace security boundary (denial of service). The marshaller passes the thread argument to the verifier without any prior KSYSCALLOBJ validation, so the bad pointer reaches the defect directly.
The flaw affects builds with CONFIGUSERSPACE and CONFIGTHREADNAME enabled and has been present since the special-case lookup was introduced around v2.0.0; it is present in v4.4.0 and earlier. The fix changes the guard to check the kobjectfind() return value (ko == NULL) before dereferencing it.
subsys/net/ip/ipv6mld.c:mldsend() read the packet interface via netpktiface(pkt) after netsenddata(pkt) returned successfully. Per the network stack's ownership contract (include/zephyr/net/netcore.h, and the explicit warning in subsys/net/ip/netcore.c:453-460 'do not use pkt after that call'), a successful send transfers ownership of the netpkt and the L2 driver frees it (e.g. ethernetsend() unrefs the packet on success, subsys/net/l2/ethernet/ethernet.c:790), returning it to its kmemslab.
The subsequent netpktiface(pkt) is therefore a read of a freed object; the recovered interface pointer is then dereferenced and incremented by the per-interface statistics path (netstats.h UPDATESTAT/SETSTAT) when CONFIGNETSTATISTICSPERINTERFACE is enabled. If the freed slot is concurrently reallocated, pkt->iface may read back as NULL (NULL-pointer dereference / crash) or as a stale/garbage pointer (stray increment write / memory corruption).
The path is reachable remotely on the local link without authentication: handlemldquery() (registered for NETICMPV6MLDQUERY) responds to a valid MLDv2 General Query (unspecified multicast address, hop limit 1) by calling sendmldreport() -> mldsend().
The result is a remotely triggerable denial of service of the networking stack, with a narrow possibility of memory corruption. The fix caches the interface in a local before sending and no longer touches the packet after netsenddata(). The IPv4/IGMP sibling (igmpsend) already used the corrected pattern.
btsdpparseattribute() in subsys/bluetooth/host/classic/sdp.c validated only that the SDP record buffer held the type-marker byte plus the 2-byte attribute ID (a check of buf->len < 3) but then read a fourth byte, the data-element descriptor (type), via netbufsimplepullu8(). Because netbufsimplepullu8() dereferences buf->data[0] before its only bounds guard (an ASSERTNOMSG that compiles out when CONFIGASSERT is disabled, the production default), a record of exactly three bytes (0x09 followed by a 2-byte attribute ID) causes a one-byte read past the end of the logical buffer. The parser is reachable from inbound, remote-controlled data: a Bluetooth BR/EDR peer acting as an SDP server returns discovery-response records that are stored verbatim in the client receive buffer and parsed via the public btsdpgetattr()/btsdphasattr()/btsdprecordparse() helpers. The over-read is bounded to a single byte that is used only as an internal length selector and is never leaked to the attacker; subsequent length checks then reject the malformed record. Realistic impact is therefore limited to an edge-case denial of service (a fault only if the record ends exactly at a mapped-memory boundary, or a deterministic assert panic when CONFIGASSERT=y). Affects Zephyr v4.3.0 and v4.4.0; fixed by adding sizeof(type) to the length check.
In Zephyr's userspace dynamic-objects subsystem, threadidxalloc() in kernel/userspace/userspace.c allocated a new thread permission index from the global threadidxmap[] bitmap without holding listslock.
On SMP systems, two user-mode threads invoking the kobjectalloc(KOBJTHREAD) syscall concurrently can both observe the same low free bit, perform the same non-atomic RMW to clear it, and return the identical tidx.
The two newly created KOBJTHREAD objects are then assigned the same threadid, so the two user threads alias a single bit position in every kernel object's perms[] bitfield: any subsequent grant of access on a kernel object to one thread is implicitly a grant to the other, defeating userspace ACL isolation. A secondary lost-update window between the unlocked &=~BIT() in alloc and the locked |= BIT() in threadidxfree() can also leak entries from the thread-index pool.
The defect is reachable from any user-mode thread via the unrestricted syscall kobjectalloc and is gated on CONFIGUSERSPACE, CONFIGDYNAMICOBJECTS, and CONFIGSMP. The flaw was introduced when the per-thread permission index was added in 2018 and is present in every release up to and including v4.4.0. Fixed by holding listslock across the bitmap RMW and the permissions clear (and inlining the objlist traversal that previously took the lock itself).
In Zephyr's kernel pipe implementation, the userspace syscall verifier zvrfykpipeinit() in kernel/pipe.c used KSYSCALLOBJ() (which requires the kernel object to already be initialized) instead of KSYSCALLOBJNEVERINIT() (which rejects an already-initialized object). As a result, on CONFIGUSERSPACE builds an unprivileged user thread that has been granted access to a kpipe object can invoke the kpipeinit syscall to re-initialize a pipe that is already in use.
zimplkpipeinit() unconditionally resets the ring buffer, sets pipe->waiting to 0, and re-initializes both wait queues (zwaitqinit on pipe->data and pipe->space) without waking or accounting for threads currently blocked on the pipe. Any thread already pended in kpiperead()/kpipewrite() is left orphaned: still marked pending with pendedon pointing at the cleared wait queue and with stale qnodedlist links into the (now re-initialized) embedded list head.
When such an orphaned waiter is later timed out or woken, the scheduler calls sysdlistremove() on its stale node, writing through dangling prev/next pointers into kernel wait-queue/scheduler structures, causing list corruption (an attacker-driven invalid kernel write), lost wakeups, indefinitely blocked threads, and silent data loss. The flaw lets a deprivileged user thread corrupt the state of a kernel object shared with other threads/partitions.
The fix switches the verifier to KSYSCALLOBJNEVERINIT(), matching the existing kmsgqinit verifier, so a user thread can no longer re-initialize a live pipe. The vulnerable code shipped in v4.1.0 and remained through v4.4.0.
The Zephyr ext2 filesystem driver (subsys/fs/ext2) trusted the on-disk directory entry fields dereclen and denamelen when walking a directory block. ext2fetchdirentry() guarded only with denamelen > EXT2MAXFILENAME, but denamelen is a uint8t and EXT2MAXFILENAME is 255, so the check is always false; the function then memcpy'd up to 255 name bytes and the lookup/readdir paths advanced traversal by an unvalidated dereclen. Each directory block is read into a blocksize-sized slab buffer, and blockoff can be driven near the block end by preceding entries' reclen, so the 8-byte header read and the subsequent name memcpy can read up to ~263 bytes past the end of the block buffer into adjacent heap/slab memory. On the readdir path those bytes are returned to the caller in fsdirent.name, leaking adjacent kernel heap memory; a dereclen of 0 also causes a zero-progress infinite loop (denial of service), and the unlink path's memmove(de, next, nextreclen) over unvalidated records is an additional OOB read/write source. The defect is reached by any path-based operation (open, stat, unlink, rename, mkdir) or directory listing on a mounted ext2 volume, so a crafted or corrupted ext2 image on attacker-supplied storage (SD card, USB mass storage, or otherwise mounted image) triggers it. Affected: Zephyr ext2 from its introduction in v3.5.0 through v4.4.0. The fix validates reclen and namelen in the parser and rejects entries whose header does not fit the remaining block or whose reclen crosses the block boundary in every traversal caller.
The Zephyr PL011 UART driver (drivers/serial/uartpl011.c) contains an unbounded software loop in pl011irqtxenable() that repeatedly invokes the interrupt-driven application callback while the TX interrupt mask bit (PL011IMSCTXIM) is set, to work around the controller's level-transition TX-interrupt behavior.
When CTS hardware flow control is enabled (devicetree hw-flow-control or runtime UARTCFGFLOWCTRLRTSCTS) and the wired serial peer de-asserts CTS, the controller stops draining the TX FIFO; pl011fifofill() then returns 0 on every call while the application still has pending data and therefore never disables the TX interrupt. The loop condition never clears, so the thread that called uartirqtxenable() (e.g. h4send() in the Bluetooth HCI H4 driver) spins indefinitely, hanging the executing context and stalling the transport — a denial of service (CWE-835).
An attacker controlling the device attached to the UART's CTS line can trigger the hang by withholding CTS during transmission. Because that peer is the device wired to the UART — which may be a removable or external module (e.g. an off-board Bluetooth controller on the HCI H4 link) rather than a permanently-bonded on-PCB part — the attack vector is scored Adjacent (AV:A) rather than Physical; the security subcommittee should confirm the vector against the specific deployment. Impact is availability only; there is no memory-safety, confidentiality, or integrity consequence.
The vulnerable loop was introduced in commit b783bc8448ef (Feb 2025) and shipped in releases v4.1.0 through v4.4.0. The fix breaks out of the loop when CTS is blocking and arms the CTS modem-status interrupt to resume transmission when CTS re-asserts.
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 PSA Protected Storage credential backend (subsys/net/lib/tlscredentials/tlscredentialstrusted.c) declared its credential-store mutex as a plain zero-filled static struct kmutex credentiallock; and never called kmutexinit() on it. A statically zero-filled kmutex has an uninitialized wait queue (its dlist head/tail are NULL instead of the self-referential sentinels that kmutexinit/KMUTEXDEFINE install). The uncontended lock path does not touch the wait queue, so the defect is latent and serialized use behaves correctly.
When two execution contexts contend on the lock, kmutexlock() pends the blocking thread on the wait queue via zpendcurr(), which calls sysdlistappend() on the zeroed list and dereferences a NULL tail pointer (tail->next = node), faulting the kernel. The lock is held during TLS handshake credential loading and by all credential add/get/delete operations, so a deployment performing concurrent TLS handshakes (for example a server handling multiple simultaneous connections from a remote peer) or a credential-management operation concurrent with a handshake can trigger the dereference.
The impact is a denial of service: a deterministic kernel panic / device reset on the first contention. There is no memory corruption beyond the NULL dereference and no confidentiality or integrity impact; mutual exclusion on the fast path remains correct. Exposure is limited to builds with CONFIGTLSCREDENTIALSBACKENDPROTECTEDSTORAGE enabled (PSA Protected Storage / TF-M platforms); the default volatile RAM backend initializes its lock correctly and is unaffected.
The fix initializes the mutex statically with KMUTEXDEFINE(credentiallock), providing a valid wait queue so the contended path no longer touches a NULL list.
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 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.
ieee802154decipherdataframe() in subsys/net/l2/ieee802154/ieee802154frame.c computed payloadlen = netpktgetlen(pkt) - llhdrlen - authtaglen without first checking that the received frame is at least llhdrlen + authtaglen bytes long. All three variables are uint8t, so a frame whose payload is shorter than the configured authentication tag makes the subtraction wrap around to a large value (up to 255).
The wrapped length is passed unchanged to ieee802154decryptauth() and on to the CCM operation as cipherpkt.inlen/outbufmax, with apkt->tag pointing at frame + llhdrlen + payloadlen. Because the receive buffer is allocated to the exact length of the frame received from the radio driver, the crypto layer then reads several hundred bytes past the end of the packet buffer and writes the same number of decrypted bytes back over it in place. The frame's authentication tag is only verified after this processing has taken place, so no key material, association or prior authentication is needed — a single crafted short frame from any device in radio range is sufficient. Frame validation in ieee802154validateframe() does not prevent it: a data frame is accepted with a one-byte payload.
The result is an out-of-bounds read and an out-of-bounds write of up to roughly 240 bytes into the adjacent network-buffer pool, corrupting other packets or allocator metadata and typically faulting the target. The out-of-bounds content is not attacker-chosen (it is ciphertext XOR keystream over out-of-bounds memory) and the frame is dropped when tag verification fails, so the primary impact is memory corruption and denial of service rather than information disclosure.
Exposure is limited to configurations that enable the experimental CONFIGNETL2IEEE802154SECURITY option, select a crypto device via CONFIGNETL2IEEE802154SECURITYCRYPTODEVNAME, and have established a security session with a level other than IEEE802154SECURITYLEVELNONE; with security disabled or at level NONE the tag length is zero and no underflow occurs. The fix rejects frames shorter than llhdrlen + authtaglen before the subtraction, and adds the matching guard on the transmit side in ieee802154createdataframe().
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 IPv6 stack derived the long-lived secret key used to compute RFC 7217 stable interface identifiers (genstableiid() in subsys/net/ip/ipv6.c) and RFC 8981 temporary interface identifiers (gentemporaryiid() in subsys/net/ip/ipv6pe.c) with sysrandget(). That API is documented as not cryptographically secure, while both RFCs require this key to be an unpredictable random value of at least 128 bits — the unpredictability of every generated IID rests entirely on it.
Depending on the platform, sysrandget() resolves either to a hardware entropy device or, on boards whose entropy driver sets CONFIGENTROPYNEEDSPRNG, to the non-cryptographic xoshiro128++ generator in subsys/random/randomxoshiro128.c, which keeps a single global 128-bit state shared with every other sysrandget() consumer. Many of those consumers put their output directly on the wire (ephemeral source ports, DNS query IDs, CoAP message IDs and tokens, DHCPv6 transaction IDs, WebSocket masking keys), and the xoshiro state transition is invertible, so an observer of a device's traffic can recover the generator state and reconstruct the IID secret key. Additionally, with the entropy-device backend sysrandget() silently falls back to kcycleget32() samples when the entropy driver fails, and returns no error, so the key could be derived from the cycle counter without the caller noticing.
With the key known, stable IIDs become directly computable for any prefix (the remaining HMAC inputs — prefix, interface index, interface name, DAD counter — are guessable), and temporary IIDs become linkable after a cheap search over the mixed-in kuptimeget32() value. This defeats the properties both features exist to provide: an attacker can track a device across networks and prefixes, and can predict its address inside a /64 without scanning, removing the usual barrier to directly targeting the host. There is no memory-safety, integrity, or availability impact.
The fix generates the key with syscsrandget() and aborts IID generation with -EIO when no cryptographic randomness is available, instead of silently deriving addresses from a predictable key. Both CONFIGNETIPV6IIDSTABLE and CONFIGNETIPV6PE are experimental and disabled by default; builds using CONFIGTESTRANDOMGENERATOR remain predictable by design, as syscsrandget() is deliberately routed back to the test generator there.
The Sierra Wireless HL7800 cellular modem driver (drivers/modem/vendorstandalone/hl7800.c, located at drivers/modem/hl7800.c in v4.4.0 and earlier) parses AT responses with roughly twenty handlers that call netbuflinearize(value, sizeof(value), buf, 0, len) into a 128-byte stack buffer and then write value[outlen] = 0. Because netbuflinearize() (lib/netbuf/buf.c) can return a count equal to its destination-length argument, a field that exactly fills the buffer makes the terminating NUL land one byte past the end, a single-byte out-of-bounds write into adjacent stack memory.
The +KCELLMEAS cell-measurement handler oncmdatcmdinforssi() is worse: it passed the wire length len as the destination size (netbuflinearize(value, len, buf, 0, len)), so a response line longer than 128 bytes overflows the value stack buffer with attacker-influenceable content. The line length comes from netbuffindcrlf(), which accumulates bytes across the whole netbuf fragment chain and is not bounded to 128, so an over-long line reaches the defect.
The data originates from the cellular modem over UART, driven by the network: operator-scan results, +CGCONTRDP IP/DNS info, socket indications, and +KCELLMEAS neighbour-cell reports. An attacker able to shape what the modem emits — a rogue base station, a compromised modem baseband, or a remote peer feeding oversized response framing — can drive a line past 128 bytes. The handlers run in the driver's RX thread in kernel context, so the corruption is kernel-side.
The +KCELLMEAS path is a full stack buffer overflow whose worst case is code execution in kernel context and whose floor is a reliable crash; the remaining sites are single-byte NUL out-of-bounds writes. Exploitation requires the modem to emit an over-long AT response line, giving high attack complexity over an adjacent (cellular radio) vector. The fix passes sizeof(dst) - 1 (and correct explicit bounds for the IMSI and +KCELLMEAS sites) so the terminator always stays in bounds.
The user-mode verification handler for the devicedeinit() system call, zvrfydevicedeinit() in kernel/device.c, validated its dev argument with KSYSCALLOBJINIT(dev, KOBJANY). kobjectvalidate() short-circuits its type comparison when the requested type is KOBJANY, so the check reduced to "this pointer is the base address of some kernel object the calling thread has been granted" — the object's actual type was never compared, and KSYSCALLOBJINIT also skips the initialization-state check. The sibling handlers zvrfydeviceinit() and zvrfydeviceisready() already used KOBJDRIVERANY and were unaffected.
A thread running in user mode can therefore pass any kernel object it holds permission on — most usefully a thread stack object obtained from the kthreadstackalloc() syscall or a statically defined KTHREADSTACK it was granted in order to spawn a child user thread — whose backing memory is writable from user mode. zimpldevicedeinit() then interprets those attacker-written bytes as a struct device: it dereferences the state pointer read out of the object, calls the function pointer read out of ops.deinit, and on success writes through state again. The result is an indirect call to an arbitrary address executed in supervisor mode, plus an arbitrary kernel read and a single-byte kernel write.
Exploitation gives a local unprivileged thread full kernel code execution, defeating the CONFIGUSERSPACE isolation boundary entirely; a less precise attempt yields a supervisor-mode fault and a system crash. The defect is only reachable in builds that enable both CONFIGUSERSPACE and CONFIGDEVICEDEINITSUPPORT — with de-initialization support disabled, zimpldevicedeinit() returns -ENOTSUP without ever dereferencing the pointer. In v4.2.x and v4.3.x, CONFIGDEVICEDEINITSUPPORT defaulted to y, so every CONFIGUSERSPACE build of those releases is exposed unless the option was explicitly turned off. From v4.4.0 the option is opt-in (no default, and not selected by any in-tree subsystem), so a v4.4.x build is exposed only if it enables the option explicitly. The v4.2 line is no longer maintained and receives no backport.
The fix changes the object check to KOBJDRIVERANY, which constrains the argument to the build-generated driver object type range (KOBJDRIVERFIRST..KOBJDRIVERLAST) — the real struct device instances placed by the linker — so the state and ops.deinit fields are once again kernel-controlled.
The I3C IBI subsystem in drivers/i3c/i3cibiworkq.c hands out statically-allocated work nodes through a free-list i3cibiworknodesfree implemented as a plain sysslistt, which provides no synchronization. The allocation helpers (i3cibiworkenqueue, i3cibiworkenqueuetargetirq, i3cibiworkenqueuehotjoin, i3cibiworkenqueuecontrollerrequest, i3cibiworkenqueuecb) called sysslistget() directly from ISR context, while the workqueue handler i3cibiworkhandler() returned nodes with sysslistappend() from the workqueue thread, with no lock on either side.
Because sysslistget() and sysslistappend() are neither atomic nor interrupt-safe, an IBI interrupt that fires while the workqueue thread is mid-append (or a truly parallel access under CONFIGSMP) races on the shared list. This corrupts the list linkage: a node may be handed to two consumers, a node may be lost, or the head/tail pointers may be left inconsistent so sysslistget() returns a stale or garbage pointer. In the double-hand-out case the subsequent memcpy(ibinode, ibiwork, sizeof(ibinode)) overwrites a node still in flight; a garbage pointer turns the same memcpy into an out-of-bounds write.
The race is driven by I3C bus traffic — IBIs, hot-joins, and controller-role requests originate from target devices on the bus, and I3C supports hot-joining devices. An attacker controlling an I3C peripheral on the board's chip-to-chip bus can generate high-frequency interrupts timed to collide with the free operation. Exploitation requires physical access to the bus and winning a narrow timing window; the most realistic impact is a crash or hang (denial of service), with memory corruption possible but hard to control.
The fix wraps all free-list sysslistget()/sysslistappend() operations in the new ibiworkalloc()/ibiworkfree() helpers, each guarded by a kspinlock (ibiworklock), closing the race across ISR and thread contexts.
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 experimental USB host stack allocates a per-device configuration-descriptor buffer, udev->cfgdesc, from the dedicated usbdeviceheap in usbhdevicesetconfiguration() (subsys/usb/host/usbhdevice.c). On three failure paths — a failed full-length GETDESCRIPTOR(CONFIGURATION) read, a mismatch between the short and full descriptor reads, and a rejected descriptor in parseconfigurationdescriptor() — the buffer was released with kheapfree() but the pointer was left dangling. The cleanup in usbhdevicefree() is guarded only by if (udev->cfgdesc != NULL), so it frees the same block a second time.
The path is driven entirely by the attached peripheral: usbhdeviceconnect() calls usbhdeviceinit(), which ends in usbhdevicesetconfiguration(), and on failure usbhdeviceconnect() calls usbhdevicefree(). On v4.4.x this happens during the same enumeration, with no unplug required; on v4.1.0–v4.3.x the second free instead arrives via devremovedhandler()/devconnectedhandler() in subsys/usb/host/usbhcore.c, so it requires a removal or duplicate-connect event after the failed enumeration — a sequence the attached device fully controls. A malicious or malformed USB device only has to answer the first 9-byte configuration-descriptor request with a well-formed header and then fail any of the three checks, for example by returning a full descriptor whose interface count disagrees with bNumInterfaces, or by answering the second read with different bytes.
The result is a double free on usbdeviceheap. On builds where lib/heap hardening is active (the current default CONFIGSYSHEAPHARDENINGBASIC), sysheapfree() detects the already-free chunk and calls kpanic(), giving a deterministic, peripheral-triggered denial of service of the USB host. On builds without that detection — earlier releases, or CONFIGSYSHEAPHARDENINGNONE — the second free manipulates a chunk already on the free list, corrupting the heap's free list so that later allocations can return overlapping or invalid blocks.
Exploitation beyond denial of service is bounded by the fact that usbdeviceheap is a small dedicated heap (CONFIGUSBHUSBDEVICEHEAP, default 1024 bytes) whose only client is this descriptor buffer, and by CONFIGUSBHOSTSTACK being marked experimental and disabled by default. The fix sets udev->cfgdesc = NULL after every kheapfree(), making the cleanup guard sound.
The ARM64 MMU back-end allocated address space identifiers (ASIDs) for memory domains with a bare round-robin counter in archmemdomaininit() (arch/arm64/core/mmu.c). VMASIDBITS is 8, so only 255 ASIDs exist; once the counter wrapped, archmemdomaininit() could hand an ASID to a new domain while a still-live domain held the same one. Domain-private mappings are installed non-global (MTNG), so the ASID is the only tag separating one domain's cached translations from another's in the TLB.
The context-switch path in zarm64swapptables() only flushes the TLB when the outgoing and incoming domains carry the same ASID, which does not cover a duplicate reached through a third domain: for domains A and C sharing an ASID and an unrelated domain B, the schedule A -> B -> C never takes the flush branch, so the ASID-tagged entries A populated remain resident while C runs. Under SMP two live domains sharing an ASID can additionally be resident on two CPUs at once, which the architecture does not allow for distinct translation-table sets.
Triggering the wrap requires a CONFIGUSERSPACE application on ARM64 that creates more than 255 memory domains over its lifetime; kmemdomaininit() and kmemdomaindeinit() are supervisor-only APIs and are not exposed as syscalls, so an unprivileged thread cannot drive the counter directly. Once two live domains alias, however, a user-mode thread in one domain can read and write memory belonging to the other domain's partitions and thread stacks with that domain's permissions, defeating the memory-domain isolation boundary.
The fix scans the live domainlist before assigning an ASID, advances the round-robin counter past ASIDs already in use, and returns -ENOMEM when all are taken, so domain creation fails closed instead of silently aliasing.
The Bluetooth Classic (BR/EDR) L2CAP receive handler btl2capbrrecv() in subsys/bluetooth/host/classic/l2capbr.c dispatched inbound data PDUs based only on the destination channel ID, without checking that the target channel had reached the BTL2CAPCONNECTED state. A dynamic channel is assigned its RX CID and added to the connection's channel list while still in BTL2CAPCONNECTING (and later BTL2CAPCONFIG) — before configuration completes and, for PSMs that require security, before the peer is authenticated (l2capbrconnreq()).
Because the channel is already findable by btl2capbrlookuprxcid() during this window, a remote peer within radio range can send a data PDU addressed to that CID and have it processed on a not-yet-established channel. The dispatch keys off channel fields (BRCHAN(chan)->rx.mode, rx.mps) that are only initialized during configuration by l2capbrconf(); since channel objects are pooled and btl2capbrchandel() does not reset rx.mode or the reassembly buffer sdu, a reused channel can carry stale state into the CONNECTING window and route the frame into the retransmission/flow-control path (btl2capbrretfcrecv()) with stale parameters and a possibly stale sdu pointer.
The impact is delivery of attacker data to upper-layer protocol handlers on a half-open (and possibly unauthenticated) channel, plus operation on stale or partially initialized channel state on reused channel objects — leading to channel/link teardown (denial of service) and, in the stale-sdu case, a dangling-pointer condition. The fix adds an explicit BRCHAN(chan)->state < BTL2CAPCONNECTED guard that drops any data received before the channel is fully connected.