github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/stage1/enterexec/enterexec.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 "diagnostic-util.h"
    31  
    32  /* Create keep_env file from keep_env, if they're present in
    33   * current environment and file doesn't exist */
    34  void initialize_keep_env(const char *keep_env_file, const char **keep_env)
    35  {
    36  	FILE		*f;
    37  	const char	**p;
    38  	char		*v;
    39  	char		nul = '\0';
    40  
    41  	if (!access(keep_env_file, F_OK)) return;
    42  	pexit_if((f = fopen(keep_env_file, "a")) == NULL,
    43  		"Unable to fopen \"%s\"", keep_env_file);
    44  
    45  	p = keep_env;
    46  	while (*p) {
    47  		v = getenv(*p);
    48  		if (v) {
    49  			pexit_if(fprintf(f, "%s=%s%c", *p, v, nul) != (strlen(*p) + strlen(v) + 2),
    50  				"Unable to write to \"%s\"", keep_env_file);
    51  		}
    52  		p++;
    53  	}
    54  
    55  	pexit_if(fclose(f) == EOF,
    56  		"Unable to fclose \"%s\"", keep_env_file);
    57  }
    58  
    59  /* Try to set current env from keep_env and env file. */
    60  static void set_env(const char *env_file)
    61  {
    62  	FILE 		*f;
    63  	char 		*line = NULL;
    64  	size_t 		len = 0;
    65  	ssize_t 	read;
    66  	char 		*v, *nl;
    67  
    68  	pexit_if((f = fopen(env_file, "r")) == NULL,
    69               "Unable to fopen \"%s\"", env_file);
    70  	while ((read = getline(&line, &len, f)) != -1) {
    71  		pexit_if((v = strchr(line, '=')) == NULL,
    72  				"Malformed environment entry: \"%s\"", line);
    73  		*v = '\0';
    74  		v++;
    75  		/* remove new line character */
    76  		if ((nl = strchr(v, '\n')) != NULL)
    77  			*nl = '\0';
    78  		pexit_if(setenv(line, v, 1) == -1,
    79  				 "Unable to set env variable: \"%s\"=\"%s\"", line, v);
    80  	}
    81  	free(line);
    82  	pexit_if(fclose(f) == EOF,
    83  			 "Unable to fclose \"%s\"", env_file);
    84  }
    85  
    86  /* Read environment from env and keep_env files make it our own, keeping the env variables in
    87   * if they're present in the current environment.
    88   * The environment files must exist, may be empty, and are expected to be of the format:
    89   * key=value\nkey=value\n...
    90   */
    91  static void load_env(const char *env_file, const char *keep_env_file, int entering)
    92  {
    93  	char *term = getenv("TERM"); /* useful to keep during entering. */
    94  	pexit_if(clearenv() != 0,
    95  		"Unable to clear environment");
    96  
    97  	set_env(env_file);
    98  	set_env(keep_env_file);
    99  	if (entering) {
   100  		// enter is typically interactive; ensure we always have a sane enough term
   101  		// variable.
   102  		if (term == NULL) {
   103  			setenv("TERM", "vt100", 1);
   104  		} else {
   105  			setenv("TERM", term, 1);
   106  		}
   107  	}
   108  }
   109  
   110  /* Parse a comma-separated list of numeric gids from str, returns an malloc'd
   111   * array of gids in *gids_p with the number of elements in *n_gids_p.
   112   */
   113  static void parse_gids(const char *str, size_t *n_gids_p, gid_t **gids_p)
   114  {
   115  	char	c = ',', last_c;
   116  	int	i, n_gids = 0, done = 0;
   117  	gid_t	gid = 0;
   118  	gid_t	*gids = NULL;
   119  
   120  	for(i = 0; !done; i++) {
   121  		last_c = c;
   122  		switch(c = str[i]) {
   123  		case '0' ... '9':
   124  			gid *= 10;
   125  			gid += c - '0';
   126  			break;
   127  
   128  		case '\0':
   129  			done = 1;
   130  			/* fallthrough */
   131  		case ',':
   132  			exit_if(last_c == ',',
   133  				"Gids contains an empty gid: \"%s\"", str);
   134  			pexit_if((gids = realloc(gids, sizeof(*gids) * (n_gids + 1))) == NULL,
   135  				"Unable to allocate gids: \"%s\"", str);
   136  			gids[n_gids++] = gid;
   137  			gid = 0;
   138  			break;
   139  
   140  		default:
   141  			exit_if(1,
   142  				"Gids contains invalid input (%c): \"%s\"",
   143  				c, str);
   144  		}
   145  	}
   146  
   147  	exit_if(!n_gids, "At least one gid is required, got: \"%s\"", str);
   148  
   149  	*gids_p = gids;
   150  	*n_gids_p = n_gids;
   151  }
   152  
   153  int main(int argc, char *argv[])
   154  {
   155  	int entering = 0;
   156  
   157  	/* '-e' optional flag passed only during 'entering' phase from stage1.
   158  	 */
   159  	int c;
   160  	while ((c = getopt(argc, argv, "e")) != -1)
   161  		switch (c) {
   162  			case 'e':
   163  				entering = 1;
   164  				break;
   165  		}
   166  
   167  	/* We need to keep these env variables since systemd uses them for socket
   168  	 * activation
   169  	 */
   170  	static const char *keep_env[] = {
   171  		"LISTEN_FDS",
   172  		"LISTEN_PID",
   173  		NULL
   174  	};
   175  
   176  	const char *keep_env_file = "/rkt/env/keep_env";
   177  	const char	*root, *cwd, *env_file, *uid_str, *gid_str, *exe;
   178  
   179  	char		**args;
   180  	uid_t		uid;
   181  	gid_t		*gids;
   182  	size_t		n_gids;
   183  
   184  	exit_if(argc < 7,
   185  		"Usage: %s /path/to/root /work/directory /env/file uid gid[,gid...] [-e] /to/exec [args ...]", argv[0]);
   186  
   187  	root = argv[optind];
   188  	cwd = argv[optind+1];
   189  	env_file = argv[optind+2];
   190  	uid_str = argv[optind+3];
   191  	uid = atoi(uid_str);
   192  	gid_str = argv[optind+4];
   193  	args = &argv[optind+5];
   194  	exe = args[0];
   195  
   196  	parse_gids(gid_str, &n_gids, &gids);
   197  
   198  	initialize_keep_env(keep_env_file, keep_env);
   199  	load_env(env_file, keep_env_file, entering);
   200  
   201  	pexit_if(chroot(root) == -1, "Chroot \"%s\" failed", root);
   202  	pexit_if(chdir(cwd) == -1, "Chdir \"%s\" failed", cwd);
   203  	pexit_if(gids[0] > 0 && setresgid(gids[0], gids[0], gids[0]) == -1,
   204  		"Setresgid \"%s\" failed", gid_str);
   205  	pexit_if(n_gids > 1 && setgroups(n_gids - 1, &gids[1]) == -1,
   206  		"Setgroups \"%s\" failed", gid_str);
   207  	pexit_if(uid > 0 && setresuid(uid, uid, uid) == -1,
   208  		"Setresuid \"%s\" failed", uid_str);
   209  
   210  	/* XXX(vc): note that since execvp() is happening post-chroot, the
   211  	 * app's environment settings correctly affect the PATH search.
   212  	 * This is why execvpe() isn't being used, we manipulate the environment
   213  	 * manually then let it potentially affect execvp().  execvpe() simply
   214  	 * passes the environment to execve() _after_ performing the search, not
   215  	 * what we want here. */
   216  	pexit_if(execvp(exe, args) == -1 &&
   217  		 errno != ENOENT && errno != EACCES,
   218  		 "Exec of \"%s\" failed", exe);
   219  	diag(exe);
   220  
   221  	return EXIT_FAILURE;
   222  }