~mgedmin/apport/fix-lp1453011

« back to all changes in this revision

Viewing changes to data/apport

  • Committer: Martin Pitt
  • Date: 2015-04-16 22:00:42 UTC
  • Revision ID: martin.pitt@canonical.com-20150416220042-fxbbu2vmp1riffs6
* SECURITY UPDATE: Disable crash forwarding to containers. The previous fix in 2.17.1 was not sufficient against all attack scenarios. By binding to specially crafted sockes, a normal user program could forge arbitrary entries in /proc/net/unix. We cannot currently rely on a kernel-side solution for this; this feature will be re-enabled once it gets re-done to be secure. (LP: #1444518)

Show diffs side-by-side

added added

removed removed

Lines of Context:
261
261
 
262
262
# Check if we received a valid global PID (kernel >= 3.12). If we do,
263
263
# then compare it with the local PID. If they don't match, it's an
264
 
# indication that the crash originated from another PID namespace. In that
265
 
# case, attempt to forward the crash to apport in that namespace. If
266
 
# apport can't be found, then simply log an entry in the host error log
267
 
# and exit 0.
 
264
# indication that the crash originated from another PID namespace.
 
265
# Simply log an entry in the host error log and exit 0.
268
266
if len(sys.argv) == 5 and sys.argv[4].isdigit() and sys.argv[4] != sys.argv[1]:
269
 
    host_pid = sys.argv[4]
270
 
    pid = sys.argv[1]
271
 
    if os.path.exists('/proc/%s/root/%s' % (host_pid, __file__)) \
272
 
            and os.path.exists('/proc/%s/ns/' % host_pid):
273
 
        try:
274
 
            import lxc
275
 
        except:
276
 
            error_log('pid %s crashed in a container without python3-lxc' % host_pid)
277
 
            sys.exit(0)
278
 
 
279
 
        def list_containers():
280
 
            containers = []
281
 
 
282
 
            with open('/proc/net/unix', 'r') as fd:
283
 
                for line in sorted(fd):
284
 
                    fields = line.strip().split(' ')
285
 
                    if len(fields) != 8:
286
 
                        continue
287
 
 
288
 
                    inode = fields[6].strip()
289
 
                    path = fields[7].split('/')
290
 
                    if path[-1] != 'command':
291
 
                        continue
292
 
 
293
 
                    if len(path) < 2:
294
 
                        continue
295
 
 
296
 
                    real_path = '/'.join(path[:-2]).lstrip('@')
297
 
 
298
 
                    container = lxc.Container(path[-2], real_path)
299
 
 
300
 
                    if not container.controllable:
301
 
                        continue
302
 
 
303
 
                    if container.state == 'STOPPED':
304
 
                        continue
305
 
 
306
 
                    ppid = 0
307
 
                    with open('/proc/%s/status' % container.init_pid) as fd:
308
 
                        for line in fd:
309
 
                            if line.startswith('PPid:'):
310
 
                                ppid = int(line.strip().split(':')[-1].strip())
311
 
                                break
312
 
                        else:
313
 
                            continue
314
 
 
315
 
                    stat = os.stat('/proc/%s' % ppid)
316
 
                    uid = stat.st_uid
317
 
                    gid = stat.st_gid
318
 
 
319
 
                    for fd in os.listdir('/proc/%s/fd/' % ppid):
320
 
                        if os.readlink('/proc/%s/fd/%s' % (ppid, fd)) \
321
 
                                == 'socket:[%s]' % inode:
322
 
                            break
323
 
                    else:
324
 
                        continue
325
 
 
326
 
                    containers.append((container, (uid, gid)))
327
 
 
328
 
            return containers
329
 
 
330
 
        def list_ns(pid):
331
 
            return [os.readlink('/proc/%s/ns/%s' % (pid, ns))
332
 
                    for ns in sorted(os.listdir('/proc/%s/ns' % pid))]
333
 
 
334
 
        def get_container(pid):
335
 
            pid_ns = list_ns(pid)
336
 
 
337
 
            for container, owner in list_containers():
338
 
                container_ns = list_ns(container.init_pid)
339
 
 
340
 
                if container_ns == pid_ns:
341
 
                    return container, owner
342
 
 
343
 
            return (None, None)
344
 
 
345
 
        container, owner = get_container(host_pid)
346
 
        if not container:
347
 
            error_log('pid %s crashed in an unknown container' % host_pid)
348
 
            sys.exit(0)
349
 
 
350
 
        if owner[0] or owner[1]:
351
 
            error_log('pid %s crashed in an untrusted container' % host_pid)
352
 
            sys.exit(0)
353
 
 
354
 
        error_log('pid %s (host pid %s) crashed in container '
355
 
                  '"%s" (%s), forwarding' % (pid, host_pid,
356
 
                                             container.name,
357
 
                                             container.get_config_path()))
358
 
 
359
 
        sys.stderr.flush()
360
 
 
361
 
        def run_command(cmd):
362
 
            os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
363
 
 
364
 
            return subprocess.call(cmd)
365
 
 
366
 
        cmd = [__file__, sys.argv[1], sys.argv[2], sys.argv[3]]
367
 
        retval = container.attach_wait(run_command, cmd,
368
 
                                       env_policy=lxc.LXC_ATTACH_CLEAR_ENV)
369
 
 
370
 
        sys.exit(0)
371
 
    else:
372
 
        error_log('host pid %s crashed in a container without apport support' %
373
 
                  host_pid)
374
 
        sys.exit(0)
 
267
    error_log('host pid %s crashed in a container without apport support' %
 
268
              sys.argv[4])
 
269
    sys.exit(0)
375
270
 
376
271
check_lock()
377
272