aboutsummaryrefslogtreecommitdiff
path: root/rust/minijail/tests/fork_remap.rs
blob: 6cf3415a1fe23aa303c9d2482cdd6a8cb71019fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! A test of Minijail::fork_remap.
//!
//! It needs to be run on its own because it forks the process and by default cargo test is
//! multi-threaded, and we do not want copies of the other worker threads leaking into the child
//! process.

use std::fs::{read_link, File, OpenOptions};
use std::io::{self, Read};
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use std::path::Path;

use minijail::Minijail;

const DEV_NULL: &str = "/dev/null";
const DEV_ZERO: &str = "/dev/zero";
const PROC_CMDLINE: &str = "/proc/self/cmdline";

fn open_path(path: &str) -> Result<File, io::Error> {
    OpenOptions::new()
        .read(true)
        .write(false)
        .open(Path::new(path))
}

fn main() {
    let mut check_file1 = open_path(DEV_ZERO).unwrap();
    let mut check_file2 = open_path(PROC_CMDLINE).unwrap();
    let j = Minijail::new().unwrap();

    let mut stdio_expected = String::new();
    let mut file2_expected = String::new();
    for &p in &[0, 1, 2, check_file1.as_raw_fd(), check_file2.as_raw_fd()] {
        let path = format!("/proc/self/fd/{}", p);
        let target = read_link(Path::new(&path));
        eprintln!("P: {} -> {:?}", p, &target);
        if p == 2 {
            stdio_expected = target.unwrap().to_string_lossy().to_string();
        } else if p == check_file2.as_raw_fd() {
            file2_expected = target.unwrap().to_string_lossy().to_string();
        }
    }

    // Swap fd1 and fd2.
    let dest_fd1: RawFd = check_file2.as_raw_fd();
    let dest_fd2: RawFd = check_file1.as_raw_fd();

    if unsafe {
        j.fork_remap(&[
            // fd 0 tests stdio mapped to /dev/null.
            (2, 1),                              // One-to-many.
            (2, 2),                              // Identity.
            (check_file1.as_raw_fd(), dest_fd1), // Cross-over.
            (check_file2.as_raw_fd(), dest_fd2), // Cross-over.
        ])
    }
    .unwrap()
        != 0
    {
        j.wait().unwrap();
        eprintln!("Parent done.");
        return;
    }

    // Safe because we are re-taking ownership of remapped fds after forking.
    unsafe {
        check_file1.into_raw_fd();
        check_file1 = File::from_raw_fd(dest_fd1);

        check_file2.into_raw_fd();
        check_file2 = File::from_raw_fd(dest_fd2);
    }

    for (p, expected) in &[
        (0, DEV_NULL),
        (1, &stdio_expected),
        (2, &stdio_expected),
        (dest_fd1, DEV_ZERO),
        (dest_fd2, &file2_expected),
    ] {
        let path = format!("/proc/self/fd/{}", p);
        let target = read_link(Path::new(&path));
        eprintln!("  C: {} -> {:?}", p, &target);
        if !matches!(&target, Ok(p) if p == Path::new(expected)) {
            panic!("  C: got {:?}; expected Ok({:?})", target, expected);
        }
    }

    const BUFFER_LEN: usize = 16;
    let mut buffer = [0xffu8; BUFFER_LEN];
    check_file1.read_exact(&mut buffer).unwrap();
    assert_eq!(&buffer, &[0u8; BUFFER_LEN]);

    let mut file2_contents = Vec::<u8>::new();
    check_file2.read_to_end(&mut file2_contents).unwrap();

    eprintln!("  Child done.");
}