diff options
Diffstat (limited to 'usr.bin/elfctl/elfctl.c')
-rw-r--r-- | usr.bin/elfctl/elfctl.c | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/usr.bin/elfctl/elfctl.c b/usr.bin/elfctl/elfctl.c new file mode 100644 index 000000000000..d9cb557bbad4 --- /dev/null +++ b/usr.bin/elfctl/elfctl.c @@ -0,0 +1,466 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 The FreeBSD Foundation. + * + * This software was developed by Bora Ozarslan under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/elf_common.h> +#include <sys/endian.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <getopt.h> +#include <libelf.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "_elftc.h" +static bool convert_to_feature_val(const char *, uint32_t *); +static bool edit_file_features(Elf *, int, int, char *, bool); +static bool get_file_features(Elf *, int, int, uint32_t *, uint64_t *, bool); +static void print_features(void); +static bool print_file_features(Elf *, int, int, char *, bool); +static void usage(void) __dead2; + +struct ControlFeatures { + const char *alias; + unsigned long value; + const char *desc; +}; + +static struct ControlFeatures featurelist[] = { + { "noaslr", NT_FREEBSD_FCTL_ASLR_DISABLE, "Disable ASLR" }, + { "noprotmax", NT_FREEBSD_FCTL_PROTMAX_DISABLE, + "Disable implicit PROT_MAX" }, + { "nostackgap", NT_FREEBSD_FCTL_STKGAP_DISABLE, "Disable stack gap" }, + { "wxneeded", NT_FREEBSD_FCTL_WXNEEDED, "Requires W+X mappings" }, + { "la48", NT_FREEBSD_FCTL_LA48, "amd64: Limit user VA to 48bit" }, +}; + +static struct option long_opts[] = { + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } +}; + +#if BYTE_ORDER == LITTLE_ENDIAN +#define HOST_ENDIAN ELFDATA2LSB +#define SWAP_ENDIAN ELFDATA2MSB +#else +#define HOST_ENDIAN ELFDATA2MSB +#define SWAP_ENDIAN ELFDATA2LSB +#endif + +static bool iflag; + +int +main(int argc, char **argv) +{ + GElf_Ehdr ehdr; + Elf *elf; + Elf_Kind kind; + int ch, fd, retval; + char *features; + bool editfeatures, lflag, endian_swap; + + lflag = 0; + editfeatures = false; + retval = 0; + features = NULL; + + if (elf_version(EV_CURRENT) == EV_NONE) + errx(EXIT_FAILURE, "elf_version error"); + + while ((ch = getopt_long(argc, argv, "hile:", long_opts, NULL)) != -1) { + switch (ch) { + case 'i': + iflag = true; + break; + case 'l': + print_features(); + lflag = true; + break; + case 'e': + if (features != NULL) + errx(1, "-e may be specified only once"); + features = optarg; + editfeatures = true; + break; + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (argc == 0) { + if (lflag) + exit(0); + else { + warnx("no file(s) specified"); + usage(); + } + } + + while (argc) { + elf = NULL; + + if ((fd = open(argv[0], + editfeatures ? O_RDWR : O_RDONLY, 0)) < 0) { + warn("error opening file %s", argv[0]); + retval = 1; + goto fail; + } + + if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { + warnx("elf_begin failed: %s", elf_errmsg(-1)); + retval = 1; + goto fail; + } + + if ((kind = elf_kind(elf)) != ELF_K_ELF) { + if (kind == ELF_K_AR) + warnx("file '%s' is an archive", argv[0]); + else + warnx("file '%s' is not an ELF file", argv[0]); + retval = 1; + goto fail; + } + + if (gelf_getehdr(elf, &ehdr) == NULL) { + warnx("gelf_getehdr: %s", elf_errmsg(-1)); + retval = 1; + goto fail; + } + + if (ehdr.e_ident[EI_DATA] == HOST_ENDIAN) { + endian_swap = false; + } else if (ehdr.e_ident[EI_DATA] == SWAP_ENDIAN) { + endian_swap = true; + } else { + warnx("file endianness unknown"); + retval = 1; + goto fail; + } + + if (!editfeatures) { + if (!print_file_features(elf, ehdr.e_phnum, fd, + argv[0], endian_swap)) { + retval = 1; + goto fail; + } + } else if (!edit_file_features(elf, ehdr.e_phnum, fd, + features, endian_swap)) { + retval = 1; + goto fail; + } +fail: + if (elf != NULL) + elf_end(elf); + + if (fd >= 0) + close(fd); + + argc--; + argv++; + } + + return (retval); +} + +#define USAGE_MESSAGE \ + "\ +Usage: %s [options] file...\n\ + Set or display the control features for an ELF object.\n\n\ + Supported options are:\n\ + -l List known control features.\n\ + -i Ignore unknown features.\n\ + -e [+-=]feature,list Edit features from a comma separated list.\n\ + -h | --help Print a usage message and exit.\n" + +static void +usage(void) +{ + + fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME()); + exit(1); +} + +static bool +convert_to_feature_val(const char *feature_str, uint32_t *feature_val) +{ + char *feature, *feature_tmp; + int i, len; + uint32_t input; + char operation; + + input = 0; + operation = *feature_str; + feature_str++; + + if (operation != '+' && operation != '-' && operation != '=') + errx(1, "'%c' not an operator - use '+', '-', '='", operation); + + if ((feature_tmp = strdup(feature_str)) == NULL) + err(1, "strdup"); + len = nitems(featurelist); + while ((feature = strsep(&feature_tmp, ",")) != NULL) { + for (i = 0; i < len; ++i) { + if (strcmp(featurelist[i].alias, feature) == 0) { + input |= featurelist[i].value; + break; + } + /* XXX Backwards compatibility for "no"-prefix flags. */ + if (strncmp(featurelist[i].alias, "no", 2) == 0 && + strcmp(featurelist[i].alias + 2, feature) == 0) { + input |= featurelist[i].value; + warnx( + "interpreting %s as %s; please specify %s", + feature, featurelist[i].alias, + featurelist[i].alias); + break; + } + } + if (i == len) { + if (isdigit(feature[0])) { + char *eptr; + unsigned long long val; + + errno = 0; + val = strtoll(feature, &eptr, 0); + if (eptr == feature || *eptr != '\0') + errno = EINVAL; + else if (val > UINT32_MAX) + errno = ERANGE; + if (errno != 0) { + warn("%s invalid", feature); + free(feature_tmp); + return (false); + } + input |= val; + } else { + warnx("%s is not a valid feature", feature); + if (!iflag) { + free(feature_tmp); + return (false); + } + } + } + } + + if (operation == '+') { + *feature_val |= input; + } else if (operation == '=') { + *feature_val = input; + } else if (operation == '-') { + *feature_val &= ~input; + } + free(feature_tmp); + return (true); +} + +static bool +edit_file_features(Elf *elf, int phcount, int fd, char *val, bool endian_swap) +{ + uint32_t features, prev_features; + uint64_t off; + + if (!get_file_features(elf, phcount, fd, &features, &off, + endian_swap)) { + warnx("NT_FREEBSD_FEATURE_CTL note not found"); + return (false); + } + + prev_features = features; + if (!convert_to_feature_val(val, &features)) + return (false); + /* Avoid touching file if no change. */ + if (features == prev_features) + return (true); + + if (endian_swap) + features = bswap32(features); + + if (lseek(fd, off, SEEK_SET) == -1 || + write(fd, &features, sizeof(features)) < + (ssize_t)sizeof(features)) { + warnx("error writing feature value"); + return (false); + } + return (true); +} + +static void +print_features(void) +{ + size_t i; + + printf("Known features are:\n"); + for (i = 0; i < nitems(featurelist); ++i) + printf("%-16s%s\n", featurelist[i].alias, + featurelist[i].desc); +} + +static bool +print_file_features(Elf *elf, int phcount, int fd, char *filename, + bool endian_swap) +{ + uint32_t features; + unsigned long i; + + if (!get_file_features(elf, phcount, fd, &features, NULL, + endian_swap)) { + return (false); + } + + printf("File '%s' features:\n", filename); + for (i = 0; i < nitems(featurelist); ++i) { + printf("%-16s'%s' is ", featurelist[i].alias, + featurelist[i].desc); + + if ((featurelist[i].value & features) == 0) + printf("un"); + + printf("set.\n"); + } + return (true); +} + +static bool +get_file_features(Elf *elf, int phcount, int fd, uint32_t *features, + uint64_t *off, bool endian_swap) +{ + GElf_Phdr phdr; + Elf_Note note; + unsigned long read_total; + int namesz, descsz, i; + char *name; + + /* + * Go through each program header to find one that is of type PT_NOTE + * and has a note for feature control. + */ + for (i = 0; i < phcount; ++i) { + if (gelf_getphdr(elf, i, &phdr) == NULL) { + warnx("gelf_getphdr failed: %s", elf_errmsg(-1)); + return (false); + } + + if (phdr.p_type != PT_NOTE) + continue; + + if (lseek(fd, phdr.p_offset, SEEK_SET) < 0) { + warn("lseek() failed:"); + return (false); + } + + read_total = 0; + while (read_total < phdr.p_filesz) { + if (read(fd, ¬e, sizeof(note)) < + (ssize_t)sizeof(note)) { + warnx("elf note header too short"); + return (false); + } + read_total += sizeof(note); + + if (endian_swap) { + note.n_namesz = bswap32(note.n_namesz); + note.n_descsz = bswap32(note.n_descsz); + note.n_type = bswap32(note.n_type); + } + + /* + * XXX: Name and descriptor are 4 byte aligned, however, + * the size given doesn't include the padding. + */ + namesz = roundup2(note.n_namesz, 4); + name = malloc(namesz); + if (name == NULL) { + warn("malloc() failed."); + return (false); + } + descsz = roundup2(note.n_descsz, 4); + if (read(fd, name, namesz) < namesz) { + warnx("elf note name too short"); + free(name); + return (false); + } + read_total += namesz; + + if (note.n_namesz != 8 || + strncmp("FreeBSD", name, 7) != 0 || + note.n_type != NT_FREEBSD_FEATURE_CTL) { + /* Not the right note. Skip the description */ + if (lseek(fd, descsz, SEEK_CUR) < 0) { + warn("lseek() failed."); + free(name); + return (false); + } + read_total += descsz; + free(name); + continue; + } + + if (note.n_descsz < sizeof(uint32_t)) { + warnx("Feature descriptor can't " + "be less than 4 bytes"); + free(name); + return (false); + } + + /* + * XXX: For now we look at only 4 bytes of the + * descriptor. This should respect descsz. + */ + if (note.n_descsz > sizeof(uint32_t)) + warnx("Feature note is bigger than expected"); + if (read(fd, features, sizeof(uint32_t)) < + (ssize_t)sizeof(uint32_t)) { + warnx("feature note data too short"); + free(name); + return (false); + } + if (endian_swap) + *features = bswap32(*features); + if (off != NULL) + *off = phdr.p_offset + read_total; + free(name); + return (true); + } + } + + warnx("NT_FREEBSD_FEATURE_CTL note not found"); + return (false); +} |