xref: /freebsd/usr.bin/elfctl/elfctl.c (revision a90b9d0159070121c221b966469c3e36d912bf82)
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"
51 static bool convert_to_feature_val(const char *, uint32_t *);
52 static bool edit_file_features(Elf *, int, int, char *, bool);
53 static bool get_file_features(Elf *, int, int, uint32_t *, uint64_t *, bool);
54 static void print_features(void);
55 static bool print_file_features(Elf *, int, int, char *, bool);
56 static void usage(void) __dead2;
57 
58 struct ControlFeatures {
59 	const char *alias;
60 	unsigned long value;
61 	const char *desc;
62 };
63 
64 static 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 
73 static 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 
86 static bool iflag;
87 
88 int
89 main(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 		}
189 fail:
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 	"\
205 Usage: %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 
213 static void
214 usage(void)
215 {
216 
217 	fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME());
218 	exit(1);
219 }
220 
221 static bool
222 convert_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 
294 static bool
295 edit_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 
325 static void
326 print_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 
336 static bool
337 print_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 
361 static bool
362 get_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