michael orlitzky

CVE-2018-2773: MySQL/MariaDB privilege escalation via PID file manipulation

Product
MySQL / MariaDB mysqld server daemon
Vendor
Oracle / MariaDB Foundation
Versions affected
all
Published on
2018-04-22
Author
Michael Orlitzky
Bug report
https://jira.mariadb.org/browse/MDEV-13402
MITRE
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-2773

Summary

The mysqld daemon creates its PID file after dropping privileges to a non-root user typically named mysql. That may be exploited by the unprivileged user to kill root processes, since when the daemon is stopped via its init script, root sends a SIGINT to the contents of the PID file (which are under the control of the runtime user).

Details

The purpose of the PID file is to hold the PID of the running daemon, so that later it can be stopped, restarted, or otherwise signaled (many daemons reload their configurations in response to a SIGHUP). To fulfil that purpose, the contents of the PID file need to be trustworthy. If the PID file is writable by a non-root user, then he can replace its contents with the PID of a root process. Afterwards, any attempt to signal the PID contained in the PID file will instead signal a root process chosen by the non-root user. The MySQL daemon creates its PID file after dropping its privileges, as the following code in src/mysqld.cc (MySQL v5.7.21) shows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int mysqld_main(int argc, char **argv) {

  ...

#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT)
    if (locked_in_memory) // getuid() == 0 here
      set_effective_user(user_info);
    else
  #endif
      set_user(mysqld_user, user_info);
    }
  #endif // !_WIN32

  ...

  /* Save pid of this process in a file */
  if (!opt_bootstrap)
    create_pid_file();

This is commonly exploitable through init scripts that are run as root and which blindly trust the contents of their PID files. The MySQL package itself ships a few such init scripts,

both of which execute code like the following:

1
2
3
4
5
6
7
stop() {
  ...
  pid=$(cat "$pidfile")

  # We use a signal to avoid having to know the root password
  # Send single kill command and then wait
  if kill $pid >/dev/null 2>&1; then ...

The PID is obtained from the untrusted $pidfile, and a SIGTERM is sent to it as root.

Exploitation

An example exploit involving an init script is,

  1. I run /etc/init.d/mysqld start to start the daemon.
  2. mysqld drops privileges to the mysql user.
  3. mysqld writes its PID file, now owned by the mysql user.
  4. Someone compromises the daemon, which sits on the network.
  5. The attacker is generally limited in what he can do because the daemon doesn't run as root. However, he can write “1” into the PID file, and he does.
  6. I run /etc/init.d/mysqld stop to stop the daemon while I investigate the weird behavior resulting from the hack.
  7. The machine reboots, because I killed PID 1 (this is normally restricted to root).

Resolution

The April 2018 MySQL Critical Patch Update was supposed to fix this issue, but no change has been made to the PID file handling. The PID file is still created as mysql, and the init scripts still call kill on its contents as root. Oracle has been notified of this in an email.

The October 2018 MySQL Critical Patch Update is again supposed to fix this, but all they've done in commit e1fdeb2 is add an Oracle-specific hack into the bundled init scripts. If you're not running SLES or Oracle's own brand of Linux, then it's still up to you to realize (a) that they're putting you at danger, and (b) that you have to find a distribution-specific workaround for it. Oracle has not been notified. I don't have time for this shit.