InfinitumIT
Analysis Report

CVE-2026-41651 — Pack2TheRoot: A Race Condition to Root via PackageKit

Pack2TheRoot vulnerability discovered in the default PackageKit service on Linux — three separate bugs (unconditional flag overwrite, silent state rejection, late flag read) combine so an ordinary user can install arbitrary packages as root. Technical analysis of the Time-of-Check / Time-of-Use race condition.

04.06.2026 · 9 min read · InfinitumIT
CVE-2026-41651 — Pack2TheRoot: A Race Condition to Root via PackageKit

PackageKit is a package management tool widely used in Linux desktop environments. In this article we present a technical analysis of CVE-2026-41651 — Pack2TheRoot, a vulnerability identified in the authorization chain that operates over polkit and D-Bus; how three separate bugs (unconditional flag overwrite, silent state rejection, late flag read) combine to enable arbitrary package installation with root privileges, the Time-of-Check / Time-of-Use race condition involved, and the recommended patch.

PackageKit is a package management tool widely used in Linux desktop environments. It runs as a daemon on the system, listens to requests arriving over D-Bus, and forwards package management operations performed from the graphical interface to the underlying package manager. Different package managers are used across distributions: APT on Ubuntu and Debian systems, DNF on Fedora and RHEL systems, and pacman on Arch Linux. PackageKit unifies these different backends under a common API, allowing applications to install, remove, update and query packages through a single standard interface.

Modern Linux desktop systems use a multi-layered authorization architecture to preserve security while improving user experience. Three components sit at the core of this architecture: the D-Bus IPC system that provides inter-process communication, the PackageKit service layer that manages package operations, and the polkit mechanism that makes authorization decisions. When a user initiates a package installation from the graphical interface, the request first reaches the PackageKit daemon over D-Bus; PackageKit asks polkit whether the request requires authorization, and only after approval is granted does it carry out the operation with root privileges. The Pack2TheRoot vulnerability arises precisely from a gap in this chain of trust.

Package Installation Layers

CVE-2026-41651 figure 1

GUI Application / pkcon Layer: This is the layer where the user initiates package installation, update or removal operations. Tools such as GNOME Software, KDE Discover and pkcon reside in this layer. These applications do not run directly with root privileges; they only produce the user's request and forward it to the relevant system services. This prevents a potential security flaw in the user interface layer from directly turning into a system-level privilege escalation.

D-Bus Layer: D-Bus is the standard IPC (Inter-Process Communication) mechanism that provides communication between processes running in Linux user space. GUI applications do not communicate with PackageKit directly over sockets or shared memory, but through D-Bus. This structure works with RPC-like method calls. For example, a GUI application sends the InstallPackages() call over D-Bus.

PackageKit Layer: PackageKit is a central service layer that provides distribution-agnostic package management on Linux systems. In the Linux ecosystem, Debian-based systems use apt, Fedora-based systems use dnf, and Arch Linux uses pacman. Because of these differences, instead of developing desktop applications separately for each package manager, an abstraction layer providing a common interface was needed. PackageKit was developed to meet this need.

Authorization Granted Layer: The Authorization Granted stage represents the point at which, as a result of the evaluation performed by polkit, the user is permitted to carry out the relevant operation. At this stage the system accepts that the user has the required privileges and that the initiated operation is trustworthy. After receiving a positive authorization decision returned by polkit, PackageKit moves on to the next stage to perform the operation. From this stage onward, the system assumes that the operation evaluated during the authorization process and the operation that will be carried out at the execution stage are the same. In modern Linux systems this point is referred to as the authorization boundary. The violation of this trust assumption is precisely what lies at the heart of the Pack2TheRoot vulnerability. When the integrity between the authorized operation and the executed operation is broken, serious privilege escalation vulnerabilities emerge.

apt / dnf / rpm Backend Layer: This is the execution layer where the actual package managers run and make direct changes on the system. Depending on the Linux distribution in use, this role is fulfilled by apt on Debian and Ubuntu systems, dnf on Fedora and Red Hat based systems, and pacman on Arch Linux systems. Backend package managers run with root privileges. For this reason, operations such as downloading packages, resolving dependencies, placing files into system directories and updating package databases are carried out at this stage, and the post-install scripts, trigger mechanisms and maintenance scripts contained within the packages are also executed at this stage. Because these scripts are executed with root privileges, the backend layer is regarded directly as a privileged code execution surface.

