
Local privilege escalation via TOCTOU race condition in PackageKit's.
CVSS 3.x : 7.8 HIGH
CWE : CWE-367 (Time-of-Check Time-of-Use)
Fix : PackageKit 1.3.5+
Affects : PackageKit 1.0.2 – 1.3.4
Local privilege escalation via TOCTOU race condition in PackageKit's D-Bus transaction handler.
Any unprivileged local user can install arbitrary packages as root — no authentication beyond having a local session.
Lab / authorized testing only (HTB, CTF, engagement with written scope).
python3 exploit.py
That's it. Drops /tmp/.suid_bash, then exec into a root shell.
Custom SUID path or timeout:
SUID_BIN=/dev/shm/.sh TIMEOUT=90 python3 exploit.py
apt install python3-gi dpkg-dev
The exploit builds two .deb packages at runtime — no compiler needed.
Three bugs in src/pk-transaction.c chain together:
InstallFiles() overwrites cached_transaction_flags and cached_full_paths with no state check. A second D-Bus call on the same transaction blindly replaces them — even while the transaction is already running.
pk_transaction_set_state() rejects illegal backward transitions (RUNNING→READY, etc.) without rolling back the overwritten flags from Bug 1. The corrupted flags persist.
pk_transaction_run() reads cached_transaction_flags at dispatch time (GLib idle), not at authorization time. The backend sees the attacker's flags.
Setting PK_TRANSACTION_FLAG_SIMULATE (0x4) skips the polkit authorization check entirely. Call 1 gets authorized with no user interaction.
Attacker packagekitd
│ │
│ CreateTransaction() │ state = NEW
│─────────────────────────────────►│
│ │
│ InstallFiles(SIMULATE, dummy) │ polkit skipped (SIMULATE)
│─────────────────────────────────►│ state = READY, idle queued
│ │
│ InstallFiles(NONE, payload) │ flags+paths overwritten
│─────────────────────────────────►│ set_state(WAITING_FOR_AUTH) rejected
│ │ state stays READY (BUG 2)
│ │
│ [idle fires]│
│ │ pk_transaction_run() reads NONE (BUG 3)
│ │ → dpkg installs payload as root
│ │ → postinst: chmod +s /tmp/.suid_bash
│ │
│ execv("/tmp/.suid_bash -p") │
│─────────────────────────────────►│ euid=0(root)
Both calls arrive before the GLib idle fires — no race to win, it's deterministic.
Ubuntu Desktop 18.04–26.04, Debian Trixie, Fedora 43, RockyLinux 10.1, and any distro shipping PackageKit 1.0.2–1.3.4 with the daemon active.
systemctl mask packagekittransaction->state != PK_TRANSACTION_STATE_NEW → reject