ZEPHYR · CVE-2026-87038 · Runtime
FIX → 4.3.2 · 4.5.0 ✓ verified
The Zephyr I3C transfer system call checks the caller-supplied device descriptor in user memory, then reads the bus field again when it is used, so a second thread can swap it in between. A user thread can then make the kernel call an attacker-chosen function table in supervisor mode, giving kernel-mode code execution.
first public Nov 4, 2026 · 2 sources
Running Zephyr? Find out in 2 minutes.
Tell InfraStack what you run. It checks this one, and every next one, against your stack.
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
Zephyr 3.2.0 to 4.3.1 and 4.4.x builds with CONFIG_USERSPACE and an I3C controller driver. The attacker needs a user thread granted an I3C device object plus a second thread in the same memory domain. Builds without userspace or without an I3C driver can skip it.
Not affected if no-i3c-driver, no-userspace
Who cares embedded, i3c, userspace, zephyr
Upgrade to 4.3.2 · 4.5.0
4.3.2 (3.2.0–4.3.1 line) or 4.5.0 (4.4.x line)The user-mode verification handler for the i3c_transfer() system call, z_vrfy_i3c_transfer() in drivers/i3c/i3c_handlers.c, validated the caller-supplied struct i3c_device_desc in place instead of snapshotting it. It confirmed only that the descriptor was readable by the calling thread (K_SYSCALL_MEMORY_READ()), read target->bus once to check it with K_SYSCALL_OBJ(), and then passed the caller's live pointer down. The implementation z_impl_i3c_transfer() re-reads target->bus from that user memory and dispatches through it as DEVICE_API_GET(i3c, target->bus)->i3c_xfers(...), so the bus that was validated is not necessarily the bus that is used. DEVICE_API_GET() performs no runtime type check outside CONFIG_DEVICE_API_ASSERT builds.
On a build with CONFIG_USERSPACE and an I3C controller driver, a user thread that has been granted an I3C device object can exploit this with a second thread in the same memory domain: one thread calls i3c_transfer() while the other rewrites the bus field of the descriptor. The window between the check and the use spans the num_msgs validation, the message-array access check, a stack VLA allocation and a memcpy() of up to 32 struct i3c_msg, so the race is repeatable and can simply be retried until it lands.
When the substitution succeeds, the kernel dereferences an attacker-chosen pointer as a struct device, loads a function pointer from the attacker-controlled api table and calls it in supervisor mode with attacker-influenced arguments. This yields kernel-mode code execution from an unprivileged user thread — a complete escape of the userspace memory-protection sandbox; lesser outcomes are an arbitrary two-level kernel read or a kernel fault. Builds without CONFIG_USERSPACE, or without an I3C driver, are unaffected.
The fix copies the descriptor into a kernel-stack local with k_usermode_from_copy(), validates the bus in that copy, and dispatches on the copy, so the validated value is provably the value used.
This page as .md · .json · in your agent: get_signal("CVE-2026-87038") · CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.