top | item 27154534

Using Rust Macros to exfiltrate secrets

122 points| superjared | 4 years ago |github.com

75 comments

order

the_duke|4 years ago

Proc macros can run arbitrary code, so this POC is not that interesting - apart from raising awareness for the problem.

This can be done even easier without users having to use a macro: with `build.rs` build scripts, which are run by default. So all you'd need is to compromise some popular dependency with a custom build.rs

Many other languages have the same (or at least similar) problem (Makefiles, npm hooks, ...)

There is an interesting proposal and prototype for compiling proc macros to WASM so they can be run in a sandbox: https://github.com/dtolnay/watt

But in the end it doesn't make that much difference: nothing prevents a random library from just reading your secrets and calling curl to send it to a server at runtime.

Build time execution is definitely an additional attack vector.

But if you use a third party dependency, you have to trust it or review all it's code for every version. There is no way around this, and it's true for any language.

judofyr|4 years ago

> But in the end it doesn't make that much difference: nothing prevents a random library from just reading your secrets and calling curl to send it to a server at runtime.

The difference here is that it happens when you open the project in the editor. If I'm suspicious of some code my first reaction would be to open it my editor and inspect it.

The ESLint extension always asks whether you trust the `eslint` executable before it's enabled. It's still quite easy to click "allow" without thinking about it, but at least you'll have a choice to not execute potentially random code.

parhamn|4 years ago

> Many other languages have the same problem (Makefiles, npm hooks, ...)

This simply isn’t true. All of these require an action by a user to execute the command (e.g npm install, make build). What the author is claiming is that a typical rust LSP setup will execute the arbitrary macro code simply by viewing the file in certain IDEs.

Feel free to show me an example of this in makefiles or npm and I’m happy to retract.

rabidferret|4 years ago

I would just like to tack on that malicious code is against the crates.io terms of service, and something like exfiltrating secrets in a build script is something that very clearly qualifies as malicious. If you ever encounter this in the wild, please make sure you report it to the crates.io team, so it can be removed.

db48x|4 years ago

Also, it’s not a new problem; a Makefile or configure script can run arbitrary code as well.

terseus|4 years ago

I can't believe that people is comparing opening a project in a code editor with running a build script.

The PoC doesn't even open a file, it just opens the directory. It's a pretty big difference, when you execute a build script you _expect_ to run code, when you open a directory in your editor you don't expect any side effect _at all_.

My guess is that since the proc_macros returns a TokenStream, rust-analyzer have no way to know what it provides except running it.

I'm not sure there's a solution for this that doesn't cripple macros in Rust, apart from being able to configure rust-analyzer to ignore the macros, which clearly limit its usefulness.

db48x|4 years ago

More specifically, a proc macro is a Rust function that is compiled and run inside the compiler at build time. With IDEs, LSP and other protocols for having your editor query the compiler (or language runtime, like SLIME/SWANK), the compiler now runs whenever you open your editor.

It’s just not a new problem. Bash does auto–completion on Makefiles, which requires running make and asking it what the make targets are. IDEs can and will run ./configure for you, so that it can find the right include paths. Etc, etc.

Personally, I thought everyone already knew about this. I knew that proc macros would be a risk when I first heard about rls, years ago.

Certainly editors need to confirm with the user that they are ok with starting the compiler when they load a new project, but also we need to use fine–grained security systems like SELinux that can and do prevent programs from accessing things that they’re not supposed to access.

Jakobeha|4 years ago

One potential solution:

- During a session, the first time rust-toolchain encounters a proc macro it must run to analyze, it will first prompt the user and warn them.

- If the user accepts the prompt, rust-toolchain will freely run any proc macros until the next session.

- If the user rejects the prompt, that analysis will be disabled until the next session.

Similar to how VSCode and other apps handle opening links.

cogman10|4 years ago

You'd have to sandbox the analyzer. Let it run arbitrary code but don't let it do IO. That can be pretty tricky to do for a language not designed to be sandboxed.