Package Installation Layer: Package files are written to the relevant directories, services belonging to the application are created, configuration files are updated and the required symbolic links are created. Once the installation operation is complete, the package manager updates its own database and the transaction state is marked as completed. Because this stage is performed directly with root privileges, it represents the highest privilege level in the Linux package management architecture. All persistent changes made on the system are applied at this stage and the package installation process is concluded.

CVE-2026-41651- Pack2TheRoot Vulnerability

Pack2TheRoot is a vulnerability identified in the PackageKit service that ships by default on Linux. The attacker first obtains a passwordless bypass via the "simulation" flag, then switches the flag on the same transaction to "real installation"; the system does not notice this change. As a result, an ordinary user installs any package they choose as root.

::BUG ANALYSIS

1. Unconditional flag overwrite

CVE-2026-41651 figure 2

CVE-2026-41651 figure 3

Lines 4036–4037: These two lines are the root of the vulnerability. Each time InstallFiles() is called, it unconditionally overwrites the flags and file paths without checking the transaction's current state. It makes no difference whether the transaction is NEW, READY or RUNNING. With a second D-Bus call, the attacker can remove the SIMULATE flag and place the real package path in its stead.

What should have been: the following check should have been present on the first line of this function:

CVE-2026-41651 figure 4

2. Silent state transition rejection

CVE-2026-41651 figure 5

Lines 876–881: The state machine blocks backward state transitions (READY -> NEW). But our problem is this: the flag overwrite in InstallFiles() (Bug 1, line 4036) happens before this function is called. Therefore, by the time the state transition is rejected, the flags have already been corrupted.

Line 878: g_warning() only writes a log message; it does not stop the operation, does not throw an exception, and does not clean up the corrupted state.

3. Late flag read

CVE-2026-41651 figure 6

Lines 2276–2277: The scheduler reads the flags and file paths at dispatch time, not at authorization time. Due to Bug 1, these values have been altered by the attacker's second D-Bus call. The SIMULATE flag is no longer present -> pk_backend_install_files() initiates a real installation -> the package is installed with root privileges.

Time-of-Check, Time-of-Use

CHECK time: Polkit assumes the operation is safe by looking at the SIMULATE flag.

USE time: The scheduler reads the flags in the idle callback, but they are now NONE (real installation)

[Within the time window between Check and Use, the flags have been altered. The Check decision is now invalid, but the Use stage is unaware of this].

Polkit Bypass Mechanism

CVE-2026-41651 figure 7

Lines 2895–2902: If the SIMULATE (bit 2 = 0x4) or ONLY_DOWNLOAD flags are set, polkit authorization is bypassed entirely. This design decision is reasonable on its own — simulations do not modify the system. But when combined with Bug 1 it becomes dangerous, because the attacker bypasses polkit with SIMULATE on the first call, then switches the flags to a real installation on the second call.

This block of code is not a vulnerability on its own. But when combined with Bug 1 (unconditional overwrite) + Bug 2 (silent rejection) + Bug 3 (late read), it makes it possible for the attacker to install a package as root with no authorization whatsoever.

Assertion Crash

CVE-2026-41651 figure 8

Line 508: After the exploit succeeds, two different code paths try to emit the Finished signal for the same transaction: (1) polkitd's NOT_AUTHORIZED response, and (2) the backend completing the actual installation. On the second call, because emitted_finished is already TRUE, g_assert() is triggered -> SIGABRT -> daemon crash.

This crash occurs after the exploit has succeeded. The package has already been installed. The crash does not prevent the attack, but it does leave a persistent trace in journalctl. Security teams can use this assertion failure as an IOC.

After the exploit, the emitted_finished assertion failure can be observed in the packagekitd process. This condition alone should not be regarded as conclusive proof that the exploit succeeded. Similar crashes can also occur due to different software bugs. It should nevertheless be evaluated together with additional findings such as suspicious package installations, abnormal transaction records, or indicators of privilege escalation.

Transaction struct — cached fields

CVE-2026-41651 figure 9

GObject parent: GObject is the foundational structure that provides GLib's object system infrastructure. Because the C language does not have built-in inheritance and other object-oriented programming features, GObject is used to simulate these features (classes, inheritance, virtual methods, runtime type information (RTTI), etc.). The first member of structures derived from GObject is typically GObject parent_instance. In this way, the parent class's data sits at the start of the subclass structure and inheritance-like behavior is achieved.

