Evil-nix: a Nix library to download files from the internet without requiring a hash

I’d like to announce evil-nix. This is a Nix library that allows downloading arbitrary files from the internet without needing to specify a hash. It even works in Nix’s pure-eval mode.

You can try out evil-nix like the following. First, download the repo:

$ git clone git@github.com:cdepillabout/evil-nix.git
$ cd evil-nix/

You can use the default.nix file to try downloading any arbitrary file from the internet:

$ nix-build --argstr url "https://raw.githubusercontent.com/cdepillabout/small-example-text-files/177c95e490cf44bcc42860bf0652203d3dc87900/hello-world.txt"
$ cat ./result
hello world

Due to the way evil-nix is implemented, I highly recommend not downloading files more than a few tens of bytes.

There is also a Flake in the repo that can be used as well. Checkout the README.md for more information.

evil-nix exploits Nix’s support of broken hash functions, like SHA1, to allow downloading arbitrary files from the network without requiring a hash.

24 Likes

For the curious:

How does this work

The evilDownloadUrl function works by internally creating a fixed-output derivation which outputs one of two known PDF files, both with the same SHA1 hash. This fixed-output derivation is allowed to access the network, and outputs one PDF file to represent a single 1 bit, and the other PDF file to represent a single 0 bit. This effectively leaks one bit of information from the internet in a non-reproducible manner.

evilDownloadUrl combines many of these 1-bit-leaking fixed-output derivations in order to download the entire specified file from the internet.

[…]

Due to the way this hack works, evilDownloadUrl is extremely inefficient. It performs one request to the URL for every bit (!!) of the file it is trying to download. For instance, if you were trying to download a 10 byte file, evilDownloadUrl would make 80 requests to the URL, and download the file 80 times.

That is so genius and dumb at the same time :joy:

Though in all seriousness, might that be an argument to retire support for SHA1? Is there a potential attack this could be used for?

17 Likes

There are a few GitHub issues in the Nix repo that suggest that MD5 and SHA1 support are still available in Nix for backwards compatibility. Eelco wants to make sure that new versions of Nix can correctly evaluate very old Nix code. I personally think this policy of backwards compatibility is really nice, even if it does lead to silly things like evil-nix.

I did have a few suggestions on how Nix could be changed to stop evil-nix from working, while still keeping some level of backwards compatibility:

I spent some time thinking about this, but I couldn’t come up with anything. evil-nix can of course be used to create Nix code that is not reproducible, but that’s not a security issue.

2 Likes

Historically, Nix has been extremely compatible and it doesn’t hurt much to keep support.

Perhaps bad hash support could be a nix.conf option that is off by default.

6 Likes

Is the tricks in Useful Nix Hacks no longer doable since fetchGit is a builtin?

Are you referring to the section called “Hashless Git Fetching”?

fetchGitHashless = args: stdenv.lib.overrideDerivation
  # Use a dummy hash, to appease fetchgit's assertions
  (fetchgit (args // { sha256 = hashString "sha256" args.url; }))
  # Remove the hash-checking
  (old: {
    outputHash     = null;
    outputHashAlgo = null;
    outputHashMode = null;
    sha256         = null;
  });

With this, we can specify a git repo without needing a hash:

fetchGitHashless {
   url = "http://chriswarbo.net/git/chriswarbo-net.git";
   rev = "7a5788e";
 }

I’ve never seen this before, but it does look like it is no longer possible.