test_mutants.py 8.3 KB

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