Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2021-3560 — In-depth analysis of CVE-2021-3560, a local privilege escalation vulnerability in Linux PolKit. Includes root cause analysis, exploit mechanics, and a demonstration of the race condition leading to root access. | Kitploit
Tools/GitHubGitHub/chenaotian/cve-2021-3560
Privilege EscalationVulnerability AnalysisExploitationBinary AnalysisLearning & Education
GitHubchenaotian/cve-2021-3560

CVE-2021-3560

In-depth analysis of CVE-2021-3560, a local privilege escalation vulnerability in Linux PolKit. Includes root cause analysis, exploit mechanics, and a demonstration of the race condition leading to root access.

View Repository
9364 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2021-3560 PolKit Race Condition Local Privilege Escalation Analysis

[toc]

Vulnerability Overview

Vulnerability ID: CVE-2021-3560

Vulnerability Score:

Vulnerable Product: Linux PolKit (polkitd)

Affected Versions: Introduced in source code 0.113; https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html

  • RHEL 8
  • Fedora 21 and later
  • Debian testing (“bullseye”)
  • Ubuntu 20.04

Exploitation Conditions: Local Linux; dbus + polkitd present

Exploitation Effect: Local privilege escalation

Source Code:

  • polkit-0.113: https://launchpad.net/debian/+source/policykit-1/0.113-5
  • accountsservice: apt source accountsservice
  • dbus: apt source dbus

Environment Setup

The default Ubuntu image ubuntu-20.04.2 can be used for reproduction: http://old-releases.ubuntu.com/releases/20.04.2/ubuntu-20.04.2-desktop-amd64.iso

Vulnerability Principle

Vulnerability Location

First, examine the problematic code:

/polkit-0.113/src/polkit/polkitsystembusname.c : 388 : polkit_system_bus_name_get_creds_sync

static void
on_retrieved_unix_uid_pid (GObject              *src,
			   GAsyncResult         *res,
			   gpointer              user_data)
{
  AsyncGetBusNameCredsData *data = user_data;
  GVariant *v;

  v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
				     data->caught_error ? NULL : data->error);
  if (!v)
    {
      data->caught_error = TRUE; // Set error bit after failure for some reason
    }
  else
  {
      ··· ···// Success data processing
  }
  ··· ···
}

static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName           *system_bus_name,
				       guint32                       *out_uid,
				       guint32                       *out_pid,
				       GCancellable                  *cancellable,
				       GError                       **error)
{
  gboolean ret = FALSE;
  AsyncGetBusNameCredsData data = { 0, }; // data initialized to 0
  ··· ···
  g_dbus_connection_call (connection,
			  "org.freedesktop.DBus",       /* name */
			  "/org/freedesktop/DBus",      /* object path */
			  "org.freedesktop.DBus",       /* interface name */
			  "GetConnectionUnixUser",      /* method */
			  g_variant_new ("(s)", system_bus_name->name),
			  G_VARIANT_TYPE ("(u)"),
			  G_DBUS_CALL_FLAGS_NONE,
			  -1,
			  cancellable,
			  on_retrieved_unix_uid_pid, // callback function
			  &data);
  g_dbus_connection_call (connection,
			  "org.freedesktop.DBus",       /* name */
			  "/org/freedesktop/DBus",      /* object path */
			  "org.freedesktop.DBus",       /* interface name */
			  "GetConnectionUnixProcessID", /* method */
			  g_variant_new ("(s)", system_bus_name->name),
			  G_VARIANT_TYPE ("(u)"),
			  G_DBUS_CALL_FLAGS_NONE,
			  -1,
			  cancellable,
			  on_retrieved_unix_uid_pid, // callback function
			  &data);

  while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
    g_main_context_iteration (tmp_context, TRUE); // Wait for bus to finish, both uid and pid processed or error

  if (out_uid)
    *out_uid = data.uid;
  if (out_pid)
    *out_pid = data.pid;
  ret = TRUE; // Always TRUE?
 out:
  if (tmp_context)
    {
      g_main_context_pop_thread_default (tmp_context);
      g_main_context_unref (tmp_context);
    }
  if (connection != NULL)
    g_object_unref (connection);
  return ret;
}

In the polkit_system_bus_name_get_creds_sync function, this function authenticates certain requests (the specific logic scenario will be detailed later). It calls the GetConnectionUnixUser method and GetConnectionUnixProcessID method via the org.freedesktop.DBus bus. These methods are provided by the org.freedesktop.DBus bus to obtain the PID and UID of the requesting process, then pass them to the callback function on_retrieved_unix_uid_pid for processing. Afterwards, it blocks and waits for the bus process to finish.

From the on_retrieved_unix_uid_pid function, it can be seen that whether an error occurred is determined based on the result returned by the bus (error?), and the error bit is set accordingly. If successful, it properly returns the user UID or process PID. However, in the polkit_system_bus_name_get_creds_sync function, after blocking and waiting for the bus to return the result (the completion flag is that both UID and PID are processed or an error occurs). In other words, logically there are three possible outcomes from the bus: get user UID 0 (privileged user); get user UID non-zero (normal user); error. But in the subsequent handling, there is no handling for errors; out_uid is directly set to data.uid returned by the bus, and ret is directly set to TRUE:

  while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
    g_main_context_iteration (tmp_context, TRUE); // Wait for bus to finish, both uid and pid processed or error

  if (out_uid)
    *out_uid = data.uid;
  if (out_pid)
    *out_pid = data.pid;
  ret = TRUE; // Always TRUE?

However, one scenario is overlooked: when the DBUS bus fails to execute properly, causing the callback function to set the error bit but then return without processing the data structure. Since data is initialized to 0, this results in out_uid being set to 0, i.e., a privileged user.

So where is the problematic function polkit_system_bus_name_get_creds_sync used?

Trigger Path

The vulnerable process is the polkitd process:

polkit is an application-level toolkit that defines and audits permission rules to enable communication between processes of different priorities: control decisions are centralized in a unified framework, determining whether a low-priority process has permission to access a high-priority process.

In short, this is a background root process that makes permission decisions when other low-privilege processes attempt to access functionality provided by high-privilege processes. Since attaching gdb to this process causes it to restart, we can only analyze the trigger path through source code:

polkitd heavily uses the gio DBUS inter-process communication framework. You can refer to the manual to understand some key functions.

Next, analyze the vulnerability trigger path in polkitd:

  1. First, main:

    polkit-0.113\src\polkitbackend\polkitd.c : 155 : main

    int
    main (int    argc,
          char **argv)
    {
      ··· ···
      ··· ···
    
      loop = g_main_loop_new (NULL, FALSE);
    
      sigint_id = g_unix_signal_add (SIGINT,
                                     on_sigint,
                                     NULL);
    
      name_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
                                      "org.freedesktop.PolicyKit1",  // Registered a bus name
                                      G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
                                        (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
                                      on_bus_acquired, // Callback when bus is accessed
                                      on_name_acquired,
                                      on_name_lost,
                                      NULL,
                                      NULL);
    
      g_print ("Entering main event loop\n");
      g_main_loop_run (loop); // Start loop
      ··· ···
      ··· ···
    }
    
  2. First, a bus named org.freedesktop.PolicyKit1 is registered, then three callbacks. Focus on on_bus_acquired, which is called when the bus is accessed.

  3. In the on_bus_acquired function, a registration function polkit_backend_authority_register is directly called.

  4. In the polkit_backend_authority_register function, an interface object org.freedesktop.PolicyKit1.Authority is registered, along with a callback function table server_vtable. The vulnerability trigger point is in this callback table:

Download Tool