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
| Architecture | Number | ABI | Entry point |
|---|---|---|---|
| x86 (i386) | 2 | i386 | sys_fork |
| x64 (x86_64) | 57 | common | sys_fork |
Kernel history
Introduced in Linux 1.0.
1.0
fork() has been part of Linux since 1.0 with classic Unix semantics.
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().
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.