diff options
| author | Lieuwe <lieuwemo@gmail.com> | 2011-03-18 16:04:51 (GMT) |
|---|---|---|
| committer | Lieuwe <lieuwemo@gmail.com> | 2011-03-18 16:04:51 (GMT) |
| commit | 9c39875ef09d439ef51716dba091188f72977f5e (patch) | |
| tree | fb0fd27a1bcd3e54a1913852c23aa137ee5eb30b /src/python/stdlib/test/crashers | |
| parent | bc8af4e2101ac93bdc51b59ee64fa43ecb2442f3 (diff) | |
| download | powder-9c39875ef09d439ef51716dba091188f72977f5e.zip powder-9c39875ef09d439ef51716dba091188f72977f5e.tar.gz | |
...
Diffstat (limited to 'src/python/stdlib/test/crashers')
| -rw-r--r-- | src/python/stdlib/test/crashers/README | 20 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/bogus_code_obj.py | 19 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/borrowed_ref_1.py | 29 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/borrowed_ref_2.py | 38 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/compiler_recursion.py | 5 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/gc_has_finalizer.py | 36 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/gc_inspection.py | 32 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/infinite_loop_re.py | 16 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/loosing_mro_ref.py | 35 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/mutation_inside_cyclegc.py | 31 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/nasty_eq_vs_dict.py | 47 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/recursion_limit_too_high.py | 16 | ||||
| -rw-r--r-- | src/python/stdlib/test/crashers/recursive_call.py | 15 |
13 files changed, 339 insertions, 0 deletions
diff --git a/src/python/stdlib/test/crashers/README b/src/python/stdlib/test/crashers/README new file mode 100644 index 0000000..070c3f1 --- /dev/null +++ b/src/python/stdlib/test/crashers/README @@ -0,0 +1,20 @@ +This directory only contains tests for outstanding bugs that cause +the interpreter to segfault. Ideally this directory should always +be empty. Sometimes it may not be easy to fix the underlying cause. + +Each test should fail when run from the command line: + + ./python Lib/test/crashers/weakref_in_del.py + +Each test should have a link to the bug report: + + # http://python.org/sf/BUG# + +Put as much info into a docstring or comments to help determine +the cause of the failure. Particularly note if the cause is +system or environment dependent and what the variables are. + +Once the crash is fixed, the test case should be moved into an appropriate +test (even if it was originally from the test suite). This ensures the +regression doesn't happen again. And if it does, it should be easier +to track down. diff --git a/src/python/stdlib/test/crashers/bogus_code_obj.py b/src/python/stdlib/test/crashers/bogus_code_obj.py new file mode 100644 index 0000000..613ae51 --- /dev/null +++ b/src/python/stdlib/test/crashers/bogus_code_obj.py @@ -0,0 +1,19 @@ +""" +Broken bytecode objects can easily crash the interpreter. + +This is not going to be fixed. It is generally agreed that there is no +point in writing a bytecode verifier and putting it in CPython just for +this. Moreover, a verifier is bound to accept only a subset of all safe +bytecodes, so it could lead to unnecessary breakage. + +For security purposes, "restricted" interpreters are not going to let +the user build or load random bytecodes anyway. Otherwise, this is a +"won't fix" case. + +""" + +import types + +co = types.CodeType(0, 0, 0, 0, '\x04\x71\x00\x00', (), + (), (), '', '', 1, '') +exec co diff --git a/src/python/stdlib/test/crashers/borrowed_ref_1.py b/src/python/stdlib/test/crashers/borrowed_ref_1.py new file mode 100644 index 0000000..d16ede2 --- /dev/null +++ b/src/python/stdlib/test/crashers/borrowed_ref_1.py @@ -0,0 +1,29 @@ +""" +_PyType_Lookup() returns a borrowed reference. +This attacks the call in dictobject.c. +""" + +class A(object): + pass + +class B(object): + def __del__(self): + print 'hi' + del D.__missing__ + +class D(dict): + class __missing__: + def __init__(self, *args): + pass + + +d = D() +a = A() +a.cycle = a +a.other = B() +del a + +prev = None +while 1: + d[5] + prev = (prev,) diff --git a/src/python/stdlib/test/crashers/borrowed_ref_2.py b/src/python/stdlib/test/crashers/borrowed_ref_2.py new file mode 100644 index 0000000..1a7b3ff --- /dev/null +++ b/src/python/stdlib/test/crashers/borrowed_ref_2.py @@ -0,0 +1,38 @@ +""" +_PyType_Lookup() returns a borrowed reference. +This attacks PyObject_GenericSetAttr(). + +NB. on my machine this crashes in 2.5 debug but not release. +""" + +class A(object): + pass + +class B(object): + def __del__(self): + print "hi" + del C.d + +class D(object): + def __set__(self, obj, value): + self.hello = 42 + +class C(object): + d = D() + + def g(): + pass + + +c = C() +a = A() +a.cycle = a +a.other = B() + +lst = [None] * 1000000 +i = 0 +del a +while 1: + c.d = 42 # segfaults in PyMethod_New(im_func=D.__set__, im_self=d) + lst[i] = c.g # consume the free list of instancemethod objects + i += 1 diff --git a/src/python/stdlib/test/crashers/compiler_recursion.py b/src/python/stdlib/test/crashers/compiler_recursion.py new file mode 100644 index 0000000..4954bdd --- /dev/null +++ b/src/python/stdlib/test/crashers/compiler_recursion.py @@ -0,0 +1,5 @@ +""" +The compiler (>= 2.5) recurses happily. +""" + +compile('()'*9**5, '?', 'exec') diff --git a/src/python/stdlib/test/crashers/gc_has_finalizer.py b/src/python/stdlib/test/crashers/gc_has_finalizer.py new file mode 100644 index 0000000..737959b --- /dev/null +++ b/src/python/stdlib/test/crashers/gc_has_finalizer.py @@ -0,0 +1,36 @@ +""" +The gc module can still invoke arbitrary Python code and crash. +This is an attack against _PyInstance_Lookup(), which is documented +as follows: + + The point of this routine is that it never calls arbitrary Python + code, so is always "safe": all it does is dict lookups. + +But of course dict lookups can call arbitrary Python code. +The following code causes mutation of the object graph during +the call to has_finalizer() in gcmodule.c, and that might +segfault. +""" + +import gc + + +class A: + def __hash__(self): + return hash("__del__") + def __eq__(self, other): + del self.other + return False + +a = A() +b = A() + +a.__dict__[b] = 'A' + +a.other = b +b.other = a + +gc.collect() +del a, b + +gc.collect() diff --git a/src/python/stdlib/test/crashers/gc_inspection.py b/src/python/stdlib/test/crashers/gc_inspection.py new file mode 100644 index 0000000..10caa79 --- /dev/null +++ b/src/python/stdlib/test/crashers/gc_inspection.py @@ -0,0 +1,32 @@ +""" +gc.get_referrers() can be used to see objects before they are fully built. + +Note that this is only an example. There are many ways to crash Python +by using gc.get_referrers(), as well as many extension modules (even +when they are using perfectly documented patterns to build objects). + +Identifying and removing all places that expose to the GC a +partially-built object is a long-term project. A patch was proposed on +SF specifically for this example but I consider fixing just this single +example a bit pointless (#1517042). + +A fix would include a whole-scale code review, possibly with an API +change to decouple object creation and GC registration, and according +fixes to the documentation for extension module writers. It's unlikely +to happen, though. So this is currently classified as +"gc.get_referrers() is dangerous, use only for debugging". +""" + +import gc + + +def g(): + marker = object() + yield marker + # now the marker is in the tuple being constructed + [tup] = [x for x in gc.get_referrers(marker) if type(x) is tuple] + print tup + print tup[1] + + +tuple(g()) diff --git a/src/python/stdlib/test/crashers/infinite_loop_re.py b/src/python/stdlib/test/crashers/infinite_loop_re.py new file mode 100644 index 0000000..9aecc56 --- /dev/null +++ b/src/python/stdlib/test/crashers/infinite_loop_re.py @@ -0,0 +1,16 @@ + +# This was taken from http://python.org/sf/1541697 +# It's not technically a crasher. It may not even truly be infinite, +# however, I haven't waited a long time to see the result. It takes +# 100% of CPU while running this and should be fixed. + +import re +starttag = re.compile(r'<[a-zA-Z][-_.:a-zA-Z0-9]*\s*(' + r'\s*([a-zA-Z_][-:.a-zA-Z_0-9]*)(\s*=\s*' + r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~@]' + r'[][\-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*(?=[\s>/<])))?' + r')*\s*/?\s*(?=[<>])') + +if __name__ == '__main__': + foo = '<table cellspacing="0" cellpadding="0" style="border-collapse' + starttag.match(foo) diff --git a/src/python/stdlib/test/crashers/loosing_mro_ref.py b/src/python/stdlib/test/crashers/loosing_mro_ref.py new file mode 100644 index 0000000..b5fa93b --- /dev/null +++ b/src/python/stdlib/test/crashers/loosing_mro_ref.py @@ -0,0 +1,35 @@ +""" +There is a way to put keys of any type in a type's dictionary. +I think this allows various kinds of crashes, but so far I have only +found a convoluted attack of _PyType_Lookup(), which uses the mro of the +type without holding a strong reference to it. Probably works with +super.__getattribute__() too, which uses the same kind of code. +""" + +class MyKey(object): + def __hash__(self): + return hash('mykey') + + def __cmp__(self, other): + # the following line decrefs the previous X.__mro__ + X.__bases__ = (Base2,) + # trash all tuples of length 3, to make sure that the items of + # the previous X.__mro__ are really garbage + z = [] + for i in range(1000): + z.append((i, None, None)) + return -1 + + +class Base(object): + mykey = 'from Base' + +class Base2(object): + mykey = 'from Base2' + +# you can't add a non-string key to X.__dict__, but it can be +# there from the beginning :-) +X = type('X', (Base,), {MyKey(): 5}) + +print X.mykey +# I get a segfault, or a slightly wrong assertion error in a debug build. diff --git a/src/python/stdlib/test/crashers/mutation_inside_cyclegc.py b/src/python/stdlib/test/crashers/mutation_inside_cyclegc.py new file mode 100644 index 0000000..2b67398 --- /dev/null +++ b/src/python/stdlib/test/crashers/mutation_inside_cyclegc.py @@ -0,0 +1,31 @@ + +# The cycle GC collector can be executed when any GC-tracked object is +# allocated, e.g. during a call to PyList_New(), PyDict_New(), ... +# Moreover, it can invoke arbitrary Python code via a weakref callback. +# This means that there are many places in the source where an arbitrary +# mutation could unexpectedly occur. + +# The example below shows list_slice() not expecting the call to +# PyList_New to mutate the input list. (Of course there are many +# more examples like this one.) + + +import weakref + +class A(object): + pass + +def callback(x): + del lst[:] + + +keepalive = [] + +for i in range(100): + lst = [str(i)] + a = A() + a.cycle = a + keepalive.append(weakref.ref(a, callback)) + del a + while lst: + keepalive.append(lst[:]) diff --git a/src/python/stdlib/test/crashers/nasty_eq_vs_dict.py b/src/python/stdlib/test/crashers/nasty_eq_vs_dict.py new file mode 100644 index 0000000..3f3083d --- /dev/null +++ b/src/python/stdlib/test/crashers/nasty_eq_vs_dict.py @@ -0,0 +1,47 @@ +# from http://mail.python.org/pipermail/python-dev/2001-June/015239.html + +# if you keep changing a dictionary while looking up a key, you can +# provoke an infinite recursion in C + +# At the time neither Tim nor Michael could be bothered to think of a +# way to fix it. + +class Yuck: + def __init__(self): + self.i = 0 + + def make_dangerous(self): + self.i = 1 + + def __hash__(self): + # direct to slot 4 in table of size 8; slot 12 when size 16 + return 4 + 8 + + def __eq__(self, other): + if self.i == 0: + # leave dict alone + pass + elif self.i == 1: + # fiddle to 16 slots + self.__fill_dict(6) + self.i = 2 + else: + # fiddle to 8 slots + self.__fill_dict(4) + self.i = 1 + + return 1 + + def __fill_dict(self, n): + self.i = 0 + dict.clear() + for i in range(n): + dict[i] = i + dict[self] = "OK!" + +y = Yuck() +dict = {y: "OK!"} + +z = Yuck() +y.make_dangerous() +print dict[z] diff --git a/src/python/stdlib/test/crashers/recursion_limit_too_high.py b/src/python/stdlib/test/crashers/recursion_limit_too_high.py new file mode 100644 index 0000000..1fa4d32 --- /dev/null +++ b/src/python/stdlib/test/crashers/recursion_limit_too_high.py @@ -0,0 +1,16 @@ +# The following example may crash or not depending on the platform. +# E.g. on 32-bit Intel Linux in a "standard" configuration it seems to +# crash on Python 2.5 (but not 2.4 nor 2.3). On Windows the import +# eventually fails to find the module, possibly because we run out of +# file handles. + +# The point of this example is to show that sys.setrecursionlimit() is a +# hack, and not a robust solution. This example simply exercices a path +# where it takes many C-level recursions, consuming a lot of stack +# space, for each Python-level recursion. So 1000 times this amount of +# stack space may be too much for standard platforms already. + +import sys +if 'recursion_limit_too_high' in sys.modules: + del sys.modules['recursion_limit_too_high'] +import recursion_limit_too_high diff --git a/src/python/stdlib/test/crashers/recursive_call.py b/src/python/stdlib/test/crashers/recursive_call.py new file mode 100644 index 0000000..31c8963 --- /dev/null +++ b/src/python/stdlib/test/crashers/recursive_call.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +# No bug report AFAIK, mail on python-dev on 2006-01-10 + +# This is a "won't fix" case. It is known that setting a high enough +# recursion limit crashes by overflowing the stack. Unless this is +# redesigned somehow, it won't go away. + +import sys + +sys.setrecursionlimit(1 << 30) +f = lambda f:f(f) + +if __name__ == '__main__': + f(f) |
