Skip to content
/linux-syscalls

Sécurité & Identifiants · Section 2

setuid(2)

Modifie l'identifiant utilisateur du processus appelant — la primitive canonique d'abandon de privilèges.

Signature

#include <unistd.h>

int setuid(uid_t uid);
uid
Identifiant utilisateur cible. 0 = root ; UID > 0 sont non privilégiés sauf capacités spécifiques.

Description

setuid() positionne l'identifiant utilisateur effectif du processus. Si l'appelant est privilégié (CAP_SETUID), il positionne aussi le RUID et le saved set-user-ID — un abandon irréversible de root vers une identité non privilégiée. Si l'appelant est non privilégié, setuid() ne peut positionner l'EUID qu'à l'une de {RUID, EUID, SUID} — garantie POSIX limitée qui permet aux binaires set-UID de basculer entre leurs deux identités. setuid(0) appelé par un non-root échoue toujours. Le motif daemon-privilégié typique : ouvrir les ressources privilégiées (bind port < 1024, lire /etc/shadow, abandonner les CAP_*), puis setuid() vers un utilisateur dédié — libérant root pour le reste de la vie du processus. À noter : setuid() NE modifie PAS les capacités directement ; utiliser prctl(PR_SET_KEEPCAPS) avant setuid() pour conserver une capacité, ou capset() après.

Numéros par architecture

ArchitectureNuméroABIPoint d'entrée
x86 (i386)23i386sys_setuid16
x64 (x86_64)105commonsys_setuid
ARM64 (aarch64)146—sys_setuid

Historique noyau

Introduit dans Linux 1.0.

  1. 1.0

    setuid() est présent dans Linux depuis 1.0 avec la sémantique POSIX. Le point d'entrée i386 sys_setuid16 reflète la largeur UID 16 bits originelle ; la variante 32 bits passe par un autre numéro d'appel sur les chemins hérités.

  2. 2.4

    Les UID 32 bits ont été introduits ; les appels système 16 bits (sys_setuid16) restent par compatibilité ABI mais le code récent utilise les variantes 32 bits.

  3. 3.8

    Les espaces utilisateur (CLONE_NEWUSER) ont ajouté un mapping UID par espace. setuid() dans un userns non privilégié opère dans le mapping de cet espace — l'appelant peut être 'root' dans le userns tout en étant utilisateur normal sur l'hôte. Fondation des conteneurs rootless mais aussi zone riche en CVE.

seccomp & conteneurs

Docker default profile

Autorisé

Podman default profile

Autorisé

setuid() et le reste de la famille UID/GID sont dans les profils Docker / Podman par défaut car les init de conteneur les utilisent pour abandonner les privilèges avant de lancer les workers. Les charges qui n'ont pas besoin de changer de privilèges (chaque conteneur qui tourne sous un utilisateur unique fixe) peuvent refuser toute la famille — durcissement utile fermant la voie « enfant root compromis qui escalade vers une autre identité ». La recette libseccomp ci-dessus bloque les huit variantes pertinentes.

libseccomp

// Block uid manipulation if the workload never needs to drop privileges
for (int s : { SCMP_SYS(setuid), SCMP_SYS(setreuid), SCMP_SYS(setresuid),
               SCMP_SYS(setgid), SCMP_SYS(setregid), SCMP_SYS(setresgid),
               SCMP_SYS(setfsuid), SCMP_SYS(setfsgid) })
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), s, 0);

Exemple strace

$ strace -e setuid,setresuid sudo -u nobody true 2>&1 | head -3
setresuid(65534, 65534, 65534)          = 0
setuid(65534)                           = 0

Le code moderne passe généralement par setresuid() plutôt que setuid() (plus sûr et explicite). strace affiche les deux. Une trace de démarrage de démon montre setuid() / setgid() / capset() exactement une fois chacun, tôt ; des appels ultérieurs sont inhabituels.

Sécurité & observabilité

setuid() est le point focal de la gestion de privilèges — auditer quels processus l'appellent et vers quels UID est la base de tout durcissement d'hôte. Les binaires set-UID-root sont la surface classique d'élévation de privilèges (CVE-2021-3156 sudo, CVE-2023-22809 sudoedit…). Au sein d'un processus, échecs de sécurité fréquents : oublier setgid() avant setuid() (le saved set-group-ID est perdu si setuid() précède), ignorer la valeur de retour (setuid() peut échouer en EAGAIN sous charge — nombre de démons continueraient en root), et supposer que setuid() abandonne les capacités (non — utiliser prctl ou capset). Le tracepoint eBPF sys_enter_setuid capture chaque appel ; alerter sur setuid(0) depuis un PID non-init de conteneur est un signal fort.

Erreurs

EAGAIN
uid ne correspond pas à l'UID actuel et RLIMIT_NPROC pour l'utilisateur cible est atteint.
EINVAL
uid invalide dans l'espace utilisateur de l'appelant (par ex. UID non mappé dans un userns non privilégié).
EPERM
Appelant sans CAP_SETUID et uid n'est pas l'un des RUID/EUID/SUID actuels.

Syscalls liés