blob: 39ea806b6d366905c75671b43ef801bec7b9954e [file] [log] [blame]
Barry Warsaw04f357c2002-07-23 19:04:11 +00001from test.test_support import verbose, TESTFN
Tim Peters95bf9392001-05-10 08:32:44 +00002import random
Tim Peters23cf6be2001-06-02 08:02:56 +00003import os
Tim Peters95bf9392001-05-10 08:32:44 +00004
5# From SF bug #422121: Insecurities in dict comparison.
6
Tim Peters8c3e91e2001-05-10 19:40:30 +00007# Safety of code doing comparisons has been an historical Python weak spot.
8# The problem is that comparison of structures written in C *naturally*
Tim Peters95bf9392001-05-10 08:32:44 +00009# wants to hold on to things like the size of the container, or "the
10# biggest" containee so far, across a traversal of the container; but
11# code to do containee comparisons can call back into Python and mutate
12# the container in arbitrary ways while the C loop is in midstream. If the
13# C code isn't extremely paranoid about digging things out of memory on
14# each trip, and artificially boosting refcounts for the duration, anything
15# from infinite loops to OS crashes can result (yes, I use Windows <wink>).
16#
17# The other problem is that code designed to provoke a weakness is usually
18# white-box code, and so catches only the particular vulnerabilities the
19# author knew to protect against. For example, Python's list.sort() code
20# went thru many iterations as one "new" vulnerability after another was
21# discovered.
22#
23# So the dict comparison test here uses a black-box approach instead,
24# generating dicts of various sizes at random, and performing random
25# mutations on them at random times. This proved very effective,
26# triggering at least six distinct failure modes the first 20 times I
27# ran it. Indeed, at the start, the driver never got beyond 6 iterations
28# before the test died.
29
Guido van Rossum47b9ff62006-08-24 00:41:19 +000030# The dicts are global to make it easy to mutate them from within functions.
Tim Peters95bf9392001-05-10 08:32:44 +000031dict1 = {}
32dict2 = {}
33
34# The current set of keys in dict1 and dict2. These are materialized as
35# lists to make it easy to pick a dict key at random.
36dict1keys = []
37dict2keys = []
38
Neal Norwitz2fcf2062005-11-24 23:28:37 +000039# Global flag telling maybe_mutate() whether to *consider* mutating.
Tim Peters95bf9392001-05-10 08:32:44 +000040mutate = 0
41
42# If global mutate is true, consider mutating a dict. May or may not
43# mutate a dict even if mutate is true. If it does decide to mutate a
44# dict, it picks one of {dict1, dict2} at random, and deletes a random
Tim Peters4c02fec2001-05-10 20:18:30 +000045# entry from it; or, more rarely, adds a random element.
Tim Peters95bf9392001-05-10 08:32:44 +000046
47def maybe_mutate():
Tim Peters4c02fec2001-05-10 20:18:30 +000048 global mutate
Tim Peters95bf9392001-05-10 08:32:44 +000049 if not mutate:
50 return
51 if random.random() < 0.5:
52 return
Tim Peters4c02fec2001-05-10 20:18:30 +000053
Tim Peters95bf9392001-05-10 08:32:44 +000054 if random.random() < 0.5:
55 target, keys = dict1, dict1keys
56 else:
57 target, keys = dict2, dict2keys
Tim Peters4c02fec2001-05-10 20:18:30 +000058
59 if random.random() < 0.2:
60 # Insert a new key.
61 mutate = 0 # disable mutation until key inserted
62 while 1:
63 newkey = Horrid(random.randrange(100))
64 if newkey not in target:
65 break
66 target[newkey] = Horrid(random.randrange(100))
67 keys.append(newkey)
68 mutate = 1
69
70 elif keys:
71 # Delete a key at random.
Armin Rigo57179fe2005-05-15 13:29:26 +000072 mutate = 0 # disable mutation until key deleted
Tim Peters95bf9392001-05-10 08:32:44 +000073 i = random.randrange(len(keys))
74 key = keys[i]
75 del target[key]
Tim Peters95bf9392001-05-10 08:32:44 +000076 del keys[i]
Armin Rigo57179fe2005-05-15 13:29:26 +000077 mutate = 1
Tim Peters95bf9392001-05-10 08:32:44 +000078
79# A horrid class that triggers random mutations of dict1 and dict2 when
80# instances are compared.
81
82class Horrid:
83 def __init__(self, i):
84 # Comparison outcomes are determined by the value of i.
85 self.i = i
86
87 # An artificial hashcode is selected at random so that we don't
Tim Peters8c3e91e2001-05-10 19:40:30 +000088 # have any systematic relationship between comparison outcomes
Tim Peters95bf9392001-05-10 08:32:44 +000089 # (based on self.i and other.i) and relative position within the
Tim Peters8c3e91e2001-05-10 19:40:30 +000090 # hash vector (based on hashcode).
Tim Peters95bf9392001-05-10 08:32:44 +000091 self.hashcode = random.randrange(1000000000)
92
93 def __hash__(self):
94 return self.hashcode
95
Guido van Rossum47b9ff62006-08-24 00:41:19 +000096 def __eq__(self, other):
Tim Peters95bf9392001-05-10 08:32:44 +000097 maybe_mutate() # The point of the test.
Guido van Rossum47b9ff62006-08-24 00:41:19 +000098 return self.i == other.i
Tim Peters95bf9392001-05-10 08:32:44 +000099
100 def __repr__(self):
101 return "Horrid(%d)" % self.i
102
103# Fill dict d with numentries (Horrid(i), Horrid(j)) key-value pairs,
104# where i and j are selected at random from the candidates list.
105# Return d.keys() after filling.
106
107def fill_dict(d, candidates, numentries):
108 d.clear()
109 for i in xrange(numentries):
110 d[Horrid(random.choice(candidates))] = \
111 Horrid(random.choice(candidates))
112 return d.keys()
113
114# Test one pair of randomly generated dicts, each with n entries.
115# Note that dict comparison is trivial if they don't have the same number
116# of entires (then the "shorter" dict is instantly considered to be the
117# smaller one, without even looking at the entries).
118
119def test_one(n):
120 global mutate, dict1, dict2, dict1keys, dict2keys
121
122 # Fill the dicts without mutating them.
123 mutate = 0
124 dict1keys = fill_dict(dict1, range(n), n)
125 dict2keys = fill_dict(dict2, range(n), n)
126
127 # Enable mutation, then compare the dicts so long as they have the
128 # same size.
129 mutate = 1
130 if verbose:
131 print "trying w/ lengths", len(dict1), len(dict2),
132 while dict1 and len(dict1) == len(dict2):
133 if verbose:
134 print ".",
Guido van Rossum47b9ff62006-08-24 00:41:19 +0000135 c = dict1 == dict2
136 XXX # Can't figure out how to make this work
Tim Peters95bf9392001-05-10 08:32:44 +0000137 if verbose:
138 print
139
140# Run test_one n times. At the start (before the bugs were fixed), 20
141# consecutive runs of this test each blew up on or before the sixth time
142# test_one was run. So n doesn't have to be large to get an interesting
143# test.
144# OTOH, calling with large n is also interesting, to ensure that the fixed
145# code doesn't hold on to refcounts *too* long (in which case memory would
146# leak).
147
148def test(n):
149 for i in xrange(n):
150 test_one(random.randrange(1, 100))
151
152# See last comment block for clues about good values for n.
153test(100)
Tim Peters23cf6be2001-06-02 08:02:56 +0000154
155##########################################################################
Tim Petersfa517b22001-06-02 08:18:58 +0000156# Another segfault bug, distilled by Michael Hudson from a c.l.py post.
Tim Peters23cf6be2001-06-02 08:02:56 +0000157
158class Child:
159 def __init__(self, parent):
160 self.__dict__['parent'] = parent
161 def __getattr__(self, attr):
162 self.parent.a = 1
163 self.parent.b = 1
164 self.parent.c = 1
165 self.parent.d = 1
166 self.parent.e = 1
167 self.parent.f = 1
168 self.parent.g = 1
169 self.parent.h = 1
170 self.parent.i = 1
171 return getattr(self.parent, attr)
172
173class Parent:
174 def __init__(self):
175 self.a = Child(self)
176
177# Hard to say what this will print! May vary from time to time. But
178# we're specifically trying to test the tp_print slot here, and this is
179# the clearest way to do it. We print the result to a temp file so that
180# the expected-output file doesn't need to change.
181
182f = open(TESTFN, "w")
183print >> f, Parent().__dict__
184f.close()
185os.unlink(TESTFN)
186
187##########################################################################
188# And another core-dumper from Michael Hudson.
189
190dict = {}
191
192# Force dict to malloc its table.
193for i in range(1, 10):
194 dict[i] = i
195
196f = open(TESTFN, "w")
197
198class Machiavelli:
199 def __repr__(self):
200 dict.clear()
201
202 # Michael sez: "doesn't crash without this. don't know why."
203 # Tim sez: "luck of the draw; crashes with or without for me."
204 print >> f
205
206 return `"machiavelli"`
207
208 def __hash__(self):
209 return 0
210
211dict[Machiavelli()] = Machiavelli()
212
213print >> f, str(dict)
214f.close()
215os.unlink(TESTFN)
216del f, dict
Tim Peters453163d2001-06-03 04:54:32 +0000217
218
219##########################################################################
220# And another core-dumper from Michael Hudson.
221
222dict = {}
223
224# let's force dict to malloc its table
225for i in range(1, 10):
226 dict[i] = i
227
228class Machiavelli2:
229 def __eq__(self, other):
230 dict.clear()
231 return 1
232
233 def __hash__(self):
234 return 0
235
236dict[Machiavelli2()] = Machiavelli2()
237
238try:
239 dict[Machiavelli2()]
240except KeyError:
241 pass
242
243del dict
244
245##########################################################################
246# And another core-dumper from Michael Hudson.
247
248dict = {}
249
250# let's force dict to malloc its table
251for i in range(1, 10):
252 dict[i] = i
253
254class Machiavelli3:
255 def __init__(self, id):
256 self.id = id
257
258 def __eq__(self, other):
259 if self.id == other.id:
260 dict.clear()
261 return 1
262 else:
263 return 0
264
265 def __repr__(self):
266 return "%s(%s)"%(self.__class__.__name__, self.id)
267
268 def __hash__(self):
269 return 0
270
271dict[Machiavelli3(1)] = Machiavelli3(0)
272dict[Machiavelli3(2)] = Machiavelli3(0)
273
274f = open(TESTFN, "w")
275try:
276 try:
277 print >> f, dict[Machiavelli3(2)]
278 except KeyError:
279 pass
280finally:
281 f.close()
282 os.unlink(TESTFN)
283
284del dict
Neal Norwitz2fcf2062005-11-24 23:28:37 +0000285del dict1, dict2, dict1keys, dict2keys