Crate capability lists

I’ve been thinking along similar lines, but specifically around the problem of restricting usage of unsafe. I posted some initial thoughts here:

https://groups.google.com/d/msg/cap-talk/t9al5hjN19U/XzHfR1peBAAJ

I’ve thought about posting a “Pre-Pre-RFC” about this, but I guess I can start by spitballing here.

Unsafe Features

Synopsis: Extend the existing idea of cargo features with a special notion of “unsafe features” which can be used to whitelist usage of unsafe in dependencies (and their transitive dependencies).

Goals:

  • Opt-in feature which has no effect on code that doesn’t make use of the feature explicitly
  • Cover all usages of unsafe, including std
  • Provide a path for retrofitting std with unsafe features, which could eventually be used to eliminate ambient authority from Rust and thereby provide a foundation for an object capability model

Non-Goals:

  • Full OCap semantics out of the box
  • Breaking changes of any kind

Cargo.toml changes

Let’s start with something which is perhaps worthy of a pre-pre-RFC in and of itself, an allow-unsafe option:

[dependencies]
foobar = { version = "0.2", allow-unsafe = false }

This would transitively disallow use of unsafe by foobar and all of foobar's transitive dependencies.

Ideally though, we could whitelist specific usages of unsafe in the foobar crate. Enter “unsafe features”.

In the Cargo.toml for the foobar crate, we could imagine something like this:

[features.unsafe]
fire_the_missiles=[]

And in the code, something like this:

#[cfg(unsafe_feature = "fire_the_missiles")]
fn fire_the_missiles(...) {
   unsafe {
      // Use your imagination. How about some pointer arithmetic based on attacker-controlled data?
      [...]
   }
}

Now let’s imagine the baz crate wants to consume the foobar crate and use this feature. It will need to opt into using this unsafe behavior (I am imaging that unsafe features cannot be default features, and must be opted into explicitly).

In the Cargo.toml for the baz crate, we do the following:

[dependencies]
foobar = { version = "0.2", unsafe-features = ["fire_the_missiles"] }

Now the baz crate is able to make use of the fire_the_missiles function. We can imagine that all of the rest of the cargo feature behavior continues to work, for example foobar could be an optional dependency, pulled in via a regular (non-unsafe) cargo feature.

But for simplicity’s sake (and to illustrate an example), let’s suppose that baz always includes the foobar crate and makes use of the foobar/fire_the_missiles unsafe feature. Now what happens when the quux crate tries to include baz?

In the Cargo.toml for the quux crate, imagine we did this:

[dependencies]
baz = "0.1"

This is where things get a bit interesting. baz is making use of the foobar/fire_the_missiles feature, and quux has not explicitly authorized it. This is an error:

error: crate `quux` makes use of `unsafe-feature` not whitelisted in Cargo.toml: foobar/fire_the_missiles`

…or thereabouts.

To correct this, we need to explicitly whitelist this relationship in the quux crate’s Cargo.toml:

[dependencies]
baz = { version = "0.1", unsafe-features = ["foobar/fire_the_missiles"] }

Explicitly whitelisting these features would be required at any level.

Imagine we’re in a completely different project which is including the quux crate. It would be required to do the following in its Cargo.toml

[dependencies]
quux = { version = "0.0.1", unsafe-features = ["baz/foobar/fire_the_missiles"] }

Tough questions

I stated one of the goals is “opt-in feature which has no effect on code that doesn’t make use of the feature explicitly”. In my spitball description, I’m suggesting whitelisting of unsafe usages “kicks in” when allow-unsafe = false or unsafe-features is added to a crate’s attributes in the [dependencies] section. But what about:

  • Q1: Usages of unsafe which aren’t tagged with #[cfg(unsafe_feature)]?
  • Q2: Usages of unsafe in std

Well, short answers:

  • A1: When unsafe-features is used, all usages of unsafe MUST be gated on a #[cfg(unsafe_feature)]. If that were the case, it would probably make sense to make these usages compile errors.
  • A2: It should probably apply to std, possibly with an opt-out mechanism for truly side-effect and ambient authority-free things like mutating the interiors of strings.

This means to get the unsafe parts of std “back” in these crates and make them accessible, std itself would need to be retrofitted with unsafe-features.

A quick summary of the philosophy here:

  • Some crates export unsafe-features
  • Other crates consume them, explicitly whitelisting the ones they use
  • At each level of the dependency hierarchy, crates must opt into these features, or these uses of unsafe will be a compile error
3 Likes