diff mbox series

[v8,6/7] posix: Add fork_np (BZ 26371)

Message ID 20230818140642.1623571-7-adhemerval.zanella@linaro.org
State New
Headers show
Series Add pidfd and cgroupv2 support for process creation | expand

Commit Message

Adhemerval Zanella Netto Aug. 18, 2023, 2:06 p.m. UTC
Returning a pidfd allows a process to keep a race-free handle to a child
process. However, to create a process file descriptor the caller needs
to use pidfd_open which still might be subject to TOCTOU.

The implementation assures that the kernel must support the complete
pidfd interface, meaning that waitid (P_PIDFD) should be supported. It
ensures that a non-racy workaround is required (such as reading procfs
fdinfo pid to use along with old wait interfaces).  If the kernel does
not have the required support the interface returns -1 and set errno to
ENOSYS.

The interface is:

  typedef union
  {
    struct
    {
      __uint64_t fork_np_flags;
      int fork_np_pidfd;
      int fork_np_cgroup;
      int fork_np_exit_signal;
  #define fork_np_flags       __data.fork_np_flags
  #define fork_np_pidfd       __data.fork_np_pidfd
  #define fork_np_cgroup      __data.fork_np_cgroup
  #define fork_np_exit_signal __data.fork_np_exit_signal
    } __data;
    char __size [FORK_NP_ARGS_SIZE_VER0];
  } fork_np_args_t;

  #define FORK_NP_PIDFD        (1ULL << 1)
  #define FORK_NP_CGROUP       (1ULL << 2)
  #define FORK_NP_ASYNCSAFE    (1ULL << 3)
  #define FORK_NP_EXIT_SIGNAL  (1ULL << 4)

  pid_t fork_np (fork_np_args_t *args, size_t size)

The SIZE must represent a supported fork_np_args_t type, otherwise, the
function returns EINVAL.  Also, each new member should add a new flag so
fork_np can be extended.

If ARGS has all members set to 0, no file descriptor is returned and
fork_np acts as fork.  If FORK_NP_PIDFD is set on the flags member, a
new file descriptor is returned on the pidfd member and the kernel sets
O_CLOEXEC as default.  The fork_np follows the fork/_Fork convention on
returning a positive or negative value to the parent (with a negative
indicating an error) and zero to the child.

If FORK_NP_CGROUP is set, the value on the cgroup member is used as the
cgroupv2 to be placed in the new process (by using the CLONE_INTO_CGROUP
clone flag).

If FORK_NP_EXIT_SIGNAL is set, the new process will send the exit signal
defined by exit_signal on termination or none if it is set to 0.  When
using this flag, the parent process must specify the __WALL or __WCLONE
Linux-specific options when waiting for the child with wait or waitid.

If FORK_NP_ASYNCSAFE is set, fork_np acts as _Fork, thus avoiding
running pthread_atfork handlers.

Checked on x86_64-linux-gnu on Linux 4.15 (no CLONE_PIDFD or waitid
support), Linux 5.4 (full support), and Linux 6.2.
---
 NEWS                                          |   7 +
 include/clone_internal.h                      |  17 ++
 manual/process.texi                           |  82 +++++-
 posix/Makefile                                |   3 +-
 posix/fork-internal.c                         | 127 ++++++++++
 posix/fork-internal.h                         |  36 +++
 posix/fork.c                                  | 107 +-------
 sysdeps/nptl/_Fork.c                          |   2 +-
 sysdeps/unix/sysv/linux/Makefile              |   3 +
 sysdeps/unix/sysv/linux/Versions              |   1 +
 sysdeps/unix/sysv/linux/aarch64/libc.abilist  |   1 +
 sysdeps/unix/sysv/linux/alpha/libc.abilist    |   1 +
 sysdeps/unix/sysv/linux/arc/libc.abilist      |   1 +
 sysdeps/unix/sysv/linux/arch-fork.h           |  16 +-
 sysdeps/unix/sysv/linux/arm/be/libc.abilist   |   1 +
 sysdeps/unix/sysv/linux/arm/le/libc.abilist   |   1 +
 sysdeps/unix/sysv/linux/bits/unistd_ext.h     |  51 ++++
 sysdeps/unix/sysv/linux/clone-internal.c      |  58 ++++-
 sysdeps/unix/sysv/linux/csky/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/fork_np.c             |  97 +++++++
 sysdeps/unix/sysv/linux/hppa/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/i386/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/ia64/libc.abilist     |   1 +
 .../sysv/linux/loongarch/lp64/libc.abilist    |   1 +
 .../sysv/linux/m68k/coldfire/libc.abilist     |   1 +
 .../unix/sysv/linux/m68k/m680x0/libc.abilist  |   1 +
 .../sysv/linux/microblaze/be/libc.abilist     |   1 +
 .../sysv/linux/microblaze/le/libc.abilist     |   1 +
 .../sysv/linux/mips/mips32/fpu/libc.abilist   |   1 +
 .../sysv/linux/mips/mips32/nofpu/libc.abilist |   1 +
 .../sysv/linux/mips/mips64/n32/libc.abilist   |   1 +
 .../sysv/linux/mips/mips64/n64/libc.abilist   |   1 +
 sysdeps/unix/sysv/linux/nios2/libc.abilist    |   1 +
 sysdeps/unix/sysv/linux/or1k/libc.abilist     |   1 +
 .../linux/powerpc/powerpc32/fpu/libc.abilist  |   1 +
 .../powerpc/powerpc32/nofpu/libc.abilist      |   1 +
 .../linux/powerpc/powerpc64/be/libc.abilist   |   1 +
 .../linux/powerpc/powerpc64/le/libc.abilist   |   1 +
 .../unix/sysv/linux/riscv/rv32/libc.abilist   |   1 +
 .../unix/sysv/linux/riscv/rv64/libc.abilist   |   1 +
 .../unix/sysv/linux/s390/s390-32/libc.abilist |   1 +
 .../unix/sysv/linux/s390/s390-64/libc.abilist |   1 +
 sysdeps/unix/sysv/linux/sh/be/libc.abilist    |   1 +
 sysdeps/unix/sysv/linux/sh/le/libc.abilist    |   1 +
 .../sysv/linux/sparc/sparc32/libc.abilist     |   1 +
 .../sysv/linux/sparc/sparc64/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/tst-fork_np-cgroup.c  | 170 +++++++++++++
 sysdeps/unix/sysv/linux/tst-fork_np.c         | 236 ++++++++++++++++++
 .../unix/sysv/linux/x86_64/64/libc.abilist    |   1 +
 .../unix/sysv/linux/x86_64/x32/libc.abilist   |   1 +
 50 files changed, 928 insertions(+), 119 deletions(-)
 create mode 100644 posix/fork-internal.c
 create mode 100644 posix/fork-internal.h
 create mode 100644 sysdeps/unix/sysv/linux/fork_np.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-fork_np-cgroup.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-fork_np.c

Comments

