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
50struct 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
59static bool	core_offset(struct procstat_core *core, off_t offset);
60static bool	core_read(struct procstat_core *core, void *buf, size_t len);
61static ssize_t	core_read_mem(struct procstat_core *core, void *buf,
62    size_t len, vm_offset_t addr, bool readall);
63static void	*get_args(struct procstat_core *core, vm_offset_t psstrings,
64    enum psc_type type, void *buf, size_t *lenp);
65
66struct procstat_core *
67procstat_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);
130fail:
131	if (e != NULL)
132		elf_end(e);
133	close(fd);
134
135	return (NULL);
136}
137
138void
139procstat_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
149void *
150procstat_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
278static bool
279core_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
291static bool
292core_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
310static ssize_t
311core_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
350static void *
351get_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	}
426fail:
427	free(freeargs);
428	args = NULL;
429done:
430	*lenp = done;
431	free(argv);
432	return (args);
433}
434