xref: /freebsd/usr.bin/elfctl/elfctl.c (revision ef698fabe42827bad43bf046143ef26e47d57514)
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 <err.h>
37 #include <fcntl.h>
38 #include <gelf.h>
39 #include <getopt.h>
40 #include <libelf.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include "_elftc.h"
48 
49 __FBSDID("$FreeBSD$");
50 
51 static bool convert_to_feature_val(char *, uint32_t *);
52 static bool edit_file_features(Elf *, int, int, char *);
53 static bool get_file_features(Elf *, int, int, uint32_t *, uint64_t *);
54 static void print_features(void);
55 static bool print_file_features(Elf *, int, int, char *);
56 static void usage(void);
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 	{ "noaslrstkgap", NT_FREEBSD_FCTL_ASG_DISABLE,
72 	    "Disable ASLR stack gap" },
73 };
74 
75 static struct option long_opts[] = {
76 	{ "help",	no_argument,	NULL,	'h' },
77 	{ NULL,		0,		NULL,	0 }
78 };
79 
80 #if BYTE_ORDER == LITTLE_ENDIAN
81 #define SUPPORTED_ENDIAN ELFDATA2LSB
82 #else
83 #define SUPPORTED_ENDIAN ELFDATA2MSB
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;
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 			features = optarg;
117 			editfeatures = true;
118 			break;
119 		case 'h':
120 		default:
121 			usage();
122 		}
123 	}
124 	argc -= optind;
125 	argv += optind;
126 	if (argc == 0) {
127 		if (lflag)
128 			exit(0);
129 		else {
130 			warnx("no file(s) specified");
131 			usage();
132 		}
133 	}
134 
135 	while (argc) {
136 		elf = NULL;
137 
138 		if ((fd = open(argv[0],
139 		    editfeatures ? O_RDWR : O_RDONLY, 0)) < 0) {
140 			warn("error opening file %s", argv[0]);
141 			retval = 1;
142 			goto fail;
143 		}
144 
145 		if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
146 			warnx("elf_begin failed: %s", elf_errmsg(-1));
147 			retval = 1;
148 			goto fail;
149 		}
150 
151 		if ((kind = elf_kind(elf)) != ELF_K_ELF) {
152 			if (kind == ELF_K_AR)
153 				warnx("file '%s' is an archive", argv[0]);
154 			else
155 				warnx("file '%s' is not an ELF file", argv[0]);
156 			retval = 1;
157 			goto fail;
158 		}
159 
160 		if (gelf_getehdr(elf, &ehdr) == NULL) {
161 			warnx("gelf_getehdr: %s", elf_errmsg(-1));
162 			retval = 1;
163 			goto fail;
164 		}
165 		/*
166 		 * XXX need to support cross-endian operation, but for now
167 		 * exit on error rather than misbehaving.
168 		 */
169 		if (ehdr.e_ident[EI_DATA] != SUPPORTED_ENDIAN) {
170 			warnx("file endianness must match host");
171 			retval = 1;
172 			goto fail;
173 		}
174 
175 		if (!editfeatures) {
176 			if (!print_file_features(elf, ehdr.e_phnum, fd,
177 			    argv[0])) {
178 				retval = 1;
179 				goto fail;
180 			}
181 		} else if (!edit_file_features(elf, ehdr.e_phnum, fd,
182 		    features)) {
183 			retval = 1;
184 			goto fail;
185 		}
186 fail:
187 		if (elf != NULL)
188 			elf_end(elf);
189 
190 		if (fd >= 0)
191 			close(fd);
192 
193 		argc--;
194 		argv++;
195 	}
196 
197 	return (retval);
198 }
199 
200 #define USAGE_MESSAGE \
201 	"\
202 Usage: %s [options] file...\n\
203   Set or display the control features for an ELF object.\n\n\
204   Supported options are:\n\
205   -l                        List known control features.\n\
206   -i                        Ignore unknown features.\n\
207   -e [+-=]feature,list      Edit features from a comma separated list.\n\
208   -h | --help               Print a usage message and exit.\n"
209 
210 static void
211 usage(void)
212 {
213 
214 	fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME());
215 	exit(1);
216 }
217 
218 static bool
219 convert_to_feature_val(char *feature_str, uint32_t *feature_val)
220 {
221 	char *feature;
222 	int i, len;
223 	uint32_t input;
224 	char operation;
225 
226 	input = 0;
227 	operation = *feature_str;
228 	feature_str++;
229 	len = nitems(featurelist);
230 	while ((feature = strsep(&feature_str, ",")) != NULL) {
231 		for (i = 0; i < len; ++i) {
232 			if (strcmp(featurelist[i].alias, feature) == 0) {
233 				input |= featurelist[i].value;
234 				break;
235 			}
236 			/* XXX Backwards compatibility for "no"-prefix flags. */
237 			if (strncmp(featurelist[i].alias, "no", 2) == 0 &&
238 			    strcmp(featurelist[i].alias + 2, feature) == 0) {
239 				input |= featurelist[i].value;
240 				warnx(
241 				    "interpreting %s as %s; please specify %s",
242 				    feature, featurelist[i].alias,
243 				    featurelist[i].alias);
244 				break;
245 			}
246 		}
247 		if (i == len) {
248 			warnx("%s is not a valid feature", feature);
249 			if (!iflag)
250 				return (false);
251 		}
252 	}
253 
254 	if (operation == '+') {
255 		*feature_val |= input;
256 	} else if (operation == '=') {
257 		*feature_val = input;
258 	} else if (operation == '-') {
259 		*feature_val &= ~input;
260 	} else {
261 		warnx("'%c' not an operator - use '+', '-', '='",
262 		    feature_str[0]);
263 		return (false);
264 	}
265 	return (true);
266 }
267 
268 static bool
269 edit_file_features(Elf *elf, int phcount, int fd, char *val)
270 {
271 	uint32_t features;
272 	uint64_t off;
273 
274 	if (!get_file_features(elf, phcount, fd, &features, &off)) {
275 		warnx("NT_FREEBSD_FEATURE_CTL note not found");
276 		return (false);
277 	}
278 
279 	if (!convert_to_feature_val(val, &features))
280 		return (false);
281 
282 	if (lseek(fd, off, SEEK_SET) == -1 ||
283 	    write(fd, &features, sizeof(features)) <
284 	    (ssize_t)sizeof(features)) {
285 		warnx("error writing feature value");
286 		return (false);
287 	}
288 	return (true);
289 }
290 
291 static void
292 print_features(void)
293 {
294 	size_t i;
295 
296 	printf("Known features are:\n");
297 	for (i = 0; i < nitems(featurelist); ++i)
298 		printf("%-16s%s\n", featurelist[i].alias,
299 		    featurelist[i].desc);
300 }
301 
302 static bool
303 print_file_features(Elf *elf, int phcount, int fd, char *filename)
304 {
305 	uint32_t features;
306 	unsigned long i;
307 
308 	if (!get_file_features(elf, phcount, fd, &features, NULL)) {
309 		return (false);
310 	}
311 
312 	printf("File '%s' features:\n", filename);
313 	for (i = 0; i < nitems(featurelist); ++i) {
314 		printf("%-16s'%s' is ", featurelist[i].alias,
315 		    featurelist[i].desc);
316 
317 		if ((featurelist[i].value & features) == 0)
318 			printf("un");
319 
320 		printf("set.\n");
321 	}
322 	return (true);
323 }
324 
325 static bool
326 get_file_features(Elf *elf, int phcount, int fd, uint32_t *features,
327     uint64_t *off)
328 {
329 	GElf_Phdr phdr;
330 	Elf_Note note;
331 	unsigned long read_total;
332 	int namesz, descsz, i;
333 	char *name;
334 
335 	/*
336 	 * Go through each program header to find one that is of type PT_NOTE
337 	 * and has a note for feature control.
338 	 */
339 	for (i = 0; i < phcount; ++i) {
340 		if (gelf_getphdr(elf, i, &phdr) == NULL) {
341 			warnx("gelf_getphdr failed: %s", elf_errmsg(-1));
342 			return (false);
343 		}
344 
345 		if (phdr.p_type != PT_NOTE)
346 			continue;
347 
348 		if (lseek(fd, phdr.p_offset, SEEK_SET) < 0) {
349 			warn("lseek() failed:");
350 			return (false);
351 		}
352 
353 		read_total = 0;
354 		while (read_total < phdr.p_filesz) {
355 			if (read(fd, &note, sizeof(note)) <
356 			    (ssize_t)sizeof(note)) {
357 				warnx("elf note header too short");
358 				return (false);
359 			}
360 			read_total += sizeof(note);
361 
362 			/*
363 			 * XXX: Name and descriptor are 4 byte aligned, however,
364 			 * 	the size given doesn't include the padding.
365 			 */
366 			namesz = roundup2(note.n_namesz, 4);
367 			name = malloc(namesz);
368 			if (name == NULL) {
369 				warn("malloc() failed.");
370 				return (false);
371 			}
372 			descsz = roundup2(note.n_descsz, 4);
373 			if (read(fd, name, namesz) < namesz) {
374 				warnx("elf note name too short");
375 				free(name);
376 				return (false);
377 			}
378 			read_total += namesz;
379 
380 			if (note.n_namesz != 8 ||
381 			    strncmp("FreeBSD", name, 7) != 0 ||
382 			    note.n_type != NT_FREEBSD_FEATURE_CTL) {
383 				/* Not the right note. Skip the description */
384 				if (lseek(fd, descsz, SEEK_CUR) < 0) {
385 					warn("lseek() failed.");
386 					free(name);
387 					return (false);
388 				}
389 				read_total += descsz;
390 				free(name);
391 				continue;
392 			}
393 
394 			if (note.n_descsz < sizeof(uint32_t)) {
395 				warnx("Feature descriptor can't "
396 				    "be less than 4 bytes");
397 				free(name);
398 				return (false);
399 			}
400 
401 			/*
402 			 * XXX: For now we look at only 4 bytes of the
403 			 * 	descriptor. This should respect descsz.
404 			 */
405 			if (note.n_descsz > sizeof(uint32_t))
406 				warnx("Feature note is bigger than expected");
407 			if (read(fd, features, sizeof(uint32_t)) <
408 			    (ssize_t)sizeof(uint32_t)) {
409 				warnx("feature note data too short");
410 				free(name);
411 				return (false);
412 			}
413 			if (off != NULL)
414 				*off = phdr.p_offset + read_total;
415 			free(name);
416 			return (true);
417 		}
418 	}
419 
420 	warnx("NT_FREEBSD_FEATURE_CTL note not found");
421 	return (false);
422 }
423