Repository: rust
Issue: type_id is not sufficiently collision-resistant
Labels: T-compiler,I-unsound,C-bug,C-discussion

*This is a re-post of https://github.com/rust-lang/rust/issues/10389, attempting to summarize the current state of the discussion since that issue had too many comments to still be useful.*

The soundness of functions like [`downcast`](https://doc.rust-lang.org/nightly/std/boxed/struct.Box.html#method.downcast-2) relies on the type_id of two different types never being equal. Currently, the type_id is a 128-bit hash of the full type identity, computed specifically via SipHash-1-3 with an all-zero key. This is not a strong enough hash function for this purpose.

The lang team [decided](https://github.com/rust-lang/rust/pull/95845#issuecomment-1175324596) that relying on a full (non-truncated) cryptographic hash is fine -- we don't have to guarantee soundness against an infinite-resource attacker that can generate collisions in cryptographic hash functions.
However, SipHash even in its default configuration (SipHash-2-4) is *not* a cryptographic hash, as clarified [by its author](https://github.com/rust-lang/rust/issues/10389#issuecomment-1887239511)[^siphash] -- it is a pseudo-random function (PRF); that means it assumes a secret key, but Rust hard-codes an all-zero key and in fact has no way to keep a key secret. By standards for cryptographic hash functions, SipHash-2-4 with an all-zero-key is [weaker than MD5](https://github.com/rust-lang/rust/issues/10389#issuecomment-2181169783), and generating a collision for that [is pretty easy](https://x.com/realhashbreaker/status/1770161965006008570) these days. SipHash-1-3 is even weaker, we should thus expect it to be a matter of hours to create a collision, if someone really tried.

[^siphash]: In the [original paper](https://cr.yp.to/siphash/siphash-20120918.pdf), they even write: "We comment that SipHash is not meant to be, and (obviously) is not, collision-resistant."

Generating a concrete example of an unsoundness from that is a bit more tricky since one would have to find a Rust type generating the collision, but it seems fairly clear that the bar of "full (non-truncated) cryptographic hash" is *not* met by the current type_id implementation. The point of this issue is to determine how the compiler implementation can best satisfy the soundness expectation set by the lang team.

If you instead want to argue that the lang team should change its mind, please open a new issue and gather arguments in favor of that position, so that a summary of all the arguments for either option can be brought to the lang team for discussion. Also, this issue is only about type_id. According to [this comment](https://github.com/rust-lang/rust/issues/10389#issuecomment-2284095948), the only other case where a hash collision could cause unsoundness is with incremental builds; see https://github.com/rust-lang/rust/issues/129016 for the issue tracking that. All other hashes are actively checked for collisions by Rust. That said, it is not clear to me whether that also covers dynamic linking scenarios -- if someone knows about hash-related soundness concerns for that case, please file a new issue.

### Possible solutions

We should do one of the following:
1. switch to a stronger hash function, or
2. switch to a different scheme that doesn't rely on collision-resistance of the hash function.

Worth noting is that a cryptographic hash function these days must output [at least 256 bits](https://github.com/rust-lang/rust/issues/10389#issuecomment-2243114649) to be considered worth its salt. The lang team has not spelled out their exact definition of "cryptographic hash function", but the [standard definition](https://en.wikipedia.org/wiki/Cryptographic_hash_function) includes collision resistance, and in fact collisions are exactly what we are most worried about here, so it seems reasonable to assume that this is part of the lang team intent. A lang team member [mentioned](https://github.com/rust-lang/rust/pull/95845#issuecomment-1169054470) BLAKE3 as a candidate, further corroborating this claim. So we'd have to switch to BLAKE3 or SHA2 or something like that.

Therefore, in both cases will we need more than the currently available 128 bits of a `TypeId` to obtain the desired level of collision resistance. To avoid further increasing the size of `TypeId`, the most likely scheme would be to include a pointer in `TypeId` that points to the remaining information -- either a 256-bit hash, or a string (null-terminated or with leading length information, to obtain a "thin" pointer), or something like that.

https://github.com/rust-lang/rust/pull/95845 explores what this could look like without depending on a hash function: `TypeId` becomes a pair of a (not-soundness-critical) hash and a pointer. If the hashes are different, we can quickly conclude "inequal". If the pointers are equal, we can quickly conclude "equal". (Many linkers should be able to deduplicate the data the pointers point to, making the "equal" optimization even work cross-crate in many cases.) The same approach could easily be used without a full type name, by storing a pair of a low-quality hash for quick "inequal" checks and a high-equality hash behind the pointer to provide soundness.

*Relying* on the linker to deduplicate is unlikely to work since not all platforms have linkers that can do that (in particular for dynamic linking). C++ has a similar problem to solve and, at least on Windows, [seems to](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/Const.20.60TypeId.3A.3Aof.60.2C.20.60TypeId.3A.3Amatches.60.2C.20.60type_name.60/near/450558553) do something like the first of these options: compare pointers, and fall back to comparing strings.

Therefore, the next step here seems to be for the compiler team to decide whether to use a hash function or to include the type name in the binary. The latter has an existing [implementation](https://github.com/rust-lang/rust/pull/95845), but there were concerns about leaking type names (and the paths leading to those types) in the binary.