1 from test
.test_support
import verbose
, TESTFN
5 # From SF bug #422121: Insecurities in dict comparison.
7 # Safety of code doing comparisons has been an historical Python weak spot.
8 # The problem is that comparison of structures written in C *naturally*
9 # 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>).
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
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.
30 # The dicts are global to make it easy to mutate tham from within functions.
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.
39 # Global flag telling maybe_mutate() whether to *consider* mutating.
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
45 # entry from it; or, more rarely, adds a random element.
51 if random
.random() < 0.5:
54 if random
.random() < 0.5:
55 target
, keys
= dict1
, dict1keys
57 target
, keys
= dict2
, dict2keys
59 if random
.random() < 0.2:
61 mutate
= 0 # disable mutation until key inserted
63 newkey
= Horrid(random
.randrange(100))
64 if newkey
not in target
:
66 target
[newkey
] = Horrid(random
.randrange(100))
71 # Delete a key at random.
72 mutate
= 0 # disable mutation until key deleted
73 i
= random
.randrange(len(keys
))
79 # A horrid class that triggers random mutations of dict1 and dict2 when
80 # instances are compared.
83 def __init__(self
, i
):
84 # Comparison outcomes are determined by the value of i.
87 # An artificial hashcode is selected at random so that we don't
88 # have any systematic relationship between comparison outcomes
89 # (based on self.i and other.i) and relative position within the
90 # hash vector (based on hashcode).
91 self
.hashcode
= random
.randrange(1000000000)
97 def __cmp__(self
, other
):
98 maybe_mutate() # The point of the test.
99 return cmp(self
.i
, other
.i
)
101 def __eq__(self
, other
):
102 maybe_mutate() # The point of the test.
103 return self
.i
== other
.i
106 return "Horrid(%d)" % self
.i
108 # Fill dict d with numentries (Horrid(i), Horrid(j)) key-value pairs,
109 # where i and j are selected at random from the candidates list.
110 # Return d.keys() after filling.
112 def fill_dict(d
, candidates
, numentries
):
114 for i
in xrange(numentries
):
115 d
[Horrid(random
.choice(candidates
))] = \
116 Horrid(random
.choice(candidates
))
119 # Test one pair of randomly generated dicts, each with n entries.
120 # Note that dict comparison is trivial if they don't have the same number
121 # of entires (then the "shorter" dict is instantly considered to be the
122 # smaller one, without even looking at the entries).
125 global mutate
, dict1
, dict2
, dict1keys
, dict2keys
127 # Fill the dicts without mutating them.
129 dict1keys
= fill_dict(dict1
, range(n
), n
)
130 dict2keys
= fill_dict(dict2
, range(n
), n
)
132 # Enable mutation, then compare the dicts so long as they have the
136 print "trying w/ lengths", len(dict1
), len(dict2
),
137 while dict1
and len(dict1
) == len(dict2
):
140 if random
.random() < 0.5:
141 c
= cmp(dict1
, dict2
)
147 # Run test_one n times. At the start (before the bugs were fixed), 20
148 # consecutive runs of this test each blew up on or before the sixth time
149 # test_one was run. So n doesn't have to be large to get an interesting
151 # OTOH, calling with large n is also interesting, to ensure that the fixed
152 # code doesn't hold on to refcounts *too* long (in which case memory would
157 test_one(random
.randrange(1, 100))
159 # See last comment block for clues about good values for n.
162 ##########################################################################
163 # Another segfault bug, distilled by Michael Hudson from a c.l.py post.
166 def __init__(self
, parent
):
167 self
.__dict
__['parent'] = parent
168 def __getattr__(self
, attr
):
178 return getattr(self
.parent
, attr
)
184 # Hard to say what this will print! May vary from time to time. But
185 # we're specifically trying to test the tp_print slot here, and this is
186 # the clearest way to do it. We print the result to a temp file so that
187 # the expected-output file doesn't need to change.
189 f
= open(TESTFN
, "w")
190 print >> f
, Parent().__dict
__
194 ##########################################################################
195 # And another core-dumper from Michael Hudson.
199 # Force dict to malloc its table.
200 for i
in range(1, 10):
203 f
= open(TESTFN
, "w")
209 # Michael sez: "doesn't crash without this. don't know why."
210 # Tim sez: "luck of the draw; crashes with or without for me."
213 return repr("machiavelli")
218 dict[Machiavelli()] = Machiavelli()
220 print >> f
, str(dict)
226 ##########################################################################
227 # And another core-dumper from Michael Hudson.
231 # let's force dict to malloc its table
232 for i
in range(1, 10):
236 def __eq__(self
, other
):
243 dict[Machiavelli2()] = Machiavelli2()
252 ##########################################################################
253 # And another core-dumper from Michael Hudson.
257 # let's force dict to malloc its table
258 for i
in range(1, 10):
262 def __init__(self
, id):
265 def __eq__(self
, other
):
266 if self
.id == other
.id:
273 return "%s(%s)"%(self
.__class
__.__name
__, self
.id)
278 dict[Machiavelli3(1)] = Machiavelli3(0)
279 dict[Machiavelli3(2)] = Machiavelli3(0)
281 f
= open(TESTFN
, "w")
284 print >> f
, dict[Machiavelli3(2)]
292 del dict1
, dict2
, dict1keys
, dict2keys