1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 The FreeBSD Foundation.
5 *
6 * This software was developed by Bora Ozarslan under sponsorship from
7 * the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/param.h>
32#include <sys/elf_common.h>
33#include <sys/endian.h>
34#include <sys/stat.h>
35
36#include <ctype.h>
37#include <err.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <gelf.h>
41#include <getopt.h>
42#include <libelf.h>
43#include <stdbool.h>
44#include <stdint.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49
50#include "_elftc.h"
51static bool convert_to_feature_val(const char *, uint32_t *);
52static bool edit_file_features(Elf *, int, int, char *, bool);
53static bool get_file_features(Elf *, int, int, uint32_t *, uint64_t *, bool);
54static void print_features(void);
55static bool print_file_features(Elf *, int, int, char *, bool);
56static void usage(void) __dead2;
57
58struct ControlFeatures {
59	const char *alias;
60	unsigned long value;
61	const char *desc;
62};
63
64static struct ControlFeatures featurelist[] = {
65	{ "noaslr",	NT_FREEBSD_FCTL_ASLR_DISABLE,	"Disable ASLR" },
66	{ "noprotmax",	NT_FREEBSD_FCTL_PROTMAX_DISABLE,
67	    "Disable implicit PROT_MAX" },
68	{ "nostackgap",	NT_FREEBSD_FCTL_STKGAP_DISABLE, "Disable stack gap" },
69	{ "wxneeded",	NT_FREEBSD_FCTL_WXNEEDED, "Requires W+X mappings" },
70	{ "la48",	NT_FREEBSD_FCTL_LA48, "amd64: Limit user VA to 48bit" },
71};
72
73static struct option long_opts[] = {
74	{ "help",	no_argument,	NULL,	'h' },
75	{ NULL,		0,		NULL,	0 }
76};
77
78#if BYTE_ORDER == LITTLE_ENDIAN
79#define	HOST_ENDIAN	ELFDATA2LSB
80#define	SWAP_ENDIAN	ELFDATA2MSB
81#else
82#define	HOST_ENDIAN	ELFDATA2MSB
83#define	SWAP_ENDIAN	ELFDATA2LSB
84#endif
85
86static bool iflag;
87
88int
89main(int argc, char **argv)
90{
91	GElf_Ehdr ehdr;
92	Elf *elf;
93	Elf_Kind kind;
94	int ch, fd, retval;
95	char *features;
96	bool editfeatures, lflag, endian_swap;
97
98	lflag = 0;
99	editfeatures = false;
100	retval = 0;
101	features = NULL;
102
103	if (elf_version(EV_CURRENT) == EV_NONE)
104		errx(EXIT_FAILURE, "elf_version error");
105
106	while ((ch = getopt_long(argc, argv, "hile:", long_opts, NULL)) != -1) {
107		switch (ch) {
108		case 'i':
109			iflag = true;
110			break;
111		case 'l':
112			print_features();
113			lflag = true;
114			break;
115		case 'e':
116			if (features != NULL)
117				errx(1, "-e may be specified only once");
118			features = optarg;
119			editfeatures = true;
120			break;
121		case 'h':
122		default:
123			usage();
124		}
125	}
126	argc -= optind;
127	argv += optind;
128	if (argc == 0) {
129		if (lflag)
130			exit(0);
131		else {
132			warnx("no file(s) specified");
133			usage();
134		}
135	}
136
137	while (argc) {
138		elf = NULL;
139
140		if ((fd = open(argv[0],
141		    editfeatures ? O_RDWR : O_RDONLY, 0)) < 0) {
142			warn("error opening file %s", argv[0]);
143			retval = 1;
144			goto fail;
145		}
146
147		if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
148			warnx("elf_begin failed: %s", elf_errmsg(-1));
149			retval = 1;
150			goto fail;
151		}
152
153		if ((kind = elf_kind(elf)) != ELF_K_ELF) {
154			if (kind == ELF_K_AR)
155				warnx("file '%s' is an archive", argv[0]);
156			else
157				warnx("file '%s' is not an ELF file", argv[0]);
158			retval = 1;
159			goto fail;
160		}
161
162		if (gelf_getehdr(elf, &ehdr) == NULL) {
163			warnx("gelf_getehdr: %s", elf_errmsg(-1));
164			retval = 1;
165			goto fail;
166		}
167
168		if (ehdr.e_ident[EI_DATA] == HOST_ENDIAN) {
169			endian_swap = false;
170		} else if (ehdr.e_ident[EI_DATA] == SWAP_ENDIAN) {
171			endian_swap = true;
172		} else {
173			warnx("file endianness unknown");
174			retval = 1;
175			goto fail;
176		}
177
178		if (!editfeatures) {
179			if (!print_file_features(elf, ehdr.e_phnum, fd,
180			    argv[0], endian_swap)) {
181				retval = 1;
182				goto fail;
183			}
184		} else if (!edit_file_features(elf, ehdr.e_phnum, fd,
185		    features, endian_swap)) {
186			retval = 1;
187			goto fail;
188		}
189fail:
190		if (elf != NULL)
191			elf_end(elf);
192
193		if (fd >= 0)
194			close(fd);
195
196		argc--;
197		argv++;
198	}
199
200	return (retval);
201}
202
203#define USAGE_MESSAGE \
204	"\
205Usage: %s [options] file...\n\
206  Set or display the control features for an ELF object.\n\n\
207  Supported options are:\n\
208  -l                        List known control features.\n\
209  -i                        Ignore unknown features.\n\
210  -e [+-=]feature,list      Edit features from a comma separated list.\n\
211  -h | --help               Print a usage message and exit.\n"
212
213static void
214usage(void)
215{
216
217	fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME());
218	exit(1);
219}
220
221static bool
222convert_to_feature_val(const char *feature_str, uint32_t *feature_val)
223{
224	char *feature, *feature_tmp;
225	int i, len;
226	uint32_t input;
227	char operation;
228
229	input = 0;
230	operation = *feature_str;
231	feature_str++;
232
233	if (operation != '+' && operation != '-' && operation != '=')
234		errx(1, "'%c' not an operator - use '+', '-', '='", operation);
235
236	if ((feature_tmp = strdup(feature_str)) == NULL)
237		err(1, "strdup");
238	len = nitems(featurelist);
239	while ((feature = strsep(&feature_tmp, ",")) != NULL) {
240		for (i = 0; i < len; ++i) {
241			if (strcmp(featurelist[i].alias, feature) == 0) {
242				input |= featurelist[i].value;
243				break;
244			}
245			/* XXX Backwards compatibility for "no"-prefix flags. */
246			if (strncmp(featurelist[i].alias, "no", 2) == 0 &&
247			    strcmp(featurelist[i].alias + 2, feature) == 0) {
248				input |= featurelist[i].value;
249				warnx(
250				    "interpreting %s as %s; please specify %s",
251				    feature, featurelist[i].alias,
252				    featurelist[i].alias);
253				break;
254			}
255		}
256		if (i == len) {
257			if (isdigit(feature[0])) {
258				char *eptr;
259				unsigned long long val;
260
261				errno = 0;
262				val = strtoll(feature, &eptr, 0);
263				if (eptr == feature || *eptr != '\0')
264					errno = EINVAL;
265				else if (val > UINT32_MAX)
266					errno = ERANGE;
267				if (errno != 0) {
268					warn("%s invalid", feature);
269					free(feature_tmp);
270					return (false);
271				}
272				input |= val;
273			} else {
274				warnx("%s is not a valid feature", feature);
275				if (!iflag) {
276					free(feature_tmp);
277					return (false);
278				}
279			}
280		}
281	}
282
283	if (operation == '+') {
284		*feature_val |= input;
285	} else if (operation == '=') {
286		*feature_val = input;
287	} else if (operation == '-') {
288		*feature_val &= ~input;
289	}
290	free(feature_tmp);
291	return (true);
292}
293
294static bool
295edit_file_features(Elf *elf, int phcount, int fd, char *val, bool endian_swap)
296{
297	uint32_t features, prev_features;
298	uint64_t off;
299
300	if (!get_file_features(elf, phcount, fd, &features, &off,
301	    endian_swap)) {
302		warnx("NT_FREEBSD_FEATURE_CTL note not found");
303		return (false);
304	}
305
306	prev_features = features;
307	if (!convert_to_feature_val(val, &features))
308		return (false);
309	/* Avoid touching file if no change. */
310	if (features == prev_features)
311		return (true);
312
313	if (endian_swap)
314		features = bswap32(features);
315
316	if (lseek(fd, off, SEEK_SET) == -1 ||
317	    write(fd, &features, sizeof(features)) <
318	    (ssize_t)sizeof(features)) {
319		warnx("error writing feature value");
320		return (false);
321	}
322	return (true);
323}
324
325static void
326print_features(void)
327{
328	size_t i;
329
330	printf("Known features are:\n");
331	for (i = 0; i < nitems(featurelist); ++i)
332		printf("%-16s%s\n", featurelist[i].alias,
333		    featurelist[i].desc);
334}
335
336static bool
337print_file_features(Elf *elf, int phcount, int fd, char *filename,
338    bool endian_swap)
339{
340	uint32_t features;
341	unsigned long i;
342
343	if (!get_file_features(elf, phcount, fd, &features, NULL,
344	    endian_swap)) {
345		return (false);
346	}
347
348	printf("File '%s' features:\n", filename);
349	for (i = 0; i < nitems(featurelist); ++i) {
350		printf("%-16s'%s' is ", featurelist[i].alias,
351		    featurelist[i].desc);
352
353		if ((featurelist[i].value & features) == 0)
354			printf("un");
355
356		printf("set.\n");
357	}
358	return (true);
359}
360
361static bool
362get_file_features(Elf *elf, int phcount, int fd, uint32_t *features,
363    uint64_t *off, bool endian_swap)
364{
365	GElf_Phdr phdr;
366	Elf_Note note;
367	unsigned long read_total;
368	int namesz, descsz, i;
369	char *name;
370
371	/*
372	 * Go through each program header to find one that is of type PT_NOTE
373	 * and has a note for feature control.
374	 */
375	for (i = 0; i < phcount; ++i) {
376		if (gelf_getphdr(elf, i, &phdr) == NULL) {
377			warnx("gelf_getphdr failed: %s", elf_errmsg(-1));
378			return (false);
379		}
380
381		if (phdr.p_type != PT_NOTE)
382			continue;
383
384		if (lseek(fd, phdr.p_offset, SEEK_SET) < 0) {
385			warn("lseek() failed:");
386			return (false);
387		}
388
389		read_total = 0;
390		while (read_total < phdr.p_filesz) {
391			if (read(fd, &note, sizeof(note)) <
392			    (ssize_t)sizeof(note)) {
393				warnx("elf note header too short");
394				return (false);
395			}
396			read_total += sizeof(note);
397
398			if (endian_swap) {
399				note.n_namesz = bswap32(note.n_namesz);
400				note.n_descsz = bswap32(note.n_descsz);
401				note.n_type = bswap32(note.n_type);
402			}
403
404			/*
405			 * XXX: Name and descriptor are 4 byte aligned, however,
406			 * the size given doesn't include the padding.
407			 */
408			namesz = roundup2(note.n_namesz, 4);
409			name = malloc(namesz);
410			if (name == NULL) {
411				warn("malloc() failed.");
412				return (false);
413			}
414			descsz = roundup2(note.n_descsz, 4);
415			if (read(fd, name, namesz) < namesz) {
416				warnx("elf note name too short");
417				free(name);
418				return (false);
419			}
420			read_total += namesz;
421
422			if (note.n_namesz != 8 ||
423			    strncmp("FreeBSD", name, 7) != 0 ||
424			    note.n_type != NT_FREEBSD_FEATURE_CTL) {
425				/* Not the right note. Skip the description */
426				if (lseek(fd, descsz, SEEK_CUR) < 0) {
427					warn("lseek() failed.");
428					free(name);
429					return (false);
430				}
431				read_total += descsz;
432				free(name);
433				continue;
434			}
435
436			if (note.n_descsz < sizeof(uint32_t)) {
437				warnx("Feature descriptor can't "
438				    "be less than 4 bytes");
439				free(name);
440				return (false);
441			}
442
443			/*
444			 * XXX: For now we look at only 4 bytes of the
445			 * descriptor. This should respect descsz.
446			 */
447			if (note.n_descsz > sizeof(uint32_t))
448				warnx("Feature note is bigger than expected");
449			if (read(fd, features, sizeof(uint32_t)) <
450			    (ssize_t)sizeof(uint32_t)) {
451				warnx("feature note data too short");
452				free(name);
453				return (false);
454			}
455			if (endian_swap)
456				*features = bswap32(*features);
457			if (off != NULL)
458				*off = phdr.p_offset + read_total;
459			free(name);
460			return (true);
461		}
462	}
463
464	warnx("NT_FREEBSD_FEATURE_CTL note not found");
465	return (false);
466}
467