Skip to content

Pipe Permissions on Restore: owned by root, not readable by process #2984

@lrbison

Description

@lrbison

I am attempting to checkpoint a program that uses unix pipes for communication within its own process. In particular, it attempts to open /proc/%d/fd/%d. CRIU's restore completes successfully but during restored program execution, the open() call on /proc/%d/fd/%d fails with invalid permission.

I believe criu launched as root creates the pipe with rw------- mode, then attaches the file descriptors. Later when application is restored it is no longer running as root, and the open fails.

Here is ls -l and stat -L on the fd when criu is run as root:

lr-x------ 1 lrobison lrobison 64 Mar 23 19:11 /proc/432216/fd/3 -> 'pipe:[20980593]'
  File: /proc/432216/fd/3
  Size: 0               Blocks: 0          IO Block: 4096   fifo
Device: 0,14    Inode: 20980593    Links: 1
Access: (0600/prw-------)  Uid: (    0/    root)   Gid: (    0/    root)

Here is the same if restoring with --unprivileged and running criu as my user:

lr-x------ 1 lrobison lrobison 64 Mar 23 19:18 /proc/432612/fd/3 -> 'pipe:[21002808]'
  File: /proc/432612/fd/3
  Size: 0               Blocks: 0          IO Block: 4096   fifo
Device: 0,14    Inode: 21002808    Links: 1
Access: (0600/prw-------)  Uid: ( 1050/lrobison)   Gid: ( 1051/lrobison)

And in this case the application can continue as expected.

However, for other reasons I need restore to run as root. Given that CRIU should know it will hand off the pipe to the unprivileged user, can CRIU change the pipe's ownership to that user?

Small reproducer
/*
 * pipetest.c - Minimal CRIU reproducer: open a pipe, checkpoint, then
 * re-open the read end via /proc/self/fd/<n> after restore.
 *
 * Steps to reproduce:
 *   1. Build:   gcc -o pipetest pipetest.c
 *   2. Run:     ./pipetest &
 *   3. In another terminal, checkpoint:
 *        criu dump -t $(pgrep pipetest) -D /tmp/pipetest-img --shell-job
 *   4. Restore:
 *        criu restore -D /tmp/pipetest-img --shell-job
 *
 * Expected: open(/proc/self/fd/<read_end>) succeeds after restore.
 * Observed: open() fails with Permission denied.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int fds[2];

    if (pipe(fds) < 0) {
        perror("pipe");
        return 1;
    }

    printf("pipe read_fd=%d write_fd=%d\n", fds[0], fds[1]);
    printf("sleeping 20s — checkpoint pid %d now\n", getpid());
    fflush(stdout);

    sleep(20);
    printf("Continuing.  5 seconds of sleep for debug.\n");
    sleep(5);

    /* After restore, attempt to open the read end via /proc/self/fd */
    char path[64];
    snprintf(path, sizeof(path), "/proc/%d/fd/%d", getpid(), fds[0]);

    printf("re-opening pipe read end via %s\n", path);
    fflush(stdout);

    int reopen_fd = open(path, O_RDONLY);
    if (reopen_fd < 0) {
        fprintf(stderr, "open(%s) failed: %s\n", path, strerror(errno));
        return 1;
    }

    printf("open succeeded: new_fd=%d\n", reopen_fd);

    /* Quick sanity check: write to write end, read from re-opened fd */
    const char msg[] = "hello through pipe\n";
    if (write(fds[1], msg, sizeof(msg) - 1) < 0) {
        perror("write");
        return 1;
    }

    char buf[64] = {0};
    ssize_t n = read(reopen_fd, buf, sizeof(buf) - 1);
    if (n < 0) {
        perror("read");
        return 1;
    }

    printf("read %zd bytes: %s", n, buf);

    close(reopen_fd);
    close(fds[0]);
    close(fds[1]);
    return 0;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions