I was recently surprised to hear about the use of anti-hash-table in codeforces:
https://codeforces.com/blog/entry/101817
For some context, “hacking” in codeforces is a specific part of the competition. The idea is to “hack” other participants solutions by providing valid input data that would cause their code to fail on a given problem. In this particular instance, “hackers” would provide some input data that maximize the amount of hash collisions for a specific implementation of hash tables, causing the participant’s code to run too slowly for the problem constrains, eventually failing with a time limit exceeded status.
It turns out that Python in particular is vulnerable to this specific “hack” when the keys of a dictionary are integers. The surprising part is that this attack does not rely on the integers having the same hash (typically, the size of the input data is limited in a way such that it does not exceed the value ofsys.hash_info.modulus). Instead, it relies on abusing the sequence of cells that the lookup algorithm will use to check whether a given item is already part of the hash table or not.
This can be easily reproduced with the following code:
def corrupt_keys(n):
# Find the power of two lower than n
bit_length = n.bit_length() - 1
size = 1 << bit_length
# Fill the dictionary with at least 33% and at most 66% of n
fill = int(size * 0.65)
# Insert a key in slot 1
yield size + 1
# Insert a key in each subsequent index
x = 1
mask = size - 1
for _ in range(fill):
x = (x * 5 + 1) & mask
yield x
# Each 1 will have to go through `fill + 1` steps to find its correct spot
# In total, this produces n²/2 lookup on average
for _ in range(n - fill - 1):
yield 1
def test():
n = 100000
keys = list(corrupt_keys(n))
assert len(keys) == n
# Takes 5 seconds
{k: 1 for k in keys}
Note how inserting 100_000 integers in a dictionary takes about 5 seconds, even though all those keys are in the [1, 65537]range.
For reference, there are plenty of useful information in the comment that explains the logic behind the use of the x * 5 + 1 recurrence in the cpython source code:
What surprised me is that I haven’t been able to find any discussion about this “attack”, and whether something should be done about it (although I most probably missed some). Maybe an actual denial-of-service attack based on this is not realistic, but I would have at least expected some comments mentioning it (especially since a protection against dos attacks already exists for bytes and strings).
Can anyone provide me with more resources regarding this issue? Thank you ![]()