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