Skip to content

Commit e104c08

Browse files
committed
gh-153400: Resolve newer libc/syscall wrappers at runtime on Linux
os functions like copy_file_range() are gated on a configure-time AC_CHECK_FUNCS probe and compiled out when the build libc lacks the symbol. A redistributable built against an old glibc (the python-build-standalone builds target glibc 2.17) therefore never exposes them, even when run on a newer glibc or a capable kernel. Add Modules/posixshims.h, which on Linux always exposes _Py_<func>(), resolves the libc symbol once at load time via dlsym(RTLD_DEFAULT) from a constructor, and falls back to the raw syscall when the running libc lacks the wrapper. Resolving in a constructor keeps dlsym(), which is not async-signal-safe, out of signal handlers and the fork()/exec() window. Off Linux the classic build-time HAVE_* direct calls are kept. Limit the shims to wrappers newer than the glibc 2.17 baseline, since anything at or below it is always present in the build libc: copy_file_range (glibc 2.27) memfd_create (glibc 2.27) pidfd_open (glibc 2.36) pidfd_getfd (glibc 2.36) pidfd_open() and pidfd_getfd() previously issued raw syscalls unconditionally; they now prefer the glibc wrapper when present. Signed-off-by: Daan De Meyer <daan@amutable.com>
1 parent b4db948 commit e104c08

5 files changed

Lines changed: 171 additions & 22 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
On Linux, :func:`os.copy_file_range`, :func:`os.memfd_create`,
2+
:func:`os.pidfd_open` and :func:`os.pidfd_getfd` are now resolved at runtime, so
3+
they stay available on interpreters built against an older glibc when the
4+
running system provides them.

Modules/clinic/posixmodule.c.h

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
#include <stdio.h> // ctermid()
4747
#include <stdlib.h> // system()
4848

49+
#include "posixshims.h" // _Py_copy_file_range()
50+
4951
#ifdef HAVE_UNISTD_H
5052
# include <unistd.h> // symlink()
5153
#endif
@@ -10798,8 +10800,7 @@ os_wait_impl(PyObject *module)
1079810800

1079910801

1080010802
// This system call always crashes on older Android versions.
10801-
#if defined(__linux__) && defined(__NR_pidfd_open) && \
10802-
!(defined(__ANDROID__) && __ANDROID_API__ < 31)
10803+
#ifdef _Py_HAVE_PIDFD_OPEN
1080310804
/*[clinic input]
1080410805
os.pidfd_open
1080510806
pid: pid_t
@@ -10815,7 +10816,7 @@ static PyObject *
1081510816
os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags)
1081610817
/*[clinic end generated code: output=5c7252698947dc41 input=03058b32c389f874]*/
1081710818
{
10818-
int fd = syscall(__NR_pidfd_open, pid, flags);
10819+
int fd = _Py_pidfd_open(pid, flags);
1081910820
if (fd < 0) {
1082010821
return posix_error();
1082110822
}
@@ -10824,8 +10825,7 @@ os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags)
1082410825
#endif
1082510826

1082610827

10827-
#if defined(__linux__) && defined(__NR_pidfd_getfd) && \
10828-
!(defined(__ANDROID__) && __ANDROID_API__ < 31)
10828+
#ifdef _Py_HAVE_PIDFD_GETFD
1082910829
/*[clinic input]
1083010830
os.pidfd_getfd
1083110831
pidfd: int
@@ -10844,7 +10844,7 @@ os_pidfd_getfd_impl(PyObject *module, int pidfd, int targetfd,
1084410844
unsigned int flags)
1084510845
/*[clinic end generated code: output=e1a1415a13c7137f input=ef6417fb10deb1cc]*/
1084610846
{
10847-
int fd = syscall(__NR_pidfd_getfd, pidfd, targetfd, flags);
10847+
int fd = _Py_pidfd_getfd(pidfd, targetfd, flags);
1084810848
if (fd < 0) {
1084910849
return posix_error();
1085010850
}
@@ -13044,7 +13044,7 @@ os_pwritev_impl(PyObject *module, int fd, PyObject *buffers, Py_off_t offset,
1304413044
}
1304513045
#endif /* HAVE_PWRITEV */
1304613046

13047-
#ifdef HAVE_COPY_FILE_RANGE
13047+
#ifdef _Py_HAVE_COPY_FILE_RANGE
1304813048
/*[clinic input]
1304913049

1305013050
os.copy_file_range
@@ -13096,7 +13096,7 @@ os_copy_file_range_impl(PyObject *module, int src, int dst, Py_ssize_t count,
1309613096

1309713097
do {
1309813098
Py_BEGIN_ALLOW_THREADS
13099-
ret = copy_file_range(src, p_offset_src, dst, p_offset_dst, count, flags);
13099+
ret = _Py_copy_file_range(src, p_offset_src, dst, p_offset_dst, count, flags);
1310013100
Py_END_ALLOW_THREADS
1310113101
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
1310213102

@@ -13106,7 +13106,7 @@ os_copy_file_range_impl(PyObject *module, int src, int dst, Py_ssize_t count,
1310613106

1310713107
return PyLong_FromSsize_t(ret);
1310813108
}
13109-
#endif /* HAVE_COPY_FILE_RANGE*/
13109+
#endif /* _Py_HAVE_COPY_FILE_RANGE */
1311013110

1311113111
#if (defined(HAVE_SPLICE) && !defined(_AIX))
1311213112
/*[clinic input]
@@ -15808,7 +15808,7 @@ os_urandom_impl(PyObject *module, Py_ssize_t size)
1580815808
return PyBytesWriter_Finish(writer);
1580915809
}
1581015810

15811-
#ifdef HAVE_MEMFD_CREATE
15811+
#ifdef _Py_HAVE_MEMFD_CREATE
1581215812
/*[clinic input]
1581315813
os.memfd_create
1581415814

@@ -15824,7 +15824,7 @@ os_memfd_create_impl(PyObject *module, PyObject *name, unsigned int flags)
1582415824
int fd;
1582515825
const char *bytes = PyBytes_AS_STRING(name);
1582615826
Py_BEGIN_ALLOW_THREADS
15827-
fd = memfd_create(bytes, flags);
15827+
fd = _Py_memfd_create(bytes, flags);
1582815828
Py_END_ALLOW_THREADS
1582915829
if (fd == -1) {
1583015830
return PyErr_SetFromErrno(PyExc_OSError);
@@ -18433,7 +18433,7 @@ all_ins(PyObject *m)
1843318433
if (PyModule_AddIntMacro(m, GRND_RANDOM)) return -1;
1843418434
if (PyModule_AddIntMacro(m, GRND_NONBLOCK)) return -1;
1843518435
#endif
18436-
#ifdef HAVE_MEMFD_CREATE
18436+
#ifdef _Py_HAVE_MEMFD_CREATE
1843718437
if (PyModule_AddIntMacro(m, MFD_CLOEXEC)) return -1;
1843818438
if (PyModule_AddIntMacro(m, MFD_ALLOW_SEALING)) return -1;
1843918439
#ifdef MFD_HUGETLB
@@ -18481,7 +18481,7 @@ all_ins(PyObject *m)
1848118481
#ifdef MFD_HUGE_16GB
1848218482
if (PyModule_AddIntMacro(m, MFD_HUGE_16GB)) return -1;
1848318483
#endif
18484-
#endif /* HAVE_MEMFD_CREATE */
18484+
#endif /* _Py_HAVE_MEMFD_CREATE */
1848518485

1848618486
#if defined(HAVE_EVENTFD) && defined(EFD_CLOEXEC)
1848718487
if (PyModule_AddIntMacro(m, EFD_CLOEXEC)) return -1;

Modules/posixshims.h

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/* posixshims.h: resolve newer libc/syscall wrappers at runtime, not build time.
2+
3+
CPython gates functions like copy_file_range() on a configure-time
4+
AC_CHECK_FUNCS probe, compiling them out when the build libc lacks the symbol.
5+
That permanently disables them in a redistributable built against an old glibc
6+
even when it later runs on a newer one. Instead, on Linux we always expose
7+
_Py_<func>(), resolve the libc symbol once at load time via dlsym(RTLD_DEFAULT)
8+
from a constructor, and fall back to the raw syscall. A constructor (vs. lazy
9+
resolution) keeps dlsym(), which is not async-signal-safe, out of signal
10+
handlers and the fork()/exec() window, and lets the cache skip synchronization.
11+
12+
Each _Py_<func>() is declared explicitly; only the shared body is a macro.
13+
Off Linux, each function keeps its classic build-time HAVE_* direct call. */
14+
15+
#ifndef Py_POSIXSHIMS_H
16+
#define Py_POSIXSHIMS_H
17+
18+
/* Needs ELF constructors, dlsym() and Linux syscall numbers. */
19+
#if defined(__linux__) && (defined(__GNUC__) || defined(__clang__)) \
20+
&& defined(HAVE_DLFCN_H) && defined(HAVE_SYS_SYSCALL_H)
21+
# define _Py_HAVE_POSIX_SHIMS
22+
#endif
23+
24+
#ifdef _Py_HAVE_POSIX_SHIMS
25+
26+
#include <dlfcn.h> // dlsym(), RTLD_DEFAULT
27+
#include <sys/syscall.h> // __NR_* syscall numbers
28+
#include <sys/types.h> // off_t, ssize_t
29+
#include <unistd.h> // syscall()
30+
31+
/* The raw-syscall fallback needs a syscall number. If a stale build header
32+
predates one, default it to an invalid number so the shim still builds and the
33+
dlsym'd libc symbol stays usable; only the syscall fallback is lost, returning
34+
-1/ENOSYS at runtime. <sys/syscall.h> above is the sole source of these
35+
numbers in this translation unit, so nothing redefines them afterwards. */
36+
#ifndef __NR_copy_file_range
37+
# define __NR_copy_file_range (-1)
38+
#endif
39+
#ifndef __NR_memfd_create
40+
# define __NR_memfd_create (-1)
41+
#endif
42+
#ifndef __NR_pidfd_open
43+
# define __NR_pidfd_open (-1)
44+
#endif
45+
#ifndef __NR_pidfd_getfd
46+
# define __NR_pidfd_getfd (-1)
47+
#endif
48+
49+
/* A per-function cache plus a constructor that resolves the libc symbol once at
50+
load time. The __asm__ barrier after dlsym() is load-bearing under LTO:
51+
without it the compiler may tail-call dlsym(), breaking glibc's
52+
return-address caller lookup and crashing at startup (glibc BZ #34156). */
53+
#define _Py_SHIM_RESOLVER(func) \
54+
static void *_Py_shim_##func; \
55+
__attribute__((constructor)) \
56+
static void _Py_shim_##func##_resolve(void) { \
57+
void *p = dlsym(RTLD_DEFAULT, #func); \
58+
__asm__ volatile("" ::: "memory"); \
59+
_Py_shim_##func = p; \
60+
}
61+
62+
/* Body of an explicitly declared _Py_<func>() wrapper: call the resolved libc
63+
symbol if present, else fall back to syscall(__NR_<func>, ...). On a kernel
64+
too old for the syscall that returns -1/ENOSYS, which posixmodule.c reports as
65+
an ordinary OSError. The cast is __typeof__ of _Py_<func> (the wrapper
66+
itself), not of <func>, which may be undeclared at build time. */
67+
#define _Py_SHIM_SYSCALL(func, ...) \
68+
if (_Py_shim_##func != NULL) { \
69+
return ((__typeof__(&_Py_##func)) _Py_shim_##func)(__VA_ARGS__); \
70+
} \
71+
return syscall(__NR_##func, __VA_ARGS__)
72+
73+
#endif /* _Py_HAVE_POSIX_SHIMS */
74+
75+
76+
/* ---- copy_file_range() (glibc 2.27) ------------------------------------- */
77+
78+
#if defined(_Py_HAVE_POSIX_SHIMS)
79+
# define _Py_HAVE_COPY_FILE_RANGE
80+
_Py_SHIM_RESOLVER(copy_file_range)
81+
/* off_t is 64-bit (_FILE_OFFSET_BITS=64), matching the kernel's loff_t. */
82+
static inline ssize_t
83+
_Py_copy_file_range(int fd_in, off_t *off_in, int fd_out, off_t *off_out,
84+
size_t len, unsigned int flags)
85+
{
86+
_Py_SHIM_SYSCALL(copy_file_range,
87+
fd_in, off_in, fd_out, off_out, len, flags);
88+
}
89+
90+
#elif defined(HAVE_COPY_FILE_RANGE)
91+
# define _Py_HAVE_COPY_FILE_RANGE
92+
# define _Py_copy_file_range copy_file_range
93+
#endif
94+
95+
96+
/* ---- memfd_create() (glibc 2.27) ---------------------------------------- */
97+
98+
#if defined(_Py_HAVE_POSIX_SHIMS)
99+
# define _Py_HAVE_MEMFD_CREATE
100+
_Py_SHIM_RESOLVER(memfd_create)
101+
static inline int
102+
_Py_memfd_create(const char *name, unsigned int flags)
103+
{
104+
_Py_SHIM_SYSCALL(memfd_create, name, flags);
105+
}
106+
107+
#elif defined(HAVE_MEMFD_CREATE)
108+
# define _Py_HAVE_MEMFD_CREATE
109+
# define _Py_memfd_create memfd_create
110+
#endif
111+
112+
113+
/* ---- pidfd_open(), pidfd_getfd() (glibc 2.36) --------------------------- *
114+
115+
Previously unconditional raw syscalls; the shim now prefers glibc's wrapper.
116+
Linux-only from the start (no non-Linux branch); the Android API floor from
117+
the original guards is preserved. */
118+
119+
#if defined(_Py_HAVE_POSIX_SHIMS) \
120+
&& !(defined(__ANDROID__) && __ANDROID_API__ < 31)
121+
# define _Py_HAVE_PIDFD_OPEN
122+
_Py_SHIM_RESOLVER(pidfd_open)
123+
static inline int
124+
_Py_pidfd_open(pid_t pid, unsigned int flags)
125+
{
126+
_Py_SHIM_SYSCALL(pidfd_open, pid, flags);
127+
}
128+
#endif
129+
130+
#if defined(_Py_HAVE_POSIX_SHIMS) \
131+
&& !(defined(__ANDROID__) && __ANDROID_API__ < 31)
132+
# define _Py_HAVE_PIDFD_GETFD
133+
_Py_SHIM_RESOLVER(pidfd_getfd)
134+
static inline int
135+
_Py_pidfd_getfd(int pidfd, int targetfd, unsigned int flags)
136+
{
137+
_Py_SHIM_SYSCALL(pidfd_getfd, pidfd, targetfd, flags);
138+
}
139+
#endif
140+
141+
#endif /* !Py_POSIXSHIMS_H */

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,3 +792,7 @@ Objects/dictobject.c - PyFrozenDict_Type -
792792
## False positives
793793
Python/specialize.c - _Py_InitCleanup -
794794
Python/pystate.c - _no_tstate_sentinel -
795+
Modules/posixshims.h - _Py_shim_copy_file_range -
796+
Modules/posixshims.h - _Py_shim_memfd_create -
797+
Modules/posixshims.h - _Py_shim_pidfd_open -
798+
Modules/posixshims.h - _Py_shim_pidfd_getfd -

0 commit comments

Comments
 (0)