A Desktop-Only Path to Root: Abusing Ubuntu's Accounts Service

Ubuntu's security posture is generally solid, with a large codebase that's been scrutinized by many eyes. Finding trivial vulnerabilities is rare. However, a recent discovery highlights a surprisingly simple privilege escalation chain that affects desktop users. With a few terminal commands and GUI clicks, a standard user can create a new administrator account for themselves.

This attack requires access to the graphical desktop session, so it does not affect server or headless installations. It also hinges on two separate bugs: a denial-of-service flaw in the accountsservice daemon and a privilege escalation bug in the GNOME Display Manager (gdm3).

Disclaimer: This issue affects desktop users only, as exploitation requires access to the graphical desktop session.

The Exploitation Walkthrough

The attack begins in a terminal window. First, the attacker creates a symlink in their home directory:

ln -s /dev/zero .pam_environment

If a file named .pam_environment already exists, it should be temporarily renamed, as it will need to be restored later.

Next, the attacker opens the "Region & Language" settings dialog and attempts to change the language. The dialog freezes as a result, and a system process called accounts-daemon becomes stuck in an infinite loop, consuming 100% of a CPU core. This is expected behavior for the exploit.

The attacker must then delete the symlink from the terminal to avoid being locked out of their own account:

rm .pam_environment

To stop the CPU thrashing, the attacker sends a SIGSTOP signal to accounts-daemon. First, they identify its process ID (PID), which can be done using a process monitor like top or the pidof utility:

$ pidof accounts-daemon
597

With the PID in hand, the attacker sends the stop signal using the kill command:

kill -SIGSTOP 597

Now the system can perform normally again. The crucial next step is to schedule a recovery action for accounts-daemon before logging out, otherwise the exploit will fail and the user will be locked out until a reboot. A timer is set using nohup to run a background script after logout:

nohup bash -c "sleep 30s; kill -SIGSEGV 597; kill -SIGCONT 597"

This command sets up a bash script that performs three actions:

  1. Sleeps for 30 seconds to allow time to log out (10 seconds was used in the demonstration).
  2. Sends a SIGSEGV signal to accounts-daemon, causing it to crash.
  3. Sends a SIGCONT signal to deactivate the earlier SIGSTOP, allowing the SIGSEGV to take effect.

After executing this, the attacker logs out of their session and waits for the crash to trigger. If successful, a series of dialog boxes appear, allowing the creation of a new user account. This new account is created with administrator privileges. A cartoon-style drawing of a man sitting in front of a laptop with a big grin and his arms raised up in the air in victory.

Underlying Vulnerability 1: Identifying the accountsservice Flaw

The first bug is a denial-of-service issue in accountsservice (GHSL-2020-187 and GHSL-2020-188, tracked as CVE-2020-16126 and CVE-2020-16127). The accounts-daemon service manages user accounts, handling tasks like account creation and password changes, which require root privileges. To carry out these functions, the system settings dialog communicates with the daemon over D-Bus. System settings for users System settings for region and language

In this exploit, the attacker uses the language-changing feature on their own account, which does not require administrator privileges. This sends the org.freedesktop.Accounts.User.SetLanguage command to the daemon. Ubuntu's version of accountsservice includes a custom patch that introduces a function called is_in_pam_environment, which reads the user's .pam_environment file. By making this file a symlink to /dev/zero—a virtual file of infinite zeros—the daemon is tricked into an endless read loop.

The second part of this bug involves a privilege drop. Before reading the .pam_environment file, the daemon intentionally lowers its own privileges to match the invoking user, a security measure intended to protect sensitive system files. However, this means the daemon is now running with the same permissions as the user, allowing the user to send it signals like SIGSEGV.

Underlying Vulnerability 2: The gdm3 Privilege Escalation

The second part of the chain is a privilege escalation flaw in gdm3 (GHSL-2020-202, tracked as CVE-2020-16125). This component manages user login sessions and the initial system setup flow. gdm3 login screen

During a fresh Ubuntu installation, the initial setup screen—shown in the screenshot—triggers a separate application called gnome-initial-setup. gnome initial setup This is launched whenever gdm3 determines there are zero user accounts on the machine. To make this determination, gdm3 queries accounts-daemon.

If accounts-daemon is unresponsive, the D-Bus query fails after a timeout of roughly 20 seconds. This error causes the code to leave a critical variable, priv->have_existing_user_accounts, unset. Unfortunately, its default value is false, which wrongly indicates that no user accounts exist. As a result, gdm3 launches gnome-initial-setup, granting the logged-out attacker the ability to create a new administrator account and gain root access.

An accidental discovery

The route to root came about by accident. On the evening of October 14, I had been testing denial-of-service issues in accountsservice and left a .pam_environment symlink in place before closing my laptop lid. When I returned and unlocked the screen, I was locked out of my session. A console login still worked—the accountsservice issue didn't affect that—and I killed accounts-daemon with a SIGSEGV. Thanks to the privilege-dropping flaw, no sudo was needed. Suddenly I was staring at gnome-initial-setup, and found I could create an account with administrator privileges.

Reproducing it proved elusive at first. The obvious trigger—logging out—wasn't part of what I'd done. System logs offered little, since gdm's debug output was off. The breakthrough came while discussing the incident with colleagues. I mentioned having noticed gnome-initial-setup.pkla, a PolicyKit rule granting a user named gnome-initial-setup rights to perform security-sensitive operations like creating accounts and mounting filesystems. That's when Bas Alberts suggested the key idea: gdm likely starts gnome-initial-setup when it can't confirm an existing account belongs to the logging-in user. That turned out to be precisely the mechanism.

From there, it was a matter of locating the relevant gdm3 code path and determining how to hit it while accounts-daemon was down. The trigger turns out to be user logout.