composefs/fsverity/
ioctl.rs

1//! Low-level ioctl interfaces for fs-verity kernel operations.
2//!
3//! This module provides safe wrappers around the Linux fs-verity ioctls
4//! for enabling and measuring fs-verity on files, handling the conversion
5//! between kernel and userspace data structures.
6
7#![allow(unsafe_code)]
8
9use core::mem::size_of;
10
11use std::{io::Error, os::fd::AsFd};
12
13use rustix::{
14    io::Errno,
15    ioctl::{ioctl, opcode, Opcode, Setter, Updater},
16};
17
18pub use super::{EnableVerityError, FsVerityHashValue, MeasureVerityError};
19
20// See /usr/include/linux/fsverity.h
21#[repr(C)]
22#[derive(Debug)]
23struct FsVerityEnableArg {
24    version: u32,
25    hash_algorithm: u32,
26    block_size: u32,
27    salt_size: u32,
28    salt_ptr: u64,
29    sig_size: u32,
30    __reserved1: u32,
31    sig_ptr: u64,
32    __reserved2: [u64; 11],
33}
34
35// #define FS_IOC_ENABLE_VERITY    _IOW('f', 133, struct fsverity_enable_arg)
36const FS_IOC_ENABLE_VERITY: Opcode = opcode::write::<FsVerityEnableArg>(b'f', 133);
37
38/// Enable fsverity on the target file. This is a thin safe wrapper for the underlying base `ioctl`
39/// and hence all constraints apply such as requiring the file descriptor to already be `O_RDONLY`
40/// etc.
41pub(super) fn fs_ioc_enable_verity<H: FsVerityHashValue>(
42    fd: impl AsFd,
43) -> Result<(), EnableVerityError> {
44    unsafe {
45        match ioctl(
46            fd,
47            Setter::<{ FS_IOC_ENABLE_VERITY }, FsVerityEnableArg>::new(FsVerityEnableArg {
48                version: 1,
49                hash_algorithm: H::ALGORITHM as u32,
50                block_size: 4096,
51                salt_size: 0,
52                salt_ptr: 0,
53                sig_size: 0,
54                __reserved1: 0,
55                sig_ptr: 0,
56                __reserved2: [0; 11],
57            }),
58        ) {
59            Err(Errno::NOTTY) | Err(Errno::OPNOTSUPP) => {
60                Err(EnableVerityError::FilesystemNotSupported)
61            }
62            Err(Errno::EXIST) => Err(EnableVerityError::AlreadyEnabled),
63            Err(Errno::TXTBSY) => Err(EnableVerityError::FileOpenedForWrite),
64            Err(e) => Err(Error::from(e).into()),
65            Ok(_) => Ok(()),
66        }
67    }
68}
69
70/// Core definition of a fsverity digest.
71#[repr(C)]
72#[derive(Debug)]
73struct FsVerityDigest<F> {
74    digest_algorithm: u16,
75    digest_size: u16,
76    digest: F,
77}
78
79// #define FS_IOC_MEASURE_VERITY   _IORW('f', 134, struct fsverity_digest)
80const FS_IOC_MEASURE_VERITY: Opcode = opcode::read_write::<FsVerityDigest<()>>(b'f', 134);
81
82/// Measure the fsverity digest of the provided file descriptor.
83pub(super) fn fs_ioc_measure_verity<H: FsVerityHashValue>(
84    fd: impl AsFd,
85) -> Result<H, MeasureVerityError> {
86    let digest_size = size_of::<H>() as u16;
87    let digest_algorithm = H::ALGORITHM as u16;
88
89    let mut digest = FsVerityDigest::<H> {
90        digest_algorithm,
91        digest_size,
92        digest: H::EMPTY,
93    };
94
95    let r = unsafe {
96        ioctl(
97            fd,
98            Updater::<{ FS_IOC_MEASURE_VERITY }, FsVerityDigest<H>>::new(&mut digest),
99        )
100    };
101    match r {
102        Ok(()) => {
103            if digest.digest_algorithm != digest_algorithm {
104                return Err(MeasureVerityError::InvalidDigestAlgorithm {
105                    expected: digest.digest_algorithm,
106                    found: digest_algorithm,
107                });
108            }
109            if digest.digest_size != digest_size {
110                return Err(MeasureVerityError::InvalidDigestSize {
111                    expected: digest.digest_size,
112                });
113            }
114            Ok(digest.digest)
115        }
116        Err(Errno::NODATA) => Err(MeasureVerityError::VerityMissing),
117        Err(Errno::NOTTY | Errno::OPNOTSUPP) => Err(MeasureVerityError::FilesystemNotSupported),
118        Err(Errno::OVERFLOW) => Err(MeasureVerityError::InvalidDigestSize {
119            expected: digest.digest_size,
120        }),
121        Err(e) => Err(Error::from(e).into()),
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use std::{mem::ManuallyDrop, os::fd::OwnedFd};
128
129    use rustix::fd::FromRawFd;
130    use tempfile::tempfile_in;
131
132    use crate::{fsverity::Sha256HashValue, test::tempfile};
133
134    use super::*;
135
136    #[test]
137    fn test_measure_verity_opt() {
138        let tf = tempfile();
139        assert!(matches!(
140            fs_ioc_measure_verity::<Sha256HashValue>(&tf),
141            Err(MeasureVerityError::VerityMissing)
142        ));
143    }
144
145    #[test_with::path(/dev/shm)]
146    #[test]
147    fn test_measure_verity_not_supported() {
148        let tf = tempfile_in("/dev/shm").unwrap();
149        assert!(matches!(
150            fs_ioc_measure_verity::<Sha256HashValue>(&tf),
151            Err(MeasureVerityError::FilesystemNotSupported)
152        ));
153    }
154
155    #[test_with::path(/dev/shm)]
156    #[test]
157    fn test_fs_ioc_enable_verity_wrong_fs() {
158        let file = tempfile_in("/dev/shm").unwrap();
159        let fd = OwnedFd::from(file);
160        let err = fs_ioc_enable_verity::<Sha256HashValue>(&fd).unwrap_err();
161        assert!(matches!(err, EnableVerityError::FilesystemNotSupported));
162        assert_eq!(err.to_string(), "Filesystem does not support fs-verity",);
163    }
164
165    #[test]
166    fn test_fs_ioc_enable_verity_bad_fd() {
167        let fd = ManuallyDrop::new(unsafe { OwnedFd::from_raw_fd(123456) });
168        let res = fs_ioc_enable_verity::<Sha256HashValue>(fd.as_fd());
169        let err = res.err().unwrap();
170        assert!(matches!(err, EnableVerityError::Io(..)));
171        assert_eq!(err.to_string(), "Bad file descriptor (os error 9)",);
172    }
173}