xref: /freebsd/stand/kboot/libkboot/dfk.c (revision e2afbc45258f2fa4bdcf126e959ac660e76fc802)
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