std.ns — Namespaces & pidfd

import std.ns;

Zwei Mechanismen zur Prozess-Isolation:

  • pidfd: Stabiler Prozess-Deskriptor — bleibt gültig auch wenn die PID wiederverwendet wird. Race-condition-freie Signal-Zustellung und fd-Duplikation.
  • Namespaces: Isolieren Systemressourcen (PID, Netzwerk, Mounts, User, UTS, IPC) zwischen Prozessgruppen.

Syscalls: pidfd_open(434), pidfd_send_signal(424), pidfd_getfd(438), unshare(272), setns(308).

std.debug · std.security_ext · std.ipc_sysv


Namespace-Konstanten

CLONE_NEW*-Flags

Konstante Wert Namespace-Typ
CLONE_NEWNS 131072 Mount-Namespace
CLONE_NEWUTS 67108864 UTS (Hostname/Domainname)
CLONE_NEWIPC 134217728 IPC-Namespace
CLONE_NEWUSER 268435456 User-Namespace
CLONE_NEWPID 536870912 PID-Namespace
CLONE_NEWNET 1073741824 Netzwerk-Namespace
CLONE_NEWCGROUP 33554432 cgroup-Namespace
CLONE_NEWTIME 128 Zeit-Namespace (Kernel 5.6+)

Namespace-Typen für NamespaceJoin

Konstante Wert Bedeutung
NSTYPE_ANY 0 Automatisch vom fd ableiten
NSTYPE_IPC 134217728 IPC-Namespace beitreten
NSTYPE_UTS 67108864 UTS-Namespace beitreten
NSTYPE_NET 1073741824 Netz-Namespace beitreten
NSTYPE_PID 536870912 PID-Namespace beitreten
NSTYPE_USER 268435456 User-Namespace beitreten
NSTYPE_MNT 131072 Mount-Namespace beitreten

pidfd-Funktionen

Funktion Signatur Beschreibung
PidFdOpen (pid: int64, flags: int64): int64 Öffnet stabilen Prozess-Deskriptor für pid; flags=0
PidFdSendSignal (pidfd: int64, sig: int64): int64 Sendet Signal race-condition-frei; sig=0 = Existenz-Check
PidFdGetFd (pidfd: int64, targetFd: int64): int64 Dupliziert fd des Zielprozesses in aktuellen Prozess

Namespace-Funktionen

Funktion Signatur Beschreibung
NamespaceUnshare (flags: int64): int64 Erzeugt neue Namespaces für aktuellen Prozess
NamespaceJoin (fd: int64, nsType: int64): int64 Tritt bestehendem Namespace bei (via Namespace-fd)

Verwendung

Race-freies Signal senden

import std.ns;

fn KillSafely(pid: int64): int64 {
    // PID kann wiederverwendet werden — pidfd bleibt eindeutig
    var pidfd: int64 := PidFdOpen(pid, 0);
    if (pidfd < 0) { return pidfd; }

    var r: int64 := PidFdSendSignal(pidfd, 15);  // SIGTERM
    close(pidfd);
    return r;
}

fd eines anderen Prozesses duplizieren

import std.ns;

fn StealFd(targetPid: int64, targetFdNum: int64): int64 {
    // Erfordert CAP_SYS_PTRACE oder gleicher User-Namespace
    var pidfd: int64 := PidFdOpen(targetPid, 0);
    if (pidfd < 0) { return pidfd; }

    var newFd: int64 := PidFdGetFd(pidfd, targetFdNum);
    close(pidfd);
    return newFd;  // Eigener fd auf die Datei des Zielprozesses
}

Netzwerk-Namespace isolieren

import std.ns;

fn IsolateNetwork(): int64 {
    // Neuen Netz-Namespace für diesen Prozess erzeugen
    // (nur eigene Loopback-Schnittstelle sichtbar)
    return NamespaceUnshare(CLONE_NEWNET);
}

User-Namespace (ohne root)

import std.ns;

fn UnprivilegedNamespace(): int64 {
    // CLONE_NEWUSER benötigt kein root (Kernel 3.8+, sofern aktiviert)
    var r: int64 := NamespaceUnshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID);
    return r;
}

Bestehendem Namespace beitreten

import std.ns;
import std.fs;

fn JoinContainerNet(containerPid: int64): int64 {
    // /proc/<pid>/ns/net öffnen
    // Pfad muss manuell aufgebaut werden (kein sprintf in dieser Unit)
    var fd: int64 := OpenFile("/proc/1/ns/net", 0);
    if (fd < 0) { return fd; }

    var r: int64 := NamespaceJoin(fd, NSTYPE_NET);
    close(fd);
    return r;
}


Hinweise

  • pidfd vs. PID: PIDs werden nach Prozessende wiederverwendet. Ein pidfd bleibt eindeutig für die Lebensdauer des Prozesses — unverzichtbar wenn zwischen getpid() und Signal-Send ein Prozess sterben kann.
  • PidFdGetFd: Erfordert CAP_SYS_PTRACE oder dass beide Prozesse im selben User-Namespace laufen. Nutzbar um Dateideskriptoren aus Containern zu extrahieren.
  • CLONE_NEWUSER: Einziger Namespace der ohne root erzeugt werden kann (wenn kernel.unprivileged_userns_clone=1). Ubuntu deaktiviert das standardmäßig.
  • Namespace-fd-Pfad: Für NamespaceJoin muss /proc/<pid>/ns/<type> manuell als Pfad geöffnet werden; die Unit stellt kein Pfad-Bauen bereit.
  • NamespaceUnshare + fork(): Namespaces werden von Kindprozessen geerbt. Erzeugt der Elternprozess zuerst Namespaces, gelten diese für alle fork()-Kinder.

Letzte Aktualisierung: 2026-06-06