github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/stage1/appexec/appexec.c (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #define _GNU_SOURCE
    16  #include <errno.h>
    17  #include <fcntl.h>
    18  #include <grp.h>
    19  #include <inttypes.h>
    20  #include <limits.h>
    21  #include <stdint.h>
    22  #include <stdio.h>
    23  #include <stdlib.h>
    24  #include <string.h>
    25  #include <sys/mman.h>
    26  #include <sys/stat.h>
    27  #include <sys/types.h>
    28  #include <unistd.h>
    29  
    30  #include "elf.h"
    31  
    32  static int exit_err;
    33  #define exit_if(_cond, _fmt, _args...)				\
    34  	exit_err++;						\
    35  	if(_cond) {						\
    36  		fprintf(stderr, "Error: " _fmt "\n", ##_args);	\
    37  		exit(exit_err);					\
    38  	}
    39  #define pexit_if(_cond, _fmt, _args...)				\
    40  	exit_if(_cond, _fmt ": %s", ##_args, strerror(errno))
    41  
    42  #define MAX_DIAG_DEPTH 10
    43  #define MIN(_a, _b) (((_a) < (_b)) ? (_a) : (_b))
    44  
    45  static void map_file(const char *path, int prot, int flags, struct stat *st, void **map)
    46  {
    47  	int fd;
    48  
    49  	pexit_if((fd = open(path, O_RDONLY)) == -1,
    50  		"Unable to open \"%s\"", path);
    51  	pexit_if(fstat(fd, st) == -1,
    52  		"Cannot stat \"%s\"", path);
    53  	exit_if(!S_ISREG(st->st_mode), "\"%s\" is not a regular file", path);
    54  	pexit_if(!(*map = mmap(NULL, st->st_size, prot, flags, fd, 0)),
    55  		"Mmap of \"%s\" failed", path);
    56  	pexit_if(close(fd) == -1,
    57  		"Close of %i [%s] failed", fd, path);
    58  }
    59  
    60  static void diag(const char *exe)
    61  {
    62  	static const uint8_t	elf[] = {0x7f, 'E', 'L', 'F'};
    63  	static const uint8_t	shebang[] = {'#','!'};
    64  	static int		diag_depth;
    65  	struct stat		st;
    66  	const uint8_t		*mm;
    67  	const char		*itrp = NULL;
    68  
    69  	map_file(exe, PROT_READ, MAP_SHARED, &st, (void **)&mm);
    70  	exit_if(!((S_IXUSR|S_IXGRP|S_IXOTH) & st.st_mode),
    71  		"\"%s\" is not executable", exe)
    72  
    73  	if(st.st_size >= sizeof(shebang) &&
    74  	   !memcmp(mm, shebang, sizeof(shebang))) {
    75  		const uint8_t	*nl;
    76  		int		maxlen = MIN(PATH_MAX, st.st_size - sizeof(shebang));
    77  		/* TODO(vc): EOF-terminated shebang lines are technically possible */
    78  		exit_if(!(nl = memchr(&mm[sizeof(shebang)], '\n', maxlen)),
    79  			"Shebang line too long");
    80  		pexit_if(!(itrp = strndup((char *)&mm[sizeof(shebang)], (nl - mm) - 2)),
    81  			"Failed to dup interpreter path");
    82  	} else if(st.st_size >= sizeof(elf) &&
    83  		  !memcmp(mm, elf, sizeof(elf))) {
    84  		uint64_t	(*lget)(const uint8_t *) = NULL;
    85  		uint32_t	(*iget)(const uint8_t *) = NULL;
    86  		uint16_t	(*sget)(const uint8_t *) = NULL;
    87  		const void	*phoff = NULL, *phesz = NULL, *phecnt = NULL;
    88  		const uint8_t	*ph = NULL;
    89  		int		i, phreloff, phrelsz;
    90  
    91  		exit_if(mm[ELF_VERSION] != 1,
    92  			"Unsupported ELF version: %hhx", mm[ELF_VERSION]);
    93  
    94  		/* determine which accessors to use and where */
    95  		if(mm[ELF_BITS] == ELF_BITS_32) {
    96  			if(mm[ELF_ENDIAN] == ELF_ENDIAN_LITL) {
    97  				lget = le32_lget;
    98  				sget = le_sget;
    99  				iget = le_iget;
   100  			} else if(mm[ELF_ENDIAN] == ELF_ENDIAN_BIG) {
   101  				lget = be32_lget;
   102  				sget = be_sget;
   103  				iget = be_iget;
   104  			}
   105  			phoff = &mm[ELF32_PHT_OFF];
   106  			phesz = &mm[ELF32_PHTE_SIZE];
   107  			phecnt = &mm[ELF32_PHTE_CNT];
   108  			phreloff = ELF32_PHE_OFF;
   109  			phrelsz = ELF32_PHE_SIZE;
   110  		} else if(mm[ELF_BITS] == ELF_BITS_64) {
   111  			if(mm[ELF_ENDIAN] == ELF_ENDIAN_LITL) {
   112  				lget = le64_lget;
   113  				sget = le_sget;
   114  				iget = le_iget;
   115  			} else if(mm[ELF_ENDIAN] == ELF_ENDIAN_BIG) {
   116  				lget = be64_lget;
   117  				sget = be_sget;
   118  				iget = be_iget;
   119  			}
   120  			phoff = &mm[ELF64_PHT_OFF];
   121  			phesz = &mm[ELF64_PHTE_SIZE];
   122  			phecnt = &mm[ELF64_PHTE_CNT];
   123  			phreloff = ELF64_PHE_OFF;
   124  			phrelsz = ELF64_PHE_SIZE;
   125  		}
   126  
   127  		exit_if(!lget, "Unsupported ELF format");
   128  
   129  		if(!phoff) /* program header may be absent, don't make it an error */
   130  			return;
   131  
   132  		/* TODO(vc): sanity checks on values before using them */
   133  		for(ph = &mm[lget(phoff)], i = 0; i < sget(phecnt); i++, ph += sget(phesz)) {
   134  			if(iget(ph) == ELF_PT_INTERP) {
   135  				itrp = strndup((char *)&mm[lget(&ph[phreloff])], lget(&ph[phrelsz]));
   136  				break;
   137  			}
   138  		}
   139  	} else {
   140  		exit_if(1, "Unsupported file type");
   141  	}
   142  
   143  	exit_if(!itrp, "Unable to determine interpreter for \"%s\"", exe);
   144  	exit_if(*itrp != '/', "Path must be absolute: \"%s\"", itrp);
   145  	exit_if(++diag_depth > MAX_DIAG_DEPTH,
   146  		"Excessive interpreter recursion, giving up");
   147  	diag(itrp);
   148  }
   149  
   150  /* Append env variables listed in keep_env to env_file if they're present in
   151   * current environment */
   152  void append_env(const char *env_file, const char **keep_env)
   153  {
   154  	FILE		*f;
   155  	const char	**p;
   156  	char		*v;
   157  	char		nul = '\0';
   158  
   159  	pexit_if((f = fopen(env_file, "a")) == NULL,
   160  		"Unable to fopen \"%s\"", env_file);
   161  
   162  	p = keep_env;
   163  	while (*p) {
   164  		v = getenv(*p);
   165  		if (v) {
   166  			pexit_if(fprintf(f, "%s=%s%c", *p, v, nul) != (strlen(*p) + strlen(v) + 2),
   167  				"Unable to write to \"%s\"", env_file);
   168  		}
   169  		p++;
   170  	}
   171  
   172  	pexit_if(fclose(f) == EOF,
   173  		"Unable to fclose \"%s\"", env_file);
   174  
   175  	return;
   176  }
   177  
   178  /* Read environment from env and make it our own keeping the env variables in
   179   * keep_env if they're present in the current environment.
   180   * The environment file must exist, may be empty, and is expected to be of the format:
   181   * key=value\0key=value\0...
   182   */
   183  static void load_env(const char *env, const char **keep_env)
   184  {
   185  	struct stat		st;
   186  	char			*map, *k, *v;
   187  	typeof(st.st_size)	i;
   188  
   189  	append_env(env, keep_env);
   190  
   191  	map_file(env, PROT_READ|PROT_WRITE, MAP_PRIVATE, &st, (void **)&map);
   192  
   193  	pexit_if(clearenv() != 0,
   194  		"Unable to clear environment");
   195  
   196  	if(!st.st_size)
   197  		return;
   198  
   199  	map[st.st_size - 1] = '\0'; /* ensure the mapping is null-terminated */
   200  
   201  	for(i = 0; i < st.st_size;) {
   202  		k = &map[i];
   203  		i += strlen(k) + 1;
   204  		exit_if((v = strchr(k, '=')) == NULL,
   205  			"Malformed environment entry: \"%s\"", k);
   206  		/* a private writable map is used permitting s/=/\0/ */
   207  		*v = '\0';
   208  		v++;
   209  		pexit_if(setenv(k, v, 1) == -1,
   210  			"Unable to set env variable: \"%s\"=\"%s\"", k, v);
   211  	}
   212  }
   213  
   214  /* Parse a comma-separated list of numeric gids from str, returns an malloc'd
   215   * array of gids in *gids_p with the number of elements in *n_gids_p.
   216   */
   217  static void parse_gids(const char *str, size_t *n_gids_p, gid_t **gids_p)
   218  {
   219  	char	c = ',', last_c;
   220  	int	i, n_gids = 0, done = 0;
   221  	gid_t	gid = 0;
   222  	gid_t	*gids = NULL;
   223  
   224  	for(i = 0; !done; i++) {
   225  		last_c = c;
   226  		switch(c = str[i]) {
   227  		case '0' ... '9':
   228  			gid *= 10;
   229  			gid += c - '0';
   230  			break;
   231  
   232  		case '\0':
   233  			done = 1;
   234  			/* fallthrough */
   235  		case ',':
   236  			exit_if(last_c == ',',
   237  				"Gids contains an empty gid: \"%s\"", str);
   238  			pexit_if((gids = realloc(gids, sizeof(*gids) * (n_gids + 1))) == NULL,
   239  				"Unable to allocate gids: \"%s\"", str);
   240  			gids[n_gids++] = gid;
   241  			gid = 0;
   242  			break;
   243  
   244  		default:
   245  			exit_if(1,
   246  				"Gids contains invalid input (%c): \"%s\"",
   247  				c, str);
   248  		}
   249  	}
   250  
   251  	exit_if(!n_gids, "At least one gid is required, got: \"%s\"", str);
   252  
   253  	*gids_p = gids;
   254  	*n_gids_p = n_gids;
   255  }
   256  
   257  int main(int argc, char *argv[])
   258  {
   259  	/* We need to keep these env variables since systemd uses them for socket
   260  	 * activation
   261  	 */
   262  	static const char *keep_env[] = {
   263  		"LISTEN_FDS",
   264  		"LISTEN_PID",
   265  		NULL
   266  	};
   267  
   268  	const char	*root, *cwd, *env, *uid_str, *gid_str, *exe;
   269  	char		**args;
   270  	uid_t		uid;
   271  	gid_t		*gids;
   272  	size_t		n_gids;
   273  
   274  	exit_if(argc < 7,
   275  		"Usage: %s /path/to/root /work/directory /env/file uid gid[,gid...] /to/exec [args ...]", argv[0]);
   276  
   277  	root = argv[1];
   278  	cwd = argv[2];
   279  	env = argv[3];
   280  	uid_str = argv[4];
   281  	uid = atoi(uid_str);
   282  	gid_str = argv[5];
   283  	args = &argv[6];
   284  	exe = args[0];
   285  
   286  	parse_gids(gid_str, &n_gids, &gids);
   287  	load_env(env, keep_env);
   288  
   289  	pexit_if(chroot(root) == -1, "Chroot \"%s\" failed", root);
   290  	pexit_if(chdir(cwd) == -1, "Chdir \"%s\" failed", cwd);
   291  	pexit_if(gids[0] > 0 && setresgid(gids[0], gids[0], gids[0]) == -1,
   292  		"Setresgid \"%s\" failed", gid_str);
   293  	pexit_if(n_gids > 1 && setgroups(n_gids - 1, &gids[1]) == -1,
   294  		"Setgroups \"%s\" failed", gid_str);
   295  	pexit_if(uid > 0 && setresuid(uid, uid, uid) == -1,
   296  		"Setresuid \"%s\" failed", uid_str);
   297  
   298  	/* XXX(vc): note that since execvp() is happening post-chroot, the
   299  	 * app's environment settings correctly affect the PATH search.
   300  	 * This is why execvpe() isn't being used, we manipulate the environment
   301  	 * manually then let it potentially affect execvp().  execvpe() simply
   302  	 * passes the environment to execve() _after_ performing the search, not
   303  	 * what we want here. */
   304  	pexit_if(execvp(exe, args) == -1 &&
   305  		 errno != ENOENT && errno != EACCES,
   306  		 "Exec of \"%s\" failed", exe);
   307  	diag(exe);
   308  
   309  	return EXIT_FAILURE;
   310  }