From: Daan De Meyer Date: Wed, 25 Jun 2025 10:47:56 +0000 (+0200) Subject: sandbox: Work around extra file descriptor opened by importing ctypes since python... X-Git-Tag: v26~193^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=694c8d2721cd6d1081caf39641f615aa81d5f8a0;p=thirdparty%2Fmkosi.git sandbox: Work around extra file descriptor opened by importing ctypes since python 3.14 Since python 3.14, importing ctypes opens an extra file descriptor which is used to allocate libffi closures which are in turn used by ctypes to pass python functions as C callback function pointers. We don't use this functionality, yet the file descriptor is still opened and messes with the file descriptor packing logic since the file descriptor to libffi will be passed as a packed file descriptor to the executable we're invoking. To avoid that from happening, we close libffi's file descriptor after importing ctypes. See https://github.com/python/cpython/issues/135893. --- diff --git a/mkosi/sandbox.py b/mkosi/sandbox.py index d210c8f1d..a265e5276 100755 --- a/mkosi/sandbox.py +++ b/mkosi/sandbox.py @@ -8,7 +8,6 @@ minimum, please don't import any extra modules if it can be avoided. """ -import ctypes import os import sys import warnings # noqa: F401 (loaded lazily by os.execvp() which happens too late) @@ -33,7 +32,6 @@ EPERM = 1 ENOENT = 2 ENOSYS = 38 F_DUPFD = 0 -F_GETFD = 1 FS_IOC_GETFLAGS = 0x80086601 FS_IOC_SETFLAGS = 0x40086602 FS_NOCOW_FL = 0x00800000 @@ -66,6 +64,59 @@ SD_LISTEN_FDS_START = 3 SIGSTOP = 19 +def is_valid_fd(fd: int) -> bool: + try: + os.close(os.dup(fd)) + except OSError as e: + if e.errno != EBADF: + raise + + return False + + return True + + +def open_file_descriptors() -> list[int]: + fds = [] + + with os.scandir("/proc/self/fd") as it: + for e in it: + if not e.is_symlink() and (e.is_file() or e.is_dir()): + continue + + try: + fd = int(e.name) + except ValueError: + continue + + fds.append(fd) + + # os.scandir() either opens a file descriptor to the given path or dups the given file descriptor. Either + # way, there will be an extra file descriptor in the fds array that's not valid anymore now, so find out + # which one and drop it. + return sorted(fd for fd in fds if is_valid_fd(fd)) + + +class CloseNewFileDescriptors: + def __enter__(self) -> None: + self.fds = set(open_file_descriptors()) + + def __exit__(self, *args: object, **kwargs: object) -> None: + for fd in open_file_descriptors(): + if fd not in self.fds: + os.close(fd) + + +# Since python 3.14, importing ctypes opens an extra file descriptor which is used to allocate libffi +# closures which are in turn used by ctypes to pass python functions as C callback function pointers. We +# don't use this functionality, yet the file descriptor is still opened and messes with the file descriptor +# packing logic since the file descriptor to libffi will be passed as a packed file descriptor to the +# executable we're invoking. To avoid that from happening, we close libffi's file descriptor after importing +# ctypes. See https://github.com/python/cpython/issues/135893. +with CloseNewFileDescriptors(): + import ctypes + + class mount_attr(ctypes.Structure): _fields_ = [ ("attr_set", ctypes.c_uint64), @@ -509,27 +560,7 @@ def is_relative_to(one: str, two: str) -> bool: def pack_file_descriptors() -> int: - fds = [] - - with os.scandir("/proc/self/fd") as it: - for e in it: - if not e.is_symlink() and (e.is_file() or e.is_dir()): - continue - - try: - fd = int(e.name) - except ValueError: - continue - - if fd < SD_LISTEN_FDS_START: - continue - - fds.append(fd) - - # os.scandir() either opens a file descriptor to the given path or dups the given file descriptor. Either - # way, there will be an extra file descriptor in the fds array that's not valid anymore now, so find out - # which one and drop it. - fds = sorted(fd for fd in fds if libc.fcntl(fd, F_GETFD, 0) >= 0) + fds = [fd for fd in open_file_descriptors() if fd >= SD_LISTEN_FDS_START] # The following is a reimplementation of pack_fds() in systemd.