1 /* 2 * Copyright (c) 2025 Netflix, Inc 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 /* 8 * Common macros to allow compiling this as a Linux binary or in libsa. 9 */ 10 #ifdef _STANDALONE 11 #include "stand.h" 12 /* Not ideal, but these are missing in libsa */ 13 #define perror(msg) printf("ERROR %d: %s\n", errno, msg) 14 #define fprintf(x, ...) printf( __VA_ARGS__ ) 15 #include <machine/elf.h> 16 #include <sys/param.h> 17 #include "util.h" 18 #else 19 #include <elf.h> 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <fcntl.h> 23 #include <stdbool.h> 24 #include <stdint.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <asm/bootparam.h> 30 31 #define PAGE_SIZE 4096 32 #define IS_ELF(ehdr) ((ehdr).e_ident[EI_MAG0] == ELFMAG0 && \ 33 (ehdr).e_ident[EI_MAG1] == ELFMAG1 && \ 34 (ehdr).e_ident[EI_MAG2] == ELFMAG2 && \ 35 (ehdr).e_ident[EI_MAG3] == ELFMAG3) 36 37 #define ELF_TARG_CLASS ELFCLASS64 38 #define ELF_TARG_MACH EM_X86_64 39 #define ELF_TARG_DATA ELFDATA2LSB 40 #endif 41 42 #ifndef _STANDALONE 43 #define KCORE_PATH "/proc/kcore" 44 #define KALLSYMS_PATH "/proc/kallsyms" 45 #else 46 #define KCORE_PATH "host:/proc/kcore" 47 #define KALLSYMS_PATH "host:/proc/kallsyms" 48 #endif 49 50 struct elf_file 51 { 52 uint8_t buf[PAGE_SIZE]; 53 int fd; 54 }; 55 56 // All the line_buffer stuff can be replaced by fgetstr() 57 58 struct line_buffer 59 { 60 int fd; 61 char buf[PAGE_SIZE]; 62 char *pos; 63 char *eos; 64 }; 65 66 /* 67 * We just assume we have to fill if we are called. 68 */ 69 static bool 70 lb_fill(struct line_buffer *lb) 71 { 72 ssize_t rv; 73 74 lb->pos = lb->eos = lb->buf; // Reset to no data condition 75 rv = read(lb->fd, lb->buf, sizeof(lb->buf)); 76 if (rv <= 0) 77 return (false); 78 lb->pos = lb->buf; 79 lb->eos = lb->buf + rv; 80 return (true); 81 } 82 83 static bool 84 lb_fini(struct line_buffer *lb) 85 { 86 close(lb->fd); 87 return (true); 88 } 89 90 static bool 91 lb_init(struct line_buffer *lb, const char *fn) 92 { 93 lb->fd = open(fn, O_RDONLY); 94 if (lb->fd == -1) 95 return (false); 96 lb->pos = lb->eos = lb->buf; 97 if (!lb_fill(lb)) { 98 lb_fini(lb); 99 return (false); 100 } 101 return (true); 102 } 103 104 // True -> data returned 105 // False -> EOF / ERROR w/o data 106 static bool 107 lb_1line(struct line_buffer *lb, char *buffer, size_t buflen) 108 { 109 char *bufeos = buffer + buflen - 1; // point at byte for NUL at eos 110 char *walker = buffer; 111 112 while (walker < bufeos) { // < to exclude space for NUL 113 if (lb->pos >= lb->eos) { // Refill empty buffer 114 if (!lb_fill(lb)) { // Hit EOF / error 115 if (walker > buffer) // Have data? return it 116 break; 117 // No data, signal EOF/Error 118 return (false); 119 } 120 } 121 *walker = *lb->pos++; 122 if (*walker == '\n') 123 break; 124 walker++; 125 } 126 /* 127 * We know walker <= bufeos, so NUL will fit. 128 */ 129 *++walker = '\0'; 130 return (true); 131 } 132 133 /* 134 * Scan /proc/kallsyms to find @symbol and return the value it finds there. 135 */ 136 unsigned long 137 symbol_addr(const char *symbol) 138 { 139 struct line_buffer lb; 140 unsigned long addr; 141 char line[256]; 142 143 if (!lb_init(&lb, KALLSYMS_PATH)) { 144 printf("Cannot open symbol file %s\n", KALLSYMS_PATH); 145 return (0); 146 } 147 while (lb_1line(&lb, line, sizeof(line))) { 148 char *val, *name, *x, t; 149 150 /* 151 * Parse lines of the form 152 * val<sp>t<sp>name\n 153 * looking for one with t in [dDbB] (so data) name == symbol, 154 * skipping lines that don't match the pattern. 155 */ 156 val = line; 157 x = strchr(val, ' '); 158 if (x == NULL) 159 continue; /* No 1st <sp> */ 160 *x++ = '\0'; 161 t = *x++; 162 if (strchr("dDbB", t) == NULL) 163 continue; /* Only data types */ 164 if (*x++ != ' ') 165 continue; /* No 2nd <sp> */ 166 name = x; 167 x = strchr(x, '\n'); 168 if (x == NULL) 169 continue; /* No traling newline */ 170 *x++ = '\0'; 171 if (strcmp(name, symbol) == 0) { 172 unsigned long v; 173 char *eop = NULL; 174 lb_fini(&lb); 175 v = strtoul(val, &eop, 16); 176 if (*eop == '\0') 177 return (v); 178 return (0); /* PARSE ERROR -- what to do? */ 179 } 180 /* No match, try next */ 181 } 182 183 lb_fini(&lb); 184 return (0); 185 } 186 187 /* 188 * Parse /proc/kcore to find if we can get the data for @len bytes that are 189 * mapped in the kernel at VA @addr. It's a CORE file in ELF format that the 190 * kernel exports for the 'safe' areas to touch. We can read random kernel 191 * varaibles, but we can't read arbitrary addresses since it doesn't export 192 * the direct map. 193 */ 194 bool 195 read_at_address(unsigned long addr, void *buf, size_t len) 196 { 197 struct elf_file ef; 198 Elf64_Ehdr *hdr; 199 Elf64_Phdr *phdr; 200 ssize_t rv; 201 202 bzero(&ef, sizeof(ef)); 203 ef.fd = open(KCORE_PATH, O_RDONLY); 204 if (ef.fd == -1) { 205 perror("open " KCORE_PATH "\n"); 206 return (false); 207 } 208 209 /* 210 * Read in the first page. ELF files have a header that says how many 211 * sections are in the file, whre they are, etc. All the Phdr are in the 212 * first page. Read it, verify the headers, then loop through these Phdr 213 * to find the address where addr is mapped to read it. 214 */ 215 rv = read(ef.fd, ef.buf, sizeof(ef.buf)); 216 if (rv != sizeof(ef.buf)) { 217 perror("short hdr read\n"); 218 close(ef.fd); 219 return (false); 220 } 221 hdr = (Elf64_Ehdr *)&ef.buf; 222 if (!IS_ELF(*hdr)) { 223 fprintf(stderr, "Not Elf\n"); 224 close(ef.fd); 225 return (false); 226 } 227 if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS || /* Layout ? */ 228 hdr->e_ident[EI_DATA] != ELF_TARG_DATA || 229 hdr->e_ident[EI_VERSION] != EV_CURRENT || /* Version ? */ 230 hdr->e_version != EV_CURRENT || 231 hdr->e_machine != ELF_TARG_MACH || /* Machine ? */ 232 hdr->e_type != ET_CORE) { 233 fprintf(stderr, "Not what I expect\n"); 234 close(ef.fd); 235 return (false); 236 } 237 238 phdr = (Elf64_Phdr *)(ef.buf + hdr->e_phoff); 239 for (int i = 0; i < hdr->e_phnum; i++) { 240 if (phdr[i].p_type != PT_LOAD) 241 continue; 242 if (addr < phdr[i].p_vaddr || 243 addr >= phdr[i].p_vaddr + phdr[i].p_filesz) 244 continue; 245 lseek(ef.fd, (off_t)phdr[i].p_offset + addr - phdr[i].p_vaddr, 246 SEEK_SET); 247 rv = read(ef.fd, buf, len); 248 if (rv != len) 249 perror("Can't read buffer\n"); 250 close(ef.fd); 251 return (rv == len); 252 } 253 254 close(ef.fd); 255 return (false); 256 } 257 258 /* 259 * Read a value from the Linux kernel. We lookup @sym and read @len bytes into 260 * @buf. Returns true if we got it, false on an error. 261 */ 262 bool 263 data_from_kernel(const char *sym, void *buf, size_t len) 264 { 265 unsigned long addr; 266 267 addr = symbol_addr(sym); 268 if (addr == 0) { 269 fprintf(stderr, "Can't find symbol %s\n", sym); 270 return (false); 271 } 272 if (!read_at_address(addr, buf, len)) { 273 fprintf(stderr, "Can't read from kernel\n"); 274 return (false); 275 } 276 return (true); 277 } 278 279 #ifndef _STANDALONE 280 /* 281 * Silly little test case to test on a random Linux system. 282 */ 283 int 284 main(int argc, char **argv) 285 { 286 struct boot_params bp; 287 288 if (data_from_kernel("boot_params", &bp, sizeof(bp))) { 289 fprintf(stderr, "Something went wrong\n"); 290 } else { 291 printf("sig %#x systab %#lx memmap %#lx mmapsize %d md_size %d md_vers %d\n", 292 bp.efi_info.efi_loader_signature, 293 (long)(bp.efi_info.efi_systab | ((long)bp.efi_info.efi_systab_hi << 32)), 294 (long)(bp.efi_info.efi_memmap | ((long)bp.efi_info.efi_memmap_hi << 32)), 295 bp.efi_info.efi_memmap_size, bp.efi_info.efi_memdesc_size, 296 bp.efi_info.efi_memdesc_version); 297 } 298 } 299 #endif 300