13 ms·
Using Rust Macros to exfiltrate secrets
- ngalstyan4 5y agoThe more general problem with trusting software supply chains is well described in Ken Thompson's Turing award lecture on "Trusting Trust"[1] [1]: http://users.ece.cmu.edu/~ganger/712.fall02/papers/p761-thompson.pdf http://users.ece.cmu.edu/~ganger/712.fall02/papers/p761-thom...
- the_duke 5y agoProc 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 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.
- db48x 5y agoAlso, it’s not a new problem; a Makefile or configure script can run arbitrary code as well.
- mprovost 5y agoYes this has always made me wonder about the pushback you see with the recent move towards curl | sh installers. In the past you'd download a random tarball and then run ./configure which could do anything.
- db48x 5y agoThere are three main problems with curl | sh: the file one the web server could be replaced without modifying the source in version control (and unlike a git checkout, the hash of the file is not verified), you can’t read the code before it runs, and curl could fail to download the whole file. Of course, I bet a lot of people don’t bother to read any of the source code of a program that they’ve downloaded anyway.
- fiddlerwoaroof 5y agoYou can solve the third problem by declaring a shell function `install` that is run at the send of the script. The first problem is a problem but, as far as I know, most language package managers don’t verify provenance anyway: yarn install foo can perform arbitrary side-effects either directly or through its transitive dependencies.
- mprovost 5y agoYou can break the pipe and curl the file first, read it, and then run it. But I doubt that anyone ever reads through the thousands of lines of m4 that come with a typical program that uses autoconf either.
- db48x 5y agoIf the project uses Autoconf/Automake, then you can just read the .in files instead. If they include anything unexpected, then it will be pretty obvious (since anything unexpected will be a lot more complicated–looking than anything normal). But if they do include a bunch of custom m4 files, then you’re going to be spending more time on it than you would want.
- slimsag 5y agoDownloading a tarball and running ./configure from it (pretty dang common) also does not have the changes checked into version control, nor the hash verified. Same is true of `npm install`, deb/rpm/etc packages, etc: you don't have proof what was distributed to you matches up with what was in VCS. You can read the code before it runs and solve the "curl could fail" theoretical arguments by just.. removing `| sh` and examining + running yourself.
- parhamn 5y agoCitation needed. Show me a Makefile + IDE combination that executed code by simply opening a file. I think you’re missing the language server part of this.
- efaref 5y agoIntelliJ with "Build in background" enabled? https://www.jetbrains.com/help/idea/executing-build-file-in-background.html https://www.jetbrains.com/help/idea/executing-build-file-in-...
- VWWHFSfQ 5y agoDoes opening an android project in android studio implicitly run any of the code in the project? I'm guessing it does, because the ide seems to be very busy all the time, even when idle
- Nullabillity 5y agobuild.gradle can contain arbitrary Groovy code with full system access, and needs to be executed to figure out the project structure.
- remram 5y agobash autocompletion will run arbitrary code from a Makefile. I wouldn't be surprised if many editors do too.
- mjw1007 5y agoI think programmers' editors in general have treated "automatically run arbitrary code supplied by files you're editing" seriously as a security vulnerability since sometime around 2000. (For example, Emacs realised that 'local eval' wasn't a good thing to have enabled globally in Emacs 19, in 1994, and spent the next decade or more closing many other loopholes involving local variables specified directly in files.) If modern editors and IDEs are no longer thinking that way, I think that's a mistake.
- j4yav 5y agoApart from running make which of course runs the makefile, under what scenario does viewing a makefile run it?
- efaref 5y agoYou're not just viewing it. You're opening it in an IDE which compiles it behind the scenes for you. Many IDEs also do this for other languages (e.g. by running make), and the same problem applies.
- remram 5y agoMakefiles can actually be quite dynamic, so a program merely trying to figure out the list of target has to execute code. For example put this in a Makefile and do `make <TAB>`, the file will be created (no need to press enter): VALUE := $(shell touch /tmp/something)
- JNRowe 5y agoFWIW, while both bash and fish completion execute "make -n" for tab completion that isn't the case for zsh. zsh uses an internal parser for makefiles¹, and as such won't execute the shell function or recipes that use the + prefix. ¹ https://github.com/zsh-users/zsh/blob/master/Completion/Unix/Command/_make https://github.com/zsh-users/zsh/blob/master/Completion/Unix...
- judofyr 5y 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.
- IshKebab 5y agoThat's a really recent feature and I'm sure Rust-analyzer will support it soon. I suspect the same problem exists in many other languages. How can you open a CMake project without executing it?
- banachtarski 5y agoIn a text editor....
- parhamn 5y 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.
- cogman10 5y agoThere aren't a bunch of languages with proc-macros and IDEs. That'd be where you'll see a major intersection. (Maybe C++ has this problem with some ides?) Languages with similar risks are ones where a Repl is is the key form of development. In those scenarios you are also one bad dependency from stolen info.
- trulyme 5y agoThat's not really relevant though. Anything that runs code on my computer without my awareness of it should be considered a security bug.
- tialaramex 5y agoAlas, the nature of computation makes this only ever a matter of squinting hard enough at the problem. Just as it turns out that matter and energy are almost the same thing seen from a different point of view, it's the same with code and data. Running code and processing data are no different to a computer. You think a picture of a dog and a Windows program are plainly different kinds of things, the computer does not agree. Something like Wuffs † aims to at least control the blast radius. If (in some alternate or far future world) you were only ever looking at pictures of a dog via Wuffs, you could at least feel confident that doing so did not have some entirely unforeseen consequences, like exfiltrating your SSH private keys. Today you certainly can't be sure of that, none of the tools you use have such a cautious approach. † https://github.com/google/wuffs https://github.com/google/wuffs
- foepys 5y ago
- rabidferret 5y agoI 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.
- bluejekyll 5y agoI think it would be better to report here, https://rustsec.org/ https://rustsec.org/, and folks running cargo audit would be aware of the issue even if they’ve already downloaded the dependency.
- Jaygles 5y agoThis 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.
- duped 5y agoThis 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.
- j4yav 5y agoThis isn’t build time though really, which I agree is a moment you would expect to run arbitrary code. This is “edit time.”
- duped 5y agoIt is build time. Whether rust-analyzer should run build-time code at initialization is a different discussion.
- j4yav 5y agoThat is a more philosophical definition of build time than what I am referring to.
- duped 5y agoIt's not philosophical, it is literal. Rust macros and build.rs require build commands to be executed by the rust compiler (cargo check, for example). These are third party tools that have been implemented to execute build commands during initialization. It's not an issue with Rust, it's an issue with the implementation of the language client and text editor allowing the client to initialize when opening a workspace.
- greenshackle2 5y agoYou 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 5y agoFor what it's worth, any VSCode extension that integrates with language tooling could be used to implement this.
- estebank 5y agoThis 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.
- terseus 5y agoI 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.
- parhamn 5y agoAgreed. The top comments on this thread are wrong, overconfident and silly. Read the article people.
- cogman10 5y agoYou'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.
- xfer 5y agoOpen it in notepad? You don't install software that automatically build your project and complain that it is doing that.
- Jakobeha 5y agoOne 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.
- greenshackle2 5y agoBy 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 https://rust-analyzer.github.io/manual.html
- mike-cardwell 5y agoThis doesn't affect me because I https://www.grepular.com/Sandbox_Rust_Development_with_Rust_Analyzer https://www.grepular.com/Sandbox_Rust_Development_with_Rust_...
- yannoninator 5y agoBlocks of text again... TL;DR?
- superjared 5y agoThere's a TL;DR at the top
- dnautics 5y agoas linked in the readme: https://www.youtube.com/watch?v=RRLw3OBJ0fM https://www.youtube.com/watch?v=RRLw3OBJ0fM
- kam 5y agoThis is why VSCode is adding Workspace Trust to prevent extensions from running untrusted code by merely opening a directory. https://github.com/microsoft/vscode/issues/120251#issuecomment-825832603 https://github.com/microsoft/vscode/issues/120251#issuecomme...
- juancampa 5y agoAre 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 https://www.rust-lang.org/governance
- comex 5y agoWell, the topic has come up on the internals forum from time to time, e.g.: https://internals.rust-lang.org/t/pre-rfc-procmacros-implemented-in-wasm/10860 https://internals.rust-lang.org/t/pre-rfc-procmacros-impleme... I don’t think there’s an active working group though.
- vlovich123 5y agoIs 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 5y agorust-embed is one: https://docs.rs/rust-embed/5.9.0/rust_embed/trait.RustEmbed.html 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.
- vlovich123 5y agoSure. I would expect such a tool to be happy enough with sandboxing to the folder containing the source of the project and the build folder, no?
- cryptonector 5y agoMeh. You could do this in C also. Nothing new here.
- not2b 5y agoThis 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).
- rhooke 5y agoA 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 https://code.visualstudio.com/docs/remote/containers
- akkartik 5y agoHas something like this ever been possible with Common Lisp and say Emacs?