add new Linux close_range() system call to bionic
See:
https://man7.org/linux/man-pages/man2/close_range.2.html
Note: 'man close_range' documents 'flags' as unsigned int,
while glibc unistd.h as just 'int'. Picking 'int' to match glibc,
though it probably doesn't matter.
BYPASS_INCLUSIVE_LANGUAGE_REASON=man is a cli command
Test: TreeHugger
Bug: 229913920
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: I1e2d1c8edc2ea28922d60f3ce3e534a784622cd1
diff --git a/libc/SECCOMP_ALLOWLIST_COMMON.TXT b/libc/SECCOMP_ALLOWLIST_COMMON.TXT
index c440f9b..6650d7e 100644
--- a/libc/SECCOMP_ALLOWLIST_COMMON.TXT
+++ b/libc/SECCOMP_ALLOWLIST_COMMON.TXT
@@ -74,5 +74,3 @@
int sched_rr_get_interval_time64(pid_t, timespec64*) lp32
# Since Linux 5.4, not in glibc. Probed for and conditionally used by ART.
int userfaultfd(int) all
-# Since Linux 5.9, used by POSIX_SPAWN_CLOEXEC_DEFAULT
-int close_range(unsigned int, unsigned int, int) all
diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index a09c614..1435e79 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -107,6 +107,7 @@
ssize_t __pwritev64v2:pwritev2(int, const struct iovec*, int, long, long, int) all
int __close:close(int) all
+int close_range(unsigned int, unsigned int, int) all
pid_t __getpid:getpid() all
int memfd_create(const char*, unsigned) all
int munmap(void*, size_t) all
diff --git a/libc/bionic/spawn.cpp b/libc/bionic/spawn.cpp
index 59f7631..7e80ef6 100644
--- a/libc/bionic/spawn.cpp
+++ b/libc/bionic/spawn.cpp
@@ -52,7 +52,7 @@
// mark all open fds except stdin/out/err as close-on-exec
static int cloexec_except_stdioe() {
// requires 5.11+ or ACK 5.10-T kernel, otherwise returns ENOSYS or EINVAL
- if (!syscall(SYS_close_range, 3, ~0U, CLOSE_RANGE_CLOEXEC)) return 0;
+ if (!close_range(3, ~0U, CLOSE_RANGE_CLOEXEC)) return 0;
// unfortunately getrlimit can lie:
// - both soft and hard limits can be lowered to 0, with fds still open, so it can underestimate
diff --git a/libc/include/unistd.h b/libc/include/unistd.h
index e360421..f142525 100644
--- a/libc/include/unistd.h
+++ b/libc/include/unistd.h
@@ -317,6 +317,22 @@
void swab(const void* __src, void* __dst, ssize_t __byte_count) __INTRODUCED_IN(28);
#endif
+/**
+ * [close_range(2)](https://man7.org/linux/man-pages/man2/close_range.2.html)
+ * performs an action (which depends on value of flags) on an inclusive range
+ * of file descriptors.
+ *
+ * Available since API level 34.
+ *
+ * Note: there is no emulation on too old kernels, hence this will fail with
+ * -1/ENOSYS on pre-5.9 kernels, -1/EINVAL for unsupported flags. In particular
+ * CLOSE_RANGE_CLOEXEC requires 5.11, though support was backported to Android
+ * Common Kernel 5.10-T.
+ *
+ * Returns 0 on success, and returns -1 and sets `errno` on failure.
+ */
+int close_range(unsigned int __min_fd, unsigned int __max_fd, int __flags) __INTRODUCED_IN(34);
+
#if defined(__BIONIC_INCLUDE_FORTIFY_HEADERS)
#define _UNISTD_H_
#include <bits/fortify/unistd.h>
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 7397b68..e64fe00 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1576,6 +1576,11 @@
pwritev64v2;
} LIBC_S;
+LIBC_U { # introduced=UpsideDownCake
+ global:
+ close_range;
+} LIBC_T;
+
LIBC_PRIVATE {
global:
__accept4; # arm x86
diff --git a/tests/unistd_test.cpp b/tests/unistd_test.cpp
index 6d7e687..293b45a 100644
--- a/tests/unistd_test.cpp
+++ b/tests/unistd_test.cpp
@@ -1648,3 +1648,23 @@
auto t1 = std::chrono::steady_clock::now();
ASSERT_GE(t1-t0, 1s);
}
+
+TEST(UNISTD_TEST, close_range) {
+#if defined(__GLIBC__)
+ GTEST_SKIP() << "glibc too old";
+#else // __GLIBC__
+ int fd = open("/proc/version", O_RDONLY);
+ ASSERT_GE(fd, 0);
+
+ // Try to close the file descriptor (this requires a 5.9+ kernel)
+ if (close_range(fd, fd, 0) == 0) {
+ // we can't close it *again*
+ ASSERT_EQ(close(fd), -1);
+ ASSERT_EQ(errno, EBADF);
+ } else {
+ ASSERT_EQ(errno, ENOSYS);
+ // since close_range() failed, we can close it normally
+ ASSERT_EQ(close(fd), 0);
+ }
+#endif // __GLIBC__
+}