xref: /freebsd/lib/libprocstat/core.c (revision 3f0164abf32b9b761e0a2cb4bdca3a8b84f156d4)
1 /*-
2  * Copyright (c) 2013 Mikolaj Golub <trociny@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/param.h>
31 #include <sys/elf.h>
32 #include <sys/exec.h>
33 #include <sys/user.h>
34 
35 #include <assert.h>
36 #include <err.h>
37 #include <fcntl.h>
38 #include <gelf.h>
39 #include <libelf.h>
40 #include <stdbool.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include "core.h"
48 
49 #define PROCSTAT_CORE_MAGIC	0x012DADB8
50 struct procstat_core
51 {
52 	int		pc_magic;
53 	int		pc_fd;
54 	Elf		*pc_elf;
55 	GElf_Ehdr	pc_ehdr;
56 	GElf_Phdr	pc_phdr;
57 };
58 
59 static bool	core_offset(struct procstat_core *core, off_t offset);
60 static bool	core_read(struct procstat_core *core, void *buf, size_t len);
61 static ssize_t	core_read_mem(struct procstat_core *core, void *buf,
62     size_t len, vm_offset_t addr, bool readall);
63 static void	*get_args(struct procstat_core *core, vm_offset_t psstrings,
64     enum psc_type type, void *buf, size_t *lenp);
65 
66 struct procstat_core *
67 procstat_core_open(const char *filename)
68 {
69 	struct procstat_core *core;
70 	Elf *e;
71 	GElf_Ehdr ehdr;
72 	GElf_Phdr phdr;
73 	size_t nph;
74 	int fd, i;
75 
76 	if (elf_version(EV_CURRENT) == EV_NONE) {
77 		warnx("ELF library too old");
78 		return (NULL);
79 	}
80 	fd = open(filename, O_RDONLY, 0);
81 	if (fd == -1) {
82 		warn("open(%s)", filename);
83 		return (NULL);
84 	}
85 	e = elf_begin(fd, ELF_C_READ, NULL);
86 	if (e == NULL) {
87 		warnx("elf_begin: %s", elf_errmsg(-1));
88 		goto fail;
89 	}
90 	if (elf_kind(e) != ELF_K_ELF) {
91 		warnx("%s is not an ELF object", filename);
92 		goto fail;
93 	}
94 	if (gelf_getehdr(e, &ehdr) == NULL) {
95 		warnx("gelf_getehdr: %s", elf_errmsg(-1));
96 		goto fail;
97 	}
98 	if (ehdr.e_type != ET_CORE) {
99 		warnx("%s is not a CORE file", filename);
100 		goto fail;
101 	}
102 	if (elf_getphnum(e, &nph) == 0) {
103 		warnx("program headers not found");
104 		goto fail;
105 	}
106 	for (i = 0; i < ehdr.e_phnum; i++) {
107 		if (gelf_getphdr(e, i, &phdr) != &phdr) {
108 			warnx("gelf_getphdr: %s", elf_errmsg(-1));
109 			goto fail;
110 		}
111 		if (phdr.p_type == PT_NOTE)
112 			break;
113 	}
114 	if (i == ehdr.e_phnum) {
115 		warnx("NOTE program header not found");
116 		goto fail;
117 	}
118 	core = malloc(sizeof(struct procstat_core));
119 	if (core == NULL) {
120 		warn("malloc(%zu)", sizeof(struct procstat_core));
121 		goto fail;
122 	}
123 	core->pc_magic = PROCSTAT_CORE_MAGIC;
124 	core->pc_fd = fd;
125 	core->pc_elf = e;
126 	core->pc_ehdr = ehdr;
127 	core->pc_phdr = phdr;
128 
129 	return (core);
130 fail:
131 	if (e != NULL)
132 		elf_end(e);
133 	close(fd);
134 
135 	return (NULL);
136 }
137 
138 void
139 procstat_core_close(struct procstat_core *core)
140 {
141 
142 	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
143 
144 	elf_end(core->pc_elf);
145 	close(core->pc_fd);
146 	free(core);
147 }
148 
149 void *
150 procstat_core_get(struct procstat_core *core, enum psc_type type, void *buf,
151     size_t *lenp)
152 {
153 	Elf_Note nhdr;
154 	off_t offset, eoffset;
155 	vm_offset_t psstrings;
156 	void *freebuf;
157 	size_t len;
158 	u_int32_t n_type;
159 	int cstructsize, structsize;
160 	char nbuf[8];
161 
162 	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
163 
164 	switch(type) {
165 	case PSC_TYPE_PROC:
166 		n_type = NT_PROCSTAT_PROC;
167 		structsize = sizeof(struct kinfo_proc);
168 		break;
169 	case PSC_TYPE_FILES:
170 		n_type = NT_PROCSTAT_FILES;
171 		structsize = sizeof(struct kinfo_file);
172 		break;
173 	case PSC_TYPE_VMMAP:
174 		n_type = NT_PROCSTAT_VMMAP;
175 		structsize = sizeof(struct kinfo_vmentry);
176 		break;
177 	case PSC_TYPE_GROUPS:
178 		n_type = NT_PROCSTAT_GROUPS;
179 		structsize = sizeof(gid_t);
180 		break;
181 	case PSC_TYPE_UMASK:
182 		n_type = NT_PROCSTAT_UMASK;
183 		structsize = sizeof(u_short);
184 		break;
185 	case PSC_TYPE_RLIMIT:
186 		n_type = NT_PROCSTAT_RLIMIT;
187 		structsize = sizeof(struct rlimit) * RLIM_NLIMITS;
188 		break;
189 	case PSC_TYPE_OSREL:
190 		n_type = NT_PROCSTAT_OSREL;
191 		structsize = sizeof(int);
192 		break;
193 	case PSC_TYPE_PSSTRINGS:
194 	case PSC_TYPE_ARGV:
195 	case PSC_TYPE_ENVV:
196 		n_type = NT_PROCSTAT_PSSTRINGS;
197 		structsize = sizeof(vm_offset_t);
198 		break;
199 	case PSC_TYPE_AUXV:
200 		n_type = NT_PROCSTAT_AUXV;
201 		structsize = sizeof(Elf_Auxinfo);
202 		break;
203 	default:
204 		warnx("unknown core stat type: %d", type);
205 		return (NULL);
206 	}
207 
208 	offset = core->pc_phdr.p_offset;
209 	eoffset = offset + core->pc_phdr.p_filesz;
210 
211 	while (offset < eoffset) {
212 		if (!core_offset(core, offset))
213 			return (NULL);
214 		if (!core_read(core, &nhdr, sizeof(nhdr)))
215 			return (NULL);
216 
217 		offset += sizeof(nhdr) +
218 		    roundup2(nhdr.n_namesz, sizeof(Elf32_Size)) +
219 		    roundup2(nhdr.n_descsz, sizeof(Elf32_Size));
220 
221 		if (nhdr.n_namesz == 0 && nhdr.n_descsz == 0)
222 			break;
223 		if (nhdr.n_type != n_type)
224 			continue;
225 		if (nhdr.n_namesz != 8)
226 			continue;
227 		if (!core_read(core, nbuf, sizeof(nbuf)))
228 			return (NULL);
229 		if (strcmp(nbuf, "FreeBSD") != 0)
230 			continue;
231 		if (nhdr.n_descsz < sizeof(cstructsize)) {
232 			warnx("corrupted core file");
233 			return (NULL);
234 		}
235 		if (!core_read(core, &cstructsize, sizeof(cstructsize)))
236 			return (NULL);
237 		if (cstructsize != structsize) {
238 			warnx("version mismatch");
239 			return (NULL);
240 		}
241 		len = nhdr.n_descsz - sizeof(cstructsize);
242 		if (len == 0)
243 			return (NULL);
244 		if (buf != NULL) {
245 			len = MIN(len, *lenp);
246 			freebuf = NULL;
247 		} else {
248 			freebuf = buf = malloc(len);
249 			if (buf == NULL) {
250 				warn("malloc(%zu)", len);
251 				return (NULL);
252 			}
253 		}
254 		if (!core_read(core, buf, len)) {
255 			free(freebuf);
256 			return (NULL);
257 		}
258 		if (type == PSC_TYPE_ARGV || type == PSC_TYPE_ENVV) {
259 			if (len < sizeof(psstrings)) {
260 				free(freebuf);
261 				return (NULL);
262 			}
263 			psstrings = *(vm_offset_t *)buf;
264 			if (freebuf == NULL)
265 				len = *lenp;
266 			else
267 				buf = NULL;
268 			free(freebuf);
269 			buf = get_args(core, psstrings, type, buf, &len);
270 		}
271 		*lenp = len;
272 		return (buf);
273         }
274 
275 	return (NULL);
276 }
277 
278 static bool
279 core_offset(struct procstat_core *core, off_t offset)
280 {
281 
282 	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
283 
284 	if (lseek(core->pc_fd, offset, SEEK_SET) == -1) {
285 		warn("core: lseek(%jd)", (intmax_t)offset);
286 		return (false);
287 	}
288 	return (true);
289 }
290 
291 static bool
292 core_read(struct procstat_core *core, void *buf, size_t len)
293 {
294 	ssize_t n;
295 
296 	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
297 
298 	n = read(core->pc_fd, buf, len);
299 	if (n == -1) {
300 		warn("core: read");
301 		return (false);
302 	}
303 	if (n < (ssize_t)len) {
304 		warnx("core: short read");
305 		return (false);
306 	}
307 	return (true);
308 }
309 
310 static ssize_t
311 core_read_mem(struct procstat_core *core, void *buf, size_t len,
312     vm_offset_t addr, bool readall)
313 {
314 	GElf_Phdr phdr;
315 	off_t offset;
316 	int i;
317 
318 	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
319 
320 	for (i = 0; i < core->pc_ehdr.e_phnum; i++) {
321 		if (gelf_getphdr(core->pc_elf, i, &phdr) != &phdr) {
322 			warnx("gelf_getphdr: %s", elf_errmsg(-1));
323 			return (-1);
324 		}
325 		if (phdr.p_type != PT_LOAD)
326 			continue;
327 		if (addr < phdr.p_vaddr || addr > phdr.p_vaddr + phdr.p_memsz)
328 			continue;
329 		offset = phdr.p_offset + (addr - phdr.p_vaddr);
330 		if ((phdr.p_vaddr + phdr.p_memsz) - addr < len) {
331 			if (readall) {
332 				warnx("format error: "
333 				    "attempt to read out of segment");
334 				return (-1);
335 			}
336 			len = (phdr.p_vaddr + phdr.p_memsz) - addr;
337 		}
338 		if (!core_offset(core, offset))
339 			return (-1);
340 		if (!core_read(core, buf, len))
341 			return (-1);
342 		return (len);
343 	}
344 	warnx("format error: address %ju not found", (uintmax_t)addr);
345 	return (-1);
346 }
347 
348 #define ARGS_CHUNK_SZ	256	/* Chunk size (bytes) for get_args operations. */
349 
350 static void *
351 get_args(struct procstat_core *core, vm_offset_t psstrings, enum psc_type type,
352      void *args, size_t *lenp)
353 {
354 	struct ps_strings pss;
355 	void *freeargs;
356 	vm_offset_t addr;
357 	char **argv, *p;
358 	size_t chunksz, done, len, nchr, size;
359 	ssize_t n;
360 	u_int i, nstr;
361 
362 	assert(type == PSC_TYPE_ARGV || type == PSC_TYPE_ENVV);
363 
364 	if (core_read_mem(core, &pss, sizeof(pss), psstrings, true) == -1)
365 		return (NULL);
366 	if (type == PSC_TYPE_ARGV) {
367 		addr = (vm_offset_t)pss.ps_argvstr;
368 		nstr = pss.ps_nargvstr;
369 	} else /* type == PSC_TYPE_ENVV */ {
370 		addr = (vm_offset_t)pss.ps_envstr;
371 		nstr = pss.ps_nenvstr;
372 	}
373 	if (addr == 0 || nstr == 0)
374 		return (NULL);
375 	if (nstr > ARG_MAX) {
376 		warnx("format error");
377 		return (NULL);
378 	}
379 	size = nstr * sizeof(char *);
380 	argv = malloc(size);
381 	if (argv == NULL) {
382 		warn("malloc(%zu)", size);
383 		return (NULL);
384 	}
385 	done = 0;
386 	freeargs = NULL;
387 	if (core_read_mem(core, argv, size, addr, true) == -1)
388 		goto fail;
389 	if (args != NULL) {
390 		nchr = MIN(ARG_MAX, *lenp);
391 	} else {
392 		nchr = ARG_MAX;
393 		freeargs = args = malloc(nchr);
394 		if (args == NULL) {
395 			warn("malloc(%zu)", nchr);
396 			goto fail;
397 		}
398 	}
399 	p = args;
400 	for (i = 0; ; i++) {
401 		if (i == nstr)
402 			goto done;
403 		/*
404 		 * The program may have scribbled into its argv array, e.g. to
405 		 * remove some arguments.  If that has happened, break out
406 		 * before trying to read from NULL.
407 		 */
408 		if (argv[i] == NULL)
409 			goto done;
410 		for (addr = (vm_offset_t)argv[i]; ; addr += chunksz) {
411 			chunksz = MIN(ARGS_CHUNK_SZ, nchr - 1 - done);
412 			if (chunksz <= 0)
413 				goto done;
414 			n = core_read_mem(core, p, chunksz, addr, false);
415 			if (n == -1)
416 				goto fail;
417 			len = strnlen(p, chunksz);
418 			p += len;
419 			done += len;
420 			if (len != chunksz)
421 				break;
422 		}
423 		*p++ = '\0';
424 		done++;
425 	}
426 fail:
427 	free(freeargs);
428 	args = NULL;
429 done:
430 	*lenp = done;
431 	free(argv);
432 	return (args);
433 }
434