PkRoleEnum role: Specifies which type of operation the transaction is performing. This field defines the category to which the operation belongs, such as package INSTALL, REMOVE, UPDATE or SEARCH. It is an enum type and contains one of a set of pre-defined constant values.

Enum (Enumeration) is a special data type in the C language used to name and group a specific, limited set of constant values.

PkStatusEnum status: Holds the current runtime status of the operation. The operation may be WAITING, RUNNING or DOWNLOADING while in progress. This field is typically used to convey progress and status information to the user interface.

PkTransactionState state: Shows the current stage of the transaction within the state machine. A transaction typically follows the sequence NEW -> WAITING_FOR_AUTH -> READY -> RUNNING -> FINISHED. It is critically important for determining which stage the operation is in and for correctly managing transitions. The Bug #2 examined is directly related to the management of this field.

gboolean emitted_finished: A flag that tracks whether the finished signal has already been emitted. It is used to guarantee that the finished signal is emitted only once for the same transaction. After the exploit, it is one of the fields checked by g_assert(). If an attempt is made to emit the signal a second time, an assertion error may occur and the process may terminate unexpectedly.

gboolean allow_cancel: Specifies whether the operation can be canceled by the user. If the value is TRUE, the user can stop the operation; if FALSE, the operation must continue until it is completed.

gboolean waiting_for_auth: Indicates whether the transaction is waiting for an authorization response from Polkit. When this value is TRUE, the operation is temporarily paused and the required authorization check is awaited. Once a response is received from Polkit, the transaction continues with its normal state flow. It plays an important role in the secure execution of operations that require authorization.

Fields Targeted by the Attacker

PkBitfield cached_transaction_flags: Holds the transaction flags used during the transaction. These flags determine how the operation will run; for example, options such as SIMULATE and ONLY_DOWNLOAD are controlled via this field. PkBitfield is a bit field structure where each bit represents a different flag. In this way, multiple flags can be stored within a single variable. Within the scope of Bug #1, the unconditional overwrite of this field may cause the transaction's runtime behavior to be altered.

gchar *cached_package_id: A string pointer that holds the identifier of a single package. This field is typically used when an operation will be performed on a specific package. Because it is a pointer, it does not hold the data itself but the address of the string in memory.

gchar **cached_package_ids: A string array pointer that holds the identifiers of multiple packages. The ** notation here means a pointer to a pointer, and in the C language it is typically used to represent a string array structure. When an operation will be performed on multiple packages, the package list is carried via this field.

gchar **cached_full_paths: Holds the full file paths of the files that will be installed or processed. This field is also in the form of a string array pointer. Within the scope of Bug 1, the ability to overwrite this field may cause the attacker to change the file paths that will be used in the operation. In this way, by placing the path of a malicious package into this field, the attacker can cause the transaction to operate on an unexpected file.

Vulnerability Steps

CVE-2026-41651 figure 10

Step 1: The attacker creates a new transaction object with CreateTransaction(). The transaction is born in the NEW state.

Step 2: The first InstallFiles(SIMULATE, test.deb) call arrives. The code flow proceeds as follows: at line 4036, cached_transaction_flags = SIMULATE is written. Then the polkit check at lines 2895–2902 is reached. The SIMULATE flag is seen and the polkit query is bypassed entirely. The transaction transitions directly to the READY state. An idle callback is added to the GLib event loop with g_idle_add() (so that this transaction will be executed at an appropriate time later); the scheduler will run the transaction via this callback, but before the idle callback runs the attacker sends the second request.

Step 3: The second InstallFiles(NONE, malicious.rpm) call is sent. Because of GLib's prioritization, this D-Bus message (G_PRIORITY_DEFAULT = 0) is guaranteed to be processed before the scheduler's idle callback (G_PRIORITY_DEFAULT_IDLE = 200).

In GLib, a lower priority value means a higher priority. For this reason, D-Bus messages with priority 0 are always processed before idle callbacks with priority 200. This allows new D-Bus requests to modify the transaction data before the callback runs.

CVE-2026-41651 figure 11

As a result, when the event loop holds both a D-Bus request and an idle callback, GLib first runs the D-Bus request with priority 0 -- InstallFiles(NONE, malicious.rpm)--

InstallFiles() unconditionally overwrites cached_transaction_flags and cached_full_paths without performing any state check. pk_transaction_set_state(), on the other hand, silently rejects backward state transitions. The flags have already been overwritten, but the state remains as it was.

cached_transaction_flags = NONE;

