minijail0: implement --env-reset and --env-add

The default behavior of minijail0 is to transmit its own environment
to the program it launches.
These two options enable modification of this standard behavior, by
setting up a specific environment for the target program.

* --env-reset prevents minijail0 to pass its own environment to the
program. Note that if this option is used without any --env-add option,
the program starts with a completely empty environment.

* --env-add allows setting any number of environment variables to be
added to the environment set up for the program. Note that the special
environment variable LD_PRELOAD can also be set this way, without
impacting minijail0's own invocation.

Test: `make tests`

Test: `valgrind ./minijail0 --env-add A=X /usr/bin/env`
Test: `valgrind ./minijail0 --env-add A=X --env-add A=Y /usr/bin/env`
Test: `valgrind ./minijail0 --env-reset /usr/bin/env`
Test: `valgrind ./minijail0 --env-reset --env-reset /usr/bin/env`
Test: `valgrind ./minijail0 --env-reset --env-add A=X /usr/bin/env`
Test: `valgrind ./minijail0 --env-add A=X --env-reset --env-add A=Y /usr/bin/env`
Test: `valgrind ./minijail0 --env-add A=X --env-reset --env-add A=Y --env-reset /usr/bin/env`

Test: `minijail0 --env-reset --env-add LD_PRELOAD="a b" --env-add FOO=bar /usr/bin/env`
"""
ERROR: ld.so: object 'a' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
ERROR: ld.so: object 'b' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
LD_PRELOAD=a b
FOO=bar
"""

Change-Id: I2e106003824ff6707b80b6653141657e4ec40f47
diff --git a/minijail0_cli.c b/minijail0_cli.c
index 54cfdb7..3b9b708 100644
--- a/minijail0_cli.c
+++ b/minijail0_cli.c
@@ -456,7 +456,7 @@
  * bit confusing, and honestly there's no reason to "optimize" here.
  *
  * The long enum values are internal to this file and can freely change at any
- * time without breaking anything.  Don't worry about ordering.
+ * time without breaking anything.  Please keep alphabetically ordered.
  */
 enum {
 	/* Everything after this point only have long options. */
@@ -465,6 +465,8 @@
 	OPT_ALLOW_SPECULATIVE_EXECUTION,
 	OPT_AMBIENT,
 	OPT_CONFIG,
+	OPT_ENV_ADD,
+	OPT_ENV_RESET,
 	OPT_LOGGING,
 	OPT_PRELOAD_LIBRARY,
 	OPT_PROFILE,
@@ -494,6 +496,8 @@
     {"allow-speculative-execution", no_argument, 0,
      OPT_ALLOW_SPECULATIVE_EXECUTION},
     {"config", required_argument, 0, OPT_CONFIG},
+    {"env-add", required_argument, 0, OPT_ENV_ADD},
+    {"env-reset", no_argument, 0, OPT_ENV_RESET},
     {"mount", required_argument, 0, 'k'},
     {"bind-mount", required_argument, 0, 'b'},
     {0, 0, 0, 0},
@@ -599,6 +603,12 @@
 "               This will avoid accessing <program> binary before execve(2).\n"
 "               Type 'static' will avoid preload hooking.\n"
 "  -w           Create and join a new anonymous session keyring.\n"
+"  --env-reset  Clear the current environment instead of having <program>\n"
+"               inherit the active environment. Often used to start <program>\n"
+"               with a minimal sanitized environment.\n"
+"  --env-add <NAME=value>\n"
+"               Sets the specified environment variable <NAME>\n"
+"               in the <program>'s environment before starting it.\n"
 "\n"
 "Uncommon options:\n"
 "  --allow-speculative-execution\n"
@@ -693,9 +703,33 @@
 	return opt;
 }
 
+static void set_child_env(char ***envp, char *arg, char *const environ[])
+{
+	/* We expect VAR=value format for arg. */
+	char *delim = strchr(arg, '=');
+	if (!delim) {
+		errx(1, "Expected an argument of the "
+		        "form VAR=value (got '%s')", arg);
+	}
+	*delim = '\0';
+	const char *env_value = delim + 1;
+	if (!*envp) {
+		/*
+		 * We got our first --env-add. Initialize *envp by
+		 * copying our current env to the future child env.
+		 */
+		*envp = minijail_copy_env(environ);
+		if (!*envp)
+			err(1, "Failed to allocate memory.");
+	}
+	if (minijail_setenv(envp, arg, env_value, 1))
+		err(1, "minijail_setenv() failed.");
+}
+
 int parse_args(struct minijail *j, int argc, char *const argv[],
-	       int *exit_immediately, ElfType *elftype,
-	       const char **preload_path)
+	       char *const environ[], int *exit_immediately,
+	       ElfType *elftype, const char **preload_path,
+	       char ***envp)
 {
 	enum seccomp_type { None, Strict, Filter, BpfBinaryFilter };
 	enum seccomp_type seccomp = None;
@@ -1033,6 +1067,32 @@
 			}
 			break;
 		}
+		case OPT_ENV_ADD:
+			/*
+			 * We either copy our current env to the child env
+			 * then add the requested envvar to it, or just
+			 * add the requested envvar to the already existing
+			 * envp.
+			 */
+			set_child_env(envp, optarg, environ);
+			break;
+		case OPT_ENV_RESET:
+			if (*envp && *envp != environ) {
+				/*
+				 * We already started to initialize the future
+				 * child env, because we got some --env-add
+				 * earlier on the command-line, so first,
+				 * free the memory we allocated.
+				 * If |*envp| happens to point to |environ|,
+				 * don't attempt to free it.
+				 */
+				minijail_free_env(*envp);
+			}
+			/* Allocate an empty environment for the child. */
+			*envp = calloc(1, sizeof(char *));
+			if (!*envp)
+				err(1, "Failed to allocate memory.");
+			break;
 		default:
 			usage(argv[0]);
 			exit(opt == 'h' ? 0 : 1);
@@ -1127,6 +1187,16 @@
 		minijail_mount_tmp_size(j, tmp_size);
 
 	/*
+	 * Copy our current env to the child if its |*envp| has not
+	 * already been initialized from --env-(reset|add) usage.
+	 */
+	if (!*envp) {
+		*envp = minijail_copy_env(environ);
+		if (!*envp)
+			err(1, "Failed to allocate memory.");
+	}
+
+	/*
 	 * There should be at least one additional unparsed argument: the
 	 * executable name.
 	 */