-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Put HashMap's hasher in an Result and Default::default() it to Err(Default::default) #2551
Comments
@rust-lang/libs This suggestion would help prevent one possible cause of denial-of-service vulnerabilities. Is it worth the cost of adding a Option to HashMap (which I think would increase its size from 5 words to 6)? Or is there an alternate mitigation without that cost? |
There's no issue if one writes v.resize_with(my.len(), HashSet::default); right? Is this just in the category with |
I prefer "right by default". It seems dangerous to hide a potential DoS exploit behind an innocent-looking lint that can be easily disabled. (resize_with isn't stable yet, either) |
We could always replace the hasher when cloning empty Is this worse than I do like @scottmcm answer that lints should encourage explicitness, but actually no answer above handles the perfectly reasonable We could add a clone mode via an optional const parameter to HashMap or a associated constant in BuildHasher:
I dislike this because although explicit it mostly just shifts blame onto users. Can we instead simply lint against using |
Looking at this again, I don't think the original proposed change is allowable, regardless of whether we want to, because there's no way to get an impl<K, V, S> HashMap<K, V, S> where K: Eq + Hash, S: BuildHasher {
pub fn insert(&mut self, k: K, v: V) -> Option<V>;
} It would additionally need something like pub fn hasher(&self) -> &S means it would need instead to be something like |
Add some dependent typing and we can easily solve this. It would only be None in cases where Default was called, or in HashMap::new(). Altho I do wonder if you could have two impls of HashMap, one where it is Default and one where it isn't. |
Instances where this becomes dangerous sound rare, thanks to the strong protections provided by We should just warn against using SummaryAllow implementations of traits to warn against unexpected behavior or limitations by specifing lints against their own use. Also, provide a warning that cloning a MotivationWe encounter scenarions where implementing some trait makes sense, and sems desirable in general, but caveats about specific use cases still exist. We should expect such scenarios to encourage abuse of heavy handed techniques like hidden trait implementations (RFC #2529). As an example, we want #[derive(Clone)]
pub struct HashMap<K, V, S = RandomState> {
...
}
#[caveat("A cloned HashMap can become a DoS vector, provided its Hasher is not cryptographic, because cloned BuildHasher instance can facilitate exposing the Hasher's key. RandomState has cryptographic key protections, making clone safer.")]
default impl<K: Clone, V: Clone, S: Clone> Clone for HashMap<K, V, S>
#[no_caveat]
impl<K: Clone, V: Clone> Clone for HashMap<K, V, RandomState> |
Hmm... If we're gonna be using another word for it anyway, we could always go with
|
@burdges Bikeshed: add names to #[caveat("hashmap_clone_dos", "A cloned HashMap can become a DoS vector, [etc]")] default impl<K: Clone, V: Clone, S: Clone> Clone for HashMap<K, V, S> { .. }
#[no_caveat("hashmap_clone_dos")] impl<K: Clone, V: Clone> Clone for HashMap<K, V, RandomState> { .. } |
This is not about caveats in impls, please keep that separate. |
We already established that your proposal cannot work @SoniEx2 You'd require some Afaik, there is no actual danger here so long as you use hashers like SipHasher24 that provide cryptographic security assurances for their key material, so afaik I'd argue running any adversary controlled data through some weak hasher like fnv inherently creates a DoS vector. If you do that while arguing otherwise based on an ephemeral hasher, then you could create exactly the same problem by cloning a full hashmap, like by using Afaik, there is nothing special about empty hash maps here and no nice way to exploit them even if so. |
"We" already established you can't use Option, because there's no function pointer to make the BuildHasher. I said you can use an |
You're now proposing what I called a "buildbuildhasher" scheme, but.. We've no strong reason to believe this improves security over using the current default of The right answer is to warn anyone using the |
RandomState doesn't guarantee DoS-proofing. And, in fact, RandomState isn't affected by low-entropy settings! See here. So, yes, it would improve things. At the very least, it would be no worse than what we currently have. |
Very nice! :) I hadn't know about these attacks outside the siphash threat model, so scheme like you propose actually do help, but only with empty hash maps. I'm afraid lints against the |
Well, what would it take to solve that? We could re-hash everything in the hashmap on clone, right? If But here's an interesting observation: There's no requirement from
So arguably it's a bug today that we're not re-hashing everything on |
Yes exactly @scottmcm but.. Right now I still like your original idea about using lints, but we should lint against the I also like lints here because hidden impls #2529 make me nervous. I'd hope lints giving caveats about impls should help prevent over usage of hidden impls. |
That seems implausible to me. |
Again this isn't about lints please leave those elsewhere. |
@scottmcm Yes, |
As an aside, |
hmm, is it a logic error for a key's Clone to change its hash? |
From rereading the official documentation for I think there might even be legitimate use cases for that. Say the key type is some kind of In order for it to be a logic error, you'd need a special rule just for types implementing both |
okay, so it's (IMO) reasonable to make BuildHasher also change on Clone. it'd be a better solution to this security issue and doesn't make HashMap any bigger. |
HashMap implements Clone, but cloning HashMap::default() should NOT produce the same hasher state. Instead, there should be a distinction between a default HashMap and an empty/cleared HashMap. Both are empty, but the former also has the hasher set to
Err(Default::default)
.This is important because one could easily do:
and accidentally introduce a DoS exploit to their code. (this would be partially solved by resize_default, but I don't think it's a proper solution.)
The text was updated successfully, but these errors were encountered: