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
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 aSIGCONT
, 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 thesudo
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
- 2020-10-17: reported to Ubuntu: https://bugs.launchpad.net/ubuntu/+source/accountsservice/+bug/1900255
- 2020-10-19: Reply from Marc Deslauriers.
- 2020-10-20: CVE-2020-16126 and CVE-2020-16127 assigned.
- 2020-10-27: Disclosure date agreed: 2020-11-03
- 2020-11-03: Issue disclosed by Ubuntu: https://ubuntu.com/security/notices/USN-4616-1
- 2020-11-05: Bug report made publicly visible: https://bugs.launchpad.net/ubuntu/+source/accountsservice/+bug/1900255
- 2020-11-06: Bugs explained by Alex Murray on the Ubuntu Security Podcast.
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-188
in any communication regarding this issue.