Skip to content

Commit 77fc2f5

Browse files
authored
gh-144319: Fix huge page leak in datastack chunk allocator (#147963)
Fix huge page leak in datastack chunk allocator The original fix rounded datastack chunk allocations in pystate.c so that _PyObject_VirtualFree() would receive the full huge page mapping size. Change direction and move that logic into _PyObject_VirtualAlloc() and _PyObject_VirtualFree() instead. The key invariant is that munmap() must see the full mapped size, so alloc and free now apply the same platform-specific rounding in the allocator layer. This keeps _PyStackChunk bookkeeping in requested-size units, avoids a hardcoded 2 MB assumption, and also covers other small virtual-memory users such as the JIT tracer state allocation in optimizer.c.
1 parent 668c572 commit 77fc2f5

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

Include/internal/pycore_obmalloc.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,11 @@ struct _obmalloc_state {
691691

692692

693693
/* Allocate memory directly from the O/S virtual memory system,
694-
* where supported. Otherwise fallback on malloc */
694+
* where supported. Otherwise fallback on malloc.
695+
*
696+
* Large-page and huge-page backends may round the mapped size up
697+
* internally, so pass the original requested size back to
698+
* _PyObject_VirtualFree(). */
695699
void *_PyObject_VirtualAlloc(size_t size);
696700
void _PyObject_VirtualFree(void *, size_t size);
697701

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a bug that could cause applications with specific allocation patterns to
2+
leak memory via Huge Pages if compiled with Huge Page support. Patch by
3+
Pablo Galindo

Objects/obmalloc.c

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <stdlib.h> // malloc()
1515
#include <stdbool.h>
1616
#include <stdio.h> // fopen(), fgets(), sscanf()
17+
#include <errno.h> // errno
1718
#ifdef WITH_MIMALLOC
1819
// Forward declarations of functions used in our mimalloc modifications
1920
static void _PyMem_mi_page_clear_qsbr(mi_page_t *page);
@@ -572,6 +573,49 @@ _pymalloc_system_hugepage_size(void)
572573
}
573574
#endif
574575

576+
#if (defined(MS_WINDOWS) && defined(PYMALLOC_USE_HUGEPAGES)) || \
577+
(defined(PYMALLOC_USE_HUGEPAGES) && defined(ARENAS_USE_MMAP) && defined(MAP_HUGETLB))
578+
static size_t
579+
_pymalloc_round_up_to_multiple(size_t size, size_t multiple)
580+
{
581+
if (multiple == 0 || size == 0) {
582+
return size;
583+
}
584+
585+
size_t remainder = size % multiple;
586+
if (remainder == 0) {
587+
return size;
588+
}
589+
590+
size_t padding = multiple - remainder;
591+
if (size > SIZE_MAX - padding) {
592+
return 0;
593+
}
594+
return size + padding;
595+
}
596+
#endif
597+
598+
static size_t
599+
_pymalloc_virtual_alloc_size(size_t size)
600+
{
601+
#if defined(MS_WINDOWS) && defined(PYMALLOC_USE_HUGEPAGES)
602+
if (_PyRuntime.allocators.use_hugepages) {
603+
SIZE_T large_page_size = GetLargePageMinimum();
604+
if (large_page_size > 0) {
605+
return _pymalloc_round_up_to_multiple(size, (size_t)large_page_size);
606+
}
607+
}
608+
#elif defined(PYMALLOC_USE_HUGEPAGES) && defined(ARENAS_USE_MMAP) && defined(MAP_HUGETLB)
609+
if (_PyRuntime.allocators.use_hugepages) {
610+
size_t hp_size = _pymalloc_system_hugepage_size();
611+
if (hp_size > 0) {
612+
return _pymalloc_round_up_to_multiple(size, hp_size);
613+
}
614+
}
615+
#endif
616+
return size;
617+
}
618+
575619
void *
576620
_PyMem_ArenaAlloc(void *Py_UNUSED(ctx), size_t size)
577621
{
@@ -648,7 +692,11 @@ _PyMem_ArenaFree(void *Py_UNUSED(ctx), void *ptr,
648692
if (ptr == NULL) {
649693
return;
650694
}
651-
munmap(ptr, size);
695+
if (munmap(ptr, size) < 0) {
696+
_Py_FatalErrorFormat(__func__,
697+
"munmap(%p, %zu) failed with errno %d",
698+
ptr, size, errno);
699+
}
652700
#else
653701
free(ptr);
654702
#endif
@@ -1128,13 +1176,19 @@ PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)
11281176
void *
11291177
_PyObject_VirtualAlloc(size_t size)
11301178
{
1131-
return _PyObject_Arena.alloc(_PyObject_Arena.ctx, size);
1179+
size_t alloc_size = _pymalloc_virtual_alloc_size(size);
1180+
if (alloc_size == 0 && size != 0) {
1181+
return NULL;
1182+
}
1183+
return _PyObject_Arena.alloc(_PyObject_Arena.ctx, alloc_size);
11321184
}
11331185

11341186
void
11351187
_PyObject_VirtualFree(void *obj, size_t size)
11361188
{
1137-
_PyObject_Arena.free(_PyObject_Arena.ctx, obj, size);
1189+
size_t alloc_size = _pymalloc_virtual_alloc_size(size);
1190+
assert(alloc_size != 0 || size == 0);
1191+
_PyObject_Arena.free(_PyObject_Arena.ctx, obj, alloc_size);
11381192
}
11391193

11401194

0 commit comments

Comments
 (0)