---
id: "CVE-2026-87039"
kind: "vulnerability"
aliases: ["CVE-2026-87039"]
title: "Zephyr: I2S unconfigure path lets a user thread reach kernel memory"
severity: "high"
cvss: 7.8
epss: null
exploited: false
status: "open"
category: "runtime"
products: []
ecosystems: []
tags: ["embedded", "i2s", "runtime", "userspace", "zephyr"]
not_affected_if: ["default-config", "no-i2s-device", "no-userspace"]
published_at: "2026-11-04T06:17:06+00:00"
first_seen_at: "2026-11-04T06:25:20+00:00"
updated_at: "2026-11-04T06:25:56+00:00"
sources: [{"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-87039", "first_seen_at": "2026-11-04T06:25:20+00:00"}]
---

# Zephyr: I2S unconfigure path lets a user thread reach kernel memory

A user-mode thread calling the I2S configure API with a zero frame clock can pass an unchecked pointer to the kernel, which the LiteX I2S driver later uses as a memory slab. An attacker gets an arbitrary kernel read and write primitive, breaking the user-space isolation boundary.

_Summary, check and fix written by a model from the sources; labelled as such._

## How to check

- Check whether the build enables `CONFIG_USERSPACE` and includes an I2S driver such as `drivers/i2s/i2s_litex.c`.
- Look for a custom or out-of-tree I2S driver that keeps the caller's `struct i2s_config` on the unconfigure path.

## Fix

- Zephyr fixed versions are not stated in the sources provided; see the patch link for the fix commit.

## Timeline

- 2026-11-04T06:25:20+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-87039

## Documents

- [advisory] Vendor advisory: github.com https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-g43m-x6vj-5cg2
- [fix] Patch: github.com https://github.com/zephyrproject-rtos/zephyr/commit/e9898c4d22f69575d9b5f297b3df130ceaff1b8f

## From the sources

The user-mode syscall verifier z_vrfy_i2s_configure() in drivers/i2s/i2s_handlers.c copies the caller's struct i2s_config in from user memory and then, when cfg->frame_clk_freq is 0 (the API's "unconfigure the stream" request), jumps straight to z_impl_i2s_configure() without performing the K_SYSCALL_OBJ(config.mem_slab, K_OBJ_MEM_SLAB) ownership/type check or the block_size sanity check. The mem_slab field is a struct k_mem_slab *, so on that path a completely unvalidated, user-chosen pointer reaches driver code that is entitled to trust it as a real kernel object.

Any thread running under CONFIG_USERSPACE that has access to an I2S device object can trigger this with a single i2s_configure() call. Most in-tree drivers special-case frame_clk_freq == 0 and never store the caller's structure, but drivers/i2s/i2s_litex.c does not: i2s_litex_configure() only logs a warning for a mismatched frame clock, then memcpy()s the caller's config into stream->cfg and marks the stream I2S_STATE_READY. A subsequent i2s_trigger() with I2S_TRIGGER_START enables the FIFO interrupt, and the interrupt handlers i2s_litex_isr_rx() / i2s_litex_isr_tx() then call k_mem_slab_alloc() and k_mem_slab_free() on the attacker-supplied pointer in supervisor context.

The result is an arbitrary-pointer read/write primitive executed by the kernel on behalf of an unprivileged thread — k_mem_slab_alloc() takes a spinlock at slab->lock and reads and writes slab->free_list and the link word inside the returned "block" — which defeats the CONFIG_USERSPACE isolation boundary entirely. The same missing validation is a hazard for out-of-tree I2S drivers that retain the configuration on the unconfigure path. Builds without CONFIG_USERSPACE are unaffected, since the verifier is not used.

The fix zeroes config.mem_slab and config.block_size on the unconfigure path so the driver can never receive an unvalidated pointer, while still allowing a stream to be unconfigured by a caller that does not own a memory slab, as the API documents.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
