[Python-bugs-list] [ python-Bugs-563303 ] Heap corruption in debug

noreply@sourceforge.net noreply@sourceforge.net
Mon, 03 Jun 2002 09:50:47 -0700


Bugs item #563303, was opened at 2002-06-01 10:10
You can respond by visiting: 
http://sourceforge.net/tracker/?func=detail&atid=105470&aid=563303&group_id=5470

Category: Python Interpreter Core
Group: Python 2.3
>Status: Closed
>Resolution: Invalid
Priority: 5
Submitted By: Cliff Owen (cliffo)
Assigned to: Tim Peters (tim_one)
Summary: Heap corruption in debug

Initial Comment:
PyObject_DebugMalloc() returns a pointer that is +8 
bytes to detect for underwriting the pointer.

Sometimes, PyObject_Free() is called instead of going 
through PyObject_DebugFree(). This means the pointer 
is off by 8. PyObject_Free() takes the pointer passed in 
as being valid, and will update the pool to indicate the 
passed pointer is a pointer to the new free block.

Corruption of the heap occurs when 
PyObject_DebugMalloc() is called and there are 
multiple free blocks in a row that are properly aligned. 
Writing the FORBIDDENBYTE set to the end of the 
newly allocated block may stomp the next free pointer.

2 more allocations later on this arena, and you will AV.

Why is PyObject_Free() being called instead of 
PyObject_DebugFree() (from ste_dealloc)? I'm 
assuming it was built incorrectly or there's an ordering 
issue in objimpl.h where PyObject_Free() is defined 
before the macro, and referenced by PyObject_Del.

The fact remains that a simple alignment check in 
PyObject_Free() will handle any misaligned pointer.

If I've got this completely screwed up and it is in fact 
building incorrectly, please let me know. I've traced 
through it about 100 times. I'm fairly certain I understand 
how/why it works the way it does, but I could be wrong.

This problem would only show up in debug mode.

----------------------------------------------------------------------

>Comment By: Tim Peters (tim_one)
Date: 2002-06-03 12:50

Message:
Logged In: YES 
user_id=31435

Offline, it was determined that:

+ The OP was compiling Python as well as his app.

+ Some Python modules weren't getting compiled with 
_DEBUG, via an accident in his build setup.

So closing as invalid -- the problem was due to a flawed 
custom build setup.

----------------------------------------------------------------------

Comment By: Tim Peters (tim_one)
Date: 2002-06-01 19:56

Message:
Logged In: YES 
user_id=31435

Could you try to describe exactly how the problem arises, 
holding off on speculation about causes and cures?  What I 
still don't grasp at all is how you get into this situation to 
begin with:  it should be impossible to enter PyObject_Free
() in a pymalloc debug build *except* when 
_PyObject_DebugFree() calls it.  Your report begins with

"""
Sometimes, PyObject_Free() is called instead of going 
through PyObject_DebugFree(). 
"""

and I'm still lost on that sentence:  it shouldn't be possible 
to do that in a debug build.  If there is a way to do it, it's a 
serious problem, and your suggested code won't fix it (it 
may hide bad symptoms by accident for a while, but the 
problem still exists and will come back to bite you later).

It's unclear to me why you mentioned ste_dealloc, but I've 
stepped thru that in the MSVC 6 debugger and it does call 
_PyObject_DebugFree in a debug build.  It's doing what it's 
supposed to do. 

1. Is it your claim that ste_dealloc actually calls 
PyObject_Free directly in *your* debug build?  "Yes" or "no" 
would be more helpful than outrage <wink>.

2. What does "a snapshot of the python 2.3 build" mean, 
exactly?  Did you compile it yourself?  If not, from where did 
you get it?

3. Are you writing your own C extensions?  If so, are you 
mixing _DEBUG with non-_DEBUG between Python and 
your own code?  If you are so mixing, it's never going to 
work (Python and extension modules on Windows must 
both be built with _DEBUG, or neither).

----------------------------------------------------------------------

Comment By: Cliff Owen (cliffo)
Date: 2002-06-01 18:22

Message:
Logged In: YES 
user_id=556609

Yes, I do have a program which demonstrates the problem. 
However, I cannot send it in because it is commercial in 
nature. :( I understand this poses a bit of a problem for 
debugging, but I'm hoping you understand. I do have other 
things that require more of my time than browsing the Python 
sources and making speculations on what I see. This did AV, 
and I did spend a while debugging it, with a debugger. Being 
the nice developer who's using a free product that's not being 
maintained by me, I thought I'd report this.

Yes, I also have seen what happens inside of objimpl.h, and 
do know that PyObject_Del is called in ste_dealloc. These 
are details I didn't think necessary to mention because you 
would obviously be familiar with the code and since I 
mentioned ste_dealloc, you might assume I would be familiar 
with these mappings as well since ste_dealloc() clearly calls 
PyObject_Del, which is nothing but a macro to 
PyObject_Free.

Regardless and bickering aside, I have added the following 
line to PyObject_Free() in my own code and it resolves the 
problem by assuring that any pointer passed in is properly 
aligned on the correct block size. This one line covers a 
multitude of sins from the caller.

p = (char *) p - ((char *)p - ((char *)pool + 
POOL_OVERHEAD)) % INDEX2SIZE(pool->szidx);

Right after:

if (ADDRESS_IN_RANGE(p, pool->arenaindex))

If you choose to use this line or not is up to you. If the user 
passes in a pointer that is aligned, this line does nothing. If 
the user passes in a pointer which resides anywhere in a 
valid block, that block is released. Is this the correct 
behavior? Probably not. It should probably raise an exception 
rather than allow the user to get away with it. This would at 
least force the user to figure out why they were passing in a 
bogus pointer in the first place.

Some additional information: I am building with MSVC 6.x 
(SP3), using a snapshot of the python 2.3 build. I am 
compiling into a static library using these defines only.

WIN32,_DEBUG,_MBCS,_LIB,USE_DL_EXPORT

If you need additional information, let me know. I'm not sure 
what else I can tell you.


----------------------------------------------------------------------

Comment By: Tim Peters (tim_one)
Date: 2002-06-01 12:35

Message:
Logged In: YES 
user_id=31435

Do you have an actual program that shows corruption, or 
are you speculating?

Note that in a debug build, it's not possible to call 
PyObject_Free:  the name PyObject_Free is remapped to 
_PyObject_DebugFree by objimpl.h then.  In a debug build 
when pymalloc is enabled, all calls to all PyMem_XYZ and 
PyObject_XYZ memory functions are redirected to 
_PyObject_DebugXYZ, except within obmalloc.c itself 
(which #undefs some name substitutions).

ste_dealloc doesn't call PyObject_Free.  It calls 
PyObject_Del.  In a debug pymalloc build, that ends up 
calling _PyObject_DebugFree.  Step through it in a 
debugger if you don't believe it <wink>.

----------------------------------------------------------------------

You can respond by visiting: 
http://sourceforge.net/tracker/?func=detail&atid=105470&aid=563303&group_id=5470