CVE-2026-104795 in OpenShift Container Platform
Riassunto
di VulDB • 14/10/2026
Il componente insights-runtime-extractor viene eseguito come un DaemonSet privilegiato (`hostPID: true`) che scansiona i processi dei container per effettuare il fingerprinting del runtime. Durante la scansione di un container, il coordinatore:
1. Esegue **fork()** su un processo figlio 2. Utilizza **setns()** per entrare nei namespace PID e mount del target container 3. Chiama **fchdir()** tornando a un FD orfano che punta al proprio filesystem radice dell'estrattore (dove `/crio.sock` è montato come hostPath) 4. Esegue **solo `seteuid(target_uid)`** — modificando l'UID effettivo ma lasciando il **real UID e saved-set-UID a 0** e mantenendo inalterata la set di capacità consentite (permitted capability set) 5. Il guardiano `check_no_privileged_perms()` ispeziona solo la set di capacità effettiva (quindi supera il controllo), ed è **completamente saltato quando l'utente target esegue con uid 0** 6. La fingerprinting `VersionExecutable` invoca `fpr_kind_executable` passando `process.command_line[0]` — ovvero argv[0] del target, letto da `/proc/<pid>/cmdline` e **completamente controllato dal workload**
7. `fpr_kind_executable` chiama `exec.Command(executable, "--version").Output()` — eseguendo il **binario stesso del workload** all'interno del namespace mount del workload mentre l'**ruid=0 nel namespace utente host**.
Il processo eseguito può chiamare `setuid(0)` per ripristinare i privilegi di root completi con `CAP_SYS_ADMIN`, ed eredita il cwd orfano che risolve nel filesystem del container dell'estrattore contenente la socket CRI-O dell'host.
## Codice interessato
### 1. Insufficiente riduzione dei privilegi (Rust — coordinatore estrattore)
**File:** `extractor/src/insights_runtime_extractor.rs`
```rust // Righe 139-146 — drop basato solo su seteuid, guardiano saltato per i container root join_process_namespaces(process.pid)?; change_dir(current_dir); switch_user(*process.uid)?; if *process.uid != 0 {
perms::check_no_privileged_perms() .expect("Must not have privileged permissions to run fingerprints"); } fingerprint::run_fingerprints(&config, out_dir, &process); ```
```rust // Righe 182-185 — solo l'UID effettivo viene modificato; ruid/suid restano a 0 fn switch_user(uid: u32) -> Result<(), ScannerError> {
debug!("... Becoming user: {uid}");
seteuid(uid.into()).map_err(|e| ScannerError::Errno(e)) } ```
### 2. Esecuzione di binario non attendibile (Go — fingerprinting)
**File:** `fingerprints/pkg/utils/file.go`
```go // Righe 75-80 — esegue un percorso preso verbatim da argv[0] del workload
func GetExecutableVersionOutput(executable string) (string, error) {
out, err := exec.Command(executable, "--version").Output() if err != nil {
return "", err } return string(out), nil } ```
### 3. Percorso controllato dal workload inoltrato (Rust — version_executable)
**File:** `version_executable.rs`
```rust // Righe 29-34 — command_line[0] controllato dal workload viene inoltrato al fingerprinting
return Some(vec![
fpr_kind_executable, out_dir.to_string(), String::from(&process.command_line[0]),
String::from(&version_executable.runtime_kind_name), ]); ```
## Pattern di attacco
**Attaccante:** Qualsiasi tenant in grado di schedulare un pod (non privilegiato, qualsiasi namespace) **Vittima:** Il nodo che esegue il DaemonSet insights-runtime-extractor **Trigger:** Automatico — il DaemonScanscansiona tutti i processi dei container sul nodo
**Sequenza dell'attacco:**
1. L'attaccante crea un pod il cui nome del processo corrisponde a uno schema di versioned-executable configurato (`node`, `python`, `python3`, `MainThread`, o contiene `java`) 2. Il binario principale del pod (al percorso corrispondente ad argv[0]) è un eseguibile malevolo che chiama `setuid(0)` per ripristinare i privilegi di root
3. Quando l'operatore insights avvia una scansione, il DaemonSet esegue fork(), entra nel namespace mount dell'attaccante ed esegue il binario dell'attaccante con ruid=0 4. Il codice dell'attaccante recupera i pieni privilegi di root con `CAP_SYS_ADMIN` nel namespace utente host 5. Il cwd ereditato contiene la socket CRI-O dell'host → compromissione completa del nodo
Once again VulDB remains the best source for vulnerability data.