File & I/O · Section 2
dup2(2)
Duplicate an open file descriptor to a specific number, atomically replacing newfd.
Signature
#include <unistd.h>
int dup2(int oldfd, int newfd);- oldfd
- Source descriptor — must be open. dup2() does not close oldfd.
- newfd
- Target descriptor number. If currently open, closed first. The two descriptors share file offset, status flags, and (with shared fd tables, e.g. CLONE_FILES) reference count.
Description
dup2() makes newfd refer to the same open file description as oldfd. If newfd was already open, it is silently closed first; the close and rebinding are atomic — no other thread can briefly observe newfd as unallocated. If oldfd == newfd, dup2() just validates oldfd and returns it without closing or rebinding. dup2() is the kernel mechanism behind shell I/O redirection: shells fork(), call dup2(file_fd, 0/1/2) in the child to attach the file to stdin/stdout/stderr, then execve() the command. dup3() is the modern variant: same semantics plus a flags argument (O_CLOEXEC), closing the small race where a forked child could fork-and-exec between dup2() and a separate fcntl(F_SETFD) call.
Architecture mapping
| Architecture | Number | ABI | Entry point |
|---|---|---|---|
| x86 (i386) | 63 | i386 | sys_dup2 |
| x64 (x86_64) | 33 | common | sys_dup2 |
Kernel history
Introduced in Linux 1.0.
1.0
dup() and dup2() are part of Linux since 1.0, derived from V7 Unix.
2.6.27
dup3() was added so the new descriptor can have O_CLOEXEC set atomically — eliminating the fork-and-exec race that previously required a separate fcntl(F_SETFD, FD_CLOEXEC). Modern code uses dup3() whenever the new fd should not leak across execve().
seccomp & containers
Docker default profile
Allowed
Podman default profile
Allowed
The dup family is on every default profile. No useful argument-level filtering. As with most fd-management calls, the security lever is upstream (which fds get opened, with what flags) rather than downstream.
libseccomp
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup2), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup3), 0);strace example
$ strace -e dup,dup2,dup3 bash -c 'cat </dev/null'
dup2(3, 0) = 0dup2() appears in any shell-redirection or reverse-shell trace. Combined with -f and starting from a parent shell, you can see exactly how stdin/stdout get rewired before each command. -e trace=dup2,dup3 filters.
Security & observability
dup2() is the canonical reverse-shell ingredient: a connected socket is dup2()'d to fds 0/1/2 so that an execve('/bin/sh') inherits the network connection as stdin/stdout/stderr. Searching for the pattern socket() → connect() → dup2(sockfd, 0) → dup2(sockfd, 1) → dup2(sockfd, 2) → execve('/bin/sh') in eBPF telemetry catches most off-the-shelf payloads — it's a high-precision signature. Container EDRs (Falco rule 'Reverse Shell via dup2') match exactly this. The CLOEXEC question matters here: a server that dup2()s a per-request fd without CLOEXEC and then execs may leak the fd to the executed program.
Errors
- EBADF
- oldfd is not open, or newfd is outside the allowed range.
- EBUSY
- Race with another thread closing newfd at the same time (Linux-specific, very rare).
- EINTR
- Interrupted by a signal during close of the previous newfd.
- EINVAL
- newfd == oldfd is not the issue (that's valid); but for dup3(), passing oldfd == newfd is rejected.
- EMFILE
- newfd exceeds the per-process descriptor limit.