Saturday, June 14, 2014

How to set PID using ns_last_pid

So there is this cool project called CRIU (Checkpoint/Restore In Userspace). And I was wondering how it gets certain PID when restoring a process. I always thought that it is not possible to set PID without some kind of kernel hacking.

I did some investigation and here is what I figured out. There is a file /proc/sys/kernel/ns_last_pid, which contains the last PID that was assigned by the kernel. So, when the kernel needs to assign a new one, it looks into ns_last_pid, gets last_pid, and assigns last_pid+1.

ns_last_pid was added by CRIU guys and has been available since 3.3 Kernel. Note that it requires the CONFIG_CHECKPOINT_RESTORE option to be set, which has been enabled by default in most popular distros (e.g. ubuntu and fedora), but not all. The most notable example is Arch Linux, which had it set for some time but then suddenly disabled it in ~3.11 (still disabled in 3.14.6-1).

Here is some C code to set PID for a forked child.

Update: I've also added this example to criu wiki https://criu.org/Pid_restore.

BEWARE! This program requires root. I don't take any responsibility for what this code might do to your system.

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int fd, pid;
    char buf[32];

    if (argc != 2)
     return 1;

    printf("Opening ns_last_pid...\n");
    fd = open("/proc/sys/kernel/ns_last_pid", O_RDWR | O_CREAT, 0644);
    if (fd < 0) {
        perror("Can't open ns_last_pid");
        return 1;
    }
    printf("Done\n");

    printf("Locking ns_last_pid...\n");
    if (flock(fd, LOCK_EX)) {
        close(fd);
        printf("Can't lock ns_last_pid\n");
        return 1;
    }
    printf("Done\n");

    pid = atoi(argv[1]);
    snprintf(buf, sizeof(buf), "%d", pid - 1);

    printf("Writing pid-1 to ns_last_pid...\n");
    if (write(fd, buf, strlen(buf)) != strlen(buf)) {
        printf("Can't write to buf\n");
        return 1;
    }
    printf("Done\n");

    printf("Forking...\n");
    int new_pid;
    new_pid = fork();
    if (new_pid == 0) {
        printf("I'm child!\n");
        exit(0);
    } else if (new_pid == pid) {
        printf("I'm parent. My child got right pid!\n");
    } else {
        printf("pid does not match expected one\n");
    }
    printf("Done\n");

    printf("Cleaning up...");
    if (flock(fd, LOCK_UN)) {
        printf("Can't unlock");
    }

    close(fd);

    printf("Done\n");

    return 0;
}