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