Skip to content
/linux-syscalls

Fichiers & E/S · Section 2

dup2(2)

Duplique un descripteur de fichier vers un numéro précis, en remplaçant newfd atomiquement.

Signature

#include <unistd.h>

int dup2(int oldfd, int newfd);
oldfd
Descripteur source — doit être ouvert. dup2() ne ferme pas oldfd.
newfd
Numéro de descripteur cible. S'il est ouvert, fermé d'abord. Les deux descripteurs partagent décalage, drapeaux d'état, et (avec tables fd partagées via CLONE_FILES) le compte de références.

Description

dup2() fait que newfd réfère à la même description d'ouverture qu'oldfd. Si newfd était déjà ouvert, il est silencieusement fermé d'abord ; la fermeture et la réassociation sont atomiques — aucun autre thread ne peut brièvement observer newfd comme non alloué. Si oldfd == newfd, dup2() valide oldfd et le retourne sans rien fermer ni rebinder. dup2() est le mécanisme noyau derrière la redirection d'E/S des shells : le shell fait fork(), appelle dup2(file_fd, 0/1/2) dans l'enfant pour attacher le fichier à stdin/stdout/stderr, puis execve() la commande. dup3() est la variante moderne : même sémantique plus un drapeau flags (O_CLOEXEC), fermant la petite course où un enfant pouvait fork-et-exec entre dup2() et un fcntl(F_SETFD) séparé.

Numéros par architecture

ArchitectureNuméroABIPoint d'entrée
x86 (i386)63i386sys_dup2
x64 (x86_64)33commonsys_dup2

Historique noyau

Introduit dans Linux 1.0.

  1. 1.0

    dup() et dup2() sont présents dans Linux depuis 1.0, dérivés de V7 Unix.

  2. 2.6.27

    dup3() a été ajouté pour que le nouveau descripteur ait O_CLOEXEC positionné atomiquement — éliminant la course fork-and-exec qui imposait un fcntl(F_SETFD, FD_CLOEXEC) séparé. Le code moderne utilise dup3() chaque fois que le nouveau fd ne doit pas fuir à travers execve().

seccomp & conteneurs

Docker default profile

Autorisé

Podman default profile

Autorisé

La famille dup est dans tout profil par défaut. Pas de filtrage d'arguments utile. Comme pour la plupart des appels de gestion de fd, le levier de sécurité est en amont (quels fd sont ouverts, avec quels drapeaux) plutôt qu'en aval.

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);

Exemple strace

$ strace -e dup,dup2,dup3 bash -c 'cat </dev/null'
dup2(3, 0)                              = 0

dup2() apparaît dans toute trace de redirection shell ou de reverse shell. Combiné à -f depuis un shell parent, on voit exactement comment stdin/stdout sont recâblés avant chaque commande. -e trace=dup2,dup3 filtre.

Sécurité & observabilité

dup2() est l'ingrédient canonique du reverse shell : une socket connectée est dup2()ée vers les fd 0/1/2 pour qu'un execve('/bin/sh') hérite de la connexion réseau en stdin/stdout/stderr. Repérer le motif socket() → connect() → dup2(sockfd, 0) → dup2(sockfd, 1) → dup2(sockfd, 2) → execve('/bin/sh') dans la télémétrie eBPF attrape la plupart des payloads prêts à l'emploi — signature à forte précision. Les EDR conteneurs (règle Falco « Reverse Shell via dup2 ») y correspondent exactement. La question CLOEXEC compte ici : un serveur qui dup2() un fd par requête sans CLOEXEC et exec ensuite peut fuiter le fd au programme exécuté.

Erreurs

EBADF
oldfd n'est pas ouvert, ou newfd hors plage autorisée.
EBUSY
Course avec un autre thread fermant newfd simultanément (spécifique Linux, très rare).
EINTR
Interrompu par un signal pendant la fermeture du newfd précédent.
EINVAL
newfd == oldfd n'est pas le problème (c'est valide) ; mais pour dup3(), oldfd == newfd est rejeté.
EMFILE
newfd dépasse la limite par processus de descripteurs.

Syscalls liés