10000 [3.7] bpo-9263: _PyObject_Dump() detects freed memory (GH-10061) (GH-… · python/cpython@95036ea · GitHub
[go: up one dir, main page]

Skip to content

Commit 95036ea

Browse files
authored
[3.7] bpo-9263: _PyObject_Dump() detects freed memory (GH-10061) (GH-10662)
* bpo-9263: _PyObject_Dump() detects freed memory (GH-10061) _PyObject_Dump() now uses an heuristic to check if the object memory has been freed: log "<freed object>" in that case. The heuristic rely on the debug hooks on Python memory allocators which fills the memory with DEADBYTE (0xDB) when memory is deallocated. Use PYTHONMALLOC=debug to always enable these debug hooks. (cherry picked from commit 82af0b6) * bpo-9263: Fix _PyObject_Dump() for freed object (#10661) If _PyObject_Dump() detects that the object is freed, don't try to dump it (exit immediately). Enhance also _PyObject_IsFreed(): it now detects if the pointer itself looks like freed memory. (cherry picked from commit 2cf5d32)
1 parent a519411 commit 95036ea

File tree

4 files changed

+80
-24
lines changed

4 files changed

+80
-24
lines changed

Include/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ struct _Py_Identifier;
521521
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
522522
PyAPI_FUNC(void) _Py_BreakPoint(void);
523523
PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
524+
PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
524525
#endif
525526
PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *);
526527
PyAPI_FUNC(PyObject *) PyObject_Str(PyObject *);

Include/pymem.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ PyAPI_FUNC(int) PyTraceMalloc_Untrack(
5555
PyAPI_FUNC(PyObject*) _PyTraceMalloc_GetTraceback(
5656
unsigned int domain,
5757
uintptr_t ptr);
58-
#endif /* !Py_LIMITED_API */
58+
59+
PyAPI_FUNC(int) _PyMem_IsFreed(void *ptr, size_t size);
60+
#endif /* !defined(Py_LIMITED_API) */
5961

6062

6163
/* BEWARE:

Objects/object.c

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -411,34 +411,71 @@ _Py_BreakPoint(void)
411411
}
412412

413413

414+
/* Heuristic checking if the object memory has been deallocated.
415+
Rely on the debug hooks on Python memory allocators which fills the memory
416+
with DEADBYTE (0xDB) when memory is deallocated.
417+
418+
The function can be used to prevent segmentation fault on dereferencing
419+
pointers like 0xdbdbdbdbdbdbdbdb. Such pointer is very unlikely to be mapped
420+
in memory. */
421+
int
422+
_PyObject_IsFreed(PyObject *op)
423+
{
424+
uintptr_t ptr = (uintptr_t)op;
425+
if (_PyMem_IsFreed(&ptr, sizeof(ptr))) {
426+
return 1;
427+
}
428+
int freed = _PyMem_IsFreed(&op->ob_type, sizeof(op->ob_type));
429+
/* ignore op->ob_ref: the value can have be modified
430+
by Py_INCREF() and Py_DECREF(). */
431+
#ifdef Py_TRACE_REFS
432+
freed &= _PyMem_IsFreed(&op->_ob_next, sizeof(op->_ob_next));
433+
freed &= _PyMem_IsFreed(&op->_ob_prev, sizeof(op->_ob_prev));
434+
#endif
435+
return freed;
436+
}
437+
438+
414439
/* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */
415440
void
416441
_PyObject_Dump(PyObject* op)
417442
{
418-
if (op == NULL)
419-
fprintf(stderr, "NULL\n");
420-
else {
421-
PyGILState_STATE gil;
422-
PyObject *error_type, *error_value, *error_traceback;
423-
424-
fprintf(stderr, "object : ");
425-
gil = PyGILState_Ensure();
426-
427-
PyErr_Fetch(&error_type, &error_value, &error_traceback);
428-
(void)PyObject_Print(op, stderr, 0);
429-
PyErr_Restore(error_type, error_value, error_traceback);
430-
431-
PyGILState_Release(gil);
432-
/* XXX(twouters) cast refcount to long until %zd is
433-
universally available */
434-
fprintf(stderr, "\n"
435-
"type : %s\n"
436-
"refcount: %ld\n"
437-
"address : %p\n",
438-
Py_TYPE(op)==NULL ? "NULL" : Py_TYPE(op)->tp_name,
439-
(long)op->ob_refcnt,
440-
op);
443+
if (op == NULL) {
444+
fprintf(stderr, "<NULL object>\n");
445+
fflush(stderr);
446+
return;
447+
}
448+
449+
if (_PyObject_IsFreed(op)) {
450+
/* It seems like the object memory has been freed:
451+
don't access it to prevent a segmentation fault. */
452+
fprintf(stderr, "<freed object>\n");
453+
return;
441454
}
455+
456+
PyGILState_STATE gil;
457+
PyObject *error_type, *error_value, *error_traceback;
458+
459+
fprintf(stderr, "object : ");
460+
fflush(stderr);
461+
gil = PyGILState_Ensure();
462+
463+
PyErr_Fetch(&error_type, &error_value, &error_traceback);
464+
(void)PyObject_Print(op, stderr, 0);
465+
fflush(stderr);
466+
PyErr_Restore(error_type, error_value, error_traceback);
467+
468+
PyGILState_Release(gil);
469+
/* XXX(twouters) cast refcount to long until %zd is
470+
universally available */
471+
fprintf(stderr, "\n"
472+
"type : %s\n"
473+
"refcount: %ld\n"
474+
"address : %p\n",
475+
Py_TYPE(op)==NULL ? "NULL" : Py_TYPE(op)->tp_name,
476+
(long)op->ob_refcnt,
477+
op);
478+
fflush(stderr);
442479
}
443480

444481
PyObject *

Objects/obmalloc.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2091,6 +2091,22 @@ _PyMem_DebugRawCalloc(void *ctx, size_t nelem, size_t elsize)
20912091
}
20922092

20932093

2094+
/* Heuristic checking if the memory has been freed. Rely on the debug hooks on
2095+
Python memory allocators which fills the memory with DEADBYTE (0xDB) when
2096+
memory is deallocated. */
2097+
int
2098+
_PyMem_IsFreed(void *ptr, size_t size)
2099+
{
2100+
unsigned char *bytes = ptr;
2101+
for (size_t i=0; i < size; i++) {
2102+
if (bytes[i] != DEADBYTE) {
2103+
return 0;
2104+
}
2105+
}
2106+
return 1;
2107+
}
2108+
2109+
20942110
/* The debug free first checks the 2*SST bytes on each end for sanity (in
20952111
particular, that the FORBIDDENBYTEs with the api ID are still intact).
20962112
Then fills the original bytes with DEADBYTE.

0 commit comments

Comments
 (0)
0