Safest way would probably be something hilarious like having the analyzer compiled to WASM and ran in node.js.

greenshackle2|4 years ago

By default rust-analyzer also executes Rust build scripts (build.rs) just by opening the project in an IDE, so as far as Rust goes the comparison is apt.

    rust-analyzer.cargo.runBuildScripts (default: true)

    Run build scripts (build.rs) for more precise code analysis.
https://rust-analyzer.github.io/manual.html

xfer|4 years ago

Open it in notepad? You don't install software that automatically build your project and complain that it is doing that.

parhamn|4 years ago

Agreed. The top comments on this thread are wrong, overconfident and silly. Read the article people.

Jaygles|4 years ago

This is a huge deal right? VSCode has to be one of the most popular editors and the standard way of setting up the Rust toolchain on a machine would get you in a state that makes you vulnerable to this.

kzrdude|4 years ago

The only component is an editor that runs some code automatically.

A python plugin for an editor would have the same problem - if it imports a python module for any reason, like code completion. Same problem of arbitrary code execution.

I think we should work on solutions. Sandboxing both for editor plugins and for regular rust builds, should become the norm.

duped|4 years ago

This is as huge a deal as "using ./configure && make install to exfiltrate secrets."

It's a class of supply chain attack focusing on build time code evaluation. Almost every programming language has some kind of support for arbitrary code execution at build time, and any project of scale is going to require it.

RCE isn't an interesting exploit when the system is literally designed to run code from somewhere else.

iudqnolq|4 years ago

It requires never actually running the program to be worse than the status quo in any language. If you run code it's trivially a code execution.

vlovich123|4 years ago

Is there a reason that access to the filesystem isn't sandboxed aggressively by the compiler? Even having build macros that can access arbitrary parts of the filesystem (vs a dedicated scratch directory) seems like a bad idea. Is there any legitimate use-case here?

tangent128|4 years ago

rust-embed is one: https://docs.rs/rust-embed/5.9.0/rust_embed/trait.RustEmbed....

This macro lets you embed an entire folder of assets in your binary at compile time, to simplify distribution.

Taking the concept further, I could also imagine build macros that compile Typescript or SASS files at build time, or generate data structures from a Protocol Buffers definition file, or in general operations that ingest non-Rust source code and use tools outside the repository.

greenshackle2|4 years ago

You can also just put arbitrary code in build.rs, it will be run by cargo check, rust-analyzer, etc. Though I admit macro expansion hacks are more fun and easier to hide.

duped|4 years ago

For what it's worth, any VSCode extension that integrates with language tooling could be used to implement this.

estebank|4 years ago

This is an inherent problem of languages where execution is needed to understand its semantics. Most interpreted languages have this issue, and Rust has this issue due to proc-macros being Rust code that needs to be compiled and executed to process other Rust code.

not2b|4 years ago

This issue is very similar to the problem of malicious macros in Microsoft Office documents, and I think it needs to be addressed somehow (by figuring out a proper security model and asking for user confirmation for actions outside this model).

juancampa|4 years ago

Are there any working groups or teams in the rust foundation[0] looking into stuff like this? I know every package manager has these issues but there's no technical reason preventing us from building sandboxes (i.e. WASM, deno, ...) for this and making it a first class citizen of cargo/rustup/etc.

Just installing a relatively popular crate (say Hyper) makes you realize that all of your secret could have been stolen by any of the myriad of dependencies.

[0] https://www.rust-lang.org/governance

rhooke|4 years ago

A lot of the work I do recently has been using devcontainers in VSCode [1]. They even have a Rust sample one. I feel like this would provide at least a little bit of protection against this kind of attack if you do not mount any imporant stuff into the container.

I can't see a robust solution to this, though.

[1] https://code.visualstudio.com/docs/remote/containers

akkartik|4 years ago

Has something like this ever been possible with Common Lisp and say Emacs?

cryptonector|4 years ago

Meh. You could do this in C also. Nothing new here.