cached_full_paths = malicious.rpm;

Step 4: When the idle callback is finally triggered, pk_transaction_run() reads cached_transaction_flags at line 2279. It now sees NONE rather than SIMULATE -> the real installation path is taken -> pk_backend_install_files() installs the attacker's package with root privileges.

Fix

A state guard is added in front of ALL D-Bus method calls other than SetHints and Cancel. If the transaction is not in the NEW state, the call is rejected with the PK_TRANSACTION_ERROR_INVALID_STATE error, and no method — including InstallFiles() — can be reached. The flag overwrite is prevented.

The patch adds a state guard to the pk_transaction_method_call() function — that is, to the entry point of all D-Bus method calls. If the transaction is not in the NEW state, the call is rejected with an error and the cached parameters cannot be modified.

This patch cuts the problem off at the earliest point: when the second InstallFiles() call reaches pk_transaction_method_call(), the transaction is no longer in the NEW state but in the READY state, so the call is immediately rejected and the flag overwrite at line 4036 is never reached. All three bugs are neutralized by a single entry check.

Critical architectural lesson

This vulnerability arises from the combination of three classic anti-patterns in security engineering: unconditional writability of mutable shared state (Bug 1), silent swallowing of errors (Bug 2), and late binding of security decisions (Bug 3).

Affected Versions

  1. PackageKit all versions between 1.0.2 and 1.3.4 – must be upgraded to 1.3.5 or later.
  2. Ubuntu Desktop 18.04 (EOL), 24.04 LTS, 26.04 (beta)
  3. Ubuntu Server 22.04 – 24.04 LTS
  4. Debian 13.x (Trixie)
  5. Fedora 43 (Desktop/Server)
  6. Rocky Linux 10.1
  7. RHEL (if Cockpit / PackageKit is active)

Recommended Actions

The PackageKit version should be checked and updated to 1.3.5 or a newer version.

In cases where the update cannot be performed in the short term, PackageKit access for non-root users should be blocked by using the /etc/polkit-1/rules.d/00-block-packagekit.rules file.

If there is no requirement to use PackageKit on the server, the relevant package should be removed from the system.

In order to detect potential signs of exploitation, log records should be reviewed with the command journalctl -u packagekit | grep -E "killed|signal|crash".

Recently installed packages on the system should be reviewed, and any unexpected or unverifiable packages should be examined in detail.

If Cockpit is installed on the system, it should be assessed that, together with PackageKit, it may create additional attack surface; removal should be considered if there is no operational need.

The D-Bus activation file should be checked even if the service has been stopped. As long as it is present, the daemon can be triggered automatically.

PackageKit logs should be forwarded to centralized log management or SIEM infrastructure, and monitoring mechanisms should be established for suspicious activity.

Meryem Nazım BEYOĞLU

Cyber Security Engineer

This article is based on direct analysis of the PackageKit source code and the official patch records.

Did you find this useful?

Be the first to receive our threat newsletters and MDR Insights reports.

Our team certifications

Experts accredited by SANS, Offensive Security, EC-Council, CompTIA, ISACA, CREST, and INE.

SANS GPEN
SANS GWAPT
SANS GICSP
SANS GRTP
SANS GCIH
SANS GSEC
Offensive Security OSCP
Offensive Security OSWP
EC-Council CEH
CompTIA Security+
ISACA CISM
ISACA CISA
CREST CRT
INE eWPTX
Fortinet FCP Secure Networking
Fortinet FCP Cloud Security
Fortinet FCP Security Operations
Fortinet FCSS Secure Networking
Fortinet FCSS SASE
Fortinet FCSS Cloud Security
Fortinet FCSS Security Operations
IBM QRadar Admin
SANS GPEN
SANS GWAPT
SANS GICSP
SANS GRTP
SANS GCIH
SANS GSEC
Offensive Security OSCP
Offensive Security OSWP
EC-Council CEH
CompTIA Security+
ISACA CISM
ISACA CISA
CREST CRT
INE eWPTX
Fortinet FCP Secure Networking
Fortinet FCP Cloud Security
Fortinet FCP Security Operations
Fortinet FCSS Secure Networking
Fortinet FCSS SASE
Fortinet FCSS Cloud Security
Fortinet FCSS Security Operations
IBM QRadar Admin

Cookie usage

We only use essential session and language preference cookies; no third-party tracking cookies. For details, see our Cookie Policy and KVKK Privacy Notice.