Supply-chain accountability -- coming to a Rust package near you!

Hello, Ubuntu community. I’m Petrichor Park, from the Rust Toolchains squad on the Foundations team. Foundations’ job is to make sure that all the other neat stuff in Ubuntu has a strong foundation to build on; my squad focuses on making it easy and safe for people to write Rust software, mainly by making sure all Ubuntu versions have recent versions of rustc.

Today, I’m here to announce that we now have experimental, opt-in support for cargo-auditable metadata on most Rust packages built on Launchpad.

TL;DR

  • Ubuntu now has opt-in support for embedding cargo-auditable metadata into Rust packages built on Launchpad that use dh-cargo.

  • To opt in, export UBUNTU_ENABLE_CARGO_AUDITABLE=1 at the top of your debian/rules file, and add cargo-auditable to your Build-Depends in your debian/control file.

  • This only works on Rustc 1.93 or later, and Ubuntu 26.04 Resolute Raccoon LTS or later.

  • Link to the release notes.

Why?

Compared to other languages, programs written in Rust tend to have many more dependencies. The primary reason is that Rust’s standard library is rather small, so developers look to third-party crates to do common tasks like random number generation or command-line argument parsing. This has advantages and disadvantages, discussion of which is outside the scope of this article, but one disadvantage that’s becoming more and more pressing is that it gets easier and easier for unknown and untrusted code to make it into your binaries.

One common attack on Rust developers is “typo-squatting,” where a malicious actor registers a crate with a name that sounds similar to an existing useful crate, in the hope that people mis-type the name of the useful crate and accidentally install the evil crate. Another related attack is creating a crate that provides legitimate functionality while also running an evil payload in the background.

Of course, it is also possible for developers acting in good faith to accidentally implement exploitable bugs. While Rust’s borrow checking system generally prevents C-style memory corruption exploits, no system can prevent developers from implementing logic errors. In addition, there are many crates that must use Rust’s unsafe feature, which lets developers partially bypass the borrow checker; this is often necessary for low-level memory manipulation or ultra-high-performance code, but re-opens the door to memory corruption.

What?

Cargo-auditable is a tool used during the compilation of a Rust binary. It embeds information about the libraries (or “crates” in Rust parlance) used to compile the binary, in a standard and easily machine-readable format. This means, given a random Rust executable, you can search it for this metadata and get a manifest of all the third-party code present. Then, in the event that a major vulnerability is discovered in a Rust crate, you can easily check the metadata to see if the bad crate is in your binary.

Under the hood, cargo-auditable injects a JSON representation of the dependency graph into an unused section of the binary named .dep-v0. This section is never executed or used by the binary itself, but nonetheless remains readable by external tools. See cargo-auditable’s Github repository for more information.

The Rustsec Working Group, who publishes cargo-auditable, also publishes cargo-audit, which (among other things) can read out the metadata from a binary and cross-reference it against a database of known vulnerabilities. The metadata is formatted simply enough that it is also possible to write a small script to pull out the JSON data; again, see the Github repository for more information.

Before I get to the good stuff, I need to stress that cargo-auditable is not a silver bullet against all supply-chain attacks. It does nothing to prevent developers from including malicious code in their binaries; it just helps users examine suspicious binaries in the aftermath of a CVE. (In other words, it is reactive, not proactive.) In addition, cargo-auditable cannot help you if a malicious developer chooses to produce malicious binaries; it’s very possible to fabricate false metadata that lies about what the binary contains. However, in many common situations, this metadata is still helpful…

How?

Once a vulnerability hits the news, the question on everyone’s mind is “have I been pwned?” Cargo-auditable lets users and developers collaborate on the cleanup and triage.

If you are an Ubuntu user (or archive admin), if a vulnerability is discovered, you may want to check if a given Rust program on the archives was compiled with a crate affected by it. If it was compiled with cargo-auditable metadata, then you can simply pull open the metadata and check if the vulnerable crate or buggy version is present.

If you are a developer of Rust packages on Launchpad, you target rustc 1.93 or later, and you build using dh-cargo, it’s easy to automagically wrap cargo invocations in cargo-auditable. In short, export UBUNTU_ENABLE_CARGO_AUDITABLE=1 at the top of your debian/rules, and add cargo-auditable to your Build-Depends in debian/control. (See here for more detailed documentation.) Then, all the executable build artifacts have the relevant metadata, which gives your users assurance that they know what they are running. We at Canonical already have this option enabled for high-profile packages like rust-sudo-rs.

There are, of course, some technical limitations. We currently have no plans to implement built-in cargo-auditable support for versions of rustc earlier than 1.93, or versions of Ubuntu earlier than Resolute Raccoon. We also can’t automatically enable this for Rust packages that build without dh-cargo, such as rust-coreutils and fish; we are working on manually invoking cargo-auditable for such important packages.

See debian/README.debian and debian/bin/cargo in the Rust repo for more technical information.

Please note that, at this time, this is opt-in support. If you don’t opt in by following the steps above, the compilation will continue as it did before we implemented cargo-auditable support. In the future, we plan to turn this from opt-in to opt-out, but we’d like for you, the Ubuntu community, to play with it (and break it) first before we set it to on-by-default. If you are a Rust developer and enabling this feature causes something to crash and burn, please, file a bug report. But if you don’t explicitly opt in, then nothing should change.

When?

The time to implement this kind of auditability is *now*. As Rust becomes more and more popular, the number of crates on https://crates.io only grows, and so the number of typo-squats and exploitable bugs grows as well. Some recent high-profile cases have been:

  • finch-rust, a crate named similarly to the legitimate finch, which included code to exfiltrate secrets from the user’s computer.

  • evm-units, which provided a genuine utility API for working with Ethereum cryptocurrency … while also stealing the user’s crypto keys.

  • TARmageddon, where a remote-code execution vulnerability was found in the popular library async-tar.

There are a lot of other Linux distributions that already have implemented similar cargo-auditable functionality. NixOS, Alpine Linux, openSUSE, and a few other “hobbyist” Linux distros already have cargo-auditable functionality on their respective archives. As part of Canonical’s commitment to making Ubuntu the best place to develop Rust code, we need to make sure that that code can be trusted, and that means making sure that users know what’s going into the programs they run.

Who?

First off, I’d like to thank the Rust community, for being a community worth securing. Rust is a really powerful language and project, and I’m glad that I can help defend it.

I’d also like to thank my manager and team, for having my back on such a giant project. When I joined, supply-chain auditability was on the backburner, and I’m very glad that I was given the responsibility and privilege to take that from a random item on our goals list to the concrete implementation of cargo-auditable we have today.

Finally, I’d like to thank Shnatsel, a Debian Rust developer, for helping me with the technical implementation. Like so many things in Rust infrastructure, software bills of materials are in flux, and so there are some strange technical problems to overcome in our implementation while the flagship Rust team discusses a more formal implementation. I couldn’t have finished the implementation without Shnatsel’s help; so thank you.

9 Likes

Did somebody forget to add the link? :wink:

But thank you very much for this :light_bulb:blog post, nonetheless!

Aw man I thought I fixed that. Thanks for the heads up.

1 Like