November 9, 2020

GHSL-2020-187: Denial of Service (DoS) in Ubuntu accountsservice - CVE-2020-16126 - CVE-2020-16127

Kevin Backhouse

Summary

The accountsservice daemon drops privileges to perform certain operations. For example while performing the org.freedesktop.Accounts.User.SetLanguage D-Bus method, which can be triggered by an unprivileged user, accounts-daemon temporarily drops privileges to the same UID as the user, to avoid being tricked into opening a file which the unprivileged user should not be able to access. Unfortunately, by changing its RUID it has given the user permission to send it signals. This means that the unprivileged user can send accounts-daemon a SIGSTOP signal, which stops the process and causes a denial of service.

Product

accountsservice

Tested Version

  • accountsservice, version 0.6.55-0ubuntu12~20.04.1
  • Tested on Ubuntu 20.04.1 LTS

Note: I believe these issues only exist in Ubuntu's version of accountsservice. I couldn't find the vulnerable functions in the git repos maintained by freedesktop or debian. I originally discovered the vulnerable code in the version of the code that I had obtained by running apt-get source accountsservice, but I struggled to figure out where it came from when I started searching the official repositories. I eventually tracked it down to this patch file: 0010-set-language.patch.

Details

Issue 1: accountsservice drop privileges SIGSTOP denial of service (GHSL-2020-187, CVE-2020-16126)

A source code patch that (as far as I know) only exists in Ubuntu's version of accountsservice, adds a function named user_drop_privileges_to_user:

static gboolean
user_drop_privileges_to_user (User *user)
{
        if (setresgid (user->gid, user->gid, -1) != 0) {
                g_warning ("setresgid() failed");
                return FALSE;
        }
        if (setresuid (accounts_user_get_uid (ACCOUNTS_USER (user)), accounts_user_get_uid (ACCOUNTS_USER (user)), -1) != 0) {
                g_warning ("setresuid() failed");
                return FALSE;
        }
        return TRUE;
}

This function is used to drop privileges while doing operations on behalf of an unprivileged user. Dropping the EUID is a sensible precaution, which prevents accountsservice from accessing a file which the unprivileged user cannot access themselves. Unfortunately, dropping the RUID has the opposite effect of making security worse, because it enables the unprivileged user to send signals to accountsservice. For example, they can send a SIGSTOP which stops the accountsservice daemon and causes a denial of service.

The vulnerability can be triggered via multiple different D-Bus methods. Many of them involve precise timing to send the SIGSTOP signal at just the right moment. But there is a much simpler and more reliable way to reproduce the vulnerability by combining the privilege dropping vulnerability (GHSL-2020-187) with the infinite loop vulnerability (GHSL-2020-188), which is described next. So please see the description of GHSL-2020-188, below, for a proof-of-concept that triggers both vulnerabilities.

Impact

An unprivileged local user can cause a local denial of service that affects all users of the system. Stopping the accountsservice daemon prevents the login screen from working, because gdm3 needs to talk to accounts-daemon (via D-Bus).

Unfortunately, the impact is worse than just local denial of service, because this vulnerability can be chained with a separate vulnerability in gdm3 to achieve privilege escalation. Please see the description of GHSL-2020-188, below, for a proof-of-concept of the privilege escalation vulnerability.

Issue 2: accountsservice .pam_environment infinite loop (GHSL-2020-188, CVE-2020-16127)

A source code patch that (as far as I know) only exists in Ubuntu's version of accountsservice, adds a function named is_in_pam_environment:

static gboolean
is_in_pam_environment (User        *user,
                       const gchar *property)
{
        gboolean ret = FALSE;
        const gchar *prefix;
        FILE *fp;
        g_autofree gchar *pam_env = NULL;

        if (g_strcmp0 (property, "Language") == 0)
                prefix = "LANG";
        else if (g_strcmp0 (property, "FormatsLocale") == 0)
                prefix = "LC_TIME";
        else
                return FALSE;

        pam_env = g_build_path ("/", accounts_user_get_home_directory (ACCOUNTS_USER (user)), ".pam_environment", NULL);

        if (!user_drop_privileges_to_user (user))
                return FALSE;
        if ((fp = fopen (pam_env, "r"))) {
                gchar line[50];
                while ((fgets (line, 50, fp)) != NULL) {
                        if (g_str_has_prefix (line, prefix)) {
                                ret = TRUE;
                                break;
                        }
                }
                fclose (fp);
        }
        user_regain_privileges ();

        return ret;
}

This function parses the contents of a file named .pam_environment in the (unprivileged) user's home directory. The user can trigger an infinite loop by creating a symlink to /dev/zero:

ln -s /dev/zero ~/.pam_environment

The infinite loop can then be triggered by sending a D-Bus message to the accountsservice daemon:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply=literal /org/freedesktop/Accounts/User`id -u` org.freedesktop.Accounts.User.SetLanguage string:kevwozere &

Because the accountsservice daemon is now stuck in an infinite loop, and has dropped privileges, it is now easy to also demonstrate GHSL-2020-187:

kill -SIGSTOP `pidof accounts-daemon`

The accountsservice daemon is now unresponsive, which means that the GNOME login screen no longer works. Unfortunately, this vulnerability can be chained with a separate vulnerability in gdm3 (which I have reported simultaneously to GNOME) to get privilege escalation. The steps (tested on Ubuntu Desktop 20.04.1 LTS) are as follows:

  • An unprivileged user logs into their account.
  • They send a SIGSTOP to accountsservice, using the instructions above.
  • They log out of their account.
  • They open a text console (usually with a key combination such as Ctrl-Alt-F3).
  • They login to the text console.
  • They send accounts-daemon a SIGSEGV, followed by a SIGCONT, which causes accounts-daemon to crash.
  • Because the accountsservice daemon was unresponsive, gdm3 is under the mistaken impression that there are zero user accounts on the system. So it triggers gnome-initial-setup, because it thinks this is a first-time installation.
  • The user clicks through the gnome-initial-setup dialog boxes and creates a new account for themselves. The new account is a member of the sudo group.

I have made a video of this proof-of-concept, which you can see here. The video is only visible to people who have the link, so it as safe as long as you are careful who you share the link with.

Impact

An unprivileged local user can cause a local denial of service that affects all users of the system. Making the accountsservice daemon unresponsive prevents the login screen from working, because gdm3 needs to talk to accounts-daemon (via D-Bus).

Unfortunately, the impact is worse than just local denial of service, because this vulnerability can be chained with a separate vulnerability in gdm3 to achieve privilege escalation, as explained in the proof-of-concept above.

CVE

  • CVE-2020-16126
  • CVE-2020-16127

Coordinated Disclosure Timeline

Credit

These issues were discovered and reported by GHSL team member @kevinbackhouse (Kevin Backhouse).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2020-187 or GHSL-2020-188in any communication regarding this issue.