Snap for 15110852 from d14e55fd20e12b1ce21e1b259734358a153a5b4a to 26Q2-release Change-Id: Idba9802640093e7e5bac81f14ff5f49f6a2ab890
diff --git a/benchmarks/string_benchmark.cpp b/benchmarks/string_benchmark.cpp index 1da54cd..a1653fd 100644 --- a/benchmarks/string_benchmark.cpp +++ b/benchmarks/string_benchmark.cpp
@@ -18,6 +18,8 @@ #include <stdint.h> #include <string.h> +#include <concepts> + #include <benchmark/benchmark.h> #include <util.h> @@ -359,8 +361,9 @@ } BIONIC_BENCHMARK_WITH_ARG(BM_string_strstr, "AT_ALIGNED_TWOBUF"); -template <char* fn(const char*, int)> -void BenchStrChr(benchmark::State& state) { +template <typename Fn> + requires std::invocable<Fn, char*, int> +void BenchStrChr(benchmark::State& state, Fn strchr_fn) { const size_t nbytes = state.range(0); const size_t haystack_alignment = state.range(1); @@ -369,7 +372,7 @@ haystack_aligned[nbytes-1] = '\0'; while (state.KeepRunning()) { - if (fn(haystack_aligned, 'y') != nullptr) { + if (strchr_fn(haystack_aligned, 'y') != nullptr) { errx(1, "ERROR: found a char that wasn't there."); } } @@ -378,15 +381,29 @@ } static void BM_string_strchr(benchmark::State& state) { - BenchStrChr<strchr>(state); + BenchStrChr(state, [](const char* s, int c) { return strchr(s, c); }); } BIONIC_BENCHMARK_WITH_ARG(BM_string_strchr, "AT_ALIGNED_ONEBUF"); static void BM_string_strrchr(benchmark::State& state) { - BenchStrChr<strrchr>(state); + BenchStrChr(state, [](const char* s, int c) { return strrchr(s, c); }); } BIONIC_BENCHMARK_WITH_ARG(BM_string_strrchr, "AT_ALIGNED_ONEBUF"); +#if defined(__BIONIC__) +static void BM_string_strchr_chk(benchmark::State& state) { + const size_t nbytes = state.range(0); + BenchStrChr(state, [nbytes](const char* s, int c) { return __strchr_chk(s, c, nbytes); }); +} +BIONIC_BENCHMARK_WITH_ARG(BM_string_strchr_chk, "AT_ALIGNED_ONEBUF"); + +static void BM_string_strrchr_chk(benchmark::State& state) { + const size_t nbytes = state.range(0); + BenchStrChr(state, [nbytes](const char* s, int c) { return __strrchr_chk(s, c, nbytes); }); +} +BIONIC_BENCHMARK_WITH_ARG(BM_string_strrchr_chk, "AT_ALIGNED_ONEBUF"); +#endif // __BIONIC__ + template <typename T, T fn(const char*, const char*)> void BenchStrSpn(benchmark::State& state, const char* delims) { const size_t nbytes = state.range(0);
diff --git a/libc/arch-arm/bionic/__bionic_clone.S b/libc/arch-arm/bionic/__bionic_clone.S index 9d7c8cf..db42b91 100644 --- a/libc/arch-arm/bionic/__bionic_clone.S +++ b/libc/arch-arm/bionic/__bionic_clone.S
@@ -49,13 +49,13 @@ # Are we the child? movs r0, r0 - beq L(child) + beq L(bc_child) # In the parent, reload saved registers then either return or set errno. ldmfd sp!, {r4, r5, r6, r7} DO_SYSCALL_RETURN -L(child): +L(bc_child): # We're in the child now. Set the end of the frame record chain. mov fp, #0 # Setting lr to 0 will make the unwinder stop at __start_thread. @@ -65,3 +65,33 @@ mov r1, r6 b __start_thread END(__bionic_clone) + +// pid_t __bionic_clone3(struct clone_args* cl_args, size_t size, int (*fn)(void*), void* arg); +ENTRY_PRIVATE(__bionic_clone3) + # Save r7 to the parent stack as it is clobbered by the system call number. + push {r7} + .cfi_def_cfa_offset 4 + .cfi_rel_offset r7, 0 + + # Make the system call. + ldr r7, =__NR_clone3 + swi #0 + + # Are we the child? + movs r0, r0 + beq L(bc3_child) + + # In the parent, reload saved registers then either return or set errno. + pop {r7} + DO_SYSCALL_RETURN + +L(bc3_child): + # We're in the child now. Set the end of the frame record chain. + mov fp, #0 + # Setting lr to 0 will make the unwinder stop at __start_thread. + mov lr, #0 + # Call __start_thread with the 'fn' and 'arg' + mov r0, r2 + mov r1, r3 + b __start_thread +END(__bionic_clone3)
diff --git a/libc/arch-arm64/bionic/__bionic_clone.S b/libc/arch-arm64/bionic/__bionic_clone.S index 81cbcda..829bb51 100644 --- a/libc/arch-arm64/bionic/__bionic_clone.S +++ b/libc/arch-arm64/bionic/__bionic_clone.S
@@ -36,12 +36,12 @@ svc #0 # Are we the child? - cbz x0, L(child) + cbz x0, L(bc_child) # We're either the parent or the syscall failed. DO_SYSCALL_RETURN -L(child): +L(bc_child): # We're in the child now. Set the end of the frame record chain. mov fp, #0 # Setting lr to 0 will make the unwinder stop at __start_thread. @@ -52,4 +52,27 @@ b __start_thread END(__bionic_clone) +// pid_t __bionic_clone3(struct clone_args* cl_args, size_t size, int (*fn)(void*), void* arg); +ENTRY_PRIVATE(__bionic_clone3) + # Make the system call. + mov x8, __NR_clone3 + svc #0 + + # Are we the child? + cbz x0, L(bc3_child) + + # We're either the parent or the syscall failed. + DO_SYSCALL_RETURN + +L(bc3_child): + # We're in the child now. Set the end of the frame record chain. + mov fp, #0 + # Setting lr to 0 will make the unwinder stop at __start_thread. + mov lr, #0 + # Call __start_thread with the 'fn' and 'arg' stored in x2 and x3. + mov x0, x2 + mov x1, x3 + b __start_thread +END(__bionic_clone3) + NOTE_GNU_PROPERTY()
diff --git a/libc/arch-riscv64/bionic/__bionic_clone.S b/libc/arch-riscv64/bionic/__bionic_clone.S index c236260..bdb617f 100644 --- a/libc/arch-riscv64/bionic/__bionic_clone.S +++ b/libc/arch-riscv64/bionic/__bionic_clone.S
@@ -36,12 +36,12 @@ ecall # Are we the child? - beqz a0, L(child) + beqz a0, L(bc_child) # We're either the parent or the syscall failed. DO_SYSCALL_RETURN -L(child): +L(bc_child): # We're in the child now. Set the end of the frame record chain. li fp, 0 # Setting ra to 0 will make the unwinder stop at __start_thread. @@ -51,3 +51,26 @@ mv a1, a6 tail __start_thread END(__bionic_clone) + +// pid_t __bionic_clone3(struct clone_args* cl_args, size_t size, int (*fn)(void*), void* arg); +ENTRY_PRIVATE(__bionic_clone3) + # Make the system call. + li a7, __NR_clone3 + ecall + + # Are we the child? + beqz a0, L(bc3_child) + + # We're either the parent or the syscall failed. + DO_SYSCALL_RETURN + +L(bc3_child): + # We're in the child now. Set the end of the frame record chain. + li fp, 0 + # Setting ra to 0 will make the unwinder stop at __start_thread. + li ra, 0 + # Call __start_thread with the 'fn' and 'arg' stored in a2 and a3. + mv a0, a2 + mv a1, a3 + tail __start_thread +END(__bionic_clone3)
diff --git a/libc/arch-x86/bionic/__bionic_clone.S b/libc/arch-x86/bionic/__bionic_clone.S index 2743e75..cfe5ea5 100644 --- a/libc/arch-x86/bionic/__bionic_clone.S +++ b/libc/arch-x86/bionic/__bionic_clone.S
@@ -63,3 +63,68 @@ .cfi_restore ebx ret END(__bionic_clone) + +// pid_t __bionic_clone3(struct clone_args* cl_args, size_t size, int (*fn)(void*), void* arg); +ENTRY_PRIVATE(__bionic_clone3) + pushl %ebx + .cfi_adjust_cfa_offset 4 + .cfi_rel_offset ebx, 0 + pushl %esi + .cfi_adjust_cfa_offset 4 + .cfi_rel_offset esi, 0 + pushl %edi + .cfi_adjust_cfa_offset 4 + .cfi_rel_offset edi, 0 + + # Load system call arguments into registers. + movl 16(%esp), %ebx # cl_args + movl 20(%esp), %ecx # size + + # The child stack pointer accessed through `cl_args` points to the top + # of the memory region. Rather than duplicate the kernel's stack + # alignment we will store the values in register that won't be + # clobbered by the system call and then push them onto the child stack + # before calling __start_thread. + movl 24(%esp), %esi # Copy 'fn'. + movl 28(%esp), %edi # Copy 'arg'. + + # Make the system call. + movl $__NR_clone3, %eax + int $0x80 + + # Check result. + testl %eax, %eax + jz L(bc3_child) + jg L(bc3_parent) + + # An error occurred, so set errno and return -1. + pushl %eax + call __set_errno_internal + addl $4, %esp + jmp L(bc3_return) + +L(bc3_child): + # We don't want anyone to unwind past this point. + .cfi_undefined %eip + .cfi_undefined %ebp + xorl %ebp, %ebp + + pushl %edi # Push 'arg' + pushl %esi # Push 'fn' + call __start_thread + hlt + +L(bc3_parent): + # We're the parent; nothing to do. +L(bc3_return): + popl %edi + .cfi_adjust_cfa_offset -4 + .cfi_restore edi + popl %esi + .cfi_adjust_cfa_offset -4 + .cfi_restore esi + popl %ebx + .cfi_adjust_cfa_offset -4 + .cfi_restore ebx + ret +END(__bionic_clone3)
diff --git a/libc/arch-x86_64/bionic/__bionic_clone.S b/libc/arch-x86_64/bionic/__bionic_clone.S index 514decb..7671a06 100644 --- a/libc/arch-x86_64/bionic/__bionic_clone.S +++ b/libc/arch-x86_64/bionic/__bionic_clone.S
@@ -67,3 +67,32 @@ call __start_thread hlt END(__bionic_clone) + +// pid_t __bionic_clone3(struct clone_args* cl_args, size_t size, int (*fn)(void*), void* arg); +ENTRY_PRIVATE(__bionic_clone3) + # Save the arg pointer to %r8, because %rcx is clobbered by the system + # call. + movq %rcx, %r8 + + # Make the system call. + movl $__NR_clone3, %eax + syscall + + # Check result. + testq %rax, %rax + jz L(bc3_child) + + # We're either the parent or the syscall failed. + DO_SYSCALL_RETURN + +L(bc3_child): + # We don't want anyone to unwind past this point. + .cfi_undefined %rip + .cfi_undefined %rbp + xorl %ebp, %ebp + + movq %rdx, %rdi + movq %r8, %rsi + call __start_thread + hlt +END(__bionic_clone3)
diff --git a/libc/bionic/clone.cpp b/libc/bionic/clone.cpp index 8ec5623..f03f5ff 100644 --- a/libc/bionic/clone.cpp +++ b/libc/bionic/clone.cpp
@@ -37,10 +37,12 @@ #include "pthread_internal.h" -#include "private/bionic_defs.h" #include "platform/bionic/macros.h" +#include "private/bionic_defs.h" extern "C" pid_t __bionic_clone(uint32_t flags, void* child_stack, int* parent_tid, void* tls, int* child_tid, int (*fn)(void*), void* arg); +extern "C" pid_t __bionic_clone3(clone_args* cl_args, size_t cl_args_size, int (*fn)(void*), + void* arg); extern "C" __noreturn void __exit(int status); // Called from the __bionic_clone assembler to call the thread function then exit. @@ -57,6 +59,57 @@ __exit(status); } +struct clone_id_info { + pthread_internal_t* self; + pid_t parent_pid; + pid_t caller_tid; +}; + +static inline clone_id_info clone_prologue(int flags) { + // Remember the parent pid and invalidate the cached value while we clone. + pthread_internal_t* self = __get_thread(); + pid_t parent_pid = self->invalidate_cached_pid(); + + // Remmber the caller's tid so that it can be restored in the parent after clone. + pid_t caller_tid = self->tid; + // Invalidate the tid before the syscall. The value is lazily cached in gettid(), + // and it will be updated by fork() and pthread_create(). We don't do this if + // we are sharing address space with the child. + if (!(flags & (CLONE_VM | CLONE_VFORK))) { + self->tid = -1; + } + +#if defined(__aarch64__) + // AAPCS64 defines SME ZA interface as private for clone(), which means that ZA state on entry + // can be "dormant" or "off", while on return it can be unchanged or "off". + // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#za-interfaces + // Handling the dormant state would make the code unnecessary complex, so for simplicity turn + // ZA state off on entry which ensures that the state on return will be "off" as well. + __arm_za_disable(); +#endif + + return {self, parent_pid, caller_tid}; +} + +static inline int clone_epilogue(clone_id_info& ciinfo, int clone_result) { + if (clone_result != 0) { + // We're the parent, so put our known pid and tid back in place. + // We leave the child without a cached pid and tid, but: + // 1. pthread_create gives its children their own pthread_internal_t with the correct pid and + // tid. + // 2. fork uses CLONE_CHILD_SETTID to get the new pid/tid. + // 3. The tid is lazily fetched in gettid(). + // If any other cases become important, we could use a double trampoline like __pthread_start. + ciinfo.self->tid = ciinfo.caller_tid; + ciinfo.self->set_cached_pid(ciinfo.parent_pid); + } else if (ciinfo.self->tid == -1) { + ciinfo.self->tid = syscall(__NR_gettid); + ciinfo.self->set_cached_pid(ciinfo.self->tid); + } + + return clone_result; +} + __BIONIC_WEAK_FOR_NATIVE_BRIDGE int clone(int (*fn)(void*), void* child_stack, int flags, void* arg, ...) { int* parent_tid = nullptr; @@ -87,27 +140,7 @@ child_stack_addr &= ~0xf; child_stack = reinterpret_cast<void*>(child_stack_addr); - // Remember the parent pid and invalidate the cached value while we clone. - pthread_internal_t* self = __get_thread(); - pid_t parent_pid = self->invalidate_cached_pid(); - - // Remmber the caller's tid so that it can be restored in the parent after clone. - pid_t caller_tid = self->tid; - // Invalidate the tid before the syscall. The value is lazily cached in gettid(), - // and it will be updated by fork() and pthread_create(). We don't do this if - // we are sharing address space with the child. - if (!(flags & (CLONE_VM|CLONE_VFORK))) { - self->tid = -1; - } - -#if defined(__aarch64__) - // AAPCS64 defines SME ZA interface as private for clone(), which means that ZA state on entry - // can be "dormant" or "off", while on return it can be unchanged or "off". - // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#za-interfaces - // Handling the dormant state would make the code unnecessary complex, so for simplicity turn - // ZA state off on entry which ensures that the state on return will be "off" as well. - __arm_za_disable(); -#endif + clone_id_info ciinfo = clone_prologue(flags); // Actually do the clone. int clone_result; @@ -121,19 +154,29 @@ #endif } - if (clone_result != 0) { - // We're the parent, so put our known pid and tid back in place. - // We leave the child without a cached pid and tid, but: - // 1. pthread_create gives its children their own pthread_internal_t with the correct pid and tid. - // 2. fork uses CLONE_CHILD_SETTID to get the new pid/tid. - // 3. The tid is lazily fetched in gettid(). - // If any other cases become important, we could use a double trampoline like __pthread_start. - self->set_cached_pid(parent_pid); - self->tid = caller_tid; - } else if (self->tid == -1) { - self->tid = syscall(__NR_gettid); - self->set_cached_pid(self->tid); + return clone_epilogue(ciinfo, clone_result); +} + +__BIONIC_WEAK_FOR_NATIVE_BRIDGE +int clone3(struct clone_args* cl_args, size_t size, int (*fn)(void*), void* arg) { + bool invalid_args = (cl_args == nullptr) || ((cl_args->flags & CLONE_VM) && fn == nullptr) || + (fn == nullptr && cl_args->stack != 0) || + (fn != nullptr && cl_args->stack == 0); + + if (invalid_args) { + errno = EINVAL; + return -1; } - return clone_result; + clone_id_info ciinfo = clone_prologue(cl_args->flags); + + int clone_result; + if (fn != nullptr) { + // This call does not return in the child process. + clone_result = __bionic_clone3(cl_args, size, fn, arg); + } else { + clone_result = syscall(SYS_clone3, cl_args, size); + } + + return clone_epilogue(ciinfo, clone_result); }
diff --git a/libc/include/sched.h b/libc/include/sched.h index 3d9ee19..8f2b1ac 100644 --- a/libc/include/sched.h +++ b/libc/include/sched.h
@@ -34,6 +34,7 @@ */ #include <sys/cdefs.h> +#include <stddef.h> #include <bits/timespec.h> #include <linux/sched.h> @@ -178,6 +179,19 @@ #if defined(__USE_GNU) /** + * [clone3(2)](https://man7.org/linux/man-pages/man2/clone3.2.html ) + * creates a new child process. + * + * Returns the pid of the child to the caller on success and + * returns -1 and sets `errno` on failure. + * + * Available since API level 38. + */ +int clone3(struct clone_args* __cl_args, size_t __size, int (* __BIONIC_COMPLICATED_NULLNESS __fn)(void* __BIONIC_COMPLICATED_NULLNESS), void* _Nullable __arg) __INTRODUCED_IN(38); +#endif + +#if defined(__USE_GNU) +/** * [unshare(2)](https://man7.org/linux/man-pages/man2/unshare.2.html) * disassociates part of the caller's execution context. *
diff --git a/libc/libc.map.txt b/libc/libc.map.txt index 1ae3c5e..b0633eb 100644 --- a/libc/libc.map.txt +++ b/libc/libc.map.txt
@@ -275,6 +275,7 @@ clock_nanosleep; clock_settime; clone; + clone3; # introduced=38 close; closedir; closelog; @@ -1627,6 +1628,9 @@ sched_setattr; } LIBC_36; +# Per API-level lists are no longer used. Add new symbols to the `LIBC` list +# and add the appropriate `# introduced=<LEVEL>` annotation. + LIBC_PRIVATE { global: __accept4; # arm x86
diff --git a/tests/grp_pwd_test.cpp b/tests/grp_pwd_test.cpp index d40587d..eed6459 100644 --- a/tests/grp_pwd_test.cpp +++ b/tests/grp_pwd_test.cpp
@@ -509,6 +509,43 @@ } } + // AID_SDV* (1099-1102) was added in API level 37, but "trunk stable" means + // that the 2025Q* builds are tested with the _previous_ release's CTS. + if (android::base::GetIntProperty("ro.build.version.sdk", 0) == 36) { +#if !defined(AID_SDV_SD_AGENT) +#define AID_SDV_SD_AGENT 1099 +#endif +#if !defined(AID_SDV_DT_AGENT) +#define AID_SDV_DT_AGENT 1100 +#endif +#if !defined(AID_SDV_RPC_AGENT) +#define AID_SDV_RPC_AGENT 1101 +#endif +#if !defined(AID_SDV_INIT_OPEN_DICE) +#define AID_SDV_INIT_OPEN_DICE 1102 +#endif + ids.erase(AID_SDV_SD_AGENT); + expected_ids.erase(AID_SDV_SD_AGENT); + if (getpwuid(AID_SDV_SD_AGENT)) { + EXPECT_STREQ(getpwuid(AID_SDV_SD_AGENT)->pw_name, "sdv_sd_agent"); + } + ids.erase(AID_SDV_DT_AGENT); + expected_ids.erase(AID_SDV_DT_AGENT); + if (getpwuid(AID_SDV_DT_AGENT)) { + EXPECT_STREQ(getpwuid(AID_SDV_DT_AGENT)->pw_name, "sdv_dt_agent"); + } + ids.erase(AID_SDV_RPC_AGENT); + expected_ids.erase(AID_SDV_RPC_AGENT); + if (getpwuid(AID_SDV_RPC_AGENT)) { + EXPECT_STREQ(getpwuid(AID_SDV_RPC_AGENT)->pw_name, "sdv_rpc_agent"); + } + ids.erase(AID_SDV_INIT_OPEN_DICE); + expected_ids.erase(AID_SDV_INIT_OPEN_DICE); + if (getpwuid(AID_SDV_INIT_OPEN_DICE)) { + EXPECT_STREQ(getpwuid(AID_SDV_INIT_OPEN_DICE)->pw_name, "sdv_init_open_dice"); + } + } + EXPECT_EQ(expected_ids, ids) << return_differences(); } #endif
diff --git a/tests/sched_test.cpp b/tests/sched_test.cpp index ee2e572..2de03aa 100644 --- a/tests/sched_test.cpp +++ b/tests/sched_test.cpp
@@ -18,22 +18,49 @@ #include <errno.h> #include <sched.h> +#include <stdint.h> #include <sys/types.h> #include <sys/wait.h> #include "utils.h" -static int child_fn(void* i_ptr) { +// Used to test clone/clone3 when setting CLONE_VM +static int child_fn_CLONE_VM(void* i_ptr) { *reinterpret_cast<int*>(i_ptr) = 42; return 123; } +#if defined(__BIONIC__) +static void align_clone3_stack(clone_args& cl_args) { + // Ensure that both the top and bottom of the child stack are 16-byte + // aligned. The `clone3` system call expects that the provided pointer + // refers to the lowest byte in the stack area. + uint64_t stack_ptr_misalignment = cl_args.stack & 0xf; + if (stack_ptr_misalignment != 0) { + uint64_t alignment_offset = 16 - stack_ptr_misalignment; + cl_args.stack += alignment_offset; + cl_args.stack_size -= alignment_offset; + cl_args.stack_size &= ~0xf; + } +} + +// Used to test clone/clone3 when not setting CLONE_VM. Useful for testing the +// argument juggling code independent of virtual memory shenanigans. +static int child_fn_no_CLONE_VM(void* arg) { + if (reinterpret_cast<uintptr_t>(arg) == 42) { + return 130; + } else { + return 140; + } +} +#endif + TEST(sched, clone) { #if defined(__BIONIC__) void* child_stack[1024]; int i = 0; - pid_t tid = clone(child_fn, &child_stack[1024], CLONE_VM, &i); + pid_t tid = clone(child_fn_CLONE_VM, &child_stack[1024], CLONE_VM, &i); int status; ASSERT_EQ(tid, TEMP_FAILURE_RETRY(waitpid(tid, &status, __WCLONE))); @@ -54,12 +81,114 @@ // Check that our hand-written clone assembler sets errno correctly on failure. uintptr_t fake_child_stack[16]; // If CLONE_THREAD is set, CLONE_SIGHAND must be set too. - ASSERT_ERRNO_FAILURE(EINVAL, -1, clone(child_fn, &fake_child_stack[16], CLONE_THREAD, nullptr)); + ASSERT_ERRNO_FAILURE(EINVAL, -1, + clone(child_fn_CLONE_VM, &fake_child_stack[16], CLONE_THREAD, nullptr)); } TEST(sched, clone_null_child_stack) { int i = 0; - ASSERT_ERRNO_FAILURE(EINVAL, -1, clone(child_fn, nullptr, CLONE_VM, &i)); + ASSERT_ERRNO_FAILURE(EINVAL, -1, clone(child_fn_CLONE_VM, nullptr, CLONE_VM, &i)); +} + +TEST(sched, clone3) { +#if defined(__BIONIC__) + void* child_stack[1024]; + + clone_args cl_args = {}; + cl_args.stack = reinterpret_cast<uint64_t>(&child_stack); + cl_args.stack_size = sizeof(child_stack); + cl_args.flags = CLONE_VM; + cl_args.exit_signal = SIGCHLD; + align_clone3_stack(cl_args); + + int i = 0; + pid_t tid = clone3(&cl_args, sizeof(struct clone_args), child_fn_CLONE_VM, &i); + + ASSERT_NE(-1, tid); + + int status; + ASSERT_EQ(tid, TEMP_FAILURE_RETRY(waitpid(tid, &status, __WALL))); + + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(123, WEXITSTATUS(status)); + + ASSERT_EQ(42, i); +#else + GTEST_SKIP() << "glibc does not export a clone3 wrapper"; +#endif +} + +TEST(sched, clone3_without_fn) { +#if defined(__BIONIC__) + clone_args cl_args = {.exit_signal = SIGCHLD}; + + pid_t tid = clone3(&cl_args, sizeof(struct clone_args), nullptr, nullptr); + ASSERT_NE(-1, tid); + + if (tid == 0) { + exit(42); + + } else { + AssertChildExited(tid, 42); + } +#else + GTEST_SKIP() << "glibc does not export a clone3 wrapper"; +#endif +} + +TEST(sched, clone3_with_stack) { +#if defined(__BIONIC__) + void* child_stack[1024]; + + clone_args cl_args = {}; + cl_args.exit_signal = SIGCHLD; + cl_args.stack = reinterpret_cast<uint64_t>(&child_stack); + cl_args.stack_size = sizeof(child_stack); + align_clone3_stack(cl_args); + + pid_t tid = clone3(&cl_args, sizeof(struct clone_args), child_fn_no_CLONE_VM, + reinterpret_cast<void*>(42)); + + ASSERT_NE(-1, tid); + AssertChildExited(tid, 130); +#else + GTEST_SKIP() << "glibc does not export a clone3 wrapper"; +#endif +} + +TEST(sched, clone3_errno) { +#if defined(__BIONIC__) + clone_args cl_args = {}; + cl_args.flags = CLONE_THREAD; + + errno = 0; + // If CLONE_THREAD is set, CLONE_SIGHAND must be set too. + ASSERT_ERRNO_FAILURE(EINVAL, -1, clone3(&cl_args, sizeof(struct clone_args), nullptr, nullptr)); +#else + GTEST_SKIP() << "glibc does not export a clone3 wrapper"; +#endif +} + +TEST(sched, clone3_null_child_stack) { +#if defined(__BIONIC__) + clone_args cl_args = {}; + errno = 0; + ASSERT_ERRNO_FAILURE(EINVAL, -1, + clone3(&cl_args, sizeof(struct clone_args), child_fn_no_CLONE_VM, nullptr)); +#else + GTEST_SKIP() << "glibc does not export a clone3 wrapper"; +#endif +} + +TEST(sched, clone3_vm_without_fn) { +#if defined(__BIONIC__) + clone_args cl_args = {}; + cl_args.flags = CLONE_VM; + errno = 0; + ASSERT_ERRNO_FAILURE(EINVAL, -1, clone3(&cl_args, sizeof(struct clone_args), nullptr, nullptr)); +#else + GTEST_SKIP() << "glibc does not export a clone3 wrapper"; +#endif } TEST(sched, cpu_set) {
diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 29acfdb..e3d35d4 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp
@@ -27,6 +27,7 @@ #include <algorithm> #include <array> +#include <concepts> #include <limits> #include <numeric> #include <string> @@ -449,7 +450,9 @@ } } -TEST(STRING_TEST, strchr) { +template <typename Fn> + requires std::invocable<Fn, const char*, int> +static void RunStrchrTest(Fn strchr_fn) { int seek_char = 'R'; StringTestState<char> state(SMALL); @@ -475,11 +478,33 @@ expected = state.ptr1 + pos; } - ASSERT_TRUE(strchr(state.ptr1, seek_char) == expected); + ASSERT_TRUE(strchr_fn(state.ptr1, seek_char) == expected); } } } +TEST(STRING_TEST, strchr) { + RunStrchrTest([](const char* s, int c) { return strchr(s, c); }); +} + +#if !defined(NOFORTIFY) +TEST(STRING_TEST, strchr_chk) { +#if defined(__BIONIC__) + RunStrchrTest([](const char* str, int needle) { + const size_t l = strlen(str); + const char* plus_one = __strchr_chk(str, needle, l + 1); + const char* plus_one_thousand = __strchr_chk(str, needle, l + 1000); + const char* unknown_size = __strchr_chk(str, needle, size_t(-1)); + EXPECT_EQ(plus_one, plus_one_thousand); + EXPECT_EQ(plus_one, unknown_size); + return plus_one; + }); +#else // __BIONIC__ + GTEST_SKIP() << "strchr_chk tests not available"; +#endif // __BIONIC__ +} +#endif // NOFORTIFY + TEST(STRING_TEST, strchrnul) { const char* s = "01234222"; EXPECT_TRUE(strchrnul(s, '2') == &s[2]); @@ -758,7 +783,9 @@ } } -TEST(STRING_TEST, strrchr) { +template <typename Fn> + requires std::invocable<Fn, const char*, int> +static void RunStrrchrTest(Fn strrchr_fn) { int seek_char = 'M'; StringTestState<char> state(SMALL); for (size_t i = 1; i < state.n; i++) { @@ -783,11 +810,33 @@ expected = state.ptr1 + pos; } - ASSERT_TRUE(strrchr(state.ptr1, seek_char) == expected); + ASSERT_TRUE(strrchr_fn(state.ptr1, seek_char) == expected); } } } +TEST(STRING_TEST, strrchr) { + RunStrrchrTest([](const char* s, int c) { return strrchr(s, c); }); +} + +#if !defined(NOFORTIFY) +TEST(STRING_TEST, strrchr_chk) { +#if defined(__BIONIC__) + RunStrrchrTest([](const char* str, int needle) { + const size_t l = strlen(str); + const char* plus_one = __strrchr_chk(str, needle, l + 1); + const char* plus_one_thousand = __strrchr_chk(str, needle, l + 1000); + const char* unknown_size = __strrchr_chk(str, needle, size_t(-1)); + EXPECT_EQ(plus_one, plus_one_thousand); + EXPECT_EQ(plus_one, unknown_size); + return plus_one; + }); +#else // __BIONIC__ + GTEST_SKIP() << "strrchr_chk tests not available"; +#endif // __BIONIC__ +} +#endif // NOFORTIFY + TEST(STRING_TEST, memchr) { int seek_char = 'N'; StringTestState<char> state(SMALL); @@ -1441,32 +1490,39 @@ RunSingleBufferOverreadTest(DoMemchrTest); } -static void DoStrchrTest(uint8_t* buf, size_t len) { +template <typename Fn> + requires std::invocable<Fn, const char*, int> +static void DoStrchrTestImpl(uint8_t* buf, size_t len, Fn strchr_fn) { if (len >= 1) { char value = 32 + (len % 96); char search_value = 33 + (len % 96); memset(buf, value, len - 1); buf[len - 1] = '\0'; // The buffer does not contain the search value. - ASSERT_EQ(nullptr, strchr(reinterpret_cast<char*>(buf), search_value)); + ASSERT_EQ(nullptr, strchr_fn(reinterpret_cast<char*>(buf), search_value)); // Search for the special '\0' character. - ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 1]), strchr(reinterpret_cast<char*>(buf), '\0')); + ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 1]), + strchr_fn(reinterpret_cast<char*>(buf), '\0')); if (len >= 2) { buf[0] = search_value; // The search value is the first element in the buffer. - ASSERT_EQ(reinterpret_cast<char*>(&buf[0]), strchr(reinterpret_cast<char*>(buf), - search_value)); + ASSERT_EQ(reinterpret_cast<char*>(&buf[0]), + strchr_fn(reinterpret_cast<char*>(buf), search_value)); buf[0] = value; buf[len - 2] = search_value; // The search value is the second to last element in the buffer. // The last element is the '\0' character. - ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 2]), strchr(reinterpret_cast<char*>(buf), - search_value)); + ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 2]), + strchr_fn(reinterpret_cast<char*>(buf), search_value)); } } } +static void DoStrchrTest(uint8_t* buf, size_t len) { + DoStrchrTestImpl(buf, len, [](const char* s, int c) { return strchr(s, c); }); +} + TEST(STRING_TEST, strchr_align) { RunSingleBufferAlignTest(MEDIUM, DoStrchrTest); } @@ -1475,32 +1531,39 @@ RunSingleBufferOverreadTest(DoStrchrTest); } -static void DoStrrchrTest(uint8_t* buf, size_t len) { +template <typename Fn> + requires std::invocable<Fn, const char*, int> +static void DoStrrchrTestImpl(uint8_t* buf, size_t len, Fn strrchr_fn) { if (len >= 1) { char value = 32 + (len % 96); char search_value = 33 + (len % 96); memset(buf, value, len - 1); buf[len - 1] = '\0'; // The buffer does not contain the search value. - ASSERT_EQ(nullptr, strrchr(reinterpret_cast<char*>(buf), search_value)); + ASSERT_EQ(nullptr, strrchr_fn(reinterpret_cast<char*>(buf), search_value)); // Search for the special '\0' character. - ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 1]), strrchr(reinterpret_cast<char*>(buf), '\0')); + ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 1]), + strrchr_fn(reinterpret_cast<char*>(buf), '\0')); if (len >= 2) { buf[0] = search_value; // The search value is the first element in the buffer. - ASSERT_EQ(reinterpret_cast<char*>(&buf[0]), strrchr(reinterpret_cast<char*>(buf), - search_value)); + ASSERT_EQ(reinterpret_cast<char*>(&buf[0]), + strrchr_fn(reinterpret_cast<char*>(buf), search_value)); buf[0] = value; buf[len - 2] = search_value; // The search value is the second to last element in the buffer. // The last element is the '\0' character. - ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 2]), strrchr(reinterpret_cast<char*>(buf), - search_value)); + ASSERT_EQ(reinterpret_cast<char*>(&buf[len - 2]), + strrchr_fn(reinterpret_cast<char*>(buf), search_value)); } } } +static void DoStrrchrTest(uint8_t* buf, size_t len) { + DoStrrchrTestImpl(buf, len, [](const char* s, int c) { return strrchr(s, c); }); +} + TEST(STRING_TEST, strrchr_align) { RunSingleBufferAlignTest(MEDIUM, DoStrrchrTest); } @@ -1509,6 +1572,103 @@ RunSingleBufferOverreadTest(DoStrrchrTest); } +#if !defined(NOFORTIFY) +#if defined(__BIONIC__) +static void DoStrchrChkTest(uint8_t* buf, size_t len) { + DoStrchrTestImpl(buf, len, [len](const char* s, int c) { return __strchr_chk(s, c, len); }); +} +#endif // __BIONIC__ + +TEST(STRING_TEST, strchr_chk_align) { +#if defined(__BIONIC__) + RunSingleBufferAlignTest(MEDIUM, DoStrchrChkTest); +#else // __BIONIC__ + GTEST_SKIP() << "strchr_chk_align tests not available"; +#endif // __BIONIC__ +} + +TEST(STRING_TEST, strchr_chk_overread) { +#if defined(__BIONIC__) + RunSingleBufferOverreadTest(DoStrchrChkTest); +#else // __BIONIC__ + GTEST_SKIP() << "strchr_chk_overread tests not available"; +#endif // __BIONIC__ +} + +#if defined(__BIONIC__) +static void DoStrrchrChkTest(uint8_t* buf, size_t len) { + DoStrrchrTestImpl(buf, len, [len](const char* s, int c) { return __strrchr_chk(s, c, len); }); +} +#endif // __BIONIC__ + +TEST(STRING_TEST, strrchr_chk_align) { +#if defined(__BIONIC__) + RunSingleBufferAlignTest(MEDIUM, DoStrrchrChkTest); +#else // __BIONIC__ + GTEST_SKIP() << "strrchr_chk_align tests not available"; +#endif // __BIONIC__ +} + +TEST(STRING_TEST, strrchr_chk_overread) { +#if defined(__BIONIC__) + RunSingleBufferOverreadTest(DoStrrchrChkTest); +#else // __BIONIC__ + GTEST_SKIP() << "strrchr_chk_overread tests not available"; +#endif // __BIONIC__ +} +#endif // !NOFORTIFY + +#if !defined(NOFORTIFY) +TEST(STRING_TEST, strchr_chk_bounds) { +#if defined(__BIONIC__) + char buf[32]; + memset(buf, 'A', sizeof(buf) - 1); + buf[31] = '\0'; + + buf[16] = 'X'; + EXPECT_DEATH(__strchr_chk(buf, 'X', 16), "strchr: prevented read past end of buffer"); + EXPECT_EQ(__strchr_chk(buf, 'X', 17), buf + 16); + + buf[16] = 'A'; + buf[20] = 'X'; + EXPECT_DEATH(__strchr_chk(buf, 'X', 16), "strchr: prevented read past end of buffer"); + + buf[20] = 'A'; + buf[16] = '\0'; + EXPECT_DEATH(__strchr_chk(buf, 'X', 16), "strchr: prevented read past end of buffer"); + EXPECT_EQ(__strchr_chk(buf, 'X', 17), nullptr); +#else // __BIONIC__ + GTEST_SKIP() << "strchr_chk_bounds tests not available"; +#endif // __BIONIC__ +} +#endif // !NOFORTIFY + +#if !defined(NOFORTIFY) +TEST(STRING_TEST, strrchr_chk_bounds) { +#if defined(__BIONIC__) + char buf[32]; + memset(buf, 'A', sizeof(buf)); + buf[31] = '\0'; + + buf[16] = 'X'; + buf[17] = '\0'; + EXPECT_DEATH(__strrchr_chk(buf, 'X', 17), "strrchr: prevented read past end of buffer"); + EXPECT_EQ(__strrchr_chk(buf, 'X', 18), buf + 16); + buf[16] = 'A'; + buf[17] = 'A'; + buf[20] = 'X'; + EXPECT_DEATH(__strrchr_chk(buf, 'X', 16), "strrchr: prevented read past end of buffer"); + + buf[20] = 'A'; + buf[16] = '\0'; + EXPECT_DEATH(__strrchr_chk(buf, 'X', 16), "strrchr: prevented read past end of buffer"); + EXPECT_EQ(__strrchr_chk(buf, 'X', 17), nullptr); +#else // __BIONIC__ + GTEST_SKIP() << "strrchr_chk_bounds tests not available"; +#endif // __BIONIC__ +} +#endif // !NOFORTIFY + #if !defined(ANDROID_HOST_MUSL) static void TestBasename(const char* in, const char* expected_out) { errno = 0;