diff -u apport-2.14.1/debian/changelog apport-2.14.1/debian/changelog --- apport-2.14.1/debian/changelog +++ apport-2.14.1/debian/changelog @@ -1,3 +1,18 @@ +apport (2.14.1-0ubuntu3.9) trusty-security; urgency=medium + + * SECURITY UPDATE: privilege escalation through namespaces and crafted + chroot (LP: #1438345) + - data/apport: If crash comes from a container, rather than + chrooting into it, detect what LXC container it is and then use the + attach_wait API call to execute apport in the container. + - data/apport: Don't fail when encountering unicode characters. + (Thanks to Martin Pitt) + - test/test_signal_crashes.py: Test for the unicode fix. + (Thanks to Martin Pitt) + - CVE-2015-1318 + + -- Stéphane Graber Wed, 08 Apr 2015 13:16:27 -0400 + apport (2.14.1-0ubuntu3.8) trusty-proposed; urgency=medium * Backport changes from 14.10 to ensure that automatic crash reporting only in patch2: unchanged: --- apport-2.14.1.orig/data/apport +++ apport-2.14.1/data/apport @@ -209,11 +209,11 @@ During that, crashes are common as the session D-BUS and X.org are going away, etc. These crash reports are mostly noise, so should be ignored. ''' - with open('/proc/%s/environ' % pid) as e: - env = e.read().split('\0') + with open('/proc/%s/environ' % pid, 'rb') as e: + env = e.read().split(b'\0') for e in env: - if e.startswith('DBUS_SESSION_BUS_ADDRESS='): - dbus_addr = e.split('=', 1)[1] + if e.startswith(b'DBUS_SESSION_BUS_ADDRESS='): + dbus_addr = e.split(b'=', 1)[1].decode() break else: error_log('is_closing_session(): no DBUS_SESSION_BUS_ADDRESS in environment') @@ -266,13 +266,106 @@ # apport can't be found, then simply log an entry in the host error log # and exit 0. if len(sys.argv) == 5 and sys.argv[4].isdigit() and sys.argv[4] != sys.argv[1]: - if os.path.exists('/proc/%s/root/%s' % (sys.argv[4], __file__)): - error_log('pid %s (host pid %s) crashed in a container with apport ' - 'support, forwarding' % (sys.argv[1], sys.argv[4])) + if os.path.exists('/proc/%s/root/%s' % (sys.argv[4], __file__)) \ + and os.path.exists('/proc/%s/ns/' % sys.argv[4]): + try: + import lxc + except: + error_log('pid %s crashed in a container without python3-lxc' % sys.argv[4]) + sys.exit(0) + + def list_containers(): + containers = [] + + with open('/proc/net/unix', 'r') as fd: + for line in sorted(fd): + fields = line.strip().split(' ') + if len(fields) != 8: + continue + + inode = fields[6].strip() + path = fields[7].split('/') + if path[-1] != 'command': + continue + + if len(path) < 2: + continue + + real_path = '/'.join(path[:-2]).lstrip('@') + + container = lxc.Container(path[-2], real_path) + + if not container.controllable: + continue + + if container.state == 'STOPPED': + continue + + ppid = 0 + with open('/proc/%s/status' % container.init_pid) as fd: + for line in fd: + if line.startswith('PPid:'): + ppid = int(line.strip().split(':')[-1].strip()) + break + else: + continue + + stat = os.stat('/proc/%s' % ppid) + uid = stat.st_uid + gid = stat.st_gid + + for fd in os.listdir('/proc/%s/fd/' % ppid): + if os.readlink('/proc/%s/fd/%s' % (ppid, fd)) \ + == 'socket:[%s]' % inode: + break + else: + continue + + containers.append((container, (uid, gid))) + + return containers + + def list_ns(pid): + return [os.readlink('/proc/%s/ns/%s' % (pid, ns)) + for ns in sorted(os.listdir('/proc/%s/ns' % pid))] + + def get_container(pid): + pid_ns = list_ns(pid) + + for container, owner in list_containers(): + container_ns = list_ns(container.init_pid) + + if container_ns == pid_ns: + return container, owner + + return None, None + + container, owner = get_container(sys.argv[4]) + if not container: + error_log('pid %s crashed in an unknown container' % sys.argv[4]) + sys.exit(0) + + if owner[0] or owner[1]: + error_log('pid %s crashed in an untrusted container' % sys.argv[4]) + sys.exit(0) + + error_log('pid %s (host pid %s) crashed in container ' + '"%s" (%s), forwarding' % (sys.argv[1], sys.argv[4], + container.name, + container.get_config_path())) + sys.stderr.flush() - os.execv('/usr/sbin/chroot', ('chroot', '/proc/%s/root/' % sys.argv[4], - __file__, sys.argv[1], sys.argv[2], - sys.argv[3])) + + def run_command(cmd): + os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin' + + return subprocess.call(cmd) + + cmd = [__file__, sys.argv[1], sys.argv[2], sys.argv[3]] + retval = container.attach_wait(run_command, cmd, + env_policy=lxc.LXC_ATTACH_CLEAR_ENV) + + sys.exit(0) else: error_log('pid %s crashed in a container without apport support' % sys.argv[4]) sys.exit(0) only in patch2: unchanged: --- apport-2.14.1.orig/test/test_signal_crashes.py +++ apport-2.14.1/test/test_signal_crashes.py @@ -585,6 +585,8 @@ if pid == 0: if uid is not None: os.setuid(uid) + # set UTF-8 environment variable, to check proper parsing in apport + os.putenv('utf8trap', b'\xc3\xa0\xc3\xa4') os.dup2(os.open('/dev/null', os.O_WRONLY), sys.stdout.fileno()) sys.stdin.close() os.setsid()