RFC for PR 49387. It introduces some new command-line options to sandbox env!()
and include!()
directives.
This PR introduces some simple sandboxing for process environment variables and for include files (collectively “system environment”).
This is primarily to allow a build system to more precisely control the inputs to rustc which may affect the generated output. Rust has two mechanisms by which an input source can access ambient properties of rustc’s system environment: env!()
and option_env!()
for reading environment variables, and include!()
/include_str!()
/include_bytes!()
for reading arbitrary files.
(This PR specifically does not intend to address any actual security concerns, since there are many other avenues that it does not attempt to control, such as compiler plugins/proc macros. However, it does help with unintentional problems which could result in later security problems if unaddressed.)
Environment Variables
rustc allows source code to directly access its process environment variables via the env!()
and optional_env!()
(pseudo-)macros. This poses a few of problems:
- the build system has no idea what variables the code is using for the purposes of tracking its inputs
- code can read arbitrary contents from arbitrary variables, which may pose unwanted information leakage (from build environment to final deployment environment, for example)
- it combines the environment rustc needs for its own operation (
PATH
andLD_LIBRARY_PATH
, for example) with environment the compiled code might want, and doesn’t allow them to be set independently
We introduce the following new command-line options to allow the apparent environment to be controlled at a fine-grain level:
-
--env-clear
- completely empty the logical environment visible toenv!()
/optional_env!()
, causing them to all fail/returnNone
. Without any other options, this will completely disable environment variable access. -
--env-allow NAME
- allow a specific environment variable to be read from the process environment -
--env-define NAME=VAL
- define a logical environment variable. This does not need to be present in the actual process environment, or if it is, its value is overridden
By default, the environment is completely open, leaving the existing behaviour unchanged. Once one of the options above is specified, accesses to environment variables become controlled accordingly.
Paths
Rust allows arbitrary other files to be directly included, either as more Rust source code (include!()
), a text string (include_str!()
) or arbitrary binary data (include_bytes!()
). These macros take a raw string which is used as a path which may be absolute - they therefore allow any file that rustc has permission to access to be used in the compiled output.
(This differs from separating a crate into multiple source files, as those files are always relative to the top-level lib.rs
/main.rs
source.)
This causes a couple of problems:
- the build system can’t know or constrain what files are actually inputs to the compiler
- the code can unintentionally leak state from the build environment to the deployment environment
To implement this, we introduce a couple of command-line options:
-
--clear-include-prefixes
- clear all allowable prefixes, effectively disabling allinclude*!()
macros -
--include-prefix PATH
- addPATH
to the set of valid prefixes. All included paths must match one of the valid path prefixes before it can be opened.
All paths are canonicalized before matching, so they must exist at the time they’re specified.
By default, all path prefixes are valid, leaving the current behaviour unchanged. They are only constrained once one of the options above are specified.
Note that a “path prefix” can be an entire pathname, allowing these options to explicitly specify which individual files may be included.