Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec couche 0 — Famille process : extension privsep (setgroups / setresgid / setresuid + getres*)

Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS). Extension de la famille process.

Position

Cette extension ajoute les primitives manquantes indispensables à une réduction de privilèges correcte (drop_privileges d’air-process, couche 1, Principe 10). La famille process n’expose ni setgroups, setresgid, setresuid, ni les getres* de vérification (ni même setuid/setgid), et pas de types Uid/Gid — ces types sont ajoutés par cette extension (constaté à l’implémentation, PR #34).

Émergence (méthode doc-d’abord). Manque révélé en spécifiant air-process : faire un privsep avec setuid/setgid seuls est un footgun de sécurité — les groupes supplémentaires ne sont pas largués, et le saved-set-uid/gid n’est pas fixé → possibilité de regagner les privilèges. 4ᵉ manque couche 0 identifié, et le plus prioritaire (sécurité), à côté de fs::inotify, MmapRegion (résolu) et epoll.

Sous-module : air-sys-syscall::process (extension). Privilégié : ces opérations exigent les capabilities adéquates (CAP_SETUID/CAP_SETGID) ou d’être root au moment de l’appel.


1. Types

Réutilise les newtypes typés Uid/Gid de la famille process (ADR-029 : jamais d’u32 brut pour un identifiant). S’ils n’existent pas encore, les ajouter (air-sys-types::process) en même temps — Uid(u32)/Gid(u32), #[repr(transparent)], avec as_raw/from_raw. (À confirmer à l’implémentation.)


2. Largage des groupes supplémentaires

#![allow(unused)]
fn main() {
/// `setgroups` — remplace la liste des groupes supplémentaires du processus.
/// Pour un privsep, on passe **`&[]`** (largage total) tant qu'on est privilégié.
/// # Errors `EPERM` (sans `CAP_SETGID`), `EINVAL` (trop de groupes), `EFAULT`.
pub fn set_groups(groups: &[Gid]) -> Result<(), Errno>;

/// `getgroups` — lit la liste courante (vérification défensive). Buffer fourni,
/// zéro alloc ; retourne la tranche remplie.
pub fn get_groups<'b>(buffer: &'b mut [Gid]) -> Result<&'b [Gid], Errno>;
}

Syscalls. setgroups (x86_64 n°116, ARM64 n°159), getgroups (x86_64 n°115, ARM64 n°158). Pas de sentinelle ; la liste vide est la valeur normale du privsep (et non un cas magique).


3. Identité réelle/effective/sauvegardée

#![allow(unused)]
fn main() {
/// `setresgid` — fixe **real + effective + saved** GID en un appel. À faire
/// **avant** `setresuid`. `None` pour un composant = « inchangé » (sentinelle
/// kernel `-1` typée en `Option`, ADR-021 conv. 1).
/// # Errors `EPERM`, `EINVAL`.
pub fn set_resgid(real: Option<Gid>, effective: Option<Gid>, saved: Option<Gid>)
    -> Result<(), Errno>;

/// `setresuid` — fixe **real + effective + saved** UID en un appel. Après le GID.
/// `None` = « inchangé » (`-1` typé).
/// # Errors `EPERM`, `EINVAL`.
pub fn set_resuid(real: Option<Uid>, effective: Option<Uid>, saved: Option<Uid>)
    -> Result<(), Errno>;

/// `getresgid` / `getresuid` — lecture des trois composantes (vérification
/// défensive : confirmer qu'on **ne peut plus** revenir en arrière).
pub fn get_resgid() -> Result<ResGid, Errno>;
pub fn get_resuid() -> Result<ResUid, Errno>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResUid { pub real: Uid, pub effective: Uid, pub saved: Uid }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResGid { pub real: Gid, pub effective: Gid, pub saved: Gid }
}

Syscalls. setresgid (x86_64 n°119, ARM64 n°149), setresuid (x86_64 n°117, ARM64 n°147), getresgid (x86_64 n°120, ARM64 n°150), getresuid (x86_64 n°118, ARM64 n°148). (Numéros vérifiés sur l’uapi 6.12 par arche à l’implémentation — setresgid ARM64 corrigé 143 → 149, PR #34.)

Pourquoi setres* et non set*. setresuid/setresgid fixent explicitement les trois identités (real/effective/saved) en un syscall. Fixer le saved-set est ce qui rend le retour aux privilèges impossible — garantie qu’un setuid/setgid simple ne donne pas de façon fiable. C’est la raison d’être de cette extension. None (= -1 kernel) laisse une composante inchangée, typé proprement (pas de -1 magique).


4. Récapitulatif

FonctionRôleSyscall
set_groups / get_groupslarguer / lire les groupes supplémentairessetgroups / getgroups
set_resgid / get_resgidGID real+effective+savedsetresgid / getresgid
set_resuid / get_resuidUID real+effective+savedsetresuid / getresuid

Total : ~6 fonctions. Types ajoutés à air-sys-types::process : ResUid, ResGid (+ Uid/Gid si absents).

5. Tests

  • Privilégiés (root / CAP_SETUID+CAP_SETGID), en sous-processus isolés (fork + observation via waitid), skip propre si non privilégié → COVERAGE-EXCEPTIONS.md (catégorie PRIVILEGE) :
    • set_groups(&[]) puis get_groups rend une liste vide.
    • set_resgid puis set_resuid vers un uid/gid non-root, puis vérifier l’impossibilité de revenir : set_resuid(Some(root), …)EPERM ; get_resuid confirme real==effective==saved==cible.
    • None (composante inchangée) : une seule composante modifiée.
  • Erreurs : EPERM sans capability (couvert sur exécuteur non-root), EINVAL.
  • Property-based : encodage des Option<Uid/Gid>-1 pour None, round-trip.
  • Validation cross-arch x86_64 + aarch64.

6. Décisions de fond

  1. setres* (et non set*) pour fixer le saved-set — interdiction du retour aux privilèges par construction.
  2. setgroups(&[]) de première classe — largage des groupes supplémentaires, étape obligatoire du privsep.
  3. None = composante inchangée (-1 kernel typé, ADR-021 conv. 1).
  4. getres*/getgroups exposés pour la vérification défensive de drop_privileges (Principe 5 : confirmer la réduction).

Licence du document : MPL 2.0 Statut : Spécification technique de l’extension privsep de air-sys-syscall::process (couche 0), cible kernel 6.12 LTS. Prérequis de air-process::drop_privileges.