---
id: "CVE-2026-85035"
kind: "vulnerability"
aliases: ["CVE-2026-85035"]
title: "Zephyr: CAN filter keeps freed message queue pointer, enabling kernel heap corruption"
severity: "high"
cvss: 8.8
epss: null
exploited: false
status: "open"
category: "runtime"
products: []
ecosystems: []
tags: ["can-bus", "embedded", "runtime", "userspace", "zephyr"]
not_affected_if: ["no-can-driver", "no-dynamic-objects", "no-userspace", "static-msgq-only"]
published_at: "2026-11-03T14:17:39+00:00"
first_seen_at: "2026-11-03T14:21:08+00:00"
updated_at: "2026-11-03T14:22:05+00:00"
sources: [{"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85035", "first_seen_at": "2026-11-03T14:21:08+00:00"}]
---

# Zephyr: CAN filter keeps freed message queue pointer, enabling kernel heap corruption

Zephyr's CAN receive filter API can be given a dynamically allocated message queue that a user-mode thread can free while the CAN driver still holds a pointer to it. When a matching CAN frame arrives, the kernel writes into freed memory, which lets a user-mode thread corrupt kernel heap and escape the isolation that CONFIG_USERSPACE provides.

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

## How to check

- Check whether the build sets `CONFIG_USERSPACE=y` and `CONFIG_DYNAMIC_OBJECTS=y` together with a CAN driver.
- Search the application for calls to `can_add_rx_filter_msgq()` and confirm the queues are defined with `CAN_MSGQ_DEFINE()`, not allocated at runtime.
- Check the Zephyr version in use against the fixed release named in the patch.

## Fix

- Zephyr: move to the release that includes the patch linked in the sources (version not stated in the sources provided).

## Timeline

- 2026-11-03T14:21:08+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-85035

## Documents

- [advisory] Vendor advisory: github.com https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-6pxv-r7ww-3g2q
- [fix] Patch: github.com https://github.com/zephyrproject-rtos/zephyr/commit/0114bee266211c72a49f3b6a914d3eaf821dd460

## From the sources

The can_add_rx_filter_msgq() system call installs a CAN RX filter whose callback (can_msgq_put() in drivers/can/can_common.c) is handed the caller's struct k_msgq * as user_data. 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 z_vrfy_can_add_rx_filter_msgq() in drivers/can/can_handlers.c validated the queue only with K_SYSCALL_OBJ(), 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 CONFIG_USERSPACE=y and CONFIG_DYNAMIC_OBJECTS=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: k_object_alloc(K_OBJ_MSGQ) followed by k_msgq_alloc_init(). 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 k_object_release() — or simply terminates — and unref_check() frees the struct k_msgq storage while the CAN driver still holds the pointer.

The next matching CAN frame causes k_msgq_put() 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 write_ptr, and the queue's wait_q (and poll_events under CONFIG_POLL) list nodes are manipulated. This yields kernel-heap corruption reachable from a user-mode thread, defeating the isolation CONFIG_USERSPACE is intended to provide; the same dangling pointer also arises benignly when an application thread exits without calling can_remove_rx_filter().

The fix validates the queue with K_SYSCALL_OBJ_STATIC(), which additionally rejects objects carrying K_OBJ_FLAG_ALLOC, so only statically defined queues (CAN_MSGQ_DEFINE()) may be registered from user mode. Supervisor-mode callers bypass the verifier and must themselves keep the queue valid until the filter is removed.

---

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