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;