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