aboutsummaryrefslogblamecommitdiff
path: root/usr.bin/elfctl/elfctl.c
blob: e98b1b45dcf1025b54451e8e88a7a8eea22d55af (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
   
                                                









                                                                      
                                                                   


                                                                          
  










                                                                             



                           
                       

                     
                  
                
                  




                    
                   








                      
                                                             

                                                                             
                                 
                                                               



                          
                            



                                               

                                                                         
                                          
                                                                              
                                                                            
                                                                                

  
                                    

                                                      

  
                               

                                   
     
                                   
                                   
      


                  





                           
                           
                       
                                              
 
                  
                             
                   
                        



                                                        
                                                                                
                             


                                     

                                         
                                     

                              

                                                                         
                                          
                                            

                              





                                

                          









                                                      

                                                                












                                                                      
                                                                          
                            
                                                                               








                                                                  






                                                                  


                                   

                                    
                                                                       
                                                    


                                           
                                                                     
                                             
















                                     

                       



                                                             
                                                       











                                                                         
                                                                      
 
                                    
                   
                       
                       
 
                  

                                 



                                                                               

                                                        
                                  
                                                               

                                                                         
                                                              

                                      









                                                                                

                               

                                                  
                                                       

                                          
                                                                 

                                                                     
                                                          


                                                                    
                                                          

                                                       
                                             

                                                                            

                                                          
                                                       
                                 
                         


                 
                               
                                      
                                      
                                     

                                       
         
                          



                      
                                                                              
 
                                         
                     
 

                                                                 
                                                               


                               
                                 

                                                    


                                               
 


                                             





                                                     









                                                 
                                                         



                                         

                                                                  
 
                          

                        

                                                                 




                                                   
                                                             

                                         
                                                           







                                     
                                                                    
                                    





                                 




















                                                                             


                                                                   



                                                   





                                                                       

                                                                                
                                                                      



                                                            
                                                         


                                                            

                                                                 
                                           

                                               






                                                                              
                                                                







                                                       
                                                               







                                                                      
                                                                  
                           
                                                             
                                                                              


                                                                     
                                           

                                               

                                                               






                                                                  
                                                       

                       
/*-
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 *
 * 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"

__FBSDID("$FreeBSD$");

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);

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, &note, 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);
}