coredump: fix file handle leak in _gen_mem_chunk

Use ExitStack to ensure the conditionally-opened file for
FILE_SHARED/FILE_PRIVATE VMAs is always closed, even if an
exception occurs during seek or page iteration.

Assisted-by: Claude Code (claude-opus-4-6)
Signed-off-by: Adrian Reber <areber@redhat.com>
This commit is contained in:
Adrian Reber 2026-07-06 13:29:04 +00:00 committed by Radostin Stoyanov
parent b0057d1873
commit 7e482031b6

View file

@ -32,6 +32,7 @@ import io
import sys import sys
import ctypes import ctypes
import platform import platform
from contextlib import ExitStack
from pycriu import images from pycriu import images
from . import elf from . import elf
@ -872,86 +873,86 @@ class coredump_generator:
# current process. # current process.
return b"\0" * size return b"\0" * size
if vma["status"] & status["VMA_FILE_SHARED"] or \ # ExitStack ensures the file opened below (if any) is closed
vma["status"] & status["VMA_FILE_PRIVATE"]: # when the block exits, even if an exception is raised.
# Open file before iterating vma pages with ExitStack() as stack:
shmid = vma["shmid"] if vma["status"] & status["VMA_FILE_SHARED"] or \
off = vma["pgoff"] vma["status"] & status["VMA_FILE_PRIVATE"]:
# Open file before iterating vma pages
shmid = vma["shmid"]
off = vma["pgoff"]
files = self.reg_files files = self.reg_files
fname = next(filter(lambda x: x["id"] == shmid, files))["name"] fname = next(
filter(lambda x: x["id"] == shmid, files))["name"]
try: try:
f = open(fname, 'rb') f = stack.enter_context(open(fname, 'rb'))
except FileNotFoundError: except FileNotFoundError:
sys.exit('Required file %s not found.' % fname) sys.exit('Required file %s not found.' % fname)
f.seek(off) f.seek(off)
start = vma["start"] start = vma["start"]
end = vma["start"] + size end = vma["start"] + size
# Split requested memory chunk into pages, so it could be # Split requested memory chunk into pages, so it could be
# pictured as: # pictured as:
# #
# "----" -- part of page with memory outside of our vma; # "----" -- part of page with memory outside of our vma;
# "XXXX" -- memory from our vma; # "XXXX" -- memory from our vma;
# #
# Start page Pages in the middle End page # Start page Pages in the middle End page
# [-----XXXXX]...[XXXXXXXXXX][XXXXXXXXXX]...[XXX-------] # [-----XXXXX]...[XXXXXXXXXX][XXXXXXXXXX]...[XXX-------]
# #
# Each page could be found in pages.img or in a standalone # Each page could be found in pages.img or in a standalone
# file described by shmid field in vma entry and # file described by shmid field in vma entry and
# corresponding entry in reg-files.img. # corresponding entry in reg-files.img.
# For VMA_FILE_PRIVATE vma, unchanged pages are taken from # For VMA_FILE_PRIVATE vma, unchanged pages are taken from
# a file, and changed ones -- from pages.img. # a file, and changed ones -- from pages.img.
# Finally, if no page is found neither in pages.img nor # Finally, if no page is found neither in pages.img nor
# in file, hole in inserted -- a page filled with zeroes. # in file, hole in inserted -- a page filled with zeroes.
start_page = start // PAGESIZE start_page = start // PAGESIZE
end_page = end // PAGESIZE end_page = end // PAGESIZE
buf = b"" buf = b""
for page_no in range(start_page, end_page + 1): for page_no in range(start_page, end_page + 1):
page = None page = None
# Search for needed page in pages.img and reg-files.img # Search for needed page in pages.img and reg-files.img
# and choose appropriate. # and choose appropriate.
page_mem = self._get_page(pid, page_no) page_mem = self._get_page(pid, page_no)
if f is not None: if f is not None:
page = f.read(PAGESIZE) page = f.read(PAGESIZE)
if page_mem is not None: if page_mem is not None:
# Page from pages.img has higher priority # Page from pages.img has higher priority
# than one from mapped file on disk. # than one from mapped file on disk.
page = page_mem page = page_mem
if page is None: if page is None:
# Hole # Hole
page = PAGESIZE * b"\0" page = PAGESIZE * b"\0"
# If it is a start or end page, we need to read # If it is a start or end page, we need to read
# only part of it. # only part of it.
if page_no == start_page: if page_no == start_page:
n_skip = start - page_no * PAGESIZE n_skip = start - page_no * PAGESIZE
if start_page == end_page: if start_page == end_page:
n_read = size n_read = size
else:
n_read = PAGESIZE - n_skip
elif page_no == end_page:
n_skip = 0
n_read = end - page_no * PAGESIZE
else: else:
n_read = PAGESIZE - n_skip n_skip = 0
elif page_no == end_page: n_read = PAGESIZE
n_skip = 0
n_read = end - page_no * PAGESIZE
else:
n_skip = 0
n_read = PAGESIZE
buf += page[n_skip:n_skip + n_read] buf += page[n_skip:n_skip + n_read]
# Don't forget to close file. return buf
if f is not None:
f.close()
return buf
def _gen_cmdline(self, pid): def _gen_cmdline(self, pid):
""" """