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