// https://syzkaller.appspot.com/bug?id=d21b88ad6d795080fff1a7c6ba7d1c802c955a33 #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 #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }