How does crates.io differ from npm

No, using your analogy, having a sandboxed environment will be akin to letting stranger only to your guest room without anything of value in it and having all doors in this room locked, so stranger will not be able to go into your bedroom or kitchen without being explicitly granted access to the rest of your house (or part of it).

If we're going to do this silly analogy twisting exercise, what you're actually proposing is letting the thief loot your guest room, and then bringing him over to your grandparents' house and giving him free rein.

This proposal doesn't address the npm issue at hand, anyway, so I'm not sure what the purpose is.

[Please drop the analogy thing.]

5 Likes

I think there is an interesting possible language feature that could solve some of the goals of sandboxing while making rust more expressive and more secure.

One nice thing about Haskell is that you can tell when a function is going to do IO but its type (unless it uses unsafePerformIO...). What if we added a similar feature to rust, but made it enforced by the compiler? I'm thinking something like an attribute where a given function cannot call any unsafe or IO code. This could make it possible to use such a function from an under l untrusted create without risking that a future version might make connections to the internet or modify the filesystem.

The catch here is that basically all rust code transitively calls unsafe code eg in the implementation of Vec. There would need to be some whitelisting mechanism, which could make this complicated. But my point is that rust is ideally situated to develop a "zero cost sandboxing" that is enforced by the compiler.

What you are proposing will add a large amount of weight to Rust development, while offering only a very minor security improvement for applications which are written solely in rust. That is, you're hardly securing anything.

I'll second kornel's point:

If your goal is security, and your means of accomplishing that goal is sandboxing, then what business do you have saying "we only permit the execution of Rust programs which were built using a specific whitelist and this set of compiler flags...." Now you have to enforce all that, and only a few applications will be able to meet those restrictions. (And how do you verify that the sandbox isn't escaped without sandboxing everything?) So why wouldn't you just sandbox every executable you want sandboxed, regardless of which language it was written in and which compiler was used?

2 Likes

My idea is not to implement sandboxing, but rather to provide an additional degree of safety and clarity to rust code, specifically to be able to designate that a function does not perform IO. I think this would be useful, and would make it easier to secure the ecosystem, because we would not need to audit crates that provide only non-IO functions, or crates for which we only use non-IO functions (unless they have a build.rs). This is similar to how the distinction between safe and unsafe code allows us to be confident that there is no undefined behavior caused by safe code. It is also analogous to the const functions that are in the pipeline, which we will be able to use at compile time.

Personally, I love having the compiler able to check things for me, that's what I like most about rust. It would be nice to continue expanding the set of properties that the compiler can check, and by doing so to reduce the attack surface for people trying to insert malicious code into the rust ecosystem.

The answer to that is for the same reason that applications are not currently sandboxed: defining what resources an application needs is very, very hard. It often cannot be done statically, since the resources required may depend on configuration files or command-line flags. Moreover, for many practical sets of programs there must be some interface for getting out of the sandbox through some user interaction (e.g. how javascript can access the file system through an upload file dialog), which adds even more complexity, and requires that the applications be written with the specific sandbox in mind.

That will be neither safe nor clear if you can still do IO in a function designated to not do IO. And "doing IO" can be pretty vague... (memory mapping comes to mind.) The focus on IO here is probably a red herring anyway, because the problem is accessing any shared state. (Your function isn't safe if it doesn't do IO but writes arbitrary memory... you could always effectively cause another program/function to do the IO for you.)

You would still have to audit everything unless you also forbid unsafe, extern "C", and friends. And if you do this, then you will have approximately zero rust programs left to audit.

Of course you would forbid those scary friends. That was my entire point, which I'm sorry I didn't make clear. And the issue is not about rust programs but rather about rust functions. There are many functions that do not require any of these risky features, and a way to flag this code as safe (and have that guaranteed by the compiler and build system) would be nice.

My objection is that you're saying you want to mark these functions as "pure" in some way, but you're not saying what "pure" means. If you label a function as pure and then try to assert that it does no IO, unsafe, or FFI... then have you guaranteed that the function is sufficiently pure to enhance safety? Don't you also have to forbid... statics? generics? dyn Trait? What functions will actually be left after all of this?

What notion of purity are you using, and what notion of safety is it meant to enhance?

Debian is an organization where the members are known by their real names and sign their work with electronic signatures, so they can be held accountable for it.

And I think it might work in the looser community setting too, and that it is actually the correct solution.

Therefore I propose that

  1. cargo and crates.io should learn signing releases with PGP and/or X.509 certificates.
    • There are some advantages of the ad-hoc signing in PGP, but the ability to get an X.509 certificate signed by some official authority (for a fee) has its merit too; maybe the formats can be converted to allow both options.
  2. Level of verification of the signature of a release, and level of verification of the least verified signature in the dependency chain would be indicated.
    • So there would be some visible benefit to using signed dependencies.
  3. When updating dependencies, cargo would check for any decreases in the verification level, and optionally in key changes.
    • It could optionally check that the certificate signing the new release is signed, among others, by the certificate that signed the previous one. This is to encourage some responsibility when handing over maintainership.

I would like a sandbox for the build to go with it, but not for security per se; it would be for auditing what goes into the build and making sure all of it came through the cargo signature checking logic.

1 Like

That is indeed the question that would need answering. I believe that there is probably an answer that will enable productive and significant code to be pure while still providing significant protection. I do not have a definitive definition for this, however. Like all rust features, it would require significant discussion.

My suspicion is that serde and similar, for instance, could be made pure, which would be a huge safety benefit, since complex parsing coffee is there kind of easy place to hide malicious code. IO should be possibly for a pure function, but only using methods of its inputs, in a way analogous to the memory protection that the borrow checker provides. Basically The idea is that the type signature of a pure function would define a limit on what sort of IO operations it could do. If you write a pure function of type

pure fn read_stuff<R: Read>(f: R)

Then I should be able to pass in a File and know that you will only be able to read from it, i.e. no shenanigans with Any or typecasting that would let you write to it or rename it.

The safety this would enhance would be that I could know that if I user your "leftpad" crate it won't do anything but modify my strings, so I don't need to worry about it also sending spam or mining bitcoins. The other benefit would be that by reading a type signature I could more reliable see which functions might have surprising behavior.

2 Likes