xref: /freebsd/usr.bin/elfctl/elfctl.c (revision 79ac3c12a714bcd3f2354c52d948aed9575c46d6)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 
52 __FBSDID("$FreeBSD$");
53 
54 static bool convert_to_feature_val(char *, uint32_t *);
55 static bool edit_file_features(Elf *, int, int, char *, bool);
56 static bool get_file_features(Elf *, int, int, uint32_t *, uint64_t *, bool);
57 static void print_features(void);
58 static bool print_file_features(Elf *, int, int, char *, bool);
59 static void usage(void);
60 
61 struct ControlFeatures {
62 	const char *alias;
63 	unsigned long value;
64 	const char *desc;
65 };
66 
67 static struct ControlFeatures featurelist[] = {
68 	{ "noaslr",	NT_FREEBSD_FCTL_ASLR_DISABLE,	"Disable ASLR" },
69 	{ "noprotmax",	NT_FREEBSD_FCTL_PROTMAX_DISABLE,
70 	    "Disable implicit PROT_MAX" },
71 	{ "nostackgap",	NT_FREEBSD_FCTL_STKGAP_DISABLE, "Disable stack gap" },
72 	{ "wxneeded",	NT_FREEBSD_FCTL_WXNEEDED, "Requires W+X mappings" },
73 	{ "la48",	NT_FREEBSD_FCTL_LA48, "amd64: Limit user VA to 48bit" },
74 	{ "noaslrstkgap", NT_FREEBSD_FCTL_ASG_DISABLE,
75 	    "Disable ASLR stack gap" },
76 };
77 
78 static struct option long_opts[] = {
79 	{ "help",	no_argument,	NULL,	'h' },
80 	{ NULL,		0,		NULL,	0 }
81 };
82 
83 #if BYTE_ORDER == LITTLE_ENDIAN
84 #define	HOST_ENDIAN	ELFDATA2LSB
85 #define	SWAP_ENDIAN	ELFDATA2MSB
86 #else
87 #define	HOST_ENDIAN 	ELFDATA2MSB
88 #define	SWAP_ENDIAN	ELFDATA2LSB
89 #endif
90 
91 static bool iflag;
92 
93 int
94 main(int argc, char **argv)
95 {
96 	GElf_Ehdr ehdr;
97 	Elf *elf;
98 	Elf_Kind kind;
99 	int ch, fd, retval;
100 	char *features;
101 	bool editfeatures, lflag, endian_swap;
102 
103 	lflag = 0;
104 	editfeatures = false;
105 	retval = 0;
106 	features = NULL;
107 
108 	if (elf_version(EV_CURRENT) == EV_NONE)
109 		errx(EXIT_FAILURE, "elf_version error");
110 
111 	while ((ch = getopt_long(argc, argv, "hile:", long_opts, NULL)) != -1) {
112 		switch (ch) {
113 		case 'i':
114 			iflag = true;
115 			break;
116 		case 'l':
117 			print_features();
118 			lflag = true;
119 			break;
120 		case 'e':
121 			features = optarg;
122 			editfeatures = true;
123 			break;
124 		case 'h':
125 		default:
126 			usage();
127 		}
128 	}
129 	argc -= optind;
130 	argv += optind;
131 	if (argc == 0) {
132 		if (lflag)
133 			exit(0);
134 		else {
135 			warnx("no file(s) specified");
136 			usage();
137 		}
138 	}
139 
140 	while (argc) {
141 		elf = NULL;
142 
143 		if ((fd = open(argv[0],
144 		    editfeatures ? O_RDWR : O_RDONLY, 0)) < 0) {
145 			warn("error opening file %s", argv[0]);
146 			retval = 1;
147 			goto fail;
148 		}
149 
150 		if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
151 			warnx("elf_begin failed: %s", elf_errmsg(-1));
152 			retval = 1;
153 			goto fail;
154 		}
155 
156 		if ((kind = elf_kind(elf)) != ELF_K_ELF) {
157 			if (kind == ELF_K_AR)
158 				warnx("file '%s' is an archive", argv[0]);
159 			else
160 				warnx("file '%s' is not an ELF file", argv[0]);
161 			retval = 1;
162 			goto fail;
163 		}
164 
165 		if (gelf_getehdr(elf, &ehdr) == NULL) {
166 			warnx("gelf_getehdr: %s", elf_errmsg(-1));
167 			retval = 1;
168 			goto fail;
169 		}
170 
171 		if (ehdr.e_ident[EI_DATA] == HOST_ENDIAN) {
172 			endian_swap = false;
173 		} else if (ehdr.e_ident[EI_DATA] == SWAP_ENDIAN) {
174 			endian_swap = true;
175 		} else {
176 			warnx("file endianness unknown");
177 			retval = 1;
178 			goto fail;
179 		}
180 
181 		if (!editfeatures) {
182 			if (!print_file_features(elf, ehdr.e_phnum, fd,
183 			    argv[0], endian_swap)) {
184 				retval = 1;
185 				goto fail;
186 			}
187 		} else if (!edit_file_features(elf, ehdr.e_phnum, fd,
188 		    features, endian_swap)) {
189 			retval = 1;
190 			goto fail;
191 		}
192 fail:
193 		if (elf != NULL)
194 			elf_end(elf);
195 
196 		if (fd >= 0)
197 			close(fd);
198 
199 		argc--;
200 		argv++;
201 	}
202 
203 	return (retval);
204 }
205 
206 #define USAGE_MESSAGE \
207 	"\
208 Usage: %s [options] file...\n\
209   Set or display the control features for an ELF object.\n\n\
210   Supported options are:\n\
211   -l                        List known control features.\n\
212   -i                        Ignore unknown features.\n\
213   -e [+-=]feature,list      Edit features from a comma separated list.\n\
214   -h | --help               Print a usage message and exit.\n"
215 
216 static void
217 usage(void)
218 {
219 
220 	fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME());
221 	exit(1);
222 }
223 
224 static bool
225 convert_to_feature_val(char *feature_str, uint32_t *feature_val)
226 {
227 	char *feature;
228 	int i, len;
229 	uint32_t input;
230 	char operation;
231 
232 	input = 0;
233 	operation = *feature_str;
234 	feature_str++;
235 	len = nitems(featurelist);
236 	while ((feature = strsep(&feature_str, ",")) != NULL) {
237 		for (i = 0; i < len; ++i) {
238 			if (strcmp(featurelist[i].alias, feature) == 0) {
239 				input |= featurelist[i].value;
240 				break;
241 			}
242 			/* XXX Backwards compatibility for "no"-prefix flags. */
243 			if (strncmp(featurelist[i].alias, "no", 2) == 0 &&
244 			    strcmp(featurelist[i].alias + 2, feature) == 0) {
245 				input |= featurelist[i].value;
246 				warnx(
247 				    "interpreting %s as %s; please specify %s",
248 				    feature, featurelist[i].alias,
249 				    featurelist[i].alias);
250 				break;
251 			}
252 		}
253 		if (i == len) {
254 			if (isdigit(feature[0])) {
255 				char *eptr;
256 				unsigned long long val;
257 
258 				errno = 0;
259 				val = strtoll(feature, &eptr, 0);
260 				if (eptr == feature || *eptr != '\0')
261 					errno = EINVAL;
262 				else if (val > UINT32_MAX)
263 					errno = ERANGE;
264 				if (errno != 0) {
265 					warn("%s invalid", feature);
266 					return (false);
267 				}
268 				input |= val;
269 			} else {
270 				warnx("%s is not a valid feature", feature);
271 				if (!iflag)
272 					return (false);
273 			}
274 		}
275 	}
276 
277 	if (operation == '+') {
278 		*feature_val |= input;
279 	} else if (operation == '=') {
280 		*feature_val = input;
281 	} else if (operation == '-') {
282 		*feature_val &= ~input;
283 	} else {
284 		warnx("'%c' not an operator - use '+', '-', '='",
285 		    feature_str[0]);
286 		return (false);
287 	}
288 	return (true);
289 }
290 
291 static bool
292 edit_file_features(Elf *elf, int phcount, int fd, char *val, bool endian_swap)
293 {
294 	uint32_t features, prev_features;
295 	uint64_t off;
296 
297 	if (!get_file_features(elf, phcount, fd, &features, &off,
298 	    endian_swap)) {
299 		warnx("NT_FREEBSD_FEATURE_CTL note not found");
300 		return (false);
301 	}
302 
303 	prev_features = features;
304 	if (!convert_to_feature_val(val, &features))
305 		return (false);
306 	/* Avoid touching file if no change. */
307 	if (features == prev_features)
308 		return (true);
309 
310 	if (endian_swap)
311 		features = bswap32(features);
312 
313 	if (lseek(fd, off, SEEK_SET) == -1 ||
314 	    write(fd, &features, sizeof(features)) <
315 	    (ssize_t)sizeof(features)) {
316 		warnx("error writing feature value");
317 		return (false);
318 	}
319 	return (true);
320 }
321 
322 static void
323 print_features(void)
324 {
325 	size_t i;
326 
327 	printf("Known features are:\n");
328 	for (i = 0; i < nitems(featurelist); ++i)
329 		printf("%-16s%s\n", featurelist[i].alias,
330 		    featurelist[i].desc);
331 }
332 
333 static bool
334 print_file_features(Elf *elf, int phcount, int fd, char *filename,
335     bool endian_swap)
336 {
337 	uint32_t features;
338 	unsigned long i;
339 
340 	if (!get_file_features(elf, phcount, fd, &features, NULL,
341 	    endian_swap)) {
342 		return (false);
343 	}
344 
345 	printf("File '%s' features:\n", filename);
346 	for (i = 0; i < nitems(featurelist); ++i) {
347 		printf("%-16s'%s' is ", featurelist[i].alias,
348 		    featurelist[i].desc);
349 
350 		if ((featurelist[i].value & features) == 0)
351 			printf("un");
352 
353 		printf("set.\n");
354 	}
355 	return (true);
356 }
357 
358 static bool
359 get_file_features(Elf *elf, int phcount, int fd, uint32_t *features,
360     uint64_t *off, bool endian_swap)
361 {
362 	GElf_Phdr phdr;
363 	Elf_Note note;
364 	unsigned long read_total;
365 	int namesz, descsz, i;
366 	char *name;
367 
368 	/*
369 	 * Go through each program header to find one that is of type PT_NOTE
370 	 * and has a note for feature control.
371 	 */
372 	for (i = 0; i < phcount; ++i) {
373 		if (gelf_getphdr(elf, i, &phdr) == NULL) {
374 			warnx("gelf_getphdr failed: %s", elf_errmsg(-1));
375 			return (false);
376 		}
377 
378 		if (phdr.p_type != PT_NOTE)
379 			continue;
380 
381 		if (lseek(fd, phdr.p_offset, SEEK_SET) < 0) {
382 			warn("lseek() failed:");
383 			return (false);
384 		}
385 
386 		read_total = 0;
387 		while (read_total < phdr.p_filesz) {
388 			if (read(fd, &note, sizeof(note)) <
389 			    (ssize_t)sizeof(note)) {
390 				warnx("elf note header too short");
391 				return (false);
392 			}
393 			read_total += sizeof(note);
394 
395 			if (endian_swap) {
396 				note.n_namesz = bswap32(note.n_namesz);
397 				note.n_descsz = bswap32(note.n_descsz);
398 				note.n_type = bswap32(note.n_type);
399 			}
400 
401 			/*
402 			 * XXX: Name and descriptor are 4 byte aligned, however,
403 			 * 	the size given doesn't include the padding.
404 			 */
405 			namesz = roundup2(note.n_namesz, 4);
406 			name = malloc(namesz);
407 			if (name == NULL) {
408 				warn("malloc() failed.");
409 				return (false);
410 			}
411 			descsz = roundup2(note.n_descsz, 4);
412 			if (read(fd, name, namesz) < namesz) {
413 				warnx("elf note name too short");
414 				free(name);
415 				return (false);
416 			}
417 			read_total += namesz;
418 
419 			if (note.n_namesz != 8 ||
420 			    strncmp("FreeBSD", name, 7) != 0 ||
421 			    note.n_type != NT_FREEBSD_FEATURE_CTL) {
422 				/* Not the right note. Skip the description */
423 				if (lseek(fd, descsz, SEEK_CUR) < 0) {
424 					warn("lseek() failed.");
425 					free(name);
426 					return (false);
427 				}
428 				read_total += descsz;
429 				free(name);
430 				continue;
431 			}
432 
433 			if (note.n_descsz < sizeof(uint32_t)) {
434 				warnx("Feature descriptor can't "
435 				    "be less than 4 bytes");
436 				free(name);
437 				return (false);
438 			}
439 
440 			/*
441 			 * XXX: For now we look at only 4 bytes of the
442 			 * 	descriptor. This should respect descsz.
443 			 */
444 			if (note.n_descsz > sizeof(uint32_t))
445 				warnx("Feature note is bigger than expected");
446 			if (read(fd, features, sizeof(uint32_t)) <
447 			    (ssize_t)sizeof(uint32_t)) {
448 				warnx("feature note data too short");
449 				free(name);
450 				return (false);
451 			}
452 			if (endian_swap)
453 				*features = bswap32(*features);
454 			if (off != NULL)
455 				*off = phdr.p_offset + read_total;
456 			free(name);
457 			return (true);
458 		}
459 	}
460 
461 	warnx("NT_FREEBSD_FEATURE_CTL note not found");
462 	return (false);
463 }
464