Skip to content
/linux-syscalls

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

ArchitectureNumberABIEntry point
x86 (i386)63i386sys_dup2
x64 (x86_64)33commonsys_dup2

Kernel history

Introduced in Linux 1.0.

  1. 1.0

    dup() and dup2() are part of Linux since 1.0, derived from V7 Unix.

  2. 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)                              = 0

dup2() 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.

Related syscalls