flak rss random

wrapping pids for fun and profit

After the recent OpenBSD hackathon, I took a day off to chill out in Trieste before flying home. In the mean time, a blog post regarding the perils of getpid wrapping appeared. Unfortunately, by the time I made it home and reconnected to the tubes, kettenis and bcook had already fixed the bug, before I even had a chance to shit my pants. The gall of some people.

The posted example code is somewhat (portable) LibreSSL specific, even though the real bug exists in the arc4random function, so as a programming exercise I thought I’d make another simpler example. There are a few critical preconditions in order to demonstrate the bug. First, the grandparent must fork a child, and that middle child must fork the grandchild after the grandparent exits (or else it can’t possibly match the pid). Second, the grandparent must generate random numbers before forking (to initialize the saved pid) and after forking (to continue down the track with the same state), and the middle child must not call arc4random (or it will reinitialize the pid). And, of course, the pid has to be recycled, which will never actually happen if the grandparent was the process group leader because the pid will remain in use to identify the group.

Regarding the severity of the bug, the only naturally occurring fork(); fork(); sequence I know about is when a program attempts to daemonize and detach from its own parent, but the grandparent never does anything interesting in that case. In other cases, like preforking web servers, the grandparent doesn’t exit.

Here’s my code. It demonstrates the problem on OpenBSD 5.5 as well, though you may have to run it a few times. The child detaches from the terminal, so just sit tight and be patient while it churns away.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
        int i;
        pid_t pid, c;

        pid = getpid();
        printf("start %d %u\n", getpid(), arc4random());
        if (fork() != 0) {
                printf("parent %d %u\n", getpid(), arc4random());
                printf("parent %d %u\n", getpid(), arc4random());
                _exit(0);
        }
        daemon(1, 1);
        for (i = 0; i < 100000; i++) {
                if ((c = fork())) {
                        if (c == -1) {
                                printf("fork failed\n");
                                _exit(0);
                        }
                        wait(NULL);
                        if (c == pid)
                                _exit(0);
                } else {
                        if (getpid() == pid) {
                                printf("child %d %u\n", getpid(), arc4random());
                                printf("child %d %u\n", getpid(), arc4random());
                        }
                        _exit(0);
                }
        }
        printf("couldn't get pid %d again\n", pid);
        _exit(0);
}

For more shits and giggles, read the reddit comments decrying the stubborn OpenBSD developers who will never fix this problem.

Posted 16 Jul 2014 20:40 by tedu Updated: 18 Jul 2014 01:18
Tagged: c openbsd programming security