(CVE-2025-23099) Samsung Exynos NPU Driver Out-of-Bounds Write Leading to Privilege Escalation

CVE: CVE-2025-23099

Affected Versions: Samsung Galaxy S24+ (samsung/e2sxxx/e2s:14/UP1A.231005.007/S926BXXS3AXGD:user/release-keys); Samsung Exynos 1480, 2400

CVSS3.1: 7.8 (High) — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Summary

Product Samsung Exynos NPU Driver
Vendor Samsung
Severity High — a local attacker within untrusted_app SELinux context may exploit this to achieve local privilege escalation
Affected Versions Samsung Galaxy S24+ (Android 14); Exynos 1480, 2400
Tested Versions Samsung Galaxy S24+ (S926BXXS3AXGD)
CVE Identifier CVE-2025-23099
CVE Description A missing length check in the Samsung Exynos NPU driver leads to out-of-bounds writes, exploitable for local privilege escalation
CWE Classification(s) CWE-787: Out-of-Bounds Write

CVSS3.1 Scoring System

Base Score: 7.8 (High) Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Local
Attack Complexity (AC) Low
Privileges Required (PR) Low
User Interaction (UI) None
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Product Background

The Samsung Galaxy S24+ features a Neural Processing Unit (NPU) integrated into either the Snapdragon 8 Gen 3 or Exynos 2400 chipset depending on the market. The NPU accelerates on-device AI workloads including camera processing, real-time translation, and generative photo editing. The NPU kernel driver manages session state, memory allocation, and queue management on behalf of userspace applications.

Technical Details

The out-of-bounds write exists in the NPU queue management logic within npu_queue_prepare and npu_queue_alloc.

When npu_queue_prepare is called, it allocates the input and output queues via npu_queue_alloc, then marks them as allocated by setting NPU_QUEUE_STATE_ALLOC:

int npu_queue_prepare(struct npu_queue *queue, struct vs4l_container_bundle *cbundle)
{
    ...
    ret = npu_queue_alloc(inqueue, cbundle->m[0].clist);
    if (ret) {
        npu_err("npu_queue_alloc(in) is fail(%d)\n", ret);
        goto p_err;
    }

    ret = npu_queue_alloc(otqueue, cbundle->m[1].clist);
    if (ret) {
        npu_err("npu_queue_alloc(out) is fail(%d)\n", ret);
        goto p_err;
    }
    set_bit(NPU_QUEUE_STATE_ALLOC, &inqueue->state);
    set_bit(NPU_QUEUE_STATE_ALLOC, &otqueue->state);

npu_queue_alloc skips allocation entirely if NPU_QUEUE_STATE_ALLOC is already set:

int npu_queue_alloc(struct npu_queue_list *queue_list, struct vs4l_container_list *c)
{
    int ret = 0, i = 0;
    struct nq_container *temp_container;
    struct nq_buffer *temp_buffer;
    struct nq_buffer *temp_buffer_pool;

    if (test_bit(NPU_QUEUE_STATE_ALLOC, &queue_list->state))
        return ret;

When npu_queue_prepare is called a second time with a larger c->count or containers[i]->count than used in the first call, npu_queue_alloc returns early without reallocating. Execution then continues into npu_queue_mapping, which processes the queue using the original (smaller) allocation while trusting the new (larger) user-supplied counts. This corrupts queue_list->count and q_container->count with attacker-controlled values, leading to an out-of-bounds write in kernel memory.

The vulnerability is reachable from the untrusted_app SELinux context, making it exploitable by a malicious third-party application with no special privileges.

Credit

Billy Jheng Bing Jhong, Muhammad Alifa Ramdhan and Pan ZhenPeng of STAR Labs SG Pte. Ltd.

Timeline