Skip to content

Commit 9a8baaa

Browse files
committed
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 9a8baaa

3 files changed

Lines changed: 170 additions & 20 deletions

File tree

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: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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 handlers
10+
and the fork()/exec() window, and lets the cache skip synchronization.
11+
12+
Off Linux, each function keeps its classic build-time HAVE_* direct call. */
13+
14+
#ifndef Py_POSIXSHIMS_H
15+
#define Py_POSIXSHIMS_H
16+
17+
/* Needs ELF constructors, dlsym() and Linux syscall numbers. */
18+
#if defined(__linux__) && (defined(__GNUC__) || defined(__clang__)) \
19+
&& defined(HAVE_DLFCN_H) && defined(HAVE_SYS_SYSCALL_H)
20+
# define _Py_HAVE_POSIX_SHIMS
21+
#endif
22+
23+
#ifdef _Py_HAVE_POSIX_SHIMS
24+
25+
#include <dlfcn.h> // dlsym(), RTLD_DEFAULT
26+
#include <errno.h> // ENOSYS, errno
27+
#include <sys/syscall.h> // __NR_* syscall numbers
28+
#include <sys/types.h> // off_t, ssize_t
29+
#include <unistd.h> // syscall()
30+
31+
/* Flat "type1, name1, type2, name2, ..." pair plumbing: _Py_SHIM_DECL emits the
32+
declarator "t1 n1, t2 n2, ...", _Py_SHIM_NAME the forwarding list "n1, n2,
33+
...", _Py_SHIM_PAIRS counts pairs (max 8). GCC/Clang only, so the MSVC
34+
__VA_ARGS__ quirks don't apply. */
35+
36+
#define _Py_SHIM_CAT(a, b) _Py_SHIM_CAT_(a, b)
37+
#define _Py_SHIM_CAT_(a, b) a##b
38+
39+
#define _Py_SHIM_NTH(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
40+
#define _Py_SHIM_PAIRS(...) _Py_SHIM_NTH(__VA_ARGS__, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0)
41+
42+
#define _Py_SHIM_DECL_1(t, n) t n
43+
#define _Py_SHIM_DECL_2(t, n, ...) t n, _Py_SHIM_DECL_1(__VA_ARGS__)
44+
#define _Py_SHIM_DECL_3(t, n, ...) t n, _Py_SHIM_DECL_2(__VA_ARGS__)
45+
#define _Py_SHIM_DECL_4(t, n, ...) t n, _Py_SHIM_DECL_3(__VA_ARGS__)
46+
#define _Py_SHIM_DECL_5(t, n, ...) t n, _Py_SHIM_DECL_4(__VA_ARGS__)
47+
#define _Py_SHIM_DECL_6(t, n, ...) t n, _Py_SHIM_DECL_5(__VA_ARGS__)
48+
#define _Py_SHIM_DECL_7(t, n, ...) t n, _Py_SHIM_DECL_6(__VA_ARGS__)
49+
#define _Py_SHIM_DECL_8(t, n, ...) t n, _Py_SHIM_DECL_7(__VA_ARGS__)
50+
#define _Py_SHIM_DECL(...) _Py_SHIM_CAT(_Py_SHIM_DECL_, _Py_SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__)
51+
52+
#define _Py_SHIM_NAME_1(t, n) n
53+
#define _Py_SHIM_NAME_2(t, n, ...) n, _Py_SHIM_NAME_1(__VA_ARGS__)
54+
#define _Py_SHIM_NAME_3(t, n, ...) n, _Py_SHIM_NAME_2(__VA_ARGS__)
55+
#define _Py_SHIM_NAME_4(t, n, ...) n, _Py_SHIM_NAME_3(__VA_ARGS__)
56+
#define _Py_SHIM_NAME_5(t, n, ...) n, _Py_SHIM_NAME_4(__VA_ARGS__)
57+
#define _Py_SHIM_NAME_6(t, n, ...) n, _Py_SHIM_NAME_5(__VA_ARGS__)
58+
#define _Py_SHIM_NAME_7(t, n, ...) n, _Py_SHIM_NAME_6(__VA_ARGS__)
59+
#define _Py_SHIM_NAME_8(t, n, ...) n, _Py_SHIM_NAME_7(__VA_ARGS__)
60+
#define _Py_SHIM_NAME(...) _Py_SHIM_CAT(_Py_SHIM_NAME_, _Py_SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__)
61+
62+
/* The __asm__ barrier after dlsym() is load-bearing under LTO: without it the
63+
compiler may tail-call dlsym(), breaking glibc's return-address caller lookup
64+
and crashing at startup (glibc BZ #34156). */
65+
#define _Py_SHIM_RESOLVER(func) \
66+
static void *_Py_shim_##func; \
67+
__attribute__((constructor)) \
68+
static void _Py_shim_##func##_resolve(void) { \
69+
void *p = dlsym(RTLD_DEFAULT, #func); \
70+
__asm__ volatile("" ::: "memory"); \
71+
_Py_shim_##func = p; \
72+
}
73+
74+
#define _Py_SHIM_LIBC(func) \
75+
__typeof__(&_Py_##func) _Py_libc = \
76+
(__typeof__(&_Py_##func)) _Py_shim_##func
77+
78+
/* Define _Py_<func>() + resolver from a signature given as flat type,name pairs.
79+
The _NAMED form is for wrappers whose name differs from the syscall they issue
80+
(e.g. eventfd() -> eventfd2): `nr` names the __NR_<nr> the fallback uses. */
81+
#define _Py_DEFINE_SYSCALL_SHIM_NAMED(func, nr, ret, ...) \
82+
_Py_SHIM_RESOLVER(func) \
83+
static inline ret _Py_##func(_Py_SHIM_DECL(__VA_ARGS__)) { \
84+
_Py_SHIM_LIBC(func); \
85+
if (_Py_libc != NULL) { \
86+
return _Py_libc(_Py_SHIM_NAME(__VA_ARGS__)); \
87+
} \
88+
return syscall(__NR_##nr, _Py_SHIM_NAME(__VA_ARGS__)); \
89+
}
90+
91+
#define _Py_DEFINE_SYSCALL_SHIM(func, ret, ...) \
92+
_Py_DEFINE_SYSCALL_SHIM_NAMED(func, func, ret, __VA_ARGS__)
93+
94+
/* ---- copy_file_range() (glibc 2.27) ------------------------------------- */
95+
96+
#if defined(_Py_HAVE_POSIX_SHIMS)
97+
# define _Py_HAVE_COPY_FILE_RANGE
98+
/* off_t is 64-bit (_FILE_OFFSET_BITS=64), matching the kernel's loff_t. */
99+
_Py_DEFINE_SYSCALL_SHIM(copy_file_range, ssize_t,
100+
int, fd_in,
101+
off_t *, off_in,
102+
int, fd_out,
103+
off_t *, off_out,
104+
size_t, len,
105+
unsigned int, flags)
106+
107+
#elif defined(HAVE_COPY_FILE_RANGE)
108+
# define _Py_HAVE_COPY_FILE_RANGE
109+
# define _Py_copy_file_range copy_file_range
110+
#endif
111+
112+
113+
/* ---- memfd_create() (glibc 2.27) ---------------------------------------- */
114+
115+
#if defined(_Py_HAVE_POSIX_SHIMS)
116+
# define _Py_HAVE_MEMFD_CREATE
117+
_Py_DEFINE_SYSCALL_SHIM(memfd_create, int,
118+
const char *, name,
119+
unsigned int, flags)
120+
121+
#elif defined(HAVE_MEMFD_CREATE)
122+
# define _Py_HAVE_MEMFD_CREATE
123+
# define _Py_memfd_create memfd_create
124+
#endif
125+
126+
127+
/* ---- pidfd_open(), pidfd_getfd() (glibc 2.36) --------------------------- *
128+
129+
Previously unconditional raw syscalls; the shim now prefers glibc's wrapper.
130+
Linux-only from the start (no non-Linux branch); the __NR_ and Android API
131+
floor from the original guards are preserved. */
132+
133+
#if defined(_Py_HAVE_POSIX_SHIMS) && defined(__NR_pidfd_open) \
134+
&& !(defined(__ANDROID__) && __ANDROID_API__ < 31)
135+
# define _Py_HAVE_PIDFD_OPEN
136+
_Py_DEFINE_SYSCALL_SHIM(pidfd_open, int,
137+
pid_t, pid,
138+
unsigned int, flags)
139+
#endif
140+
141+
#if defined(_Py_HAVE_POSIX_SHIMS) && defined(__NR_pidfd_getfd) \
142+
&& !(defined(__ANDROID__) && __ANDROID_API__ < 31)
143+
# define _Py_HAVE_PIDFD_GETFD
144+
_Py_DEFINE_SYSCALL_SHIM(pidfd_getfd, int,
145+
int, pidfd,
146+
int, targetfd,
147+
unsigned int, flags)
148+
#endif
149+
150+
#endif /* !Py_POSIXSHIMS_H */

0 commit comments

Comments
 (0)