1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2021 Oxide Computer Company
14  */
15 
16 /*
17  * Check that a given core dump generated as part of our test framework has the
18  * sections that we'd expect. We have here the dumper binary. In that, we expect
19  * to find the following libraries and sections:
20  *
21  *   a.out:		symtab, ctf, .debug_* (dwarf)
22  *   ld.so.1:		symtab
23  *   libc.so:		symtab, ctf
24  *   libproc.so:	symtab, ctf
25  *   libdumper.so:	symtab, ctf, .debug_* (dwarf)
26  *
27  * Note, there will also be additional libraries and things here can change over
28  * time (e.g. deps of libproc, etc.), but we try to ignore them generally
29  * speaking if we can know enough to do so.
30  */
31 
32 #include <err.h>
33 #include <stdlib.h>
34 #include <libproc.h>
35 #include <gelf.h>
36 #include <libelf.h>
37 #include <limits.h>
38 #include <string.h>
39 #include <libgen.h>
40 
41 typedef enum {
42 	SECMAP_CTF,
43 	SECMAP_SYMTAB,
44 	SECMAP_DEBUG,
45 	SECMAP_MAX
46 } secmap_type_t;
47 
48 typedef struct secmap_data {
49 	core_content_t	sd_content;
50 	const char	*sd_name;
51 } secmap_data_t;
52 
53 secmap_data_t secmap_data[SECMAP_MAX] = {
54 	{ CC_CONTENT_CTF, ".SUNW_ctf" },
55 	{ CC_CONTENT_SYMTAB, ".symtab" },
56 	{ CC_CONTENT_DEBUG, ".debug_" }
57 };
58 
59 typedef struct {
60 	uint64_t	sm_addr;
61 	char		sm_obj[PATH_MAX];
62 	size_t		sm_nfound[SECMAP_MAX];
63 	Elf		*sm_elf;
64 	GElf_Ehdr	sm_ehdr;
65 	boolean_t	sm_ctf;
66 	boolean_t	sm_debug;
67 	boolean_t	sm_symtab;
68 } secmap_t;
69 
70 static secmap_t *secmaps;
71 static size_t secmap_count;
72 static core_content_t secmap_content;
73 
74 static int secmap_exit = EXIT_SUCCESS;
75 
76 static void
secmap_fail(const char * fmt,...)77 secmap_fail(const char *fmt, ...)
78 {
79 	va_list ap;
80 
81 	va_start(ap, fmt);
82 	vwarnx(fmt, ap);
83 	va_end(ap);
84 	secmap_exit = EXIT_FAILURE;
85 }
86 
87 
88 static void
check_content(core_content_t content,struct ps_prochandle * Pr)89 check_content(core_content_t content, struct ps_prochandle *Pr)
90 {
91 	secmap_content = Pcontent(Pr);
92 
93 	if (secmap_content == CC_CONTENT_INVALID) {
94 		secmap_fail("TEST FAILED: failed to get core content");
95 		return;
96 	}
97 
98 	if (secmap_content != content) {
99 		secmap_fail("TEST FAILED: core file contains different "
100 		    "content than expected, found 0x%x, expected 0x%x",
101 		    secmap_content, content);
102 	}
103 }
104 
105 static secmap_t *
secmap_find(uint64_t addr)106 secmap_find(uint64_t addr)
107 {
108 	for (size_t i = 0; i < secmap_count; i++) {
109 		if (secmaps[i].sm_addr == addr) {
110 			return (&secmaps[i]);
111 		}
112 	}
113 
114 	return (NULL);
115 }
116 
117 static void
secmap_matches_content(secmap_type_t type)118 secmap_matches_content(secmap_type_t type)
119 {
120 	boolean_t exist = (secmap_data[type].sd_content & secmap_content) != 0;
121 	boolean_t found = B_FALSE;
122 
123 	/*
124 	 * Dumping CTF data implies that some symbol tables will exist for CTF.
125 	 */
126 	if (type == SECMAP_SYMTAB && (secmap_content & CC_CONTENT_CTF) != 0) {
127 		exist = B_TRUE;
128 	}
129 
130 	for (size_t i = 0; i < secmap_count; i++) {
131 		if (secmaps[i].sm_nfound[type] != 0) {
132 			found = B_TRUE;
133 		}
134 	}
135 
136 	if (exist != found) {
137 		secmap_fail("content type mismatch for %s: expected %s, but "
138 		    "found %s", secmap_data[type].sd_name,
139 		    exist ? "some" : "none",
140 		    found ? "some" : "none");
141 	}
142 }
143 
144 static secmap_t *
secmap_alloc(struct ps_prochandle * Pr,uint64_t addr)145 secmap_alloc(struct ps_prochandle *Pr, uint64_t addr)
146 {
147 	int fd;
148 	secmap_t *sm;
149 	char *base;
150 
151 	sm = recallocarray(secmaps, secmap_count, secmap_count + 1,
152 	    sizeof (secmap_t));
153 	if (sm == NULL) {
154 		err(EXIT_FAILURE, "TEST FAILED: failed to allocate memory for "
155 		    "secmap %zu", secmap_count + 1);
156 	}
157 
158 	secmaps = sm;
159 	sm = &secmaps[secmap_count];
160 	sm->sm_addr = addr;
161 	secmap_count++;
162 
163 	/*
164 	 * We also have some tests that we don't expect to have anything here
165 	 * because we only include the relevant sections. Experimentally, we
166 	 * know that libproc needs both anon and data mappings for this to work.
167 	 * So if we don't have both, then we'll not warn on that.
168 	 */
169 	if (Pobjname(Pr, addr, sm->sm_obj, sizeof (sm->sm_obj)) == NULL) {
170 		core_content_t need = CC_CONTENT_ANON | CC_CONTENT_DATA;
171 
172 		if ((secmap_content & need) == need) {
173 			secmap_fail("TEST FAILURE: object at address 0x%lx "
174 			    "has no name", addr);
175 		}
176 
177 		return (sm);
178 	}
179 
180 	/*
181 	 * Since we have a name, we should be able to open this elf object and
182 	 * identify it as well.
183 	 */
184 	fd = open(sm->sm_obj, O_RDONLY);
185 	if (fd < 0) {
186 		err(EXIT_FAILURE, "failed to open object %s", sm->sm_obj);
187 	}
188 
189 	sm->sm_elf = elf_begin(fd, ELF_C_READ, NULL);
190 	if (sm->sm_elf == NULL) {
191 		err(EXIT_FAILURE, "failed to find open elf object %s: %s",
192 		    sm->sm_obj, elf_errmsg(elf_errno()));
193 	}
194 
195 	if (gelf_getehdr(sm->sm_elf, &sm->sm_ehdr) == NULL) {
196 		err(EXIT_FAILURE, "failed to get ehdr for %s: %s",
197 		    sm->sm_obj, elf_errmsg(elf_errno()));
198 	}
199 
200 	base = basename(sm->sm_obj);
201 	if (strcmp(base, "dumper.32") == 0 || strcmp(base, "dumper.64") == 0) {
202 		sm->sm_debug = sm->sm_symtab = sm->sm_ctf = B_TRUE;
203 	} else if (strcmp(base, "libc.so.1") == 0) {
204 		sm->sm_symtab = sm->sm_ctf = B_TRUE;
205 	} else if (strcmp(base, "ld.so.1") == 0) {
206 		sm->sm_symtab = B_TRUE;
207 	} else if (strcmp(base, "libproc.so.1") == 0) {
208 		sm->sm_symtab = sm->sm_ctf = B_TRUE;
209 	} else if (strcmp(base,  "libdumper.so.1") == 0) {
210 		sm->sm_debug = sm->sm_symtab = sm->sm_ctf = B_TRUE;
211 	} else {
212 		sm->sm_symtab = B_TRUE;
213 	}
214 
215 	return (sm);
216 }
217 
218 static void
secmap_data_cmp(secmap_t * sm,const char * sname,Elf_Scn * scn,GElf_Shdr * shdr)219 secmap_data_cmp(secmap_t *sm, const char *sname, Elf_Scn *scn, GElf_Shdr *shdr)
220 {
221 	for (Elf_Scn *comp_scn = elf_nextscn(sm->sm_elf, NULL);
222 	    comp_scn != NULL; comp_scn = elf_nextscn(sm->sm_elf, comp_scn)) {
223 		GElf_Shdr comp_shdr;
224 		const char *comp_name;
225 		Elf_Data *src_data, *comp_data;
226 
227 		if (gelf_getshdr(comp_scn, &comp_shdr) == NULL) {
228 			secmap_fail("failed to load section header from %s "
229 			    "during data comparison", sm->sm_obj);
230 			return;
231 		}
232 
233 		comp_name = elf_strptr(sm->sm_elf, sm->sm_ehdr.e_shstrndx,
234 		    comp_shdr.sh_name);
235 		if (comp_name == NULL) {
236 			secmap_fail("failed to load section name from %s "
237 			    "with index %lu", sm->sm_obj, comp_shdr.sh_name);
238 			return;
239 		}
240 
241 		if (strcmp(comp_name, sname) != 0)
242 			continue;
243 
244 		if (comp_shdr.sh_type != shdr->sh_type ||
245 		    comp_shdr.sh_addralign != shdr->sh_addralign ||
246 		    comp_shdr.sh_size != shdr->sh_size ||
247 		    comp_shdr.sh_entsize != shdr->sh_entsize) {
248 			continue;
249 		}
250 
251 		if ((src_data = elf_getdata(scn, NULL)) == NULL) {
252 			secmap_fail("failed to load section data from "
253 			    "source to compare to %s %s", sm->sm_obj, sname);
254 			return;
255 		}
256 
257 		if ((comp_data = elf_getdata(comp_scn, NULL)) == NULL) {
258 			secmap_fail("failed to load section data from "
259 			    "source to compare to %s %s", sm->sm_obj, sname);
260 			return;
261 		}
262 
263 		if (comp_data->d_size != src_data->d_size) {
264 			secmap_fail("data size mismatch for %s: %s, core: "
265 			    "%zu, file: %zu", sm->sm_obj, sname,
266 			    src_data->d_size, comp_data->d_size);
267 			return;
268 		}
269 
270 		if (memcmp(comp_data->d_buf, src_data->d_buf,
271 		    comp_data->d_size) != 0) {
272 			secmap_fail("data mismatch between core and source "
273 			    "in %s: %s", sm->sm_obj, sname);
274 			return;
275 		}
276 
277 		return;
278 	}
279 
280 	secmap_fail("failed to find matching section for %s in %s",
281 	    sname, sm->sm_obj);
282 }
283 
284 static void
secmap_file_check(secmap_t * sm)285 secmap_file_check(secmap_t *sm)
286 {
287 	if (sm->sm_ctf && (secmap_content & CC_CONTENT_CTF) != 0 &&
288 	    sm->sm_nfound[SECMAP_CTF] == 0) {
289 		secmap_fail("expected object %s to have CTF, but it doesn't",
290 		    sm->sm_obj);
291 	}
292 
293 	if (sm->sm_symtab && (secmap_content & CC_CONTENT_SYMTAB) != 0 &&
294 	    sm->sm_nfound[SECMAP_SYMTAB] == 0) {
295 		secmap_fail("expected object %s to have a symbol table, "
296 		    "but it doesn't", sm->sm_obj);
297 	}
298 
299 	if (sm->sm_debug && (secmap_content & CC_CONTENT_DEBUG) != 0 &&
300 	    sm->sm_nfound[SECMAP_DEBUG] == 0) {
301 		secmap_fail("expected object %s to have debug sections, "
302 		    "but it doesn't", sm->sm_obj);
303 	}
304 }
305 
306 int
main(int argc,char * argv[])307 main(int argc, char *argv[])
308 {
309 	core_content_t content;
310 	struct ps_prochandle *Pr;
311 	int perr, fd;
312 	Elf *elf;
313 	Elf_Scn *scn;
314 	GElf_Ehdr ehdr;
315 
316 	if (argc != 3) {
317 		warnx("missing required file and core content");
318 		(void) fprintf(stderr, "Usage: secmapper file content\n");
319 		exit(EXIT_FAILURE);
320 	}
321 
322 	if (elf_version(EV_CURRENT) == EV_NONE) {
323 		errx(EXIT_FAILURE, "failed to init libelf");
324 	}
325 
326 	Pr = Pgrab_core(argv[1], NULL, PGRAB_RDONLY, &perr);
327 	if (Pr == NULL) {
328 		errx(EXIT_FAILURE, "failed to open %s: %s", argv[1],
329 		    Pgrab_error(perr));
330 	}
331 
332 	if ((fd = open(argv[1], O_RDONLY)) < 0) {
333 		err(EXIT_FAILURE, "failed to open %s\n", argv[1]);
334 	}
335 
336 	if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
337 		errx(EXIT_FAILURE, "failed to open elf file %s: %s", argv[1],
338 		    elf_errmsg(elf_errno()));
339 	}
340 
341 	if (proc_str2content(argv[2], &content) != 0) {
342 		err(EXIT_FAILURE, "failed to parse content %s", argv[2]);
343 	}
344 
345 	if (gelf_getehdr(elf, &ehdr) == NULL) {
346 		errx(EXIT_FAILURE, "failed to get edr: %s",
347 		    elf_errmsg(elf_errno()));
348 	}
349 
350 	/*
351 	 * Before we go futher, make sure that we have the content in this file
352 	 * that we expect.
353 	 */
354 	check_content(content, Pr);
355 
356 	for (scn = elf_nextscn(elf, NULL); scn != NULL;
357 	    scn = elf_nextscn(elf, scn)) {
358 		const char *sname;
359 		GElf_Shdr shdr;
360 		size_t index;
361 		secmap_t *secmap;
362 
363 		index = elf_ndxscn(scn);
364 		if (gelf_getshdr(scn, &shdr) ==  NULL) {
365 			errx(EXIT_FAILURE, "failed to get section header for "
366 			    "shdr %zu: %s", index, elf_errmsg(elf_errno()));
367 		}
368 
369 		/*
370 		 * Skip the strtab.
371 		 */
372 		if (shdr.sh_type == SHT_STRTAB) {
373 			continue;
374 		}
375 
376 		sname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
377 		if (sname == NULL) {
378 			secmap_fail("TEST FAILURE: string name missing for "
379 			    "shdr %zu", index);
380 			continue;
381 		}
382 
383 		/*
384 		 * Find or cons up a new secmap for this object.
385 		 */
386 		secmap = secmap_find(shdr.sh_addr);
387 		if (secmap == NULL) {
388 			secmap = secmap_alloc(Pr, shdr.sh_addr);
389 		}
390 
391 		if (strcmp(sname, ".symtab") == 0) {
392 			secmap->sm_nfound[SECMAP_SYMTAB]++;
393 		} else if (strcmp(sname, ".SUNW_ctf") == 0) {
394 			secmap->sm_nfound[SECMAP_CTF]++;
395 		} else if (strncmp(sname, ".debug_", strlen(".debug_")) == 0) {
396 			secmap->sm_nfound[SECMAP_DEBUG]++;
397 		} else {
398 			continue;
399 		}
400 
401 		/*
402 		 * For one of our three primary sections, make sure that the
403 		 * data that is in the core file that we find in it actually
404 		 * matches the underlying object. That is, if the secmap
405 		 * actually has something here.
406 		 */
407 		if (secmap->sm_elf != NULL) {
408 			secmap_data_cmp(secmap, sname, scn, &shdr);
409 		}
410 	}
411 
412 	/*
413 	 * Now that we have iterated over all of these sections, check and make
414 	 * sure certain things are true of them. In particular, go through some
415 	 * of the various types of data and make sure it exists at all or
416 	 * doesn't based on our core content.
417 	 */
418 	secmap_matches_content(SECMAP_CTF);
419 	secmap_matches_content(SECMAP_SYMTAB);
420 	secmap_matches_content(SECMAP_DEBUG);
421 
422 	/*
423 	 * Finally, if we have enough information to know that we've found
424 	 * a file that we know it should at least have a given type of data,
425 	 * check for it. Here, it is OK for data to be present we don't expect
426 	 * (assuming the core content allows it). This makes this test less
427 	 * prone to broader changes in the system.
428 	 */
429 	for (size_t i = 0; i < secmap_count; i++) {
430 		secmap_file_check(&secmaps[i]);
431 	}
432 
433 	return (secmap_exit);
434 }
435