Florian Weimer Aug. 24, 2023, 6:07 a.m. UTC | #1
* Adhemerval Zanella:

> Returning a pidfd allows a process to keep a race-free handle to a child
> process. However, to create a process file descriptor the caller needs
> to use pidfd_open which still might be subject to TOCTOU.
>
> The implementation assures that the kernel must support the complete
> pidfd interface, meaning that waitid (P_PIDFD) should be supported. It
> ensures that a non-racy workaround is required (such as reading procfs
> fdinfo pid to use along with old wait interfaces).  If the kernel does
> not have the required support the interface returns -1 and set errno to
> ENOSYS.

Please skip this for now.

Thanks,
Florian
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 97681e6796..00e9553e8f 100644
--- a/NEWS
+++ b/NEWS
@@ -27,6 +27,13 @@  Major new features:
   The pidfd functionality avoids the issue of PID reuse with the traditional
   posix_spawn interface.
 
+* On Linux, the fork_np has been added.  It has a similar semantic as ai
+  fork or _Fork, where it clones the calling process; and allows to extend
+  of the fork functionality by allowing the return of a process file
+  descriptor (as pidfd_spawn), setting a cgroupv2 of the new process (as
+  posix_spawnattr_getcgroup_np), setting a different signal on process
+  termination, and making the function act as _Fork.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
   [Add deprecations, removals and changes affecting compatibility here]
diff --git a/include/clone_internal.h b/include/clone_internal.h
index 567160ebb5..340cc39a37 100644
--- a/include/clone_internal.h
+++ b/include/clone_internal.h
@@ -2,6 +2,8 @@ 
 #define _CLONE_INTERNAL_H
 
 #include <clone3.h>
+#include <stdbool.h>
+#include <stdint.h>
 
 /* The clone3 syscall provides a superset of the functionality of the clone
    interface.  The kernel might extend __CL_ARGS struct in the future, with
@@ -35,6 +37,21 @@  extern int __clone_internal_fallback (struct clone_args *__cl_args,
 				      void *__arg)
      attribute_hidden;
 
+/* Call the clone3/clone syscall with fork semantic (i.e. no stack setting
+   required).  The EXTRA_FLAGS define any additional flag to be used besides
+   CLONE_CHILD_SETTID and CLONE_CHILD_CLEARTID, the PIDFD indicates where
+   the process file descriptor (set with CLONE_PIDFD) should be returned,
+   and the CGROUP specifies the cgroupsv2 (set with CLONE_INTO_CGROUP).
+
+   Similar to __clone3_internal, it uses the stick check to avoid re-issue
+   the clone3 syscall if kernel does not support it.
+
+   It does not provide CLONE_INTO_CGROUP/CGROUP fallback if clone3 is not
+   supported, in this case the function returns -1/ENOTSUP.  */
+extern int __clone_fork (uint64_t __extra_flags, void *__pidfd, int __cgroup,
+			 int __exit_signal)
+     attribute_hidden;
+
 /* Return whether the kernel supports pid file descriptor, including clone
    with CLONE_PIDFD and waitid with P_PIDFD.  */
 extern bool __clone_pidfd_supported (void) attribute_hidden;
diff --git a/manual/process.texi b/manual/process.texi
index 68361c3f61..e6ac1f934f 100644
--- a/manual/process.texi
+++ b/manual/process.texi
@@ -137,12 +137,12 @@  creating a process and making it run another program.
 @cindex subprocess
 A new processes is created when one of the functions
 @code{posix_spawn}, @code{fork}, @code{_Fork}, @code{vfork}, or
-@code{pidfd_spawn} is called.  (The @code{system} and @code{popen} also
-create new processes internally.)  Due to the name of the @code{fork}
-function, the act of creating a new process is sometimes called
-@dfn{forking} a process.  Each new process (the @dfn{child process} or
-@dfn{subprocess}) is allocated a process ID, distinct from the process
-ID of the parent process.  @xref{Process Identification}.
+@code{pidfd_spawn}, or @code{fork_np} is called.  (The @code{system}
+and @code{popen} also create new processes internally.)  Due to the name
+of the @code{fork} function, the act of creating a new process is
+sometimes called @dfn{forking} a process.  Each new process (the
+@dfn{child process} or @dfn{subprocess}) is allocated a process ID,
+distinct from the process ID of the parent process.  @xref{Process Identification}.
 
 After forking a child process, both the parent and child processes
 continue to execute normally.  If you want your program to wait for a
@@ -153,10 +153,10 @@  limited information about why the child terminated---for example, its
 exit status code.
 
 A newly forked child process continues to execute the same program as
-its parent process, at the point where the @code{fork} or @code{_Fork}
-call returns.  You can use the return value from @code{fork} or
-@code{_Fork} to tell whether the program is running in the parent process
-or the child.
+its parent process, at the point where the @code{fork}, @code{_Fork},
+or @code{fork_np} call returns.  You can use the return value from
+@code{fork}, @code{_Fork}, or @code{fork_np} to tell whether the
+program is running in the parent process or the child.
 
 @cindex process image
 Having several processes run the same program is only occasionally
@@ -362,6 +362,68 @@  the proper precautions for using @code{vfork}, your program will still
 work even if the system uses @code{fork} instead.
 @end deftypefun
 
