1249666Strociny/*-
2249666Strociny * Copyright (c) 2013 Mikolaj Golub <trociny@FreeBSD.org>
3249666Strociny * All rights reserved.
4249666Strociny *
5249666Strociny * Redistribution and use in source and binary forms, with or without
6249666Strociny * modification, are permitted provided that the following conditions
7249666Strociny * are met:
8249666Strociny * 1. Redistributions of source code must retain the above copyright
9249666Strociny *    notice, this list of conditions and the following disclaimer.
10249666Strociny * 2. Redistributions in binary form must reproduce the above copyright
11249666Strociny *    notice, this list of conditions and the following disclaimer in the
12249666Strociny *    documentation and/or other materials provided with the distribution.
13249666Strociny *
14249666Strociny * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15249666Strociny * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16249666Strociny * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17249666Strociny * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18249666Strociny * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19249666Strociny * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20249666Strociny * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21249666Strociny * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22249666Strociny * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23249666Strociny * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24249666Strociny * SUCH DAMAGE.
25249666Strociny */
26249666Strociny
27249731Strociny#include <sys/cdefs.h>
28249731Strociny__FBSDID("$FreeBSD$");
29249731Strociny
30249666Strociny#include <sys/param.h>
31249666Strociny#include <sys/elf.h>
32249679Strociny#include <sys/exec.h>
33249666Strociny#include <sys/user.h>
34249666Strociny
35249666Strociny#include <assert.h>
36249666Strociny#include <err.h>
37249666Strociny#include <fcntl.h>
38249666Strociny#include <gelf.h>
39249666Strociny#include <libelf.h>
40249666Strociny#include <stdbool.h>
41249666Strociny#include <stdint.h>
42249666Strociny#include <stdio.h>
43249666Strociny#include <stdlib.h>
44249666Strociny#include <string.h>
45249666Strociny#include <unistd.h>
46249666Strociny
47249666Strociny#include "core.h"
48249666Strociny
49249666Strociny#define PROCSTAT_CORE_MAGIC	0x012DADB8
50249666Strocinystruct procstat_core
51249666Strociny{
52249666Strociny	int		pc_magic;
53249666Strociny	int		pc_fd;
54249666Strociny	Elf		*pc_elf;
55249666Strociny	GElf_Ehdr	pc_ehdr;
56249666Strociny	GElf_Phdr	pc_phdr;
57249666Strociny};
58249666Strociny
59249666Strocinystatic bool	core_offset(struct procstat_core *core, off_t offset);
60249666Strocinystatic bool	core_read(struct procstat_core *core, void *buf, size_t len);
61249679Strocinystatic ssize_t	core_read_mem(struct procstat_core *core, void *buf,
62249679Strociny    size_t len, vm_offset_t addr, bool readall);
63249679Strocinystatic void	*get_args(struct procstat_core *core, vm_offset_t psstrings,
64249679Strociny    enum psc_type type, void *buf, size_t *lenp);
65249666Strociny
66249666Strocinystruct procstat_core *
67249666Strocinyprocstat_core_open(const char *filename)
68249666Strociny{
69249666Strociny	struct procstat_core *core;
70249666Strociny	Elf *e;
71249666Strociny	GElf_Ehdr ehdr;
72249666Strociny	GElf_Phdr phdr;
73249666Strociny	size_t nph;
74249666Strociny	int fd, i;
75249666Strociny
76249666Strociny	if (elf_version(EV_CURRENT) == EV_NONE) {
77249666Strociny		warnx("ELF library too old");
78249666Strociny		return (NULL);
79249666Strociny	}
80249666Strociny	fd = open(filename, O_RDONLY, 0);
81249666Strociny	if (fd == -1) {
82249666Strociny		warn("open(%s)", filename);
83249666Strociny		return (NULL);
84249666Strociny	}
85249666Strociny	e = elf_begin(fd, ELF_C_READ, NULL);
86249666Strociny	if (e == NULL) {
87249666Strociny		warnx("elf_begin: %s", elf_errmsg(-1));
88249666Strociny		goto fail;
89249666Strociny	}
90249666Strociny	if (elf_kind(e) != ELF_K_ELF) {
91249666Strociny		warnx("%s is not an ELF object", filename);
92249666Strociny		goto fail;
93249666Strociny	}
94249666Strociny	if (gelf_getehdr(e, &ehdr) == NULL) {
95249666Strociny		warnx("gelf_getehdr: %s", elf_errmsg(-1));
96249666Strociny		goto fail;
97249666Strociny	}
98249666Strociny	if (ehdr.e_type != ET_CORE) {
99249666Strociny		warnx("%s is not a CORE file", filename);
100249666Strociny		goto fail;
101249666Strociny	}
102249666Strociny	if (elf_getphnum(e, &nph) == 0) {
103249666Strociny		warnx("program headers not found");
104249666Strociny		goto fail;
105249666Strociny	}
106249666Strociny	for (i = 0; i < ehdr.e_phnum; i++) {
107249666Strociny		if (gelf_getphdr(e, i, &phdr) != &phdr) {
108249666Strociny			warnx("gelf_getphdr: %s", elf_errmsg(-1));
109249666Strociny			goto fail;
110249666Strociny		}
111249666Strociny		if (phdr.p_type == PT_NOTE)
112249666Strociny			break;
113249666Strociny	}
114249666Strociny	if (i == ehdr.e_phnum) {
115249666Strociny		warnx("NOTE program header not found");
116249666Strociny		goto fail;
117249666Strociny	}
118249666Strociny	core = malloc(sizeof(struct procstat_core));
119249666Strociny	if (core == NULL) {
120249666Strociny		warn("malloc(%zu)", sizeof(struct procstat_core));
121249666Strociny		goto fail;
122249666Strociny	}
123249666Strociny	core->pc_magic = PROCSTAT_CORE_MAGIC;
124249666Strociny	core->pc_fd = fd;
125249666Strociny	core->pc_elf = e;
126249666Strociny	core->pc_ehdr = ehdr;
127249666Strociny	core->pc_phdr = phdr;
128249666Strociny
129249666Strociny	return (core);
130249666Strocinyfail:
131249666Strociny	if (e != NULL)
132249666Strociny		elf_end(e);
133249666Strociny	close(fd);
134249666Strociny
135249666Strociny	return (NULL);
136249666Strociny}
137249666Strociny
138249666Strocinyvoid
139249666Strocinyprocstat_core_close(struct procstat_core *core)
140249666Strociny{
141249666Strociny
142249666Strociny	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
143249666Strociny
144249666Strociny	elf_end(core->pc_elf);
145249666Strociny	close(core->pc_fd);
146249666Strociny	free(core);
147249666Strociny}
148249666Strociny
149249666Strocinyvoid *
150249666Strocinyprocstat_core_get(struct procstat_core *core, enum psc_type type, void *buf,
151249666Strociny    size_t *lenp)
152249666Strociny{
153249666Strociny	Elf_Note nhdr;
154249666Strociny	off_t offset, eoffset;
155249679Strociny	vm_offset_t psstrings;
156249666Strociny	void *freebuf;
157249666Strociny	size_t len;
158249666Strociny	u_int32_t n_type;
159249666Strociny	int cstructsize, structsize;
160249666Strociny	char nbuf[8];
161249666Strociny
162249666Strociny	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
163249666Strociny
164249666Strociny	switch(type) {
165249666Strociny	case PSC_TYPE_PROC:
166249666Strociny		n_type = NT_PROCSTAT_PROC;
167249666Strociny		structsize = sizeof(struct kinfo_proc);
168249666Strociny		break;
169249666Strociny	case PSC_TYPE_FILES:
170249666Strociny		n_type = NT_PROCSTAT_FILES;
171249666Strociny		structsize = sizeof(struct kinfo_file);
172249666Strociny		break;
173249666Strociny	case PSC_TYPE_VMMAP:
174249666Strociny		n_type = NT_PROCSTAT_VMMAP;
175249666Strociny		structsize = sizeof(struct kinfo_vmentry);
176249666Strociny		break;
177249670Strociny	case PSC_TYPE_GROUPS:
178249670Strociny		n_type = NT_PROCSTAT_GROUPS;
179249670Strociny		structsize = sizeof(gid_t);
180249670Strociny		break;
181249672Strociny	case PSC_TYPE_UMASK:
182249672Strociny		n_type = NT_PROCSTAT_UMASK;
183249672Strociny		structsize = sizeof(u_short);
184249672Strociny		break;
185249674Strociny	case PSC_TYPE_RLIMIT:
186249674Strociny		n_type = NT_PROCSTAT_RLIMIT;
187249674Strociny		structsize = sizeof(struct rlimit) * RLIM_NLIMITS;
188249674Strociny		break;
189249677Strociny	case PSC_TYPE_OSREL:
190249677Strociny		n_type = NT_PROCSTAT_OSREL;
191249677Strociny		structsize = sizeof(int);
192249677Strociny		break;
193249679Strociny	case PSC_TYPE_PSSTRINGS:
194249679Strociny	case PSC_TYPE_ARGV:
195249679Strociny	case PSC_TYPE_ENVV:
196249679Strociny		n_type = NT_PROCSTAT_PSSTRINGS;
197249679Strociny		structsize = sizeof(vm_offset_t);
198249679Strociny		break;
199249681Strociny	case PSC_TYPE_AUXV:
200249681Strociny		n_type = NT_PROCSTAT_AUXV;
201249681Strociny		structsize = sizeof(Elf_Auxinfo);
202249681Strociny		break;
203249666Strociny	default:
204249666Strociny		warnx("unknown core stat type: %d", type);
205249666Strociny		return (NULL);
206249666Strociny	}
207249666Strociny
208249666Strociny	offset = core->pc_phdr.p_offset;
209249666Strociny	eoffset = offset + core->pc_phdr.p_filesz;
210249666Strociny
211249666Strociny	while (offset < eoffset) {
212249666Strociny		if (!core_offset(core, offset))
213249666Strociny			return (NULL);
214249666Strociny		if (!core_read(core, &nhdr, sizeof(nhdr)))
215249666Strociny			return (NULL);
216249666Strociny
217249666Strociny		offset += sizeof(nhdr) +
218249666Strociny		    roundup2(nhdr.n_namesz, sizeof(Elf32_Size)) +
219249666Strociny		    roundup2(nhdr.n_descsz, sizeof(Elf32_Size));
220249666Strociny
221249666Strociny		if (nhdr.n_namesz == 0 && nhdr.n_descsz == 0)
222249666Strociny			break;
223249666Strociny		if (nhdr.n_type != n_type)
224249666Strociny			continue;
225249666Strociny		if (nhdr.n_namesz != 8)
226249666Strociny			continue;
227249666Strociny		if (!core_read(core, nbuf, sizeof(nbuf)))
228249666Strociny			return (NULL);
229249666Strociny		if (strcmp(nbuf, "FreeBSD") != 0)
230249666Strociny			continue;
231249666Strociny		if (nhdr.n_descsz < sizeof(cstructsize)) {
232249666Strociny			warnx("corrupted core file");
233249666Strociny			return (NULL);
234249666Strociny		}
235249666Strociny		if (!core_read(core, &cstructsize, sizeof(cstructsize)))
236249666Strociny			return (NULL);
237249666Strociny		if (cstructsize != structsize) {
238249666Strociny			warnx("version mismatch");
239249666Strociny			return (NULL);
240249666Strociny		}
241249666Strociny		len = nhdr.n_descsz - sizeof(cstructsize);
242249666Strociny		if (len == 0)
243249666Strociny			return (NULL);
244249666Strociny		if (buf != NULL) {
245249666Strociny			len = MIN(len, *lenp);
246249666Strociny			freebuf = NULL;
247249666Strociny		} else {
248249666Strociny			freebuf = buf = malloc(len);
249249666Strociny			if (buf == NULL) {
250249666Strociny				warn("malloc(%zu)", len);
251249666Strociny				return (NULL);
252249666Strociny			}
253249666Strociny		}
254249666Strociny		if (!core_read(core, buf, len)) {
255249666Strociny			free(freebuf);
256249666Strociny			return (NULL);
257249666Strociny		}
258249679Strociny		if (type == PSC_TYPE_ARGV || type == PSC_TYPE_ENVV) {
259249679Strociny			if (len < sizeof(psstrings)) {
260249679Strociny				free(freebuf);
261249679Strociny				return (NULL);
262249679Strociny			}
263249679Strociny			psstrings = *(vm_offset_t *)buf;
264249679Strociny			if (freebuf == NULL)
265249679Strociny				len = *lenp;
266249679Strociny			else
267249679Strociny				buf = NULL;
268249679Strociny			free(freebuf);
269249679Strociny			buf = get_args(core, psstrings, type, buf, &len);
270249679Strociny		}
271249666Strociny		*lenp = len;
272249666Strociny		return (buf);
273249666Strociny        }
274249666Strociny
275249666Strociny	return (NULL);
276249666Strociny}
277249666Strociny
278249666Strocinystatic bool
279249666Strocinycore_offset(struct procstat_core *core, off_t offset)
280249666Strociny{
281249666Strociny
282249666Strociny	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
283249666Strociny
284249666Strociny	if (lseek(core->pc_fd, offset, SEEK_SET) == -1) {
285249666Strociny		warn("core: lseek(%jd)", (intmax_t)offset);
286249666Strociny		return (false);
287249666Strociny	}
288249666Strociny	return (true);
289249666Strociny}
290249666Strociny
291249666Strocinystatic bool
292249666Strocinycore_read(struct procstat_core *core, void *buf, size_t len)
293249666Strociny{
294249666Strociny	ssize_t n;
295249666Strociny
296249666Strociny	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
297249666Strociny
298249666Strociny	n = read(core->pc_fd, buf, len);
299249666Strociny	if (n == -1) {
300249666Strociny		warn("core: read");
301249666Strociny		return (false);
302249666Strociny	}
303249666Strociny	if (n < (ssize_t)len) {
304249666Strociny		warnx("core: short read");
305249666Strociny		return (false);
306249666Strociny	}
307249666Strociny	return (true);
308249666Strociny}
309249679Strociny
310249679Strocinystatic ssize_t
311249679Strocinycore_read_mem(struct procstat_core *core, void *buf, size_t len,
312249679Strociny    vm_offset_t addr, bool readall)
313249679Strociny{
314249679Strociny	GElf_Phdr phdr;
315249679Strociny	off_t offset;
316249679Strociny	int i;
317249679Strociny
318249679Strociny	assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
319249679Strociny
320249679Strociny	for (i = 0; i < core->pc_ehdr.e_phnum; i++) {
321249679Strociny		if (gelf_getphdr(core->pc_elf, i, &phdr) != &phdr) {
322249679Strociny			warnx("gelf_getphdr: %s", elf_errmsg(-1));
323249679Strociny			return (-1);
324249679Strociny		}
325249679Strociny		if (phdr.p_type != PT_LOAD)
326249679Strociny			continue;
327249679Strociny		if (addr < phdr.p_vaddr || addr > phdr.p_vaddr + phdr.p_memsz)
328249679Strociny			continue;
329249679Strociny		offset = phdr.p_offset + (addr - phdr.p_vaddr);
330249679Strociny		if ((phdr.p_vaddr + phdr.p_memsz) - addr < len) {
331249679Strociny			if (readall) {
332249679Strociny				warnx("format error: "
333249679Strociny				    "attempt to read out of segment");
334249679Strociny				return (-1);
335249679Strociny			}
336249679Strociny			len = (phdr.p_vaddr + phdr.p_memsz) - addr;
337249679Strociny		}
338249679Strociny		if (!core_offset(core, offset))
339249679Strociny			return (-1);
340249679Strociny		if (!core_read(core, buf, len))
341249679Strociny			return (-1);
342249679Strociny		return (len);
343249679Strociny	}
344249679Strociny	warnx("format error: address %ju not found", (uintmax_t)addr);
345249679Strociny	return (-1);
346249679Strociny}
347249679Strociny
348249679Strociny#define ARGS_CHUNK_SZ	256	/* Chunk size (bytes) for get_args operations. */
349249679Strociny
350249679Strocinystatic void *
351249679Strocinyget_args(struct procstat_core *core, vm_offset_t psstrings, enum psc_type type,
352249679Strociny     void *args, size_t *lenp)
353249679Strociny{
354249679Strociny	struct ps_strings pss;
355249679Strociny	void *freeargs;
356249679Strociny	vm_offset_t addr;
357249679Strociny	char **argv, *p;
358249679Strociny	size_t chunksz, done, len, nchr, size;
359249679Strociny	ssize_t n;
360249679Strociny	u_int i, nstr;
361249679Strociny
362249679Strociny	assert(type == PSC_TYPE_ARGV || type == PSC_TYPE_ENVV);
363249679Strociny
364249679Strociny	if (core_read_mem(core, &pss, sizeof(pss), psstrings, true) == -1)
365249679Strociny		return (NULL);
366249679Strociny	if (type == PSC_TYPE_ARGV) {
367249679Strociny		addr = (vm_offset_t)pss.ps_argvstr;
368249679Strociny		nstr = pss.ps_nargvstr;
369249679Strociny	} else /* type == PSC_TYPE_ENVV */ {
370249679Strociny		addr = (vm_offset_t)pss.ps_envstr;
371249679Strociny		nstr = pss.ps_nenvstr;
372249679Strociny	}
373249679Strociny	if (addr == 0 || nstr == 0)
374249679Strociny		return (NULL);
375249679Strociny	if (nstr > ARG_MAX) {
376249679Strociny		warnx("format error");
377249679Strociny		return (NULL);
378249679Strociny	}
379249679Strociny	size = nstr * sizeof(char *);
380249679Strociny	argv = malloc(size);
381249679Strociny	if (argv == NULL) {
382249679Strociny		warn("malloc(%zu)", size);
383249679Strociny		return (NULL);
384249679Strociny	}
385249679Strociny	done = 0;
386249679Strociny	freeargs = NULL;
387249679Strociny	if (core_read_mem(core, argv, size, addr, true) == -1)
388249679Strociny		goto fail;
389249679Strociny	if (args != NULL) {
390249679Strociny		nchr = MIN(ARG_MAX, *lenp);
391249679Strociny	} else {
392249679Strociny		nchr = ARG_MAX;
393249679Strociny		freeargs = args = malloc(nchr);
394249679Strociny		if (args == NULL) {
395249679Strociny			warn("malloc(%zu)", nchr);
396249679Strociny			goto fail;
397249679Strociny		}
398249679Strociny	}
399249679Strociny	p = args;
400249679Strociny	for (i = 0; ; i++) {
401249679Strociny		if (i == nstr)
402249679Strociny			goto done;
403249679Strociny		/*
404249679Strociny		 * The program may have scribbled into its argv array, e.g. to
405249679Strociny		 * remove some arguments.  If that has happened, break out
406249679Strociny		 * before trying to read from NULL.
407249679Strociny		 */
408249679Strociny		if (argv[i] == NULL)
409249679Strociny			goto done;
410249679Strociny		for (addr = (vm_offset_t)argv[i]; ; addr += chunksz) {
411249679Strociny			chunksz = MIN(ARGS_CHUNK_SZ, nchr - 1 - done);
412249679Strociny			if (chunksz <= 0)
413249679Strociny				goto done;
414249679Strociny			n = core_read_mem(core, p, chunksz, addr, false);
415249679Strociny			if (n == -1)
416249679Strociny				goto fail;
417249679Strociny			len = strnlen(p, chunksz);
418249679Strociny			p += len;
419249679Strociny			done += len;
420249679Strociny			if (len != chunksz)
421249679Strociny				break;
422249679Strociny		}
423249679Strociny		*p++ = '\0';
424249679Strociny		done++;
425249679Strociny	}
426249679Strocinyfail:
427249679Strociny	free(freeargs);
428249679Strociny	args = NULL;
429249679Strocinydone:
430249679Strociny	*lenp = done;
431249679Strociny	free(argv);
432249679Strociny	return (args);
433249679Strociny}
434