Skip to content
/linux-syscalls

Process & Thread · Section 2

fork(2)

Create a new process by duplicating the calling process.

Signature

#include <unistd.h>

pid_t fork();

Description

fork() creates a new process — the child — that is an almost-exact copy of the calling process (the parent). The child inherits the parent's memory (copy-on-write), open file descriptors, signal handlers, working directory, environment, and most attributes; it differs in PID, parent PID, accumulated CPU time (zero), pending signals (none), file locks (not inherited), and a few subtler points. On success fork() returns the child's PID in the parent and 0 in the child; -1 with errno on failure. Modern Linux implements fork() in terms of clone() with SIGCHLD as the exit signal and no sharing flags. On aarch64 the dedicated fork() syscall does not exist — glibc implements fork() via clone()/clone3().

Architecture mapping

ArchitectureNumberABIEntry point
x86 (i386)2i386sys_fork
x64 (x86_64)57commonsys_fork

Kernel history

Introduced in Linux 1.0.

  1. 1.0

    fork() has been part of Linux since 1.0 with classic Unix semantics.

  2. 2.0

    From 2.0 onwards fork() is implemented on top of clone() — it's a thin wrapper that calls clone(SIGCHLD, 0, …) under the hood. The semantics are unchanged but the kernel code path is shared with clone().

  3. 5.4

    aarch64 (and the asm-generic syscall table generally) does not export a fork() syscall — glibc implements fork() via clone()/clone3(). Calling fork() works portably in C; raw assembly attempting __NR_fork on aarch64 will get ENOSYS.

seccomp & containers

Docker default profile

Allowed

Podman default profile

Allowed

fork(), vfork() and clone() are all allowed by Docker / Podman default profiles. Blocking fork() alone is useless: glibc will fall back to clone(). To actually prevent process spawning, you must block clone()/clone3() (and on i386, vfork()) together. After init has finished spawning workers, an unprivileged seccomp filter that denies all three is a strong hardening against post-exploitation fan-out.

libseccomp

// Allow process creation through fork/vfork/clone
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fork),  0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(vfork), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 0);

strace example

$ strace -f -e fork,clone /bin/sh -c '(true)'
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1a8c4ada10) = 14523
[pid 14523] +++ exited with 0 +++

On modern Linux strace shows clone() not fork() in most cases (because that's what glibc actually invokes). Use -f to follow the child, and -e fork,clone,vfork to filter. The classic fork-bomb signature in strace -c is a high clone() count with near-zero time per call.

Security & observability

fork() is the foundation of the classic 'fork bomb' DoS — a process that recurses fork() exhausts RLIMIT_NPROC and pid_max, freezing the system if those are unset. On hardened systems, RLIMIT_NPROC and systemd's TasksMax= are mandatory. Beyond DoS, fork() is interesting in container security because new processes inherit the parent's namespaces — a process compromise that gains exec but not namespace-creation can still pivot through fork() to spawn workers. eBPF tracepoint sched_process_fork captures every fork (and clone); pairing with execve gives the full process-creation feed.

Errors

EAGAIN
RLIMIT_NPROC for the calling user is reached, or kernel-wide process limit (nr_threads) exhausted, or the user namespace's RLIMIT_NPROC is reached. Common under load and the classic 'fork bomb' symptom.
ENOMEM
Insufficient kernel memory to allocate the new task_struct, page tables, or kernel stack.
ENOSYS
fork() is not implemented on this architecture (modern aarch64). glibc shims this to clone() automatically.
ERESTARTNOINTR
Internal kernel value — should never reach userspace. If it does, suspect a buggy syscall interceptor.

Related syscalls