+@deftp {Data Type} {fork_np_args_t}
+@standards{GNU, unistd.h}
+This structure is used to along @code{fork_np} to enable extra
+functionality.
+
+@table @code
+@item uint64_t fork_np_flags
+If @code{FORK_NP_PIDFD} is set, the process file descriptor will be
+returned on @code{pidfd}.
+If @code{FORK_NP_CGROUP} is set, the value from @code{cgroup} will be used
+to specify a different cgroupv2 to start the new process.
+If @code{FORK_NP_ASYNCSAFE} is set, @code{fork_np} will not issue any
+atfork handler (similar to @code{_Fork}).
+If @code{PIDFDFORK_EXIT_SIGNAL} is set, the signal defined at @code{exit_signal}
+will be send on process termination.
+
+@item int fork_np_pidfd
+Return the process file descriptor if @code{FORK_NP_PIDFD} is set.
+
+@item int fork_np_cgroup
+Set the cgroupv2 to be used on the new process.
+
+@item int fork_np_exit_signal;
+Define which signal to send on process termination.
+@end table
+
+This union is a GNU extension.
+@end deftp
+
+@deftypefun pid_t fork_np (fork_np_args_t @var{args}, size_t @var{len})
+@standards{GNU, unistd.h}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+The @code{fork_np} function is similar to @code{fork} on both semantic and
+return code value, but allows extra functionality through the @code{args}
+parameter.  The @code{len} must be the size of @code{args}, otherwise the
+function returns with a failure.
+
+If @code{FORK_NP_PIDFD} is set on @code{fork_np_flags}, and the process is
+correctly created the @code{fork_np_pidfd} frkm @var{args} will contain a
+file descriptor that can be used along other pidfd functions (like
+@code{pidfd_send_signal} or with @code{waitid} along with@code{P_PIDFD}.
+
+If @code{FORK_NP_CGROUP} is set on @code{fork_np_flags}, the
+@code{fork_np_cgroup} value from @var{args} will be used as the cgroups v2
+control group on process creation.   There is no fallback implementation,
+meaning If the kernel does not provide the required support an error is returned.
+
+If @code{FORK_NP_ASYNCSAFE} is set on @code{fork_np_flags}, @code{fork_np}
+acts as @code{_Fork}, where it does not invoke any callbacks registered with
+@code{pthread_atfork}, nor does it reset internal state or locks (such as the
+@code{malloc} locks).
+
+If @code{FORK_NP_EXIT_SIGNAL} is set on @code{flags}, the signal number
+@code{fork_np_exit_signal} from @code{args} will be sent on process
+termination.  The @code{0} value is also valid, meaning that no signal
+will be sent.  @strong{NB:} When using this flag, the parent process must
+specify the @code{__WALL} or @code{__WCLONE} options when waiting for the
+child with @code{wait} or @code{waitid}.
+
+This function is a GNU extension and specific to Linux.
+@end deftypefun
+
 @node Executing a File
 @section Executing a File
 @cindex executing a file
diff --git a/posix/Makefile b/posix/Makefile
index 905cf9fb54..949f5632eb 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -85,6 +85,7 @@  routines := \
   fexecve \
   fnmatch \
   fork \
+  fork-internal \
   fpathconf \
   gai_strerror \
   get_child_max \
@@ -589,7 +590,7 @@  CFLAGS-execl.os = -fomit-frame-pointer
 CFLAGS-execvp.os = -fomit-frame-pointer
 CFLAGS-execlp.os = -fomit-frame-pointer
 CFLAGS-nanosleep.c += -fexceptions -fasynchronous-unwind-tables
-CFLAGS-fork.c = $(libio-mtsafe) $(config-cflags-wno-ignored-attributes)
+CFLAGS-fork-internal.c = $(libio-mtsafe) $(config-cflags-wno-ignored-attributes)
 
 tstgetopt-ARGS = -a -b -cfoobar --required foobar --optional=bazbug \
 		--none random --col --color --colour
diff --git a/posix/fork-internal.c b/posix/fork-internal.c
new file mode 100644
index 0000000000..a5e47cbe53
--- /dev/null
+++ b/posix/fork-internal.c
@@ -0,0 +1,127 @@ 
+/* Internal fork definitions.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <fork.h>
+#include <fork-internal.h>
+#include <ldsodefs.h>
+#include <libio/libioP.h>
+#include <malloc/malloc-internal.h>
+#include <register-atfork.h>
+#include <stdio-lock.h>
+#include <unwind-link.h>
+
+static void
+fresetlockfiles (void)
+{
+  _IO_ITER i;
+
+  for (i = _IO_iter_begin(); i != _IO_iter_end(); i = _IO_iter_next(i))
+    if ((_IO_iter_file (i)->_flags & _IO_USER_LOCK) == 0)
+      _IO_lock_init (*((_IO_lock_t *) _IO_iter_file(i)->_lock));
+}
+
+uint64_t
+__fork_pre (bool multiple_threads, struct nss_database_data *nss_database_data)
+{
+  uint64_t lastrun = __run_prefork_handlers (multiple_threads);
+
+  /* If we are not running multiple threads, we do not have to
+     preserve lock state.  If fork runs from a signal handler, only
+     async-signal-safe functions can be used in the child.  These data
+     structures are only used by unsafe functions, so their state does
+     not matter if fork was called from a signal handler.  */
+  if (multiple_threads)
+    {
+      call_function_static_weak (__nss_database_fork_prepare_parent,
+				 nss_database_data);
+
+      _IO_list_lock ();
+
+      /* Acquire malloc locks.  This needs to come last because fork
+	 handlers may use malloc, and the libio list lock has an
+	 indirect malloc dependency as well (via the getdelim
+	 function).  */
+      call_function_static_weak (__malloc_fork_lock_parent);
+    }
+
+  return lastrun;
+}
+
+void
+__fork_post (struct fork_post_state_t *state,
+	     struct nss_database_data *nss_database_data)
+{
+  if (state->pid == 0)
+    {
+      fork_system_setup ();
+
+      /* Reset the lock state in the multi-threaded case.  */
+      if (state->multiple_threads)
+	{
+	  __libc_unwind_link_after_fork ();
+
+	  fork_system_setup_after_fork ();
+
+	  /* Release malloc locks.  */
+	  call_function_static_weak (__malloc_fork_unlock_child);
+
+	  /* Reset the file list.  These are recursive mutexes.  */
+	  fresetlockfiles ();
+
+	  /* Reset locks in the I/O code.  */
+	  _IO_list_resetlock ();
+
+	  call_function_static_weak (__nss_database_fork_subprocess,
+				     nss_database_data);
+	}
+
+      /* Reset the lock the dynamic loader uses to protect its data.  */
+      __rtld_lock_initialize (GL(dl_load_lock));
+
+      /* Reset the lock protecting dynamic TLS related data.  */
+      __rtld_lock_initialize (GL(dl_load_tls_lock));
+
+      reclaim_stacks ();
+
+      /* Run the handlers registered for the child.  */
+      __run_postfork_handlers (atfork_run_child, state->multiple_threads,
+			       state->lastrun);
+    }
+  else
+    {
+      /* If _Fork failed, preserve its errno value.  */
+      int save_errno = errno;
+
+      /* Release acquired locks in the multi-threaded case.  */
+      if (state->multiple_threads)
+	{
+	  /* Release malloc locks, parent process variant.  */
+	  call_function_static_weak (__malloc_fork_unlock_parent);
+
+	  /* We execute this even if the 'fork' call failed.  */
+	  _IO_list_unlock ();
+	}
+
+      /* Run the handlers registered for the parent.  */
+      __run_postfork_handlers (atfork_run_parent, state->multiple_threads,
+			       state->lastrun);
+
+      if (state->pid < 0)
+	__set_errno (save_errno);
+    }
+}
diff --git a/posix/fork-internal.h b/posix/fork-internal.h
new file mode 100644
index 0000000000..5017061e1e
--- /dev/null
+++ b/posix/fork-internal.h
@@ -0,0 +1,36 @@ 
+/* Internal fork definitions.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _FORK_INTERNAL_H
+#define _FORK_INTERNAL_H
+
+#include <stdint.h>
+#include <nss/nss_database.h>
+
+struct fork_post_state_t
+{
+  bool multiple_threads;
+  pid_t pid;
+  uint64_t lastrun;
+};
+
+uint64_t __fork_pre (bool, struct nss_database_data *) attribute_hidden;
+void __fork_post (struct fork_post_state_t *, struct nss_database_data *)
+  attribute_hidden;
+
+#endif
diff --git a/posix/fork.c b/posix/fork.c
index b4aaa9fa6d..1708473e72 100644
--- a/posix/fork.c
+++ b/posix/fork.c
@@ -16,25 +16,10 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <fork.h>
-#include <libio/libioP.h>
-#include <ldsodefs.h>
-#include <malloc/malloc-internal.h>
-#include <nss/nss_database.h>
-#include <register-atfork.h>
-#include <stdio-lock.h>
+#include <fork-internal.h>
 #include <sys/single_threaded.h>
 #include <unwind-link.h>
-
-static void
-fresetlockfiles (void)
-{
-  _IO_ITER i;
-
-  for (i = _IO_iter_begin(); i != _IO_iter_end(); i = _IO_iter_next(i))
-    if ((_IO_iter_file (i)->_flags & _IO_USER_LOCK) == 0)
-      _IO_lock_init (*((_IO_lock_t *) _IO_iter_file(i)->_lock));
-}
+#include <unistd.h>
 
 pid_t
 __libc_fork (void)
@@ -45,92 +30,18 @@  __libc_fork (void)
      requirement for fork (Austin Group tracker issue #62) this is
      best effort to make is async-signal-safe at least for single-thread
      case.  */
