Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(1048)

Delta Between Two Patch Sets: Lib/test/test_hash.py

Issue 13703: Hash collision security issue
Left Patch Set: Created 1 year, 3 months ago
Right Patch Set: Created 1 year, 3 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « Lib/test/test_cmd_line.py ('k') | Lib/test/test_os.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # test the invariant that 1 # test the invariant that
2 # iff a==b then hash(a)==hash(b) 2 # iff a==b then hash(a)==hash(b)
3 # 3 #
4 # Also test that hash implementations are inherited as expected 4 # Also test that hash implementations are inherited as expected
5 5
6 import datetime
6 import os 7 import os
7 import struct 8 import struct
8 import subprocess
9 import sys
10 import unittest 9 import unittest
11 from test import test_support 10 from test import support
11 from test.script_helper import assert_python_ok
12 from collections import Hashable 12 from collections import Hashable
13 13
14 IS_64BIT = (struct.calcsize('l') == 8) 14 IS_64BIT = (struct.calcsize('l') == 8)
15
15 16
16 class HashEqualityTestCase(unittest.TestCase): 17 class HashEqualityTestCase(unittest.TestCase):
17 18
18 def same_hash(self, *objlist): 19 def same_hash(self, *objlist):
19 # Hash each object given and fail if 20 # Hash each object given and fail if
20 # the hash values are not all the same. 21 # the hash values are not all the same.
21 hashed = map(hash, objlist) 22 hashed = list(map(hash, objlist))
22 for h in hashed[1:]: 23 for h in hashed[1:]:
23 if h != hashed[0]: 24 if h != hashed[0]:
24 self.fail("hashed values differ: %r" % (objlist,)) 25 self.fail("hashed values differ: %r" % (objlist,))
25 26
26 def test_numeric_literals(self): 27 def test_numeric_literals(self):
27 self.same_hash(1, 1L, 1.0, 1.0+0.0j) 28 self.same_hash(1, 1, 1.0, 1.0+0.0j)
28 self.same_hash(0, 0L, 0.0, 0.0+0.0j) 29 self.same_hash(0, 0.0, 0.0+0.0j)
29 self.same_hash(-1, -1L, -1.0, -1.0+0.0j) 30 self.same_hash(-1, -1.0, -1.0+0.0j)
30 self.same_hash(-2, -2L, -2.0, -2.0+0.0j) 31 self.same_hash(-2, -2.0, -2.0+0.0j)
31 32
32 def test_coerced_integers(self): 33 def test_coerced_integers(self):
33 self.same_hash(int(1), long(1), float(1), complex(1), 34 self.same_hash(int(1), int(1), float(1), complex(1),
34 int('1'), float('1.0')) 35 int('1'), float('1.0'))
35 self.same_hash(int(-2**31), long(-2**31), float(-2**31)) 36 self.same_hash(int(-2**31), float(-2**31))
36 self.same_hash(int(1-2**31), long(1-2**31), float(1-2**31)) 37 self.same_hash(int(1-2**31), float(1-2**31))
37 self.same_hash(int(2**31-1), long(2**31-1), float(2**31-1)) 38 self.same_hash(int(2**31-1), float(2**31-1))
38 # for 64-bit platforms 39 # for 64-bit platforms
39 self.same_hash(int(2**31), long(2**31), float(2**31)) 40 self.same_hash(int(2**31), float(2**31))
40 self.same_hash(int(-2**63), long(-2**63), float(-2**63)) 41 self.same_hash(int(-2**63), float(-2**63))
41 self.same_hash(int(1-2**63), long(1-2**63)) 42 self.same_hash(int(2**63), float(2**63))
42 self.same_hash(int(2**63-1), long(2**63-1))
43 43
44 def test_coerced_floats(self): 44 def test_coerced_floats(self):
45 self.same_hash(long(1.23e300), float(1.23e300)) 45 self.same_hash(int(1.23e300), float(1.23e300))
46 self.same_hash(float(0.5), complex(0.5, 0.0)) 46 self.same_hash(float(0.5), complex(0.5, 0.0))
47 47
48 48
49 _default_hash = object.__hash__ 49 _default_hash = object.__hash__
50 class DefaultHash(object): pass 50 class DefaultHash(object): pass
51 51
52 _FIXED_HASH_VALUE = 42 52 _FIXED_HASH_VALUE = 42
53 class FixedHash(object): 53 class FixedHash(object):
54 def __hash__(self): 54 def __hash__(self):
55 return _FIXED_HASH_VALUE 55 return _FIXED_HASH_VALUE
56 56
57 class OnlyEquality(object): 57 class OnlyEquality(object):
58 def __eq__(self, other): 58 def __eq__(self, other):
59 return self is other 59 return self is other
60 # Trick to suppress Py3k warning in 2.x
61 __hash__ = None
62 del OnlyEquality.__hash__
63 60
64 class OnlyInequality(object): 61 class OnlyInequality(object):
65 def __ne__(self, other): 62 def __ne__(self, other):
66 return self is not other 63 return self is not other
67 64
68 class OnlyCmp(object):
69 def __cmp__(self, other):
70 return cmp(id(self), id(other))
71 # Trick to suppress Py3k warning in 2.x
72 __hash__ = None
73 del OnlyCmp.__hash__
74
75 class InheritedHashWithEquality(FixedHash, OnlyEquality): pass 65 class InheritedHashWithEquality(FixedHash, OnlyEquality): pass
76 class InheritedHashWithInequality(FixedHash, OnlyInequality): pass 66 class InheritedHashWithInequality(FixedHash, OnlyInequality): pass
77 class InheritedHashWithCmp(FixedHash, OnlyCmp): pass
78 67
79 class NoHash(object): 68 class NoHash(object):
80 __hash__ = None 69 __hash__ = None
81 70
82 class HashInheritanceTestCase(unittest.TestCase): 71 class HashInheritanceTestCase(unittest.TestCase):
83 default_expected = [object(), 72 default_expected = [object(),
84 DefaultHash(), 73 DefaultHash(),
85 OnlyEquality(),
86 OnlyInequality(), 74 OnlyInequality(),
87 OnlyCmp(),
88 ] 75 ]
89 fixed_expected = [FixedHash(), 76 fixed_expected = [FixedHash(),
90 InheritedHashWithEquality(), 77 InheritedHashWithEquality(),
91 InheritedHashWithInequality(), 78 InheritedHashWithInequality(),
92 InheritedHashWithCmp(),
93 ] 79 ]
94 error_expected = [NoHash()] 80 error_expected = [NoHash(),
81 OnlyEquality(),
82 ]
95 83
96 def test_default_hash(self): 84 def test_default_hash(self):
97 for obj in self.default_expected: 85 for obj in self.default_expected:
98 self.assertEqual(hash(obj), _default_hash(obj)) 86 self.assertEqual(hash(obj), _default_hash(obj))
99 87
100 def test_fixed_hash(self): 88 def test_fixed_hash(self):
101 for obj in self.fixed_expected: 89 for obj in self.fixed_expected:
102 self.assertEqual(hash(obj), _FIXED_HASH_VALUE) 90 self.assertEqual(hash(obj), _FIXED_HASH_VALUE)
103 91
104 def test_error_hash(self): 92 def test_error_hash(self):
105 for obj in self.error_expected: 93 for obj in self.error_expected:
106 self.assertRaises(TypeError, hash, obj) 94 self.assertRaises(TypeError, hash, obj)
107 95
108 def test_hashable(self): 96 def test_hashable(self):
109 objects = (self.default_expected + 97 objects = (self.default_expected +
110 self.fixed_expected) 98 self.fixed_expected)
111 for obj in objects: 99 for obj in objects:
112 self.assert_(isinstance(obj, Hashable), repr(obj)) 100 self.assertTrue(isinstance(obj, Hashable), repr(obj))
113 101
114 def test_not_hashable(self): 102 def test_not_hashable(self):
115 for obj in self.error_expected: 103 for obj in self.error_expected:
116 self.assertFalse(isinstance(obj, Hashable), repr(obj)) 104 self.assertFalse(isinstance(obj, Hashable), repr(obj))
117 105
118 106
119 # Issue #4701: Check that some builtin types are correctly hashable 107 # Issue #4701: Check that some builtin types are correctly hashable
120 # (This test only used to fail in Python 3.0, but has been included
121 # in 2.x along with the lazy call to PyType_Ready in PyObject_Hash)
122 class DefaultIterSeq(object): 108 class DefaultIterSeq(object):
123 seq = range(10) 109 seq = range(10)
124 def __len__(self): 110 def __len__(self):
125 return len(self.seq) 111 return len(self.seq)
126 def __getitem__(self, index): 112 def __getitem__(self, index):
127 return self.seq[index] 113 return self.seq[index]
128 114
129 class HashBuiltinsTestCase(unittest.TestCase): 115 class HashBuiltinsTestCase(unittest.TestCase):
130 hashes_to_check = [xrange(10), 116 hashes_to_check = [range(10),
131 enumerate(xrange(10)), 117 enumerate(range(10)),
132 iter(DefaultIterSeq()), 118 iter(DefaultIterSeq()),
133 iter(lambda: 0, 0), 119 iter(lambda: 0, 0),
134 ] 120 ]
135 121
136 def test_hashes(self): 122 def test_hashes(self):
137 _default_hash = object.__hash__ 123 _default_hash = object.__hash__
138 for obj in self.hashes_to_check: 124 for obj in self.hashes_to_check:
139 self.assertEqual(hash(obj), _default_hash(obj)) 125 self.assertEqual(hash(obj), _default_hash(obj))
140 126
141 class HashRandomizationTests(unittest.TestCase): 127 class HashRandomizationTests(unittest.TestCase):
142 128
143 # Each subclass should define a field "repr_", containing the repr() of 129 # Each subclass should define a field "repr_", containing the repr() of
144 # an object to be tested 130 # an object to be tested
145 131
146 def get_hash_command(self, repr_): 132 def get_hash_command(self, repr_):
147 return 'print(hash(%s))' % repr_ 133 return 'print(hash(%s))' % repr_
148 134
149 def get_hash(self, repr_, randomization=None, seed=None): 135 def get_hash(self, repr_, seed=None):
150 env = os.environ.copy() 136 env = os.environ.copy()
151 if randomization is not None: 137 env['__cleanenv'] = True # signal to assert_python not to do a copy
152 env['PYTHONHASHRANDOMIZATION'] = str(randomization) 138 # of os.environ on its own
153 if seed is not None: 139 if seed is not None:
154 env['PYTHONHASHSEED'] = str(seed) 140 env['PYTHONHASHSEED'] = str(seed)
155 cmd_line = [sys.executable, 141 else:
156 '-c', self.get_hash_command(repr_)] 142 env.pop('PYTHONHASHSEED', None)
157 p = subprocess.Popen(cmd_line, stdin=subprocess.PIPE, 143 out = assert_python_ok(
158 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 144 '-c', self.get_hash_command(repr_),
159 env=env) 145 **env)
160 out, err = p.communicate() 146 stdout = out[1].strip()
161 out = test_support.strip_python_stderr(out) 147 return int(stdout)
162 return int(out.strip())
163 148
164 def test_randomized_hash(self): 149 def test_randomized_hash(self):
165 # two runs should return different hashes 150 # two runs should return different hashes
166 run1 = self.get_hash(self.repr_, randomization=1) 151 run1 = self.get_hash(self.repr_, seed='random')
167 run2 = self.get_hash(self.repr_, randomization=1) 152 run2 = self.get_hash(self.repr_, seed='random')
168 self.assertNotEqual(run1, run2) 153 self.assertNotEqual(run1, run2)
169 154
170 class StringlikeHashRandomizationTests(HashRandomizationTests): 155 class StringlikeHashRandomizationTests(HashRandomizationTests):
171 def test_null_hash(self): 156 def test_null_hash(self):
172 # PYTHONHASHSEED=0 disables the randomized hash 157 # PYTHONHASHSEED=0 disables the randomized hash
173 if IS_64BIT: 158 if IS_64BIT:
174 known_hash_of_obj = 1453079729188098211 159 known_hash_of_obj = 1453079729188098211
175 else: 160 else:
176 known_hash_of_obj = -1600925533 161 known_hash_of_obj = -1600925533
177 162
178 # Randomization is disabled by default: 163 # Randomization is disabled by default:
179 self.assertEqual(self.get_hash(self.repr_), known_hash_of_obj) 164 self.assertEqual(self.get_hash(self.repr_), known_hash_of_obj)
180 165
181 # If enabled, it can still be disabled by setting the seed to 0: 166 # It can also be disabled by setting the seed to 0:
182 self.assertEqual(self.get_hash(self.repr_, randomization=1, seed=0), 167 self.assertEqual(self.get_hash(self.repr_, seed=0), known_hash_of_obj)
183 known_hash_of_obj)
184 168
185 def test_fixed_hash(self): 169 def test_fixed_hash(self):
186 # test a fixed seed for the randomized hash 170 # test a fixed seed for the randomized hash
187 # Note that all types share the same values: 171 # Note that all types share the same values:
188 if IS_64BIT: 172 if IS_64BIT:
189 h = -4410911502303878509 173 h = -4410911502303878509
190 else: 174 else:
191 h = -206076799 175 h = -206076799
192 self.assertEqual(self.get_hash(self.repr_, randomization=1, seed=42), 176 self.assertEqual(self.get_hash(self.repr_, seed=42), h)
193 h)
194 177
195 class StrHashRandomizationTests(StringlikeHashRandomizationTests): 178 class StrHashRandomizationTests(StringlikeHashRandomizationTests):
196 repr_ = repr('abc') 179 repr_ = repr('abc')
197 180
198 def test_empty_string(self): 181 def test_empty_string(self):
199 self.assertEqual(hash(""), 0) 182 self.assertEqual(hash(""), 0)
200 183
201 class UnicodeHashRandomizationTests(StringlikeHashRandomizationTests): 184 class BytesHashRandomizationTests(StringlikeHashRandomizationTests):
202 repr_ = repr(u'abc') 185 repr_ = repr(b'abc')
203 186
204 def test_empty_string(self): 187 def test_empty_string(self):
205 self.assertEqual(hash(u""), 0) 188 self.assertEqual(hash(b""), 0)
206 189
207 class BufferHashRandomizationTests(StringlikeHashRandomizationTests): 190 class DatetimeTests(HashRandomizationTests):
208 repr_ = 'buffer("abc")' 191 def get_hash_command(self, repr_):
209 192 return 'import datetime; print(hash(%s))' % repr_
210 def test_empty_string(self): 193
211 self.assertEqual(hash(buffer("")), 0) 194 class DatetimeDateTests(DatetimeTests):
195 repr_ = repr(datetime.date(1066, 10, 14))
196
197 class DatetimeDatetimeTests(DatetimeTests):
198 repr_ = repr(datetime.datetime(1, 2, 3, 4, 5, 6, 7))
199
200 class DatetimeTimeTests(DatetimeTests):
201 repr_ = repr(datetime.time(0))
202
212 203
213 def test_main(): 204 def test_main():
214 test_support.run_unittest(HashEqualityTestCase, 205 support.run_unittest(HashEqualityTestCase,
215 HashInheritanceTestCase, 206 HashInheritanceTestCase,
216 HashBuiltinsTestCase, 207 HashBuiltinsTestCase,
217 StrHashRandomizationTests, 208 StrHashRandomizationTests,
218 UnicodeHashRandomizationTests, 209 BytesHashRandomizationTests,
219 BufferHashRandomizationTests) 210 DatetimeDateTests,
211 DatetimeDatetimeTests,
212 DatetimeTimeTests)
220 213
221 214
222 if __name__ == "__main__": 215 if __name__ == "__main__":
223 test_main() 216 test_main()
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld cbc36f91f3f7