Skip to content

Commit e5fbe3b

Browse files
committed
gh-153711: Guard dup3/pipe2 with __builtin_available on Apple platforms
Add HAVE_DUP3_RUNTIME / HAVE_PIPE2_RUNTIME weak-linking so Xcode 27 SDKs do not crash on macOS < 27 when the symbols are weak_import NULL.
1 parent f5f5059 commit e5fbe3b

4 files changed

Lines changed: 66 additions & 13 deletions

File tree

Doc/library/os.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,8 @@ or `the MSDN <https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Windo
14481448
Return a pair of file descriptors ``(r, w)`` usable for reading and writing,
14491449
respectively.
14501450

1451-
.. availability:: Unix, not WASI, not macOS, not iOS.
1451+
.. availability:: Unix, not WASI. Availability on macOS requires macOS 27.0
1452+
or later; availability on iOS requires iOS 27.0 or later.
14521453

14531454
.. versionadded:: 3.3
14541455

Lib/test/test_os/test_posix.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,6 +2388,22 @@ def test_pwritev(self):
23882388
self.assertNotHasAttr(os, "pwritev")
23892389
self.assertNotHasAttr(os, "preadv")
23902390

2391+
def test_pipe2(self):
2392+
self._verify_available("HAVE_PIPE2")
2393+
if self.mac_ver >= (27, 0):
2394+
self.assertHasAttr(os, "pipe2")
2395+
else:
2396+
self.assertNotHasAttr(os, "pipe2")
2397+
2398+
def test_dup3(self):
2399+
self._verify_available("HAVE_DUP3")
2400+
r, w = os.pipe()
2401+
self.addCleanup(os.close, r)
2402+
self.addCleanup(os.close, w)
2403+
# Must not crash even when dup3 unavailable at runtime.
2404+
# os.dup2 returns fd2 (here w); do not double-close.
2405+
os.dup2(r, w, inheritable=False)
2406+
23912407
def test_stat(self):
23922408
self._verify_available("HAVE_FSTATAT")
23932409
if self.mac_ver >= (10, 10):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Guard ``os.dup2`` and ``os.pipe``/``os.pipe2`` against calling ``dup3``/``pipe2``
2+
when the symbols are weak-imported but unavailable at runtime (Xcode 27 SDKs on
3+
older macOS).

Modules/posixmodule.c

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,8 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
504504
# define HAVE_MKFIFOAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
505505
# define HAVE_MKNODAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
506506
# define HAVE_PTSNAME_R_RUNTIME __builtin_available(macOS 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.3, *)
507+
# define HAVE_DUP3_RUNTIME __builtin_available(macOS 27.0, iOS 27.0, tvOS 27.0, watchOS 27.0, *)
508+
# define HAVE_PIPE2_RUNTIME __builtin_available(macOS 27.0, iOS 27.0, tvOS 27.0, watchOS 27.0, *)
507509

508510
# define HAVE_POSIX_SPAWN_SETSID_RUNTIME __builtin_available(macOS 10.15, *)
509511

@@ -589,6 +591,14 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
589591
# define HAVE_PTSNAME_R_RUNTIME (ptsname_r != NULL)
590592
# endif
591593

594+
# ifdef HAVE_DUP3
595+
# define HAVE_DUP3_RUNTIME (dup3 != NULL)
596+
# endif
597+
598+
# ifdef HAVE_PIPE2
599+
# define HAVE_PIPE2_RUNTIME (pipe2 != NULL)
600+
# endif
601+
592602
#endif
593603

594604
#ifdef HAVE_FUTIMESAT
@@ -619,6 +629,8 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
619629
# define HAVE_MKFIFOAT_RUNTIME 1
620630
# define HAVE_MKNODAT_RUNTIME 1
621631
# define HAVE_PTSNAME_R_RUNTIME 1
632+
# define HAVE_DUP3_RUNTIME 1
633+
# define HAVE_PIPE2_RUNTIME 1
622634
#endif
623635

624636

@@ -11910,17 +11922,22 @@ os_dup2_impl(PyObject *module, int fd, int fd2, int inheritable)
1191011922

1191111923
#ifdef HAVE_DUP3
1191211924
if (!inheritable && dup3_works != 0) {
11913-
Py_BEGIN_ALLOW_THREADS
11914-
res = dup3(fd, fd2, O_CLOEXEC);
11915-
Py_END_ALLOW_THREADS
11916-
if (res < 0) {
11917-
if (dup3_works == -1)
11918-
dup3_works = (errno != ENOSYS);
11919-
if (dup3_works) {
11920-
posix_error();
11921-
return -1;
11925+
if (HAVE_DUP3_RUNTIME) {
11926+
Py_BEGIN_ALLOW_THREADS
11927+
res = dup3(fd, fd2, O_CLOEXEC);
11928+
Py_END_ALLOW_THREADS
11929+
if (res < 0) {
11930+
if (dup3_works == -1)
11931+
dup3_works = (errno != ENOSYS);
11932+
if (dup3_works) {
11933+
posix_error();
11934+
return -1;
11935+
}
1192211936
}
1192311937
}
11938+
else {
11939+
dup3_works = 0;
11940+
}
1192411941
}
1192511942

1192611943
if (inheritable || dup3_works == 0)
@@ -12779,9 +12796,13 @@ os_pipe_impl(PyObject *module)
1277912796
#else
1278012797

1278112798
#ifdef HAVE_PIPE2
12782-
Py_BEGIN_ALLOW_THREADS
12783-
res = pipe2(fds, O_CLOEXEC);
12784-
Py_END_ALLOW_THREADS
12799+
res = -1;
12800+
errno = ENOSYS;
12801+
if (HAVE_PIPE2_RUNTIME) {
12802+
Py_BEGIN_ALLOW_THREADS
12803+
res = pipe2(fds, O_CLOEXEC);
12804+
Py_END_ALLOW_THREADS
12805+
}
1278512806

1278612807
if (res != 0 && errno == ENOSYS)
1278712808
{
@@ -18811,6 +18832,18 @@ posixmodule_exec(PyObject *m)
1881118832
}
1881218833
#endif
1881318834

18835+
#if defined(HAVE_PIPE2)
18836+
if (HAVE_PIPE2_RUNTIME) {} else {
18837+
PyObject *dct = PyModule_GetDict(m);
18838+
if (dct == NULL) {
18839+
return -1;
18840+
}
18841+
if (PyDict_PopString(dct, "pipe2", NULL) < 0) {
18842+
return -1;
18843+
}
18844+
}
18845+
#endif
18846+
1881418847
#ifdef HAVE_STATX
1881518848
if (statx == NULL) {
1881618849
PyObject* dct = PyModule_GetDict(m);

0 commit comments

Comments
 (0)