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
main(int argc,char ** argv)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
usage(void)214 usage(void)
215 {
216
217 fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME());
218 exit(1);
219 }
220
221 static bool
convert_to_feature_val(const char * feature_str,uint32_t * feature_val)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
edit_file_features(Elf * elf,int phcount,int fd,char * val,bool endian_swap)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
print_features(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
print_file_features(Elf * elf,int phcount,int fd,char * filename,bool endian_swap)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
get_file_features(Elf * elf,int phcount,int fd,uint32_t * features,uint64_t * off,bool endian_swap)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, ¬e, 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