Over time I’ve seen a bunch of problems brought up about trusting dependencies. While they’re different concerns with different threat models, I like to think of them as being different flavors of an overarching problem I call “crate trust”. I’d like to start a discussion about this, and propose one solution. This is a pre-pre-rfc with more of a sketch of a solution instead of specifics, and if folks like this I can move on to a more fleshed out pre-rfc.
Problems
Here are some of the problems in this space I wish to solve:
Problem: Unsafe trust
The majority of folks use Rust because of its safety.
However, Rust’s safety has a major caveat in it, for it to work all of the “unsafe” code must be correct. You can audit your own crates, but when you have tens or hundreds of dependencies, how will you keep track of who is using unsafe?
The threat model here isn’t someone maliciously sneaking unsafe
under your eyes. If you want to deliberately cause safety problems you can do so without using the unsafe
keyword. The threat model is poorly written unsafe code. There are different levels of trust involved here, I may trust rust-lang-nursery/foo
and rust-lang/foo
to generally get safety correct, I will trust my own crates (because I’ve audited them), but if my dependency randomperson/foo
uses unsafe
I might want to audit them.
It would be great to have tooling that lets us keep track of which dependencies we trust to use unsafe, and be notified when a new untrusted dep uses unsafe or a dep that uses unsafe is updated to a new version such that we need to audit it again.
Problem: General code trust
Firefox (and likely other major users) needs a solution to this. Firefox is shipping code to millions of users; cargo update
should not be a vector for getting malicious code into the browser.
This is similar in structure to the unsafe code problem. Some crates we can just trust, because they’re maintained by the Rust, Servo, or other “trusted” teams. But for other crates, we need to be careful when we pull in updates, auditing the code. Tooling that lets us keep track of crate audit/code reviews would be nice.
We might end up writing this tool ourselves, but if the umbrella “crate trust” problem can be fixed it would make things easy.
Problem: Build scripts and plugins
This is essentially this problem. We allow folks to execute arbitrary code in the form of build scripts and plugins. This can do malicious things at compile time; which is pretty spooky.
It’s questionable if this is different from the “general code trust” issue. After all, if you’re building something you’re probably running it as well. However, you might be running it in a different environment, whereas build scripts can flat out find and upload developer credentials. This really makes it seem like you should always just build and run in a locked down environment, but building is a more interactive process and it would be nice if we could help secure our users here.
To this end, it would be nice if it was possible to restrict what build scripts can do (or if packages are even allowed to have build scripts, or allowed to be a dependency of a build script). You have different kinds of build scripts:
- Codegen scripts: These need read access to the package, and write access to the outdir
- Native compilation scripts: These need read access to pkg-config, gcc, /lib, and a few other places.
- Miscellaneous: These may need access to additional system folders, or even all of them.
If dependencies could declare what they need, we can actually sandbox build scripts and plugins to operate only within that sphere only. Similar to the previous problems, being able to grant these capabilities at the top level would be nice.
Solution
Bear in mind that this is a high level view of a possible solution. There are other solutions (which I’d love to hear!), and the specifics of how this will work are missing (the syntax of declaring/granting capabilities, where they get granted, etc)
Basically, this all boils down to capabilities. Allowing crates to declare capabilities, and allowing the toplevel crate (via .cargo/config
, or Cargo.toml
) to grant capabilities.
The tool that enforces this need not be cargo
, it can be a custom tool instead. However given the sandboxing requirements some cargo
integration would be good to have.
Some candidate capabilities would be:
- Allowed to use
unsafe
code. Granted to all crates by default, but you can change this at the toplevel. We can make it so that crates need not declare this and instead we get this from compiler metadata. This solves the unsafe problem - “Allowed to exist”. An implicit capability (need not be requested in Cargo.toml), and granted by default. However, you can change this at the top level, such that you have to explicitly whitelist crates allowed to be dependencies. This solves the “general code trust” problem
- Build time capabilities:
- Allowed to have a build script or be a plugin (i.e. “allowed to be executed at compile time”). Granted by default.
- Allowed to be a dependency of a build script or plugin (could be rolled into the previous one)
- Allowed to read from the crate’s folder, and write to
$OUTDIR
. Granted by default. Enforced by sandboxing. - Allowed to compile C++ code (this might be hard to specify, but it involves read access to a bunch of system folders, execute access to gcc binaries, and stuff like that). Granted by default. Enforced by sandboxing.
- Allowed to read/write/execute a custom list of folders. Not granted by default. Enforced by sandboxing.
- Allowed to do whatever it wants at build time. Not granted by default.
At the top level, you can declare what capabilities you care about (“please check for unsafe code”, “please check if all crates are allowed”, “please sandbox build scripts”), and then grant them.
Then, you can specify:
- If all versions of a crate are granted a capability (I trust
rayon
to be good at auditing its own unsafe code) - If some versions of a crate are granted a capability (semver syntax). We probably only need
foo=*
andfoo=specificversion
and nothing else. - In case we grant capabilities in
Cargo.toml
, we can make this transitive – have the ability to say that you include all capabilities granted in theCargo.toml
of one of your dependencies. This is useful for larger projects made up of multiple crates - If all crates by some author are granted a capability (for some TBD definition of authorship)
Thoughts?