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
, includingstd
- 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
instd
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-feature
s.
A quick summary of the philosophy here:
- Some crates export
unsafe-feature
s - 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