gh-138204: Forbid expansion of a shared anonymous mmap on Linux (GH-138220)

This is a Linux kernel bug which caused a bus error.
https://bugzilla.kernel.org/show_bug.cgi?id=8691
This commit is contained in:
Serhiy Storchaka 2025-09-02 19:00:39 +03:00 committed by GitHub
parent c19db1d2b8
commit 33fcb0c4a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 17 deletions

View file

@ -901,35 +901,69 @@ def test_madvise(self):
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, 2), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, size), None)
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_up_when_mapped_to_pagefile(self):
def test_resize_up_anonymous_mapping(self):
"""If the mmap is backed by the pagefile ensure a resize up can happen
and that the original data is still in place
"""
start_size = PAGESIZE
new_size = 2 * start_size
data = bytes(random.getrandbits(8) for _ in range(start_size))
data = random.randbytes(start_size)
m = mmap.mmap(-1, start_size)
m[:] = data
m.resize(new_size)
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data[:start_size])
with mmap.mmap(-1, start_size) as m:
m[:] = data
if sys.platform.startswith(('linux', 'android')):
# Can't expand a shared anonymous mapping on Linux.
# See https://bugzilla.kernel.org/show_bug.cgi?id=8691
with self.assertRaises(ValueError):
m.resize(new_size)
else:
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_down_when_mapped_to_pagefile(self):
@unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_resize_up_private_anonymous_mapping(self):
start_size = PAGESIZE
new_size = 2 * start_size
data = random.randbytes(start_size)
with mmap.mmap(-1, start_size, flags=mmap.MAP_PRIVATE) as m:
m[:] = data
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
def test_resize_down_anonymous_mapping(self):
"""If the mmap is backed by the pagefile ensure a resize down up can happen
and that a truncated form of the original data is still in place
"""
start_size = PAGESIZE
start_size = 2 * PAGESIZE
new_size = start_size // 2
data = bytes(random.getrandbits(8) for _ in range(start_size))
data = random.randbytes(start_size)
m = mmap.mmap(-1, start_size)
m[:] = data
m.resize(new_size)
self.assertEqual(len(m), new_size)
self.assertEqual(m[:new_size], data[:new_size])
with mmap.mmap(-1, start_size) as m:
m[:] = data
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:], data[:new_size])
if sys.platform.startswith(('linux', 'android')):
# Can't expand to its original size.
with self.assertRaises(ValueError):
m.resize(start_size)
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_fails_if_mapping_held_elsewhere(self):

View file

@ -0,0 +1,2 @@
Forbid expansion of shared anonymous :mod:`memory maps <mmap>` on Linux,
which caused a bus error.

View file

@ -120,6 +120,7 @@ typedef struct {
#ifdef UNIX
int fd;
_Bool trackfd;
int flags;
#endif
PyObject *weakreflist;
@ -882,6 +883,13 @@ mmap_resize_method(PyObject *op, PyObject *args)
#else
void *newmap;
#ifdef __linux__
if (self->fd == -1 && !(self->flags & MAP_PRIVATE) && new_size > self->size) {
PyErr_Format(PyExc_ValueError,
"mmap: can't expand a shared anonymous mapping on Linux");
return NULL;
}
#endif
if (self->fd != -1 && ftruncate(self->fd, self->offset + new_size) == -1) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
@ -1678,6 +1686,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
else {
m_obj->fd = -1;
}
m_obj->flags = flags;
Py_BEGIN_ALLOW_THREADS
m_obj->data = mmap(NULL, map_size, prot, flags, fd, offset);