From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:26:09 +0000 (+0200) Subject: [3.14] gh-138204: Forbid expansion of a shared anonymous mmap on Linux (GH-138220... X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=669253fde5210950ca4b32e5b22194aff7dd136e;p=thirdparty%2FPython%2Fcpython.git [3.14] gh-138204: Forbid expansion of a shared anonymous mmap on Linux (GH-138220) (GH-138386) This is a Linux kernel bug which caused a bus error. https://bugzilla.kernel.org/show_bug.cgi?id=8691 (cherry picked from commit 33fcb0c4a054f646d9d3686c145209a893b09bb0) Co-authored-by: Serhiy Storchaka --- diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index b2a299ed1729..38d1a496c30e 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -901,35 +901,69 @@ class MmapTests(unittest.TestCase): 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): diff --git a/Misc/NEWS.d/next/Library/2025-08-28-13-20-09.gh-issue-138204.8oLOud.rst b/Misc/NEWS.d/next/Library/2025-08-28-13-20-09.gh-issue-138204.8oLOud.rst new file mode 100644 index 000000000000..8eb5497f5da5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-28-13-20-09.gh-issue-138204.8oLOud.rst @@ -0,0 +1,2 @@ +Forbid expansion of shared anonymous :mod:`memory maps ` on Linux, +which caused a bus error. diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index e2ae967ce0c3..f58a48900bf6 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -120,6 +120,7 @@ typedef struct { #ifdef UNIX int fd; _Bool trackfd; + int flags; #endif PyObject *weakreflist; @@ -875,6 +876,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; @@ -1671,6 +1679,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);