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