Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pretty Good Supply Chain Security #100597

Closed
rustrust opened this issue Aug 15, 2022 · 12 comments
Closed

Pretty Good Supply Chain Security #100597

rustrust opened this issue Aug 15, 2022 · 12 comments
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR.

Comments

@rustrust
Copy link

rustrust commented Aug 15, 2022

It would be nice if rust had a pretty good answer to "why is this date formatting library reading from disk and talking to the network?"

Constantly monitoring significant numbers of rust crates across the ecosystem doesn't scale well. Is there an effective way to get Pretty Good supply chain security in rust packages? I am interested to find out whether the rust community has a method of solving 98% of this problem, not 100%.

Some example approaches here would include:

  • having crates.io make it really obvious that safe code within a dependency talks to the network or disk.
  • having dependency-level sandboxing (perhaps declare in Cargo.toml that a package may use the network/write to disk etc)
  • separating dependencies which use safe only from dependencies which include unsafe
  • requiring packages which are new (less than two years old), fewer than 100k users, and which use unsafe to be marked as "experimental"

Additional potential approaches are very welcome

@rustrust rustrust added the C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. label Aug 15, 2022
@wizzwizz4
Copy link

wizzwizz4 commented Aug 15, 2022

Crates can be divided into three categories:

  • "Pure": doesn't modify any global state, except that reachable by function arguments. (That could include plenty of side-effects, but it can't do anything behind the caller's back.)
  • "Pure" alloc::alloc::Global: "pure" but also allowed to make global allocations. The distinction is necessary because allocator_api isn't stable yet.
  • Impure: has a hidden side-effect, accessing global state without being passed the ability to do so.

This distinction replicates capability-based security in Rust, within Rust's model. As I understand it, the only ways to do "impure" things are:

There's room for more nuance here:

  • std::io::Error and std::io::Write can't be used to break out of the sandbox,
  • there might be compiler bugs that let you break out of the sandbox with the things still allowed, and
  • there are some non-obvious permissions equivalences that are worth highlighting (e.g. being able to write to arbitrary files is as powerful as unsafe on Linux, since your process's memory space is exposed as a file).

But I think, as a first attempt:

  • if we can reliably detect the presence of these things in a crate, failure to find any means we don't have much to worry about; and
  • if a previously-"pure" crate suddenly starts using "impure" functionality, it requires a new audit, stat.

@linkdd
Copy link

linkdd commented Aug 15, 2022

I think this also must impact dependency resolution.

Let's say we have 2 crates: A and B.

Crate A depends on crate B:

[dependencies]
B = "1.0"

If crate B requires the capability network or disk, then the crate A transitively requires it as well.


Now let's say, none of A and B requires any capabilities. But as you saw above A depends on B>=1.0.0,<1.1 (python notation).

What if B publishes a new version 1.0.9999-compromised requiring new capabilities. Then, new users of A might unknowingly pull in the compromised version of crate B.


A possible solution would be for Cargo to issue a warning to the one pulling the crates:

WARNING: pulling in crate B, requiring the following capabilities:
  - network
  - disk
  - bitcoin mining
Accept? [y/N] _

@rustrust
Copy link
Author

If you want to allow B to talk to the network, you do this:

[dependencies]
B = "1.0" { capability: network }

And if you don't want B to talk to the network, you do this:

[dependencies]
B = "1.0"

And perhaps if you want B to be able to run unsafe code and talk to the network, you do this:

[dependencies]
B = "1.0" { capability: unsafe, network }

@linkdd
Copy link

linkdd commented Aug 15, 2022

What about:

[capabilities]
unsafe = true
network = false

This way, this would apply to indirect dependencies as well.

@rustrust
Copy link
Author

rustrust commented Aug 15, 2022

I think something like this would be more desirable:

tokio = { version = "1.20.1", capabilities = ["network"] }
csv = { version = "1.1.6" capabilities = ["disk"] }
protobuf = { version = "3.1.0", capabilities = ["unsafe"] }

This would behave somewhat like app permissions on your phone. Note that I am thinking that unsafe probably should be a capability for a crate. If tokio contained code which tried to access your disk, those disk access attempts would simply trigger a capability error in the above Cargo.toml. If you wanted to allow tokio to access both network and disk, you could do this:

tokio = { version = "1.20.1", capabilities = ["network", "disk"] }
csv = { version = "1.1.6" capabilities = ["disk"] }
protobuf = { version = "3.1.0", capabilities = ["unsafe"] }

The lowest level of capability would bubble up through crates.

@wizzwizz4
Copy link

wizzwizz4 commented Aug 15, 2022

What if B publishes a new version 1.0.9999-compromised requiring new capabilities. Then, new users of A might unknowingly pull in the compromised version of crate B.

For checking crates on crates.io (the "server-side", doesn't-modify-cargo approach), we could check all compatible dependency versions and consider the inherited capabilities to be the union of all of those dependency capability sets. That'd defend against this attack.

@gimbling-away
Copy link
Contributor

Do note however that if the intention here is that "Crate doesn't have capabilities by default", this will be a gigantic breaking change, one which would be non-trivial to be cargo-fix'd.

Although I do like the idea, I guess something similar to Deno's runtime capabilities system made granular to crate level could prevent a load of supply chain security attacks.

This might even make finding malicious crates much easier, as it'd be quite odd if some crate which shouldn't need networking, does need it. ;). Especially in stuff like typo squatting on crates.io (😦)

@workingjubilee workingjubilee changed the title RFC: Pretty Good Supply Chain Security Pretty Good Supply Chain Security Aug 17, 2022
@workingjubilee workingjubilee added C-feature-request Category: A feature request, i.e: not implemented / a PR. and removed C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. labels Aug 17, 2022
@workingjubilee
Copy link
Member

Feature requests may be more productive to discuss on irlo.

You may be interested in the extensive feature work on IO safety, which is a prerequisite to anything even vaguely resembling this, as well as crates like cap-std.

@rustrust
Copy link
Author

rustrust commented Aug 17, 2022

It might be worthwhile to think about ways of making these capabilities as specific as possible without being a nuisance. For example capabilities might be specified at the level of a specific function call, at the level of a code block, or at the level of a use statement.

example:

[dependencies]
 // capability is denied project-wide unless specified in the dependencies
hyper = { version = "0.14", features = ["full"],  capabilities = ["std::net"]}

...

use hyper::Client capabilities { std::net }; // capability is permitted for this file

or 

fn main() -> {
   use hyper::Client capabilities { std::net }; // capability is permitted for this scope
   let client = hyper::Client::new();
   // etc..
}

or

let client = hyper::Client::new() capabilities { std::net }; // capability exists only for this statement

@rustrust
Copy link
Author

Any new thoughts on this?

@mkj
Copy link

mkj commented Aug 29, 2023

https://github.com/davidlattimore/cackle is doing some work towards this.

@workingjubilee
Copy link
Member

The most appropriate venue for an open-ended request like this is still not the Rust issue tracker, sorry.

@workingjubilee workingjubilee closed this as not planned Won't fix, can't repro, duplicate, stale Aug 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR.
Projects
None yet
Development

No branches or pull requests

6 participants