-  bool multiple_threads = !SINGLE_THREAD_P;
-  uint64_t lastrun;
-
-  lastrun = __run_prefork_handlers (multiple_threads);
-
+  struct fork_post_state_t state = {
+      .multiple_threads = !SINGLE_THREAD_P
+  };
   struct nss_database_data nss_database_data;
 
-  /* If we are not running multiple threads, we do not have to
-     preserve lock state.  If fork runs from a signal handler, only
-     async-signal-safe functions can be used in the child.  These data
-     structures are only used by unsafe functions, so their state does
-     not matter if fork was called from a signal handler.  */
-  if (multiple_threads)
-    {
-      call_function_static_weak (__nss_database_fork_prepare_parent,
-				 &nss_database_data);
-
-      _IO_list_lock ();
-
-      /* Acquire malloc locks.  This needs to come last because fork
-	 handlers may use malloc, and the libio list lock has an
-	 indirect malloc dependency as well (via the getdelim
-	 function).  */
-      call_function_static_weak (__malloc_fork_lock_parent);
-    }
-
-  pid_t pid = _Fork ();
-
-  if (pid == 0)
-    {
-      fork_system_setup ();
-
-      /* Reset the lock state in the multi-threaded case.  */
-      if (multiple_threads)
-	{
-	  __libc_unwind_link_after_fork ();
-
-	  fork_system_setup_after_fork ();
-
-	  /* Release malloc locks.  */
-	  call_function_static_weak (__malloc_fork_unlock_child);
-
-	  /* Reset the file list.  These are recursive mutexes.  */
-	  fresetlockfiles ();
-
-	  /* Reset locks in the I/O code.  */
-	  _IO_list_resetlock ();
-
-	  call_function_static_weak (__nss_database_fork_subprocess,
-				     &nss_database_data);
-	}
-
-      /* Reset the lock the dynamic loader uses to protect its data.  */
-      __rtld_lock_initialize (GL(dl_load_lock));
-
-      /* Reset the lock protecting dynamic TLS related data.  */
-      __rtld_lock_initialize (GL(dl_load_tls_lock));
-
-      reclaim_stacks ();
-
-      /* Run the handlers registered for the child.  */
-      __run_postfork_handlers (atfork_run_child, multiple_threads, lastrun);
-    }
-  else
-    {
-      /* If _Fork failed, preserve its errno value.  */
-      int save_errno = errno;
-
-      /* Release acquired locks in the multi-threaded case.  */
-      if (multiple_threads)
-	{
-	  /* Release malloc locks, parent process variant.  */
-	  call_function_static_weak (__malloc_fork_unlock_parent);
-
-	  /* We execute this even if the 'fork' call failed.  */
-	  _IO_list_unlock ();
-	}
+  state.lastrun = __fork_pre (state.multiple_threads, &nss_database_data);
 
-      /* Run the handlers registered for the parent.  */
-      __run_postfork_handlers (atfork_run_parent, multiple_threads, lastrun);
+  state.pid = _Fork ();
 
-      if (pid < 0)
-	__set_errno (save_errno);
-    }
+  __fork_post (&state, &nss_database_data);
 
-  return pid;
+  return state.pid;
 }
 weak_alias (__libc_fork, __fork)
 libc_hidden_def (__fork)
diff --git a/sysdeps/nptl/_Fork.c b/sysdeps/nptl/_Fork.c
index f8322ae557..397f059fb0 100644
--- a/sysdeps/nptl/_Fork.c
+++ b/sysdeps/nptl/_Fork.c
@@ -22,7 +22,7 @@ 
 pid_t
 _Fork (void)
 {
-  pid_t pid = arch_fork (&THREAD_SELF->tid);
+  pid_t pid = arch_fork (SIGCHLD, NULL, &THREAD_SELF->tid);
   if (pid == 0)
     {
       struct pthread *self = THREAD_SELF;
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 3ecfa184d0..c9164e9d0a 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -493,6 +493,7 @@  sysdep_headers += \
 sysdep_routines += \
   getcpu \
   oldglob \
+  fork_np \
   pidfd_spawn \
   pidfd_spawnp \
   sched_getcpu \
@@ -503,6 +504,8 @@  sysdep_routines += \
 tests += \
   tst-affinity \
   tst-affinity-pid \
+  tst-fork_np \
+  tst-fork_np-cgroup \
   tst-posix_spawn-setsid-pidfd \
   tst-spawn-cgroup \
   tst-spawn-chdir-pidfd \
diff --git a/sysdeps/unix/sysv/linux/Versions b/sysdeps/unix/sysv/linux/Versions
index a8bae0c2a2..c677631f24 100644
--- a/sysdeps/unix/sysv/linux/Versions
+++ b/sysdeps/unix/sysv/linux/Versions
@@ -322,6 +322,7 @@  libc {
 %endif
   }
   GLIBC_2.39 {
+    fork_np;
     pidfd_spawn;
     pidfd_spawnp;
     posix_spawnattr_getcgroup_np;
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index 6f23556067..dab02f0087 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2673,6 +2673,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index 02c43beb13..1db00408cf 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -2782,6 +2782,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/arc/libc.abilist b/sysdeps/unix/sysv/linux/arc/libc.abilist
index dd8e5912d8..032aacc1ba 100644
--- a/sysdeps/unix/sysv/linux/arc/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arc/libc.abilist
@@ -2434,6 +2434,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/arch-fork.h b/sysdeps/unix/sysv/linux/arch-fork.h
index 0e0eccbf38..f978d4c4f4 100644
--- a/sysdeps/unix/sysv/linux/arch-fork.h
+++ b/sysdeps/unix/sysv/linux/arch-fork.h
@@ -32,24 +32,24 @@ 
    override it with one of the supported calling convention (check generic
    kernel-features.h for the clone abi variants).  */
 static inline pid_t
-arch_fork (void *ctid)
+arch_fork (int flags, void *ptid, void *ctid)
 {
-  const int flags = CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD;
   long int ret;
+  flags |= CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID;
 #ifdef __ASSUME_CLONE_BACKWARDS
 # ifdef INLINE_CLONE_SYSCALL
-  ret = INLINE_CLONE_SYSCALL (flags, 0, NULL, 0, ctid);
+  ret = INLINE_CLONE_SYSCALL (flags, 0, ptid, 0, ctid);
 # else
-  ret = INLINE_SYSCALL_CALL (clone, flags, 0, NULL, 0, ctid);
+  ret = INLINE_SYSCALL_CALL (clone, flags, 0, ptid, 0, ctid);
 # endif
 #elif defined(__ASSUME_CLONE_BACKWARDS2)
-  ret = INLINE_SYSCALL_CALL (clone, 0, flags, NULL, ctid, 0);
+  ret = INLINE_SYSCALL_CALL (clone, 0, flags, ptid, ctid, 0);
 #elif defined(__ASSUME_CLONE_BACKWARDS3)
-  ret = INLINE_SYSCALL_CALL (clone, flags, 0, 0, NULL, ctid, 0);
+  ret = INLINE_SYSCALL_CALL (clone, flags, 0, 0, ptid, ctid, 0);
 #elif defined(__ASSUME_CLONE2)
-  ret = INLINE_SYSCALL_CALL (clone2, flags, 0, 0, NULL, ctid, 0);
+  ret = INLINE_SYSCALL_CALL (clone2, flags, 0, 0, ptid, ctid, 0);
 #elif defined(__ASSUME_CLONE_DEFAULT)
-  ret = INLINE_SYSCALL_CALL (clone, flags, 0, NULL, ctid, 0);
+  ret = INLINE_SYSCALL_CALL (clone, flags, 0, ptid, ctid, 0);
 #else
 # error "Undefined clone variant"
 #endif
diff --git a/sysdeps/unix/sysv/linux/arm/be/libc.abilist b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
index a751e5f5a9..9f3ef16280 100644
--- a/sysdeps/unix/sysv/linux/arm/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
@@ -554,6 +554,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/arm/le/libc.abilist b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
index 0eda3459ed..c2c6c8af6b 100644
--- a/sysdeps/unix/sysv/linux/arm/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
@@ -551,6 +551,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/bits/unistd_ext.h b/sysdeps/unix/sysv/linux/bits/unistd_ext.h
index c523ef67c1..1872728c51 100644
--- a/sysdeps/unix/sysv/linux/bits/unistd_ext.h
+++ b/sysdeps/unix/sysv/linux/bits/unistd_ext.h
@@ -47,4 +47,55 @@  extern __pid_t gettid (void) __THROW;
 # define CLOSE_RANGE_CLOEXEC (1U << 2)
 #endif
 
+#define FORK_NP_ARGS_SIZE_VER0 24
+typedef union
+{
+  struct
+  {
+    __uint64_t fork_np_flags;
+    int fork_np_pidfd;
+    int fork_np_cgroup;
+    int fork_np_exit_signal;
+#define fork_np_flags       __data.fork_np_flags
+#define fork_np_pidfd       __data.fork_np_pidfd
+#define fork_np_cgroup      __data.fork_np_cgroup
+#define fork_np_exit_signal __data.fork_np_exit_signal
+  } __data;
+  char __size [FORK_NP_ARGS_SIZE_VER0];
+} fork_np_args_t;
+
+/* Return the process file descriptor.  */
+#define FORK_NP_PIDFD        (1ULL << 1)
+/* Specify a different cgroup2 than the default one.  */
+#define FORK_NP_CGROUP       (1ULL << 2)
+/* Do not issue the pthread_atfork on process creation.  */
+#define FORK_NP_ASYNCSAFE    (1ULL << 3)
+/* Send a different signal to parent on child termination.  */
+#define FORK_NP_EXIT_SIGNAL  (1ULL << 4)
+
+/* Clone the calling process, creating an exact copy and return a file
+   descriptor that can be used along other pidfd functions.
+
+   The ARGS changes how the process creation is done.
+
+   If FORK_NP_PIDFD is set on FLAGS, a process file descriptor is returned on
+   PIDFD (which can be used along other pidfd function, like pidfd_signal).
+
+   If FORK_NP_CGROUP is set on FLAGS, the CGROUP file descriptor must
+   reference a cgroup v2 control group which will be used on process
+   creation.
+
+   If FORK_NP_ASYNCSAFE is set on FLAGS, fork_np does not invoke the
+   registered pthread_atfork callacks (similar to _Fork).
+
+   If FORK_NP_EXIT_SIGNAL is set on FLAGS, send the EXIT_SIGNAL signal
+   on process termination.
+
+   On success, the PID of the child process is returned in the parent,
+   and 0 is returned to child.  On failure, -1 is returned in the
+   parent, no child process is created.  */
+extern pid_t fork_np (fork_np_args_t *__args, __SIZE_TYPE__ __size)
+  __THROW;
+
+
 #endif /* __USE_GNU  */
diff --git a/sysdeps/unix/sysv/linux/clone-internal.c b/sysdeps/unix/sysv/linux/clone-internal.c
index 790739cfce..d121be48bc 100644
--- a/sysdeps/unix/sysv/linux/clone-internal.c
+++ b/sysdeps/unix/sysv/linux/clone-internal.c
@@ -16,6 +16,7 @@ 
    License along with the GNU C Library.  If not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <arch-fork.h>
 #include <sysdep.h>
 #include <stddef.h>
 #include <errno.h>
@@ -43,6 +44,11 @@  _Static_assert (offsetofend (struct clone_args, cgroup) == CLONE_ARGS_SIZE_VER2,
 _Static_assert (sizeof (struct clone_args) == CLONE_ARGS_SIZE_VER2,
 		"sizeof (struct clone_args) != CLONE_ARGS_SIZE_VER2");
 
+#if !__ASSUME_CLONE3 && defined __NR_clone3
+/* Set to 0 if kernel does not support clone3 syscall.  */
+static int clone3_supported = 1;
+#endif
+
 int
 __clone_internal_fallback (struct clone_args *cl_args,
 			   int (*func) (void *arg), void *arg)
@@ -84,7 +90,6 @@  __clone3_internal (struct clone_args *cl_args, int (*func) (void *args),
 # if __ASSUME_CLONE3
   return __clone3 (cl_args, sizeof (*cl_args), func, arg);
 # else
-  static int clone3_supported = 1;
   if (atomic_load_relaxed (&clone3_supported) == 1)
     {
       int ret = __clone3 (cl_args, sizeof (*cl_args), func, arg);
@@ -118,3 +123,54 @@  __clone_internal (struct clone_args *cl_args,
 }
 
 libc_hidden_def (__clone_internal)
+
+int
+__clone_fork (uint64_t extra_flags, void *pidfd, int cgroup, int exit_signal)
+{
+#ifdef __NR_clone3
+  struct clone_args clone_args =
+    {
+      .flags = extra_flags
+	       | CLONE_CHILD_SETTID
+	       | CLONE_CHILD_CLEARTID,
+      .exit_signal = exit_signal,
+      .cgroup = cgroup,
+      .child_tid = (uintptr_t) &THREAD_SELF->tid,
+      .pidfd = (uintptr_t) pidfd,
+      .parent_tid = (uintptr_t) pidfd
+    };
+#endif
+
+#if __ASSUME_CLONE3
+  return INLINE_SYSCALL_CALL (clone3, &clone_args, sizeof (clone_args));
+#else
+  /* Some architecture still does not export clone3.  */
+  pid_t pid;
+# ifdef __NR_clone3
+  if (atomic_load_relaxed (&clone3_supported) == 1)
+    {
+      pid = INLINE_SYSCALL_CALL (clone3, &clone_args, sizeof (clone_args));
+      if (pid != -1 || errno != ENOSYS)
+	return pid;
+
+      atomic_store_relaxed (&clone3_supported, 0);
+    }
+# endif
+
+  if (!(extra_flags & CLONE_INTO_CGROUP))
+    {
+      int flags = extra_flags | (exit_signal & 0xff);
+      pid = arch_fork (flags, pidfd, &THREAD_SELF->tid);
+    }
+  else
+    {
+      /* No fallback for POSIX_SPAWN_SETCGROUP if clone3 is not supported.  */
+      pid = -1;
+# ifdef __NR_clone3
+      if (errno == ENOSYS)
+# endif
+	errno = ENOTSUP;
+    }
+  return pid;
+#endif
+}
diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
index 4f4e99427b..4112163af2 100644
--- a/sysdeps/unix/sysv/linux/csky/libc.abilist
+++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
@@ -2710,6 +2710,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/fork_np.c b/sysdeps/unix/sysv/linux/fork_np.c
new file mode 100644
index 0000000000..ca9a83bb22
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/fork_np.c
@@ -0,0 +1,97 @@ 
+/* fork_np - Duplicated calling process and return a process file
+   descriptor.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <unistd.h>
+#include <clone_internal.h>
+#include <fork-internal.h>
+
+static pid_t
+fork_syscall (fork_np_args_t *args)
+{
+  bool use_pidfd = args->fork_np_flags & FORK_NP_PIDFD;
+  bool use_cgroup = args->fork_np_flags & FORK_NP_CGROUP;
+
+  int *pidfd = use_pidfd ? &args->fork_np_pidfd : NULL;
+  int cgroup = use_cgroup ? args->fork_np_cgroup : 0;
+
+  uint64_t extra_flags = (use_pidfd ? CLONE_PIDFD : 0)
+			 | (use_cgroup ? CLONE_INTO_CGROUP : 0);
+  int exit_signal = (args->fork_np_flags & FORK_NP_EXIT_SIGNAL)
+		     ? args->fork_np_exit_signal : SIGCHLD;
+
+  pid_t pid = __clone_fork (extra_flags, pidfd, cgroup, exit_signal);
+
+  if (pid == 0)
+    {
+      struct pthread *self = THREAD_SELF;
+
+      /* Initialize the robust mutex, check _Fork implementation for a full
+	 description why this is required.  */
+#if __PTHREAD_MUTEX_HAVE_PREV
+      self->robust_prev = &self->robust_head;
+#endif
+      self->robust_head.list = &self->robust_head;
+      INTERNAL_SYSCALL_CALL (set_robust_list, &self->robust_head,
+			     sizeof (struct robust_list_head));
+    }
+  return pid;
+}
+
+#define SUPPORTED_FLAGS (FORK_NP_PIDFD			\
+			 | FORK_NP_CGROUP		\
+			 | FORK_NP_ASYNCSAFE		\
+			 | FORK_NP_EXIT_SIGNAL)
+
+_Static_assert (sizeof (fork_np_args_t) == FORK_NP_ARGS_SIZE_VER0,
+		"sizeof (fork_np_args_t) != FORK_NP_ARGS_SIZE_VER0");
+
+pid_t
+fork_np (fork_np_args_t *args, size_t size)
+{
+  if (size != FORK_NP_ARGS_SIZE_VER0)
+    return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
+
+  if (args->fork_np_flags & ~(SUPPORTED_FLAGS))
+    return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
+
+  if ((args->fork_np_flags & FORK_NP_CGROUP) && !__clone_pidfd_supported ())
+    return INLINE_SYSCALL_ERROR_RETURN_VALUE (ENOSYS);
+
+  pid_t pid;
+  if (!(args->fork_np_flags & FORK_NP_ASYNCSAFE))
+    {
+      bool multiple_threads = !SINGLE_THREAD_P;
+      struct fork_post_state_t state = {
+	  .multiple_threads = !SINGLE_THREAD_P
+      };
+      struct nss_database_data nss_database_data;
+
+      state.lastrun = __fork_pre (multiple_threads, &nss_database_data);
+      state.pid = fork_syscall (args);
+      /* It follow the usual fork semantic, where a positive or negative
+	 value is returned to parent, and 0 for the child.  */
+      __fork_post (&state, &nss_database_data);
+
+      pid = state.pid;
+    }
+  else
+    pid = fork_syscall (args);
+
+  return pid;
+}
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index abc471dd0b..b01734661b 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -2659,6 +2659,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index 9f03c8a9a2..14e58ef02d 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2843,6 +2843,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
index ce1d20b722..25936400b8 100644
--- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
@@ -2608,6 +2608,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
index 8c3640b004..4299a45d2f 100644
--- a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
@@ -2194,6 +2194,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index a594916319..98d11f7e00 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -555,6 +555,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index 7f61d4824d..311b17c166 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -2786,6 +2786,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
index 83ebb84ff3..9a645345e7 100644
--- a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
@@ -2759,6 +2759,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
index 89a0ff83bf..bc6b3094fc 100644
--- a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
@@ -2756,6 +2756,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index e21c752057..14f2335c29 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -2751,6 +2751,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index 42f470d397..f41a1adaca 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -2749,6 +2749,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index 6907f5f98b..3500745aa0 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -2757,6 +2757,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index 4b1f017a98..64cc996c51 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -2659,6 +2659,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
index 0d45902209..723956e4be 100644
--- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
+++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
@@ -2798,6 +2798,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/or1k/libc.abilist b/sysdeps/unix/sysv/linux/or1k/libc.abilist
index c59032ef14..97657be343 100644
--- a/sysdeps/unix/sysv/linux/or1k/libc.abilist
+++ b/sysdeps/unix/sysv/linux/or1k/libc.abilist
@@ -2180,6 +2180,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index e014314d3e..a3fa2f4f87 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -2825,6 +2825,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
index ac05154915..bddf0f2d01 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
@@ -2858,6 +2858,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
index e13ee6e72a..ee9db4eff2 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
@@ -2579,6 +2579,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
index 0e8c9ab3fe..0a0c4c4650 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
@@ -2893,6 +2893,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
index b0559a5a64..0c9a1648e1 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
@@ -2436,6 +2436,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
index 5f79a84016..0acdd6fff4 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
@@ -2636,6 +2636,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index 498886ccb2..b94792e4c1 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -2823,6 +2823,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index 51679c2990..7d3a6e3c90 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -2616,6 +2616,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/sh/be/libc.abilist b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
index af7b6f5bc9..1c26740359 100644
--- a/sysdeps/unix/sysv/linux/sh/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
@@ -2666,6 +2666,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/sh/le/libc.abilist b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
index b766299f31..5b0bd8c6c8 100644
--- a/sysdeps/unix/sysv/linux/sh/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
@@ -2663,6 +2663,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index f5b9200a33..9e18f09c1e 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -2818,6 +2818,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index f6012e6e17..3a94cf17ee 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -2631,6 +2631,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/tst-fork_np-cgroup.c b/sysdeps/unix/sysv/linux/tst-fork_np-cgroup.c
new file mode 100644
index 0000000000..e024537fc8
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-fork_np-cgroup.c
@@ -0,0 +1,170 @@ 
+/* fork_np test using cgroupsv2.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <sched.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+#include <support/temp_file.h>
+#include <sys/pidfd.h>
+#include <sys/vfs.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+
+#define CGROUPFS "/sys/fs/cgroup/"
+#ifndef CGROUP2_SUPER_MAGIC
+# define CGROUP2_SUPER_MAGIC 0x63677270
+#endif
+
+#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b)
+
+static inline char *
+startswith(const char *s, const char *prefix)
+{
+  size_t l = strlen (prefix);
+  if (strncmp (s, prefix, l) == 0)
+    return (char*) s + l;
+  return NULL;
+}
+
+static char *
+get_cgroup (void)
+{
+  FILE *f = xfopen ("/proc/self/cgroup", "re");
+
+  char *cgroup = NULL;
+
+  char *line = NULL;
+  size_t linesiz = 0;
+  while (xgetline (&line, &linesiz, f) > 0)
+    {
+      char *entry = startswith (line, "0:");
+      if (entry == NULL)
+	continue;
+
+      entry = strchr (entry, ':');
+      if (entry == NULL)
+	continue;
+
+      cgroup = entry + 1;
+      size_t l = strlen (cgroup);
+      if (cgroup[l - 1] == '\n')
+	cgroup[l - 1] = '\0';
+
+      cgroup = xstrdup (entry + 1);
+      break;
+    }
+
+  xfclose (f);
+  free (line);
+
+  return cgroup;
+}
+
+static int
+do_test (void)
+{
+  struct statfs fs;
+  if (statfs (CGROUPFS, &fs) < 0)
+    {
+      if (errno == ENOENT)
+	FAIL_UNSUPPORTED ("not cgroupv2 mount found");
+      FAIL_EXIT1 ("statfs (%s): %m\n", CGROUPFS);
+    }
+
+  if (!F_TYPE_EQUAL (fs.f_type, CGROUP2_SUPER_MAGIC))
+    FAIL_UNSUPPORTED ("%s is not a cgroupv2", CGROUPFS);
+
+  char *cgroup = get_cgroup ();
+  TEST_VERIFY_EXIT (cgroup != NULL);
+  char *newcgroup = xasprintf ("%s/%s", cgroup, "test-fork_np-cgroup");
+  char *cgpath = xasprintf ("%s%s/test-fork_np-cgroup", CGROUPFS, cgroup);
+  free (cgroup);
+
+  if (mkdir (cgpath, 0755) == -1 && errno != EEXIST)
+    {
+      if (errno == EACCES || errno == EPERM || errno == EROFS)
+	FAIL_UNSUPPORTED ("can not create a new cgroupv2 group");
+      FAIL_EXIT1 ("mkdir (%s): %m", cgpath);
+    }
+  add_temp_file (cgpath);
+
+  int dfd = xopen (cgpath, O_DIRECTORY | O_RDONLY | O_CLOEXEC, 0666);
+
+  /* Check if the cgroup used at creation is the same returned by the kernel
+     and not as the parent.  */
+  {
+    fork_np_args_t pidfd_args = {
+      .fork_np_flags = FORK_NP_CGROUP,
+      .fork_np_cgroup = dfd,
+    };
+    pid_t pid = fork_np (&pidfd_args, sizeof pidfd_args);
+    if (pid == -1 && errno == ENOTSUP)
+      FAIL_UNSUPPORTED ("kernel does not support CLONE_PIDFD clone flag");
+    TEST_VERIFY_EXIT (pid != -1);
+    if (pid == 0)
+      {
+	char *child_cgroup = get_cgroup ();
+	TEST_VERIFY_EXIT (child_cgroup != NULL);
+	TEST_COMPARE_STRING (newcgroup, child_cgroup);
+	_exit (EXIT_SUCCESS);
+      }
+
+    siginfo_t sinfo;
+    TEST_COMPARE (waitid (P_PID, pid, &sinfo, WEXITED), 0);
+    TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+    TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+    TEST_COMPARE (sinfo.si_status, 0);
+  }
+
+  /* Same as before, but also check along with process file descriptor.  */
+  {
+    fork_np_args_t pidfd_args = {
+      .fork_np_flags = FORK_NP_PIDFD | FORK_NP_CGROUP,
+      .fork_np_cgroup = dfd,
+    };
+    pid_t pid = fork_np (&pidfd_args, sizeof pidfd_args);
+    TEST_VERIFY_EXIT (pid != -1);
+    if (pid == 0)
+      {
+	char *child_cgroup = get_cgroup ();
+	TEST_VERIFY_EXIT (child_cgroup != NULL);
+	TEST_COMPARE_STRING (newcgroup, child_cgroup);
+	_exit (EXIT_SUCCESS);
+      }
+
+    siginfo_t sinfo;
+    TEST_COMPARE (waitid (P_PIDFD, pidfd_args.fork_np_pidfd, &sinfo,
+			  WEXITED), 0);
+    TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+    TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+    TEST_COMPARE (sinfo.si_status, 0);
+  }
+
+  free (cgpath);
+  free (newcgroup);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/tst-fork_np.c b/sysdeps/unix/sysv/linux/tst-fork_np.c
new file mode 100644
index 0000000000..568d2245ee
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-fork_np.c
@@ -0,0 +1,236 @@ 
+/* Basic tests for fork_np.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+#include <support/xsignal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define SIG_PID_EXIT_CODE 20
+
+static bool atfork_prepare_var;
+static bool atfork_parent_var;
+static bool atfork_child_var;
+
+static sig_atomic_t sigchld_called;
+
+static void
+sigchld_handler (int sig)
+{
+  sigchld_called = 1;
+}
+
+static void
+atfork_prepare (void)
+{
+  atfork_prepare_var = true;
+}
+
+static void
+atfork_parent (void)
+{
+  atfork_parent_var = true;
+}
+
+static void
+atfork_child (void)
+{
+  atfork_child_var = true;
+}
+
+static int
+singlethread_test (bool async, bool wait_with_pid, bool nosigchld)
+{
+  const char testdata1[] = "abcdefghijklmnopqrtuvwxz";
+  enum { testdatalen1 = array_length (testdata1) };
+  const char testdata2[] = "01234567890";
+  enum { testdatalen2 = array_length (testdata2) };
+
+  pid_t ppid = getpid ();
+
+  int tempfd = create_temp_file ("tst-fork_np", NULL);
+
+  /* Check if the opened file is shared between process by read and write
+     some data on parent and child processes.  */
+  xwrite (tempfd, testdata1, testdatalen1);
+  off_t off = xlseek (tempfd, 0, SEEK_CUR);
+  TEST_COMPARE (off, testdatalen1);
+
+  fork_np_args_t fork_args = {
+    .fork_np_flags = FORK_NP_PIDFD
+		     | (async ? FORK_NP_ASYNCSAFE : 0)
+		     | (nosigchld ? FORK_NP_EXIT_SIGNAL : 0)
+  };
+  pid_t pid = fork_np (&fork_args, sizeof fork_args);
+  TEST_VERIFY_EXIT (pid != -1);
+
+  sigchld_called = 0;
+
+  if (pid == 0)
+    {
+      if (async)
+	TEST_VERIFY (!atfork_child_var);
+      else
+	TEST_VERIFY (atfork_child_var);
+
+      TEST_VERIFY_EXIT (getpid () != ppid);
+      TEST_COMPARE (getppid(), ppid);
+
+      TEST_COMPARE (xlseek (tempfd, 0, SEEK_CUR), testdatalen1);
+
+      xlseek (tempfd, 0, SEEK_SET);
+      char buf[testdatalen1];
+      TEST_COMPARE (read (tempfd, buf, sizeof (buf)), testdatalen1);
+      TEST_COMPARE_BLOB (buf, testdatalen1, testdata1, testdatalen1);
+
+      xlseek (tempfd, 0, SEEK_SET);
+      xwrite (tempfd, testdata2, testdatalen2);
+
+      xclose (tempfd);
+
+      _exit (EXIT_SUCCESS);
+    }
+
+  {
+    siginfo_t sinfo;
+    int options = WEXITED | (nosigchld ? __WCLONE : 0);
+    if (wait_with_pid)
+      TEST_COMPARE (waitid (P_PID, pid, &sinfo, options), 0);
+    else
+      TEST_COMPARE (waitid (P_PIDFD, fork_args.fork_np_pidfd, &sinfo,
+			    options), 0);
+    TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+    TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+    TEST_COMPARE (sinfo.si_status, 0);
+
+    /* If nosigchld is specified no SIGCHLD should be sent by the kernel.  */
+    TEST_COMPARE (sigchld_called, nosigchld ? 0 : 1);
+  }
+
+  TEST_COMPARE (xlseek (tempfd, 0, SEEK_CUR), testdatalen2);
+
+  xlseek (tempfd, 0, SEEK_SET);
+  char buf[testdatalen2];
+  TEST_COMPARE (read (tempfd, buf, sizeof (buf)), testdatalen2);
+
+  TEST_COMPARE_BLOB (buf, testdatalen2, testdata2, testdatalen2);
+
+  return 0;
+}
+
+static int
+do_test (void)
+{
+  /* Sanity check for pidfd support. */
+  TEST_COMPARE (fork_np (NULL, -1), -1);
+  TEST_COMPARE (errno, EINVAL);
+
+  {
+    fork_np_args_t fork_args = {
+      .fork_np_flags = FORK_NP_PIDFD,
+    };
+    pid_t pid = fork_np (&fork_args, sizeof fork_args);
+    if (pid == -1 && errno == ENOSYS)
+      FAIL_UNSUPPORTED ("kernel does not support CLONE_PIDFD clone flag");
+    TEST_VERIFY_EXIT (pid != -1);
+    if (pid == 0)
+      _exit (EXIT_SUCCESS);
+
+    siginfo_t sinfo;
+    TEST_COMPARE (waitid (P_PID, pid, &sinfo, WEXITED), 0);
+    TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+    TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+    TEST_COMPARE (sinfo.si_status, 0);
+  }
+
+  {
+    struct sigaction sa;
+    sa.sa_handler = sigchld_handler;
+    sa.sa_flags = 0;
+    sigemptyset (&sa.sa_mask);
+    xsigaction (SIGCHLD, &sa, NULL);
+  }
+
+  pthread_atfork (atfork_prepare, atfork_parent, atfork_child);
+
+  /* With default flags, fork_np acts as fork and run the pthread_atfork
+     handlers.  */
+  {
+    atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+    singlethread_test (false, false, false);
+    TEST_VERIFY (atfork_prepare_var);
+    TEST_VERIFY (atfork_parent_var);
+    TEST_VERIFY (!atfork_child_var);
+  }
+
+  /* Same as before, but also wait using the PID instead of pidfd.  */
+  {
+    atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+    singlethread_test (false, true, false);
+    TEST_VERIFY (atfork_prepare_var);
+    TEST_VERIFY (atfork_parent_var);
+    TEST_VERIFY (!atfork_child_var);
+  }
+
+  /* Using pidfd and disable SIGCHLD.  */
+  {
+    atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+    singlethread_test (false, false, true);
+    TEST_VERIFY (atfork_prepare_var);
+    TEST_VERIFY (atfork_parent_var);
+    TEST_VERIFY (!atfork_child_var);
+  }
+
+  /* With FORK_NP_ASYNCSAFE, fork_np acts as _Fork.  */
+  {
+    atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+    pthread_atfork (atfork_prepare, atfork_parent, atfork_child);
+    singlethread_test (true, false, false);
+    TEST_VERIFY (!atfork_prepare_var);
+    TEST_VERIFY (!atfork_parent_var);
+    TEST_VERIFY (!atfork_child_var);
+  }
+
+  {
+    atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+    pthread_atfork (atfork_prepare, atfork_parent, atfork_child);
+    singlethread_test (true, true, false);
+    TEST_VERIFY (!atfork_prepare_var);
+    TEST_VERIFY (!atfork_parent_var);
+    TEST_VERIFY (!atfork_child_var);
+  }
+
+  {
+    atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+    singlethread_test (true, true, true);
+    TEST_VERIFY (!atfork_prepare_var);
+    TEST_VERIFY (!atfork_parent_var);
+    TEST_VERIFY (!atfork_child_var);
+  }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index e35bf54779..bf06381f82 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -2582,6 +2582,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index e7d7eb61c0..032347e89c 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2688,6 +2688,7 @@  GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
 GLIBC_2.38 wcslcat F
 GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
 GLIBC_2.39 pidfd_spawn F
 GLIBC_2.39 pidfd_spawnp F
 GLIBC_2.39 posix_spawnattr_getcgroup_np F