// https://syzkaller.appspot.com/bug?id=327ce739f2488e377020db741245763ac94b46a4 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #define BPF_MOV64_REG(DST, SRC) \ ((struct bpf_insn) { .code = BPF_ALU64 | BPF_MOV | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = 0 }) #define BPF_MOV64_IMM(DST, IMM) \ ((struct bpf_insn) { .code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = DST, .src_reg = 0, .off = 0, .imm = IMM }) #define BPF_EMIT_CALL(FUNC) \ ((struct bpf_insn) { .code = BPF_JMP | BPF_CALL, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = FUNC }) #define BPF_EXIT_INSN() \ ((struct bpf_insn) { .code = BPF_JMP | BPF_EXIT, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = 0 }) #ifndef BPF_FUNC_clone_redirect #define BPF_FUNC_clone_redirect 13 #endif static int sys_bpf(int cmd, union bpf_attr *attr, unsigned int size) { return syscall(SYS_bpf, cmd, attr, size); } int main(void) { // Maximize memory limits to allow loading many BPF programs struct rlimit rlim_mem = {RLIM_INFINITY, RLIM_INFINITY}; setrlimit(RLIMIT_MEMLOCK, &rlim_mem); // Maximize file descriptor limits to keep many BPF programs open struct rlimit rlim_fd = {65536, 65536}; setrlimit(RLIMIT_NOFILE, &rlim_fd); int ifindex = 1; // loopback interface (lo) is always present int num_calls = 800; int max_iters = 2500; // 2500 * 800 = 2,000,000 unique stack traces int max_insns = 4096; struct bpf_insn *insns = malloc(max_insns * sizeof(struct bpf_insn)); if (!insns) { printf("[-] Failed to allocate memory for instructions\n"); exit(1); } char data_in[64] = {0}; // 64 bytes of dummy data (must be >= ETH_HLEN) int *fds = malloc(max_iters * sizeof(int)); if (!fds) { printf("[-] Failed to allocate memory for fds\n"); exit(1); } for (int i = 0; i < max_iters; i++) fds[i] = -1; printf("[+] Starting BPF program generation and execution loop...\n"); for (int iter = 0; iter < max_iters; iter++) { int i = 0; // Save ctx (R1) into R6 (callee-saved) insns[i++] = (struct bpf_insn)BPF_MOV64_REG(BPF_REG_6, BPF_REG_1); srand(iter); for (int c = 0; c < num_calls; c++) { // Insert a random number of NOPs (0 or 1) to shift the JITed code. // This guarantees that the RIP of the call instruction is unique // even if the JIT compiler reuses the same base memory address. int nops = rand() % 2; for (int n = 0; n < nops; n++) { insns[i++] = (struct bpf_insn)BPF_MOV64_IMM(BPF_REG_8, 0); } // Restore ctx into R1 insns[i++] = (struct bpf_insn)BPF_MOV64_REG(BPF_REG_1, BPF_REG_6); // R2 = ifindex insns[i++] = (struct bpf_insn)BPF_MOV64_IMM(BPF_REG_2, ifindex); // R3 = flags (0) insns[i++] = (struct bpf_insn)BPF_MOV64_IMM(BPF_REG_3, 0); // Call bpf_clone_redirect (allocates and frees an skb, saving the stack trace) insns[i++] = (struct bpf_insn)BPF_EMIT_CALL(BPF_FUNC_clone_redirect); } // Return 0 insns[i++] = (struct bpf_insn)BPF_MOV64_IMM(BPF_REG_0, 0); insns[i++] = (struct bpf_insn)BPF_EXIT_INSN(); union bpf_attr prog_attr; memset(&prog_attr, 0, sizeof(prog_attr)); prog_attr.prog_type = BPF_PROG_TYPE_SCHED_CLS; prog_attr.insn_cnt = i; prog_attr.insns = (uint64_t)insns; prog_attr.license = (uint64_t)"GPL"; // Fix EINVAL: log_level=0 strictly requires log_buf=0 and log_size=0 prog_attr.log_level = 0; prog_attr.log_size = 0; prog_attr.log_buf = 0; int prog_fd = sys_bpf(BPF_PROG_LOAD, &prog_attr, sizeof(prog_attr)); if (prog_fd < 0) { // If we hit a memory or FD limit, close the oldest FD and retry static int oldest = 0; while (oldest < iter && prog_fd < 0) { if (fds[oldest] >= 0) { close(fds[oldest]); fds[oldest] = -1; } oldest++; prog_fd = sys_bpf(BPF_PROG_LOAD, &prog_attr, sizeof(prog_attr)); } if (prog_fd < 0) { printf("[-] Failed to load BPF program (iter %d): %s\n", iter, strerror(errno)); continue; } } fds[iter] = prog_fd; union bpf_attr test_attr; memset(&test_attr, 0, sizeof(test_attr)); test_attr.test.prog_fd = prog_fd; test_attr.test.retval = 0; test_attr.test.data_size_in = sizeof(data_in); test_attr.test.data_in = (uint64_t)data_in; test_attr.test.data_size_out = 0; test_attr.test.data_out = 0; test_attr.test.repeat = 1; if (sys_bpf(BPF_PROG_TEST_RUN, &test_attr, sizeof(test_attr)) < 0) { printf("[-] Failed to run BPF program (iter %d): %s\n", iter, strerror(errno)); } if ((iter + 1) % 100 == 0) { printf("[+] Completed %d iterations...\n", iter + 1); } } printf("[+] BPF execution loop completed.\n"); sleep(2); // Allow asynchronous warnings to be printed free(insns); free(fds); return 0; }