upvote
Sandboxing is a mitigation but a very limited one. The library itself can contain a malicious payload -- rust code most often ends up as native code executed on the host.

I think we may need to enter a world where there are fewer, heavily audited, libraries and dependency depth is limited. Allowing unvetted dependencies to be installed by default is not a good way forward. App stores have a similar problem and I think a lot of lessons have been learned there that can be applied.

reply
That world can’t exist unfortunately, because the minute someone wants a feature that isn’t in your core libraries, you just create a new library and we’re back to now.
reply
That's why languages need sandboxing at runtime as well
reply
This is exactly what PMG is designed for ie. install/build time process level sandboxing. It currently doesn't support cargo, but I believe the challenges are same.

Here is my learning building PMG:

Sandboxing is good when the workload is predictable, and the goal of sandbox is to guard against exploitation of vulnerabilities, like sandbox protecting chrome tabs (renderers). But unfortunately build scripts are not predictable, at least not in npm/pypi world and I have seen build scripts doing weirdest of the things which is no different from malware. When popular packages do weird things, build breaks and users end up turning off the sandbox. This is a perpetual problem to deal with while building sandbox (or any least privilege solution) to protect unbounded workloads.

https://github.com/safedep/pmg

reply
The "How PMG Works" section on Github does not actually explain how it works
reply
build.rs by design can run absolutely anything. There tons of build.rs scripts that invoke a whole-ass C compiler toolchain to build and link C dependencies...

It isn't so much a question of sandboxing build.rs, as fundamentally changing the way that foreign dependencies are integrated into the rust toolchain (i.e. moving from a rust-centric system like Cargo to something more general like buck2)

reply
Worth noting that the JVM ecosystem doesn't have this issue because there is no notion of installing dependencies, and the package managers are just downloaders with nothing else. No build.rs equivalent.

To solve the problem of native/C dependencies, they just bundle pre-compiled libraries as data files.

Rust could do the same thing.

reply
But then that shifts the issue. You've now got an opaque binary blob being injected into programs. What if it is malicious?
reply
ABI stability is significantly different in a VM language to a native language, and reified generics necessarily require source compilation. Binary-only development for Rust would be exactly as 'sort of not really' as C++, for exactly the same reasons. (There is also no notion of 'installing' dependencies in Rust, and build-time code being malicious isn't much worse than runtime code being malicious.)
reply
The vast and overwhelming majority of build scripts are building C code, so the other solution is to move away from integrating with C dependencies to native Rust dependencies, in which case adding friction to build scripts would be less noticeable.
reply
Just denying write access outside the build directory and denying network access would go a long way and won't break pretty much any well-behaved build systems.

Any C library that's also packaged by debian supports being built under these conditions because it's required for everything except non-free packages: https://www.debian.org/doc/debian-policy/ch-source.html#main...

reply
> Just denying write access outside the build directory and denying network access

As the link you posted mentions, you need a tiny bit more than that: you also need write access to the temporary directory (/tmp and similar). Many build tools temporarily store files there; for instance, unless things have changed since I last looked, if you don't use the -pipe argument the C compiler stores its temporary intermediate files (preprocessor output, assembler input) there.

reply
One of rust's strengths is it's ability to interface relatively easily with existing c code without having to rewrite absolutely everything in rust. I don't think that is something we want to give up.
reply
build.rs changes nothing about how easy it is to integrate with C. What does simplify: figuring out how to supply library you need at build time.

Which is the result of how bad dependency managment is outside (i.e. DLL-hell).

Pretty much all other use cases of build.rs can be sandboxed. Well, there is sqlx that wants to connect to database at expansion time to compile check-queries (yew).

reply
sqlx at least has the (optional) offline mode, where you "cargo sqlx prepare" once (which wants access to a db) and then you can build in offline mode which typechecks your queries against local files.

Although I suppose that's still doing a lot of shenanigans at compile time. It could be sandboxed pretty well (theoretically). I'd hate to give it up completely though, getting a compile time error when SELECT query params or return values have type mismatches is extremely nice.

reply
sqlx offline mode being opt-in instead of default is what bothers me. You know what else can validate that your queries return what you expect? Integration tests. Shoutout to sqlx for #[sqlx::test] though.
reply
I also wish that it would be the default. Integration tests are fine, but they aren't compile time. Elevating them to compile time and using an LSP enabled editor makes it just underline SQL errors before I've even had a chance to run a manual compile let alone a test...
reply
LSP diagnostics is actually what made me switch from compile time check queries. Whole "is db up? are migrations applied?" dance tired me pretty quickly. Its fine if you use sqlite, but anything else gets annoying.
reply
Sandboxing for build scripts can't work properly. If you sandbox too much, some necessary stuff can't be done. If you sandbox too little, it has no practical value.
reply
As an easy start, how about letting build scripts read /usr, read and write a temporary build directory, have some /tmp scratch space, and be allowed to write its final output artifact. No network and otherwise isolated from the rest of the system.

I would argue that, if a build script doesn’t work in the setting, then it doesn’t deserve to be installable by a default cargo command.

reply
Cargo is a cross-platform tool, so when it ships a sandboxing solution it will need to be a cross-platform solution, and because this is a security feature it needs to be bulletproof, so no half-measures like Docker. Something like a WASM runtime might fit the bill, though that will be much easier to get working for typical proc macros than for typical build scripts. If you only care about Unix, then you can do this yourself today by building code in your sandbox of choice.
reply
> Cargo is a cross-platform tool, so when it ships a sandboxing solution it will need to be a cross-platform solution

This seems like an excuse, not an actual objection.

Linux can do seccomp or Landlock or gVisor or a combination. Seccomp and gVisor need no privileges. Windows has its internal weird mechanisms. Mac has sandbox-exec.

Cargo could easily pick an appropriate sandbox for each major platform and ship it by default.

> If you only care about Unix, then you can do this yourself today by building code in your sandbox of choice.

This is ridiculous. The sandbox should not have network access, but cargo needs network access to download the package in the first place.

reply
So let each build script define its own level of sandboxing and then users can determine whether they are okay with that level or not, e.g. `cargo build --sandbox-level=...`
reply
That’s not solving the problem, that’s avoiding it by making it the users fault if they make a mistake.
reply
Rust, like C, C++, and every other systems programming language, is all about giving users the power to make mistakes. The philosophical difference when it comes to Rust is simply that it tries to force the user to flip off the safety on the gun before letting you shoot yourself in the foot. A Cargo config option letting people opt-out of sandboxing would be fully in line with Rust's philosophy.
reply
Sandboxing just build.rs would only be be a minor inconvenience for the attacker, nothing more. The attacker can always as easily compromise the binary you build and as soon as you run it (e.g. in a test) you are owned.

It would be a big pain for many that are in the unfortunate position to really need build scripts, though.

reply
I imagine it would be sandboxed by default with an escape hatch to run build scripts outside of the sandbox with user verification. It makes people stop and think about what’s happening. Not perfect, but it does help. When working on JS ecosystem projects I manually approve build scripts and spend some time researching dependencies with build scripts to see if I can avoid running the build script. Some people will ignore it and run everything, but it’s a huge step in the right direction to make it operator-decided.
reply
It would be more than a minor inconvenience. I can handle sandboxing my tests and production infra, but I can't handle sandboxing build scripts because I don't own that code in any sense.
reply
At the very least, it wouldn't be overly onerous when adding a dependency that requires a build script to require an opt-in via Cargo.toml, e.g. `build-script = true`. You'd make it viral so that any transitive dependency that requires a build script would affect its parent, then add the key as defaulting to `true` so as to not break backwards-compatibility, then switch the default to be more restrictive over a new edition. (This same key could be used to prevent proc-macros from having arbitrary system access as well, where by default proc macros could be compiled to WASM and run in a WASM sandbox and treated as pure functions.)
reply
I'd like to see a "no-build" option to blocks depending on crates using build.rs
reply
I mean sure, but anything the build script could do, the build artifact could also do, That is to say, if you don't trust your source why do you trust the thing it compiles into?
reply
Never going to work. Crates must be audited for behavior before use.
reply
cargo add + rust-analyzer instantly executes build.rs before you have a chance to audit the code.

Cargo, please PLEASE give me a way to disable third-party build.rs and whitelist the ones I need. And please loudly mark any update that adds a build.rs where there was none before.

reply
cargo-deny can audit build scripts, but unfortunately not prevent execution of malicious build scripts exactly for the reason you gave. It could still help if you only ever use cargo add and update in a sandbox.

See https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.h...

reply
You can disable rust-analyzer running build scripts.
reply
So, don't add dependencies before you audit the code? That seems like a pretty reasonable ask to me.
reply
Safe-ish recommendation: Only add well-known, trusted crates. Failing that, treat unknown crates as malware or containing malware dependencies until proven otherwise. Test untested tools/crates in a VM/dev container to be sure they work properly before trust them. Use Mark I eyeball and Mark III brain too. :)

Audit and limit crates (cargo-deny &| cargo-crev, && --offline) until tools exist to audit build.rs safety semi-automatically ($$ safeguard.sh maybe).

The root problem is two parts:

1. crates.io doesn't do mandatory curation. Lack of curation is fail. It's time-consuming and costly for reviewers without a doubt, but so is letting an ecosystem gain maximum entropy (go to shit) by Tragedy of the Commons depending entirely on the honor system. Name squatting, low-quality, unmaintained, typosquatting, and malware are the consequences of too much self-service / semi-self-service freedom.

2. Many, many cargo subcommands are over-eager to run build.rs because it assumes trusted crates:

In an untrusted/uncurated crates world and a build.rs exists or exists in a selected dependency, it shouldn't run at cargo-add time (unless it must). If it exists, on first run, it should be presented to the user in a viewer for manual review unless a magic CLI flag/env var is specified to accept it.

These 2 factors combined appear to create a Swiss cheese holes failure mode for running arbitrary crate `cargo add`. I have confidence a suitable add-in workflow &| standard command &| repository workflow will be adjusted to reduce the attack surface of the ecosystem.

reply
I think it would be a good start if crates at least had to opt in to a build script, and adding one later would require permission from crates that depends on it.

The vast majority of crates don't need build scripts, so it is vaguely feasible to audit the list of crates you use that might need them.

reply
https://news.ycombinator.com/item?id=49374811

(Oh and btw, proc macros also run arbitrary code.)

reply
There's no good reason a proc macro can't run in a no-IO sandbox by default. None. Doesn't require a language change. Doesn't require some microvmcapabilityeffect BS. It requires looking people straight in the eye and saying "no" when they complain about needing to prompt for privileges.
reply
How many times do we need to learn that sandboxing won't magically save us.
reply