Skip to content

Please reconsider the strategy in RandomState for getting a hashing key #48

@cbeck88

Description

@cbeck88

In order for aHash to achieve DOS resistance, the key used for the hash must be a secret not known to the attacker.
However, a problem (not only for rust but for all low-level programming that needs randomness) is that there are not usually completely portable APIs for getting randomness. In rust stdlib the APIs for randomness are in std and not core.

This is a big pain for hashmaps and AFAIK it is the only reason that the stdlib hashmap is not in alloc crate like all the other collections. That is very annoying for people trying to port their code that uses hashmaps to an environment with no operating system. Portability is super important for rust and not enough libraries in the ecosystem pay attention to this. So I appreciate your focus on this and the efforts you went to try to make something that will work.

Here's the strategy that I see based on review of the current code. (https://github.com/tkaitchuck/aHash/blob/master/src/random_state.rs)

(1) The const-random feature gets randomness at compile time and bakes it into the binary as a constant.
(2) The RandomState default implementation also mixes in the addresses of some stack and global variables, with a code comment explaining that ASLR will randomize these addresses, so this is like a source of randomness.

However, there are a few big problems with this:
(1) Turning on const-random feature basically means that I am baking my secret keys into the binary. It is NOT normally okay to assume that the attacker does not have the binary. People make releases on github of their binaries all the time. Even if their project is not open source, all kinds of engineers and contractors are likely to have access to a build that runs on the servers. If anyone who has the binary can extract the key and then DOS the server, that is terrible and way outside the threat model for most projects. This basically runs up against Kerckhoff's law: https://en.wikipedia.org/wiki/Kerckhoffs's_principle
It is not enough for the key to be chosen randomly "at some point in time", and rolling the random dice in the build.rs doesn't really fix anything. The point of choosing the key randomly at all is to make it a secret from the attacker, who is assumed not to have access to the specific machine where the process is running.

(2) Turning on the const-random feature throws repeatable builds out the window. Most of the time when people move security-critical code around, they will do things like take hashes of the binaries to confirm a correct download or a correct build. No one expects that a process is going to intentionally bake random bytes into the binary. In some cases, like SGX, not having a repeatable build destroys the guarantees of SGX -- the point is that someone else could build your software from scratch and get the same hash as the remote SGX hardware is telling them. A year ago, I had to spend several days tracking down why our SGX enclave build is not repeatable, by diffing the intermediate build artifacts repeatedly until I could isolate the problem, and the problem was aHash const-random feature. I now basically have to screen every third party lib we add to the project to check if there is an aHash somewhere in the tree without const-random feature disabled. This is a major footgun that will have to be disabled in serious projects, so I would argue that it should just be removed.

(3) If const-random is off, then the only source of entropy in our keys is ASLR. Here's the thing: ASLR is an OS feature. If you don't have an OS, you probably don't have ASLR either. You generally don't have ASLR in embedded devices, and you don't have ASLR in SGX enclaves. In fact, in any case where ASLR would be present, I expect that you can simply ask the OS for randomness instead of using pointer tricks based on ASLR assumption. The advantage of using a standard API for getting randomness from the OS, instead of trying to extract randomness indirectly via ASLR, is that ASLR is not actually an interface for getting randomness, and may not actually give you a secret random value like you would get from a normal interface. ASLR is a defense-in-depth technique to try to defend against ROP. But the address offsets can leak depending on the structure of the rest of your program. If you simply ask the OS for randomness instead of trying to rely on ASLR, then the OS will give you a value that can't be leaked or inferred in this way.

If there is no ASLR present on the system, then the secret key derivation in aHash likely fails in a manner similar to the debian Random Number Generator Bug from some years ago. Assuming that const-random is off, or that the adversary has a copy of the binary, there isn't really any other entropy present, so, game over, no DOS resistance.

So unfortunately, although we did all this work to try to make the library more portable and help the people working on no_std environments, embedded devices and such, we likely just created bigger problems for them, because we didn't use a standard API for obtaining randomness for secret keys.


I want to suggest an alternate approach: In the last year or so, the getrandom crate has matured and offers no_std support. This now appears to me to be the best and most portable way to obtain randomness. It is also the basis of OsRng now in the rand crate.

What I would suggest is:
(1) RandomState should get entropy from the getrandom crate, which becomes an optional dependency of AHash.
(2) When getrandom crate is not available, don't offer a default-initialized RandomState -- force the user to use the with_keys API and provide secret keys on their own, or tell them to patch the getrandom crate so that it will work for their target. (And ideally submit that patch upstream)
(3) All the const-random and ASLR-based key derivation should just go away.

This way, in an environment where ASLR is not actually present, instead of silently building insecure stuff, we fail at build time.

By using standard APIs and investing our maintenance energies in them, we can avoid one-off tricks and strengthen the ecosystem as a whole.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions