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
lb_fill(struct line_buffer * lb)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
lb_fini(struct line_buffer * lb)79 lb_fini(struct line_buffer *lb)
80 {
81 close(lb->fd);
82 return (true);
83 }
84
85 static bool
lb_init(struct line_buffer * lb,const char * fn)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
lb_1line(struct line_buffer * lb,char * buffer,size_t buflen)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
symbol_addr(const char * symbol)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
read_at_address(unsigned long addr,void * buf,size_t len)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
data_from_kernel(const char * sym,void * buf,size_t len)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
main(int argc,char ** argv)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