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(const 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 };
75
76 static struct option long_opts[] = {
77 { "help", no_argument, NULL, 'h' },
78 { NULL, 0, NULL, 0 }
79 };
80
81 #if BYTE_ORDER == LITTLE_ENDIAN
82 #define HOST_ENDIAN ELFDATA2LSB
83 #define SWAP_ENDIAN ELFDATA2MSB
84 #else
85 #define HOST_ENDIAN ELFDATA2MSB
86 #define SWAP_ENDIAN ELFDATA2LSB
87 #endif
88
89 static bool iflag;
90
91 int
main(int argc,char ** argv)92 main(int argc, char **argv)
93 {
94 GElf_Ehdr ehdr;
95 Elf *elf;
96 Elf_Kind kind;
97 int ch, fd, retval;
98 char *features;
99 bool editfeatures, lflag, endian_swap;
100
101 lflag = 0;
102 editfeatures = false;
103 retval = 0;
104 features = NULL;
105
106 if (elf_version(EV_CURRENT) == EV_NONE)
107 errx(EXIT_FAILURE, "elf_version error");
108
109 while ((ch = getopt_long(argc, argv, "hile:", long_opts, NULL)) != -1) {
110 switch (ch) {
111 case 'i':
112 iflag = true;
113 break;
114 case 'l':
115 print_features();
116 lflag = true;
117 break;
118 case 'e':
119 if (features != NULL)
120 errx(1, "-e may be specified only once");
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
usage(void)217 usage(void)
218 {
219
220 fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME());
221 exit(1);
222 }
223
224 static bool
convert_to_feature_val(const char * feature_str,uint32_t * feature_val)225 convert_to_feature_val(const char *feature_str, uint32_t *feature_val)
226 {
227 char *feature, *feature_tmp;
228 int i, len;
229 uint32_t input;
230 char operation;
231
232 input = 0;
233 operation = *feature_str;
234 feature_str++;
235
236 if (operation != '+' && operation != '-' && operation != '=')
237 errx(1, "'%c' not an operator - use '+', '-', '='", operation);
238
239 if ((feature_tmp = strdup(feature_str)) == NULL)
240 err(1, "strdup");
241 len = nitems(featurelist);
242 while ((feature = strsep(&feature_tmp, ",")) != NULL) {
243 for (i = 0; i < len; ++i) {
244 if (strcmp(featurelist[i].alias, feature) == 0) {
245 input |= featurelist[i].value;
246 break;
247 }
248 /* XXX Backwards compatibility for "no"-prefix flags. */
249 if (strncmp(featurelist[i].alias, "no", 2) == 0 &&
250 strcmp(featurelist[i].alias + 2, feature) == 0) {
251 input |= featurelist[i].value;
252 warnx(
253 "interpreting %s as %s; please specify %s",
254 feature, featurelist[i].alias,
255 featurelist[i].alias);
256 break;
257 }
258 }
259 if (i == len) {
260 if (isdigit(feature[0])) {
261 char *eptr;
262 unsigned long long val;
263
264 errno = 0;
265 val = strtoll(feature, &eptr, 0);
266 if (eptr == feature || *eptr != '\0')
267 errno = EINVAL;
268 else if (val > UINT32_MAX)
269 errno = ERANGE;
270 if (errno != 0) {
271 warn("%s invalid", feature);
272 free(feature_tmp);
273 return (false);
274 }
275 input |= val;
276 } else {
277 warnx("%s is not a valid feature", feature);
278 if (!iflag) {
279 free(feature_tmp);
280 return (false);
281 }
282 }
283 }
284 }
285
286 if (operation == '+') {
287 *feature_val |= input;
288 } else if (operation == '=') {
289 *feature_val = input;
290 } else if (operation == '-') {
291 *feature_val &= ~input;
292 }
293 free(feature_tmp);
294 return (true);
295 }
296
297 static bool
edit_file_features(Elf * elf,int phcount,int fd,char * val,bool endian_swap)298 edit_file_features(Elf *elf, int phcount, int fd, char *val, bool endian_swap)
299 {
300 uint32_t features, prev_features;
301 uint64_t off;
302
303 if (!get_file_features(elf, phcount, fd, &features, &off,
304 endian_swap)) {
305 warnx("NT_FREEBSD_FEATURE_CTL note not found");
306 return (false);
307 }
308
309 prev_features = features;
310 if (!convert_to_feature_val(val, &features))
311 return (false);
312 /* Avoid touching file if no change. */
313 if (features == prev_features)
314 return (true);
315
316 if (endian_swap)
317 features = bswap32(features);
318
319 if (lseek(fd, off, SEEK_SET) == -1 ||
320 write(fd, &features, sizeof(features)) <
321 (ssize_t)sizeof(features)) {
322 warnx("error writing feature value");
323 return (false);
324 }
325 return (true);
326 }
327
328 static void
print_features(void)329 print_features(void)
330 {
331 size_t i;
332
333 printf("Known features are:\n");
334 for (i = 0; i < nitems(featurelist); ++i)
335 printf("%-16s%s\n", featurelist[i].alias,
336 featurelist[i].desc);
337 }
338
339 static bool
print_file_features(Elf * elf,int phcount,int fd,char * filename,bool endian_swap)340 print_file_features(Elf *elf, int phcount, int fd, char *filename,
341 bool endian_swap)
342 {
343 uint32_t features;
344 unsigned long i;
345
346 if (!get_file_features(elf, phcount, fd, &features, NULL,
347 endian_swap)) {
348 return (false);
349 }
350
351 printf("File '%s' features:\n", filename);
352 for (i = 0; i < nitems(featurelist); ++i) {
353 printf("%-16s'%s' is ", featurelist[i].alias,
354 featurelist[i].desc);
355
356 if ((featurelist[i].value & features) == 0)
357 printf("un");
358
359 printf("set.\n");
360 }
361 return (true);
362 }
363
364 static bool
get_file_features(Elf * elf,int phcount,int fd,uint32_t * features,uint64_t * off,bool endian_swap)365 get_file_features(Elf *elf, int phcount, int fd, uint32_t *features,
366 uint64_t *off, bool endian_swap)
367 {
368 GElf_Phdr phdr;
369 Elf_Note note;
370 unsigned long read_total;
371 int namesz, descsz, i;
372 char *name;
373
374 /*
375 * Go through each program header to find one that is of type PT_NOTE
376 * and has a note for feature control.
377 */
378 for (i = 0; i < phcount; ++i) {
379 if (gelf_getphdr(elf, i, &phdr) == NULL) {
380 warnx("gelf_getphdr failed: %s", elf_errmsg(-1));
381 return (false);
382 }
383
384 if (phdr.p_type != PT_NOTE)
385 continue;
386
387 if (lseek(fd, phdr.p_offset, SEEK_SET) < 0) {
388 warn("lseek() failed:");
389 return (false);
390 }
391
392 read_total = 0;
393 while (read_total < phdr.p_filesz) {
394 if (read(fd, ¬e, sizeof(note)) <
395 (ssize_t)sizeof(note)) {
396 warnx("elf note header too short");
397 return (false);
398 }
399 read_total += sizeof(note);
400
401 if (endian_swap) {
402 note.n_namesz = bswap32(note.n_namesz);
403 note.n_descsz = bswap32(note.n_descsz);
404 note.n_type = bswap32(note.n_type);
405 }
406
407 /*
408 * XXX: Name and descriptor are 4 byte aligned, however,
409 * the size given doesn't include the padding.
410 */
411 namesz = roundup2(note.n_namesz, 4);
412 name = malloc(namesz);
413 if (name == NULL) {
414 warn("malloc() failed.");
415 return (false);
416 }
417 descsz = roundup2(note.n_descsz, 4);
418 if (read(fd, name, namesz) < namesz) {
419 warnx("elf note name too short");
420 free(name);
421 return (false);
422 }
423 read_total += namesz;
424
425 if (note.n_namesz != 8 ||
426 strncmp("FreeBSD", name, 7) != 0 ||
427 note.n_type != NT_FREEBSD_FEATURE_CTL) {
428 /* Not the right note. Skip the description */
429 if (lseek(fd, descsz, SEEK_CUR) < 0) {
430 warn("lseek() failed.");
431 free(name);
432 return (false);
433 }
434 read_total += descsz;
435 free(name);
436 continue;
437 }
438
439 if (note.n_descsz < sizeof(uint32_t)) {
440 warnx("Feature descriptor can't "
441 "be less than 4 bytes");
442 free(name);
443 return (false);
444 }
445
446 /*
447 * XXX: For now we look at only 4 bytes of the
448 * descriptor. This should respect descsz.
449 */
450 if (note.n_descsz > sizeof(uint32_t))
451 warnx("Feature note is bigger than expected");
452 if (read(fd, features, sizeof(uint32_t)) <
453 (ssize_t)sizeof(uint32_t)) {
454 warnx("feature note data too short");
455 free(name);
456 return (false);
457 }
458 if (endian_swap)
459 *features = bswap32(*features);
460 if (off != NULL)
461 *off = phdr.p_offset + read_total;
462 free(name);
463 return (true);
464 }
465 }
466
467 warnx("NT_FREEBSD_FEATURE_CTL note not found");
468 return (false);
469 }
470