aboutsummaryrefslogtreecommitdiff
path: root/src/platform/linux/native_image_sources.rs
blob: b4d3b07376b09f033d1471b68c45719cdcc4484e (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
//! Bounded kernel CoW snapshots. No read/write or whole-file clone fallback.
use std::fs::File;
use std::io;
use std::os::fd::AsRawFd;

#[repr(C)]
struct CloneRange {
    src_fd: i64,
    src_offset: u64,
    src_length: u64,
    dest_offset: u64,
}

fn stat(fd: libc::c_int) -> io::Result<libc::stat> {
    let mut result = std::mem::MaybeUninit::<libc::stat>::uninit();
    // SAFETY: fstat initializes the supplied stat on success; fd remains borrowed.
    if unsafe { libc::fstat(fd, result.as_mut_ptr()) } != 0 {
        return Err(io::Error::last_os_error());
    }
    // SAFETY: successful fstat initialized the value.
    Ok(unsafe { result.assume_init() })
}

fn invalid() -> io::Error {
    io::Error::new(
        io::ErrorKind::InvalidInput,
        "invalid native snapshot source or destination",
    )
}

pub(crate) fn clone_native_image_source(
    source_fd: i64,
    destination: &File,
    expected_len: usize,
) -> io::Result<()> {
    let fd = libc::c_int::try_from(source_fd).map_err(|_| invalid())?;
    if fd < 0
        || expected_len == 0
        || expected_len > 16 * 1024 * 1024
        || !expected_len.is_multiple_of(4)
    {
        return Err(invalid());
    }
    let source = stat(fd)?;
    let dest = stat(destination.as_raw_fd())?;
    // SAFETY: geteuid has no preconditions.
    let uid = unsafe { libc::geteuid() };
    let valid_source = |value: &libc::stat| {
        value.st_mode & libc::S_IFMT == libc::S_IFREG
            && value.st_uid == uid
            && value.st_size == expected_len as i64
            && value.st_dev == source.st_dev
            && value.st_ino == source.st_ino
    };
    if !valid_source(&source)
        || dest.st_mode & libc::S_IFMT != libc::S_IFREG
        || dest.st_mode & 0o777 != 0o600
        || dest.st_uid != uid
        || dest.st_nlink != 1
        || dest.st_size != 0
        || (dest.st_dev == source.st_dev && dest.st_ino == source.st_ino)
    {
        return Err(invalid());
    }
    let range = CloneRange {
        src_fd: source_fd,
        src_offset: 0,
        src_length: expected_len as u64,
        dest_offset: 0,
    };
    // Explicit nonzero length is essential: FICLONE (or a zero range length)
    // clones through EOF and source growth could exceed the reservation before
    // post-validation. This request cannot extend the destination past the
    // reservation even if the producer changes size concurrently. Filesystems
    // requiring block alignment may reject an EOF range after concurrent growth;
    // that is a normal fallback, never a reason to try an unbounded clone.
    // SAFETY: the request consumes a valid repr(C) range, both descriptors stay
    // open throughout the call, and the source descriptor is never owned here.
    if unsafe { libc::ioctl(destination.as_raw_fd(), libc::FICLONERANGE, &range) } != 0 {
        return Err(io::Error::last_os_error());
    }
    let after = stat(destination.as_raw_fd())?;
    if !valid_source(&stat(fd)?)
        || after.st_dev != dest.st_dev
        || after.st_ino != dest.st_ino
        || after.st_size != expected_len as i64
        || after.st_nlink != 1
        || after.st_uid != uid
        || after.st_mode & 0o777 != 0o600
    {
        return Err(invalid());
    }
    // The kernel serializes clone against writes. These stats establish exact
    // length at validation points, not producer quiescence or a content hash.
    Ok(())
}