Re: [PATCH 4/9] MIPS: TXX9: rbtx4927: Use GPIO lookup table for TXx9 LEDs

From: XIAO WU

Date: Sun Jun 28 2026 - 15:24:42 EST


Hi,

I came across the Sashiko AI review of this series and reproduced the
use-after-free that it flagged in rbtx4927_gpioled_init() — a KASAN
slab-use-after-free triggers when platform_device_add() fails on the
error path.

The Sashiko review is at:
https://sashiko.dev/#/patchset/cover.1782389357.git.geert@xxxxxxxxxxxxxx

In rbtx4927_gpioled_init() (arch/mips/txx9/rbtx4927/setup.c), this
patch introduces a GPIO lookup table that references the platform
device name.  The error path looks like:

```c
pdev = platform_device_alloc("leds-gpio", 0);
if (!pdev)
    return;
pdev->dev.platform_data = &pdata;
if (platform_device_add(pdev))
    platform_device_put(pdev);       // frees pdev on error!
rbtx4927_gpioled_table.dev_id = dev_name(&pdev->dev);  // UAF!
```

If platform_device_add() fails (e.g., because a device with the same
name already exists in the platform bus), platform_device_put() drops
the last reference and frees the struct platform_device.  The code then
unconditionally calls dev_name(&pdev->dev) on the freed pointer, and
assigns the dangling pointer into the global GPIO lookup table.

This is a classic use-after-free: the freed memory can be reallocated
and overwritten, and the GPIO lookup table will later dereference a
dangling or corrupted dev_id pointer.

For comparison, the iocled equivalent introduced in patch 6 handles
this correctly with a goto:

```c
if (platform_device_add(pdev))
    goto out_pdev;   // skips dev_name()
txx9_iocled_table.dev_id = dev_name(&pdev->dev);
...
out_pdev:
    platform_device_put(pdev);
```

=== Reproduction ===

Kernel: 7.1.0-next-20260623-gaca8efd71d03-dirty #3 PREEMPT(full)
Arch:   x86_64 (QEMU Standard PC Q35 + ICH9, 2009)
Config: CONFIG_KASAN=y

The UAF pattern is reproduced via a kernel module that mirrors the
same platform_device_add / dev_name error path sequence.  The module
loads at boot via late_initcall.

=== Crash Log ===

[   18.070459][    T1] BUG: KASAN: slab-use-after-free in poc_uaf_init+0x25b/0x270
[   18.071324][    T1] Read of size 8 at addr ffff8880300f5060 by task swapper/0/1
[   18.072437][    T1] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.1.0-next-20260623-gaca8efd71d03-dirty #3
[   18.072448][    T1] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009)
[   18.072452][    T1] Call Trace:
[   18.072455][    T1]  <TASK>
[   18.072458][    T1]  dump_stack_lvl+0x116/0x1f0
[   18.072466][    T1]  print_report+0xf4/0x600
[   18.072497][    T1]  kasan_report+0xe0/0x110
[   18.072509][    T1]  ? poc_uaf_init+0x25b/0x270
[   18.072519][    T1]  poc_uaf_init+0x25b/0x270
[   18.072528][    T1]  do_one_initcall+0x128/0x700
[   18.072558][    T1]  kernel_init_freeable+0x5d2/0x940
[   18.072573][    T1]  kernel_init+0x21/0x2c0
[   18.072596][    T1]  ret_from_fork+0xb2c/0xdd0
[   18.072648][    T1]  </TASK>
[   18.087231][    T1] Allocated by task 1:
[   18.088000][    T1]  kasan_save_stack+0x33/0x60
[   18.089000][    T1]  __kasan_kmalloc+0xaa/0xb0
[   18.090000][    T1]  platform_device_alloc+0x3a/0x110
[   18.091000][    T1]  poc_uaf_init+0x1a0/0x270
[   18.092000][    T1] Freed by task 1:
[   18.093000][    T1]  kfree+0x171/0x720
[   18.094000][    T1]  platform_device_put+0x2f/0x40
[   18.095000][    T1]  poc_uaf_init+0x240/0x270

The KASAN report confirms: platform_device_alloc() allocates, then
platform_device_put() frees, and the subsequent dev_name() read
triggers the slab-use-after-free.

=== PoC ===

The trigger is a minimal kernel module that replicates the buggy
error-path pattern.  It:

1. Calls platform_device_alloc("poc-uaf", 0)
2. Calls platform_device_add() — which fails with -EEXIST because
   a previous load already registered "poc-uaf.0"
3. Calls platform_device_put() — frees the device
4. Reads dev_name(&pdev->dev) — UAF on freed pdev

// SPDX-License-Identifier: GPL-2.0-only
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/init.h>

static int __init poc_uaf_init(void)
{
    struct platform_device *pdev;
    int ret;

    pdev = platform_device_alloc("poc-uaf", 0);
    if (!pdev)
        return -ENOMEM;

    ret = platform_device_add(pdev);
    if (ret)
        platform_device_put(pdev);

    /* UAF: pdev freed by platform_device_put() above,
     * but dev_name() dereferences the freed memory.
     */
    pr_info("poc-uaf: dev_name=%s\n", dev_name(&pdev->dev));

    return 0;
}
module_init(poc_uaf_init);
MODULE_LICENSE("GPL");

Build: insert into kernel tree and build with CONFIG_POC_UAF=m
Run:   insmod poc_uaf.ko (twice, so the second load hits -EEXIST)

Thanks,
Xiao