Hacker News new | ask | show | jobs
by the_duke 1864 days 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.

4 comments

> 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.

That'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?

In a text editor....
If they'd wanted you to do that, they would have named the files something like "CMakeLists.txt".
You can do that with Rust too if you want to live like it's still the 70s.
> 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.

Visual Studio has the same problem with C#. By default VS will load all defined analyzers into Roslyn which can execute arbitrary code.

VS warns you with a confirmation dialog that shouldn't just be ignored because "I just want to look and not compile". So, don't open any random .csproj or .sln and assume you are safe.

There 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.

That's not really relevant though. Anything that runs code on my computer without my awareness of it should be considered a security bug.
Alas, 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

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.
I think it would be better to report here, https://rustsec.org/, and folks running cargo audit would be aware of the issue even if they’ve already downloaded the dependency.
Also, it’s not a new problem; a Makefile or configure script can run arbitrary code as well.
Yes 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.
There 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.

Downloading 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.

I agree; distribution via git is better in many ways than distribution via tarball. I believe that npm and similar package managers mostly pull code from git repositories. Of course, even then you might want to double check that the package name hasn’t been hijacked or sold off.

Of course you can break the curl|sh into separate steps and check that the script isn’t malicious before you run it, but the fact that you have to do that makes it a bad idea to distribute software this way. If you were told to download an installation script, inspect it, and only then to run it then there would be less of a problem. curl|sh is yet another sign that we so often prefer convenience over reliability and safety.

Debian are working on reproduceable builds for apt. Not there yet but going in the right direction.

https://wiki.debian.org/ReproducibleBuilds

You 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.
You 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.
If 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.
Citation 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.
IntelliJ with "Build in background" enabled?

https://www.jetbrains.com/help/idea/executing-build-file-in-...

bash autocompletion will run arbitrary code from a Makefile. I wouldn't be surprised if many editors do too.
I 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.

Does 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
build.gradle can contain arbitrary Groovy code with full system access, and needs to be executed to figure out the project structure.
Apart from running make which of course runs the makefile, under what scenario does viewing a makefile run it?
Makefiles 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)
FWIW, 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...

You'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.