Forking fixes in the context of debuggers

For the past month I've been mostly working on improving the kernel code in the ptrace(2) API. Additionally, I've prepared support for reading NetBSD/aarch64 core(5) files.

LLDB

I've rebased my old patches for handling NetBSD core(5) files to recent LLDB from HEAD (7.0svn). I had to implement the support for aarch64, as requested by Joerg Sonnenberger for debugging NetBSD/arm64 bugs. The NetBSD case is special here, as we set general purpose registers in 128bit containers, while other OSes prefer 64-bit ones. I had to add a local kludge to cast the interesting 64-bit part of the value.

I've generated a local prebuilt toolchain with lldb prepared for a NetbBSD/amd64 host and shared with developers.

Debug Registers

I've improved the security of Debug Registers, with the following changes:

fork(2) and vfork(2)

I've pushed various changes in the kernel subsystems and ATF userland tests for the ptrace(2) functionality:

Security hardening

I've prohibited calling PT_TRACE_ME for a process that is either initproc (PID1) or a direct child of PID1.

I've prohibited calling PT_ATTACH from initproc. This shouldn't happen in normal circumstances, but just in case this would lead to invalid branches in the kernel.

With the above alternations, I've removed a bug causing a creation of a process that is not debuggable. It's worth to note that this bug still exists in other popular kernels. A simple reproducer for pre-8.0 and other OSes using ptrace(2) (Linux, most BSDs ...):

    $ cat antidebug.c
    #include <sys/types.h>
    #include <sys/ptrace.h>
    
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <errno.h>
    
    int
    main(int argc, char **argv)
    {
            pid_t child;
            int rv;
            int n = 0;
    
            child = fork();
            if (child == 0) {
                    while (getppid() != 1)
                            continue;
                    rv = ptrace(PT_TRACE_ME, 0, 0, 0);
                    if (rv != 0)
                            abort();
                    printf("Try to detach to me with a debugger!! ");
                    printf("haha My PID is %d\n", getpid());
                    while (1) {
                            printf("%d\n", n++);
                            sleep(1);
                    }
            }
            exit(0);
    }    

Additionally it's no longer valid calling and returning success PT_TRACE_ME when a process is already traced.

These security changes are covered by new ATF ptrace(2) tests:

  1. traceme_pid1_parent - Assert that a process cannot mark its parent a debugger twice.
  2. traceme_twice - Verify that PT_TRACE_ME is not allowed when our parent is PID1.

Other ptrace(2) enhancements

A list of other changes:

Summary

A critical Problem Report kern/51630 regarding lack of PTRACE_VFORK implementation has been fixed. This means that there are no other unimplemented API calls, but there are still bugs in the existing ones.

With fixes and addition of new test cases, as of today we are passing 961 ptrace(2) tests and skipping 1 (out of 1018 total).

Plan for the next milestone

Cover the remaining forking corner-cases in the context of debuggers with new ATF tests and fix the remaining bugs.

The first step is to implement proper support for handling PT_TRACE_ME-traced scenarios from a vfork(2)ed child. Next I plan to keep covering the corner cases of the forking code and finish this by removal of subtle bugs that are still left in the code since the SMP'ification.

This work was sponsored by The NetBSD Foundation.

The NetBSD Foundation is a non-profit organization and welcomes any donations to help us continue funding projects and services to the open-source community. Please consider visiting the following URL, and chip in what you can:

http://netbsd.org/donations/#how-to-donate