Skip to content

Commit b7a07aa

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.
1 parent f3fd9dc commit b7a07aa

4 files changed

Lines changed: 146 additions & 20 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: 11 additions & 11 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
@@ -10773,8 +10775,7 @@ os_wait_impl(PyObject *module)
1077310775

1077410776

1077510777
// This system call always crashes on older Android versions.
10776-
#if defined(__linux__) && defined(__NR_pidfd_open) && \
10777-
!(defined(__ANDROID__) && __ANDROID_API__ < 31)
10778+
#ifdef _Py_HAVE_PIDFD_OPEN
1077810779
/*[clinic input]
1077910780
os.pidfd_open
1078010781
pid: pid_t
@@ -10790,7 +10791,7 @@ static PyObject *
1079010791
os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags)
1079110792
/*[clinic end generated code: output=5c7252698947dc41 input=03058b32c389f874]*/
1079210793
{
10793-
int fd = syscall(__NR_pidfd_open, pid, flags);
10794+
int fd = _Py_pidfd_open(pid, flags);
1079410795
if (fd < 0) {
1079510796
return posix_error();
1079610797
}
@@ -10799,8 +10800,7 @@ os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags)
1079910800
#endif
1080010801

1080110802

10802-
#if defined(__linux__) && defined(__NR_pidfd_getfd) && \
10803-
!(defined(__ANDROID__) && __ANDROID_API__ < 31)
10803+
#ifdef _Py_HAVE_PIDFD_GETFD
1080410804
/*[clinic input]
1080510805
os.pidfd_getfd
1080610806
pidfd: int
@@ -10819,7 +10819,7 @@ os_pidfd_getfd_impl(PyObject *module, int pidfd, int targetfd,
1081910819
unsigned int flags)
1082010820
/*[clinic end generated code: output=e1a1415a13c7137f input=ef6417fb10deb1cc]*/
1082110821
{
10822-
int fd = syscall(__NR_pidfd_getfd, pidfd, targetfd, flags);
10822+
int fd = _Py_pidfd_getfd(pidfd, targetfd, flags);
1082310823
if (fd < 0) {
1082410824
return posix_error();
1082510825
}
@@ -13018,7 +13018,7 @@ os_pwritev_impl(PyObject *module, int fd, PyObject *buffers, Py_off_t offset,
1301813018
}
1301913019
#endif /* HAVE_PWRITEV */
1302013020

13021-
#ifdef HAVE_COPY_FILE_RANGE
13021+
#ifdef _Py_HAVE_COPY_FILE_RANGE
1302213022
/*[clinic input]
1302313023

1302413024
os.copy_file_range
@@ -13070,7 +13070,7 @@ os_copy_file_range_impl(PyObject *module, int src, int dst, Py_ssize_t count,
1307013070

1307113071
do {
1307213072
Py_BEGIN_ALLOW_THREADS
13073-
ret = copy_file_range(src, p_offset_src, dst, p_offset_dst, count, flags);
13073+
ret = _Py_copy_file_range(src, p_offset_src, dst, p_offset_dst, count, flags);
1307413074
Py_END_ALLOW_THREADS
1307513075
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
1307613076

@@ -13080,7 +13080,7 @@ os_copy_file_range_impl(PyObject *module, int src, int dst, Py_ssize_t count,
1308013080

1308113081
return PyLong_FromSsize_t(ret);
1308213082
}
13083-
#endif /* HAVE_COPY_FILE_RANGE*/
13083+
#endif /* _Py_HAVE_COPY_FILE_RANGE */
1308413084

1308513085
#if (defined(HAVE_SPLICE) && !defined(_AIX))
1308613086
/*[clinic input]
@@ -15774,7 +15774,7 @@ os_urandom_impl(PyObject *module, Py_ssize_t size)
1577415774
return PyBytesWriter_Finish(writer);
1577515775
}
1577615776

15777-
#ifdef HAVE_MEMFD_CREATE
15777+
#ifdef _Py_HAVE_MEMFD_CREATE
1577815778
/*[clinic input]
1577915779
os.memfd_create
1578015780

@@ -15790,7 +15790,7 @@ os_memfd_create_impl(PyObject *module, PyObject *name, unsigned int flags)
1579015790
int fd;
1579115791
const char *bytes = PyBytes_AS_STRING(name);
1579215792
Py_BEGIN_ALLOW_THREADS
15793-
fd = memfd_create(bytes, flags);
15793+
fd = _Py_memfd_create(bytes, flags);
1579415794
Py_END_ALLOW_THREADS
1579515795
if (fd == -1) {
1579615796
return PyErr_SetFromErrno(PyExc_OSError);

Modules/posixshims.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
/* A per-function cache plus a constructor that resolves the libc symbol once at
32+
load time. The __asm__ barrier after dlsym() is load-bearing under LTO:
33+
without it the compiler may tail-call dlsym(), breaking glibc's
34+
return-address caller lookup and crashing at startup (glibc BZ #34156). */
35+
#define _Py_SHIM_RESOLVER(func) \
36+
static void *_Py_shim_##func; \
37+
__attribute__((constructor)) \
38+
static void _Py_shim_##func##_resolve(void) { \
39+
void *p = dlsym(RTLD_DEFAULT, #func); \
40+
__asm__ volatile("" ::: "memory"); \
41+
_Py_shim_##func = p; \
42+
}
43+
44+
/* Body of an explicitly declared _Py_<func>() wrapper: call the resolved libc
45+
symbol if present, else fall back to syscall(__NR_<func>, ...). The cast is
46+
__typeof__ of _Py_<func> (the wrapper itself), not of <func>, which may be
47+
undeclared at build time. */
48+
#define _Py_SHIM_SYSCALL(func, ...) \
49+
if (_Py_shim_##func != NULL) { \
50+
return ((__typeof__(&_Py_##func)) _Py_shim_##func)(__VA_ARGS__); \
51+
} \
52+
return syscall(__NR_##func, __VA_ARGS__)
53+
54+
#endif /* _Py_HAVE_POSIX_SHIMS */
55+
56+
57+
/* ---- copy_file_range() (glibc 2.27) ------------------------------------- */
58+
59+
#if defined(_Py_HAVE_POSIX_SHIMS)
60+
# define _Py_HAVE_COPY_FILE_RANGE
61+
_Py_SHIM_RESOLVER(copy_file_range)
62+
/* off_t is 64-bit (_FILE_OFFSET_BITS=64), matching the kernel's loff_t. */
63+
static inline ssize_t
64+
_Py_copy_file_range(int fd_in, off_t *off_in, int fd_out, off_t *off_out,
65+
size_t len, unsigned int flags)
66+
{
67+
_Py_SHIM_SYSCALL(copy_file_range,
68+
fd_in, off_in, fd_out, off_out, len, flags);
69+
}
70+
71+
#elif defined(HAVE_COPY_FILE_RANGE)
72+
# define _Py_HAVE_COPY_FILE_RANGE
73+
# define _Py_copy_file_range copy_file_range
74+
#endif
75+
76+
77+
/* ---- memfd_create() (glibc 2.27) ---------------------------------------- */
78+
79+
#if defined(_Py_HAVE_POSIX_SHIMS)
80+
# define _Py_HAVE_MEMFD_CREATE
81+
_Py_SHIM_RESOLVER(memfd_create)
82+
static inline int
83+
_Py_memfd_create(const char *name, unsigned int flags)
84+
{
85+
_Py_SHIM_SYSCALL(memfd_create, name, flags);
86+
}
87+
88+
#elif defined(HAVE_MEMFD_CREATE)
89+
# define _Py_HAVE_MEMFD_CREATE
90+
# define _Py_memfd_create memfd_create
91+
#endif
92+
93+
94+
/* ---- pidfd_open(), pidfd_getfd() (glibc 2.36) --------------------------- *
95+
96+
Previously unconditional raw syscalls; the shim now prefers glibc's wrapper.
97+
Linux-only from the start (no non-Linux branch); the __NR_ and Android API
98+
floor from the original guards are preserved. */
99+
100+
#if defined(_Py_HAVE_POSIX_SHIMS) && defined(__NR_pidfd_open) \
101+
&& !(defined(__ANDROID__) && __ANDROID_API__ < 31)
102+
# define _Py_HAVE_PIDFD_OPEN
103+
_Py_SHIM_RESOLVER(pidfd_open)
104+
static inline int
105+
_Py_pidfd_open(pid_t pid, unsigned int flags)
106+
{
107+
_Py_SHIM_SYSCALL(pidfd_open, pid, flags);
108+
}
109+
#endif
110+
111+
#if defined(_Py_HAVE_POSIX_SHIMS) && defined(__NR_pidfd_getfd) \
112+
&& !(defined(__ANDROID__) && __ANDROID_API__ < 31)
113+
# define _Py_HAVE_PIDFD_GETFD
114+
_Py_SHIM_RESOLVER(pidfd_getfd)
115+
static inline int
116+
_Py_pidfd_getfd(int pidfd, int targetfd, unsigned int flags)
117+
{
118+
_Py_SHIM_SYSCALL(pidfd_getfd, pidfd, targetfd, flags);
119+
}
120+
#endif
121+
122+
#endif /* !Py_POSIXSHIMS_H */

0 commit comments

Comments
 (0)