// https://syzkaller.appspot.com/bug?id=ebb1754545306e88da2c358cbce16c4e1bab2aba #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MACRO_WAVE_TIMERS 4000 #define SCAN_TIMERS_PER_THREAD 350 #define TOTAL_TEST_DURATION_SEC 60 static volatile bool stop_churn = false; struct thread_args { int id; int cpu; /* -1 for unpinned */ }; static void set_thread_affinity(int id, int cpu, const char *name) { if (cpu < 0) { printf("[+] %s %d running unpinned.\n", name, id); return; } cpu_set_t set; CPU_ZERO(&set); CPU_SET(cpu, &set); int res = sched_setaffinity(0, sizeof(set), &set); if (res < 0) { printf("[-] Failed to sched_setaffinity for %s %d to CPU %d: %s\n", name, id, cpu, strerror(errno)); exit(1); } printf("[+] %s %d pinned to CPU %d.\n", name, id, cpu); } /* * worker_macro_wave: * Drives macro-oscillations of pool_global and pool_to_free. * 1. Allocates and arms 4000 POSIX timers, pulling thousands of objects from slab. * 2. Holds them for 120 ms so calc_usage() samples high cur_used and ramps avg_usage. * 3. Deletes all 4000 timers. The first 1024 fill pool_global to capacity, and the * remaining ~2976 objects overflow directly into pool_to_free. * 4. Sleeps for 130 ms so avg_usage decays, calculating max_free > 0 when free_obj_work() * executes. Because pool_global is 100% full, pool_move_batch fails and free_obj_work() * calls pool_pop_batch() -> free_object_list() -> kmem_cache_free(), freeing * desynchronized batches back to slab. */ static void *worker_macro_wave(void *arg) { struct thread_args *targs = (struct thread_args *)arg; int id = targs->id; set_thread_affinity(id, targs->cpu, "worker_macro_wave"); timer_t *wave_timers = malloc(MACRO_WAVE_TIMERS * sizeof(timer_t)); if (!wave_timers) { printf("[-] Failed to malloc wave_timers: %s\n", strerror(errno)); exit(1); } struct sigevent sev; memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGALRM; struct itimerspec its_arm = { .it_value = { .tv_sec = 100, .tv_nsec = 0 }, .it_interval = { .tv_sec = 100, .tv_nsec = 0 } }; int cycle = 0; while (!stop_churn) { /* Phase 1: Allocate Burst (4000 Timers) */ int created = 0; for (int i = 0; i < MACRO_WAVE_TIMERS && !stop_churn; i++) { int res = timer_create(CLOCK_MONOTONIC, &sev, &wave_timers[i]); if (res < 0) { printf("[-] Failed to timer_create in wave %d: %s\n", cycle, strerror(errno)); exit(1); } res = timer_settime(wave_timers[i], 0, &its_arm, NULL); if (res < 0) { printf("[-] Failed to timer_settime in wave %d: %s\n", cycle, strerror(errno)); exit(1); } created++; } /* Phase 2: Hold Phase (120 ms sleep) */ usleep(120000); /* Phase 3: Delete Burst (Free all timers) */ for (int i = 0; i < created; i++) { int res = timer_delete(wave_timers[i]); if (res < 0) { printf("[-] Failed to timer_delete in wave %d: %s\n", cycle, strerror(errno)); exit(1); } } /* Phase 4: Decay & Free Phase (130 ms sleep) */ usleep(130000); cycle++; if ((cycle % 10) == 0) { printf("[+] worker_macro_wave %d completed %d wave cycles.\n", id, cycle); } } free(wave_timers); return NULL; } /* * worker_churn: * Drives fine-grained unaligned allocations and deallocations. * Interleaving single allocations (pcpu_alloc) and frees (pcpu_free) via * POSIX timers, timerfds, and immediate synchronous clock_nanosleep calls * desynchronizes per-CPU pool counters from true 16-object batch boundaries. */ static void *worker_churn(void *arg) { struct thread_args *targs = (struct thread_args *)arg; int id = targs->id; set_thread_affinity(id, targs->cpu, "worker_churn"); timer_t local_timers[120]; int num_timers = 0; int local_fds[40]; int num_fds = 0; unsigned int seed = (unsigned int)(time(NULL) ^ (id << 8) ^ (uintptr_t)pthread_self()); struct sigevent sev; memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGALRM; struct itimerspec its_arm = { .it_value = { .tv_sec = 100, .tv_nsec = 0 }, .it_interval = { .tv_sec = 100, .tv_nsec = 0 } }; int op_count = 0; while (!stop_churn) { int action = rand_r(&seed) % 4; switch (action) { case 0: { /* Action A: Random POSIX Timer Allocations (1 to 20) */ int k_count = 1 + (rand_r(&seed) % 20); for (int k = 0; k < k_count; k++) { if (num_timers >= 120) break; timer_t tid; int res = timer_create(CLOCK_MONOTONIC, &sev, &tid); if (res < 0) { printf("[-] Failed to timer_create in churn %d: %s\n", id, strerror(errno)); exit(1); } res = timer_settime(tid, 0, &its_arm, NULL); if (res < 0) { printf("[-] Failed to timer_settime in churn %d: %s\n", id, strerror(errno)); exit(1); } local_timers[num_timers++] = tid; } break; } case 1: { /* Action B: Random POSIX Timer Deletions (1 to 20) */ if (num_timers > 0) { int m_count = 1 + (rand_r(&seed) % 20); if (m_count > num_timers) m_count = num_timers; for (int m = 0; m < m_count; m++) { int idx = rand_r(&seed) % num_timers; int res = timer_delete(local_timers[idx]); if (res < 0) { printf("[-] Failed to timer_delete in churn %d: %s\n", id, strerror(errno)); exit(1); } local_timers[idx] = local_timers[num_timers - 1]; num_timers--; } } break; } case 2: { /* Action C: Synchronous hrtimer Churn via clock_nanosleep (5 to 15) */ int n_count = 5 + (rand_r(&seed) % 11); struct timespec ts = { .tv_sec = 0, .tv_nsec = 1 }; for (int n = 0; n < n_count; n++) { int res = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); if (res != 0 && res != EINTR) { printf("[-] Failed to clock_nanosleep in churn %d: %s\n", id, strerror(res)); exit(1); } } break; } case 3: { /* Action D: Timerfd Churn (1 to 8) */ int j_count = 1 + (rand_r(&seed) % 8); for (int j = 0; j < j_count; j++) { if (num_fds >= 40) { int idx = rand_r(&seed) % num_fds; int res = close(local_fds[idx]); if (res < 0) { printf("[-] Failed to close timerfd in churn %d: %s\n", id, strerror(errno)); exit(1); } local_fds[idx] = local_fds[num_fds - 1]; num_fds--; } int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if (tfd < 0) { printf("[-] Failed to timerfd_create in churn %d: %s\n", id, strerror(errno)); exit(1); } int res = timerfd_settime(tfd, 0, &its_arm, NULL); if (res < 0) { printf("[-] Failed to timerfd_settime in churn %d: %s\n", id, strerror(errno)); exit(1); } local_fds[num_fds++] = tfd; } if (num_fds > 0) { int close_count = 1 + (rand_r(&seed) % 5); for (int c = 0; c < close_count && num_fds > 0; c++) { int idx = rand_r(&seed) % num_fds; int res = close(local_fds[idx]); if (res < 0) { printf("[-] Failed to close timerfd in churn %d: %s\n", id, strerror(errno)); exit(1); } local_fds[idx] = local_fds[num_fds - 1]; num_fds--; } } break; } } op_count++; if ((op_count % 50) == 0) { sched_yield(); } } for (int i = 0; i < num_timers; i++) timer_delete(local_timers[i]); for (int i = 0; i < num_fds; i++) close(local_fds[i]); return NULL; } /* * worker_scanner: * Continuously disarms and re-arms dedicated timers to execute * debug_object_deactivate() -> lookup_object() across debug hash buckets. * When an active debug object on a bucket chain is poisoned by slab freeing, * lookup_object() dereferences the poisoned next pointer, triggering the GPF. */ static void *worker_scanner(void *arg) { struct thread_args *targs = (struct thread_args *)arg; int id = targs->id; set_thread_affinity(id, targs->cpu, "worker_scanner"); timer_t scan_timers[SCAN_TIMERS_PER_THREAD]; struct itimerspec its_arm = { .it_value = { .tv_sec = 600, .tv_nsec = 0 }, .it_interval = { .tv_sec = 600, .tv_nsec = 0 } }; struct itimerspec its_disarm = { .it_value = { .tv_sec = 0, .tv_nsec = 0 }, .it_interval = { .tv_sec = 0, .tv_nsec = 0 } }; struct sigevent sev; memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGALRM; for (int i = 0; i < SCAN_TIMERS_PER_THREAD; i++) { int res = timer_create(CLOCK_MONOTONIC, &sev, &scan_timers[i]); if (res < 0) { printf("[-] Failed to create scan timer %d in scanner %d: %s\n", i, id, strerror(errno)); exit(1); } res = timer_settime(scan_timers[i], 0, &its_arm, NULL); if (res < 0) { printf("[-] Failed to arm scan timer %d in scanner %d: %s\n", i, id, strerror(errno)); exit(1); } } printf("[+] worker_scanner %d created and armed %d scan timers.\n", id, SCAN_TIMERS_PER_THREAD); int count = 0; while (!stop_churn) { for (int i = 0; i < SCAN_TIMERS_PER_THREAD && !stop_churn; i++) { int res = timer_settime(scan_timers[i], 0, &its_disarm, NULL); if (res < 0) { printf("[-] Failed to disarm scan timer %d in scanner %d: %s\n", i, id, strerror(errno)); exit(1); } res = timer_settime(scan_timers[i], 0, &its_arm, NULL); if (res < 0) { printf("[-] Failed to re-arm scan timer %d in scanner %d: %s\n", i, id, strerror(errno)); exit(1); } count++; if ((count % 50) == 0) sched_yield(); } } for (int i = 0; i < SCAN_TIMERS_PER_THREAD; i++) { timer_delete(scan_timers[i]); } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[+] Starting reproducer for general protection fault in hrtimer_try_to_cancel / debugobjects...\n"); /* Raise limits to allow thousands of concurrent timers and descriptors */ struct rlimit rlim_sig = { 65536, 65536 }; int res = setrlimit(RLIMIT_SIGPENDING, &rlim_sig); if (res < 0) { printf("[-] Failed to setrlimit RLIMIT_SIGPENDING: %s\n", strerror(errno)); exit(1); } printf("[+] RLIMIT_SIGPENDING set to 65536.\n"); struct rlimit rlim_files = { 65536, 65536 }; res = setrlimit(RLIMIT_NOFILE, &rlim_files); if (res < 0) { printf("[-] Failed to setrlimit RLIMIT_NOFILE: %s\n", strerror(errno)); exit(1); } printf("[+] RLIMIT_NOFILE set to 65536.\n"); /* Ignore timer signals */ signal(SIGALRM, SIG_IGN); signal(SIGVTALRM, SIG_IGN); signal(SIGPROF, SIG_IGN); signal(SIGCHLD, SIG_IGN); printf("[+] Timer signals set to SIG_IGN.\n"); long nprocs = sysconf(_SC_NPROCESSORS_ONLN); if (nprocs < 1) { printf("[-] Failed to query online CPUs: %s\n", strerror(errno)); exit(1); } printf("[+] Detected %ld online CPUs.\n", nprocs); int cpu0 = 0; int cpu1 = (nprocs > 1) ? 1 : 0; /* * Spawn 8 worker threads: * Thread 0: worker_macro_wave (unpinned) * Thread 1: worker_churn 1 (pinned CPU 0) * Thread 2: worker_churn 2 (pinned CPU 0) * Thread 3: worker_churn 3 (pinned CPU 1) * Thread 4: worker_churn 4 (pinned CPU 1) * Thread 5: worker_churn 5 (unpinned) * Thread 6: worker_scanner 6 (pinned CPU 0) * Thread 7: worker_scanner 7 (pinned CPU 1) */ pthread_t threads[8]; struct thread_args targs[8] = { { .id = 0, .cpu = -1 }, { .id = 1, .cpu = cpu0 }, { .id = 2, .cpu = cpu0 }, { .id = 3, .cpu = cpu1 }, { .id = 4, .cpu = cpu1 }, { .id = 5, .cpu = -1 }, { .id = 6, .cpu = cpu0 }, { .id = 7, .cpu = cpu1 }, }; res = pthread_create(&threads[0], NULL, worker_macro_wave, &targs[0]); if (res != 0) { printf("[-] Failed to pthread_create wave 0: %s\n", strerror(res)); exit(1); } for (int i = 1; i <= 5; i++) { res = pthread_create(&threads[i], NULL, worker_churn, &targs[i]); if (res != 0) { printf("[-] Failed to pthread_create churn %d: %s\n", i, strerror(res)); exit(1); } } for (int i = 6; i <= 7; i++) { res = pthread_create(&threads[i], NULL, worker_scanner, &targs[i]); if (res != 0) { printf("[-] Failed to pthread_create scanner %d: %s\n", i, strerror(res)); exit(1); } } printf("[+] All 8 worker threads successfully spawned.\n"); /* Monitor progress for TOTAL_TEST_DURATION_SEC */ for (int elapsed = 0; elapsed < TOTAL_TEST_DURATION_SEC && !stop_churn; elapsed += 5) { sleep(5); printf("[*] Progress: %d/%d seconds elapsed, macro waves and churn active...\n", elapsed + 5, TOTAL_TEST_DURATION_SEC); } printf("[+] Test duration completed. Signaling workers to terminate...\n"); stop_churn = true; for (int i = 0; i < 8; i++) { res = pthread_join(threads[i], NULL); if (res != 0) { printf("[-] Failed to pthread_join thread %d: %s\n", i, strerror(res)); exit(1); } } printf("[+] All worker threads joined successfully.\n"); printf("[+] Reproducer run completed.\n"); return 0; }