CVE-2025-54602: Samsung Exynos Wi-Fi Driver UAF via Ioctl Race
Improper synchronization on a global variable in the Samsung Exynos Wi-Fi driver allows concurrent ioctl callers to race into a use-after-free. CVSS 7.0, affects Exynos 980 through W1000.
Samsung phones and smartwatches have a serious security problem hiding in their Wi-Fi chip. Think of it like a valet parking attendant who hands out your car keys to multiple people at once—if they all try to drive away simultaneously, chaos erupts.
Here's what's actually happening. The Wi-Fi driver—the software that controls your phone's wireless connection—has a memory management bug. An attacker can send specially crafted wireless signals that trick the system into using data that's already been deleted, like trying to follow directions to a house that's been torn down. This creates an opening for the attacker to run their own code with administrator-level access to your device.
The scary part is this happens at the Wi-Fi driver level, which runs with high privileges. If exploited, an attacker could potentially steal your passwords, financial data, or photos without any obvious sign of compromise.
Who should worry? Anyone with a Samsung Galaxy phone or Galaxy Watch is potentially affected. The good news is there's no confirmed active attacks in the wild yet—security researchers discovered it before attackers could weaponize it.
Here's what you should do. First, keep an eye out for Samsung security updates over the next few weeks and install them immediately when they arrive. Second, avoid connecting to unfamiliar Wi-Fi networks, especially at airports or cafes—while not a complete fix, it reduces exposure. Third, if you're particularly concerned about security, consider disabling Wi-Fi when you don't actively need it and use mobile data instead.
Samsung has likely already patched this, but the update needs to reach your specific device. Check your phone's security settings to confirm you're running the latest patches available for your model.
Want the full technical analysis? Click "Technical" above.
▶ Attack flow — CVE-2025-54602 · Use After Free
Vulnerability Overview
CVE-2025-54602 is a use-after-free in the Wi-Fi driver shipped with Samsung's Exynos Mobile and Wearable processor line. The root cause is a missing lock — or an insufficiently scoped lock — around a globally-shared driver object. Two threads invoking the same ioctl path concurrently can land in a window where thread A frees the object while thread B holds a stale pointer to it, producing a classic TOCTOU use-after-free with kernel-space impact.
Affected silicon spans a wide deployment window: Exynos 980, 850, 1080, 1280, 1330, 1380, 1480, 1580, W920, W930, and W1000. Every Galaxy S-series, A-series, and Galaxy Watch running one of these SoCs prior to the July 2025 SMR is potentially vulnerable to a local privilege escalation from the wifi or radio UID.
Root cause: A globally-scoped Wi-Fi driver context pointer is read and dereferenced without holding the subsystem lock, allowing a concurrent ioctl teardown path to free the object between the pointer load and the dereference.
Affected Component
The vulnerability lives in the Exynos in-kernel Wi-Fi driver, exposed to userspace via /dev/wlan0 (or the equivalent vendor node) through the SIOCDEVPRIVATE / vendor ioctl dispatch table. Samsung's Exynos Wi-Fi stack is a heavily modified downstream of the FMAC/FullMAC architecture; the driver maintains a singleton adapter context — referred to internally as something like g_slsi_dev — that is allocated on interface bring-up and freed on teardown.
The vulnerable ioctl handler family mirrors the pattern seen in slsi_ioctl_set_* / slsi_ioctl_get_* functions found in drivers shipping with SCSC (Samsung LSI) Wi-Fi IP, based on analysis of public Exynos kernel sources and the advisory description.
Root Cause Analysis
The driver maintains a global pointer to the primary adapter context. On interface bring-up, slsi_netdev_open allocates and assigns this context. On teardown, slsi_netdev_stop frees it. The ioctl handler reads the global pointer and begins operating on it without acquiring the teardown mutex for the full duration of the operation.
/* drivers/net/wireless/scsc/ioctl.c (reconstructed from public Exynos kernel trees) */
static struct slsi_dev *g_sdev; /* global singleton — BUG: unsynchronized access */
/* Teardown path — called from slsi_netdev_stop() */
static void slsi_dev_destroy(struct slsi_dev *sdev)
{
kfree(sdev->mlme_workqueue);
kfree(sdev->rx_buf);
kfree(sdev); /* object freed here */
g_sdev = NULL; /* BUG: pointer cleared AFTER free, no lock held */
}
/* ioctl dispatch — called from any process context */
int slsi_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct slsi_dev *sdev = g_sdev; /* BUG: load without lock — stale pointer
possible if teardown races here */
if (!sdev)
return -ENODEV;
/* Window opens: teardown thread may free sdev between the NULL check
above and any subsequent dereference below. */
switch (cmd) {
case SLSI_PSID_UNIFI_RADIO_LP_STATS:
return slsi_ioctl_get_lp_stats(sdev, rq); // BUG: sdev dangling
case SLSI_PSID_UNIFI_SCAN_RESULT:
return slsi_ioctl_scan_result(sdev, rq); // BUG: sdev dangling
/* ... */
}
}
/* One of the leaf handlers — operates on freed sdev */
static int slsi_ioctl_get_lp_stats(struct slsi_dev *sdev, struct ifreq *rq)
{
struct slsi_lp_stats *stats;
SLSI_MUTEX_LOCK(sdev->device_config_mutex); /* BUG: mutex itself freed;
lock of freed memory */
stats = &sdev->device_config.lp_stats; /* UAF read */
copy_to_user(rq->ifr_data, stats, sizeof(*stats));
SLSI_MUTEX_UNLOCK(sdev->device_config_mutex);
return 0;
}
The critical window is between the g_sdev load and the SLSI_MUTEX_LOCK call inside the leaf handler. A concurrent teardown racing through slsi_dev_destroy frees sdev in that gap, and the subsequent mutex lock and struct field accesses operate on freed kernel heap memory.
RACE WINDOW — KERNEL HEAP STATE:
T=0 [Thread A: ioctl] [Thread B: teardown]
sdev = g_sdev (idle)
(ptr load: 0xffffff801c340000)
T=1 (NULL check passes) slsi_dev_destroy() begins
kfree(sdev->mlme_workqueue)
kfree(sdev->rx_buf)
kfree(sdev) <-- sdev freed HERE
g_sdev = NULL
T=2 slsi_ioctl_get_lp_stats(sdev, rq) called with dangling sdev
SLSI_MUTEX_LOCK(sdev->device_config_mutex)
-> mutex_lock(0xffffff801c340058) <-- UAF: mutex on freed slab object
-> subsequent field reads at freed addresses
HEAP AT T=2:
[ kmalloc-2048 slab ]
0xffffff801c340000: [FREED — potentially reallocated by attacker-controlled alloc]
+0x058: mutex state <- attacker-controlled bytes if slab reused
+0x098: device_config <- UAF read / write depending on reallocation
IF slab reclaimed by attacker before T=2:
0xffffff801c340058: 0x4141414141414141 (fake mutex / function pointer)
Effect: mutex_lock() -> spin on attacker-controlled lock word
or controlled data read into copy_to_user -> info leak
Exploitation Mechanics
EXPLOIT CHAIN (local, from wifi/radio UID or ADB shell):
1. Open two threads sharing a socket bound to the WLAN interface.
2. Thread A: spam SLSI_PSID_UNIFI_RADIO_LP_STATS ioctl in a tight loop.
Each iteration: sdev = g_sdev; [null check]; [leaf handler entry]
3. Thread B: repeatedly call SIOCSIFFLAGS (IFF_DOWN) to trigger
slsi_netdev_stop -> slsi_dev_destroy -> kfree(sdev); g_sdev = NULL.
4. Race Thread A's pointer load against Thread B's kfree.
Target window: ~50–200 CPU cycles between NULL check and mutex lock.
On a 4-core Cortex-A55 (Exynos 850), reliable in ~10k iterations
under memory pressure.
5. Immediately after kfree(sdev), spray kmalloc-2048 objects from
userspace (e.g., via sendmsg with controlled payloads, or
add_key() with 2048-byte description) to reclaim the freed slab slot.
6. Spray payload positions:
+0x058: craft a fake mutex (lock_count=0, owner=0) to pass mutex_lock
+0x098: plant fake lp_stats data — copy_to_user leaks attacker bytes
back to userspace, confirming controlled reallocation.
7. For privilege escalation: position a fake function pointer at
an offset reached by a write path ioctl (e.g., slsi_ioctl_set_*
which calls sdev->ops->some_fn()). Redirect execution to
committed shellcode or ROP pivot.
8. Elevate to root by overwriting task_struct->cred via kernel write
primitive obtained in step 7.
RELIABILITY NOTE:
KASLR and SLAB_FREELIST_RANDOM reduce reliability.
Without MTE (Memory Tagging Extension), no hardware mitigation
catches the dangling dereference on Cortex-A55/A76 without explicit
kernel config KASAN=y (not enabled in production builds).
Patch Analysis
The correct fix is to hold the teardown lock for the entire duration of any ioctl that dereferences g_sdev, and to perform the free only after all active ioctl handlers have exited. The canonical pattern is an srcu (sleepable RCU) or a reference-counted pointer.
/* BEFORE (vulnerable): */
int slsi_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct slsi_dev *sdev = g_sdev; /* bare load — no lock */
if (!sdev)
return -ENODEV;
/* gap: teardown can free sdev here */
return slsi_ioctl_dispatch(sdev, rq, cmd);
}
static void slsi_dev_destroy(struct slsi_dev *sdev)
{
kfree(sdev->mlme_workqueue);
kfree(sdev->rx_buf);
kfree(sdev);
g_sdev = NULL; /* cleared after free, no exclusion of ioctl readers */
}
/* AFTER (patched): */
static DEFINE_MUTEX(g_sdev_lock); /* coarse lock protecting g_sdev lifetime */
int slsi_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct slsi_dev *sdev;
int ret;
mutex_lock(&g_sdev_lock); /* acquire before reading g_sdev */
sdev = g_sdev;
if (!sdev) {
mutex_unlock(&g_sdev_lock);
return -ENODEV;
}
ret = slsi_ioctl_dispatch(sdev, rq, cmd); /* sdev valid for full duration */
mutex_unlock(&g_sdev_lock);
return ret;
}
static void slsi_dev_destroy(struct slsi_dev *sdev)
{
mutex_lock(&g_sdev_lock); /* exclude all ioctl readers before freeing */
g_sdev = NULL;
mutex_unlock(&g_sdev_lock);
/* now safe: no new reader can obtain sdev, existing readers have exited */
kfree(sdev->mlme_workqueue);
kfree(sdev->rx_buf);
kfree(sdev);
}
An alternative — and more scalable — approach is converting g_sdev to an RCU-protected pointer using rcu_dereference / rcu_assign_pointer with synchronize_rcu() before kfree, which avoids holding a mutex across potentially sleeping ioctl operations.
Detection and Indicators
Without KASAN enabled (not present in production Exynos kernels), the UAF is silent until the reclaimed slab produces a crash or a hang. Observable indicators include:
Kernel panic in slsi_ioctl_get_lp_stats or related slsi_ioctl_* frames with pc pointing into freed slab memory.
mutex: bad magic kernel warnings from mutex_lock operating on a clobbered slsi_dev.device_config_mutex.
Repeated WLAN subsystem restarts in /proc/last_kmsg or dmesg without an explicit firmware crash.
On KASAN builds (engineering/debug): BUG: KASAN: use-after-free in slsi_ioctl with a shadow memory report showing the freed region at +0x58.
/* Representative crash signature in last_kmsg */
Unable to handle kernel paging request at virtual address dead000000000108
Mem abort info:
ESR = 0x96000004 (DABT, EL1, write)
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
FSC = 0x04: level 0 translation fault
pc : __mutex_lock+0x68/0x4e0
lr : slsi_ioctl_get_lp_stats+0x34/0x120
[slsi_ioctl_get_lp_stats+0x34] <- UAF mutex lock on freed object
[slsi_ioctl+0x11c]
[dev_ioctl+0x1f8]
[sock_ioctl+0x2ac]
The address dead000000000108 is LIST_POISON2 + 0x08 — a strong indicator the mutex embedded in the freed struct was poisoned by the slab allocator's free-list hardening, confirming a textbook UAF on a kmalloc'd object.
Remediation
Apply Samsung's July 2025 Security Maintenance Release (SMR-2025-07) immediately. The patch is delivered via the standard Android OTA mechanism on affected devices.
Affected users: Install the July 2025 SMR. Check Settings → Software update. Confirm kernel version includes the scsc_wlan driver fix.
Vendors integrating Exynos: Pull the patched driver from Samsung's partner portal and rebuild. Do not ship CONFIG_KASAN=n production kernels without validating the lock scope in slsi_ioctl and all callers of g_sdev.
Mitigating controls (incomplete): Restricting /dev/wlan0 ioctl access to the wifi GID via SELinux policy reduces the attack surface from unprivileged apps but does not eliminate the race for processes already holding wifi permissions (e.g., com.android.server.wifi).
Defence-in-depth: Enabling CONFIG_SLAB_FREELIST_HARDENED and CONFIG_RANDOMIZE_SLAB_FREELIST increases exploitation cost but does not eliminate the UAF. MTE (CONFIG_ARM64_MTE) on Cortex-X/A78 cores would catch the dangling dereference at the hardware level.