Skip to main content

starnix_core/vfs/
fsverity.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::mm::MemoryAccessorExt;
6use crate::task::CurrentTask;
7use mundane::hash::{Digest, Hasher, Sha256, Sha512};
8use num_derive::FromPrimitive;
9use num_traits::FromPrimitive;
10use starnix_logging::track_stub;
11use starnix_uapi::errors::Errno;
12use starnix_uapi::user_address::UserAddress;
13use starnix_uapi::{
14    FS_VERITY_HASH_ALG_SHA256, FS_VERITY_HASH_ALG_SHA512, errno, error, fsverity_descriptor,
15    fsverity_enable_arg, fsverity_read_metadata_arg,
16};
17use zerocopy::IntoBytes;
18
19#[derive(Copy, Clone, Debug, Eq, FromPrimitive, PartialEq)]
20enum HashAlgorithm {
21    SHA256 = FS_VERITY_HASH_ALG_SHA256 as isize,
22    SHA512 = FS_VERITY_HASH_ALG_SHA512 as isize,
23}
24
25/// Create a new fsverity_descriptor from the ioctl `enable_verity`.
26/// Copies salt and signature from the given user tasks memory space.
27fn fsverity_descriptor_from_enable_arg(
28    current_task: &CurrentTask,
29    filesystem_block_size: u32,
30    src: &fsverity_enable_arg,
31) -> Result<fsverity_descriptor, Errno> {
32    if src.version != 1 {
33        return error!(EINVAL);
34    }
35    // block_size must be a power of 2 in [1024,min(page_size, filesystem_block_size)].
36    if src.block_size.count_ones() != 1
37        || src.block_size < 1024
38        || src.block_size > std::cmp::min(4096, filesystem_block_size)
39    {
40        return error!(EINVAL);
41    }
42    if src.hash_algorithm != FS_VERITY_HASH_ALG_SHA256
43        && src.hash_algorithm != FS_VERITY_HASH_ALG_SHA512
44    {
45        return error!(ENOTSUP);
46    }
47    if src.salt_size > 32 {
48        return error!(EINVAL);
49    }
50    if src.sig_size > 0 {
51        // TODO(https://fxbug.dev/302620572) Under linux, signatures are supported up to 16128 bytes.
52        // We don't support these currently.
53        track_stub!(TODO("https://fxbug.dev/302620572"), "fsverity_enable signature");
54        return error!(ENOTSUP);
55    }
56    let salt =
57        current_task.read_memory_to_vec(UserAddress::from(src.salt_ptr), src.salt_size as usize)?;
58    let mut desc = fsverity_descriptor {
59        version: 1,
60        hash_algorithm: HashAlgorithm::from_u32(src.hash_algorithm).ok_or_else(|| errno!(EINVAL))?
61            as u8,
62        salt_size: salt.len() as u8,
63        log_blocksize: (u32::BITS - 1 - src.block_size.leading_zeros()) as u8,
64        ..Default::default()
65    };
66    desc.salt[..src.salt_size as usize].clone_from_slice(salt.as_slice());
67    Ok(desc)
68}
69
70/// An fsverity 'measurement' is a hash of a descriptor that contains the root hash.
71/// This ensures that the result captures file size, hash type and other relevant parameters.
72fn fsverity_measurement(descriptor: &fsverity_descriptor) -> Result<Box<[u8]>, Errno> {
73    match descriptor.hash_algorithm.into() {
74        FS_VERITY_HASH_ALG_SHA256 => {
75            let mut hasher = Sha256::default();
76            if descriptor.salt_size > 0 {
77                hasher.update(&descriptor.salt[..descriptor.salt_size as usize]);
78            }
79            hasher.update(descriptor.as_bytes());
80            Ok(Box::new(hasher.finish().bytes()))
81        }
82        FS_VERITY_HASH_ALG_SHA512 => {
83            let mut hasher = Sha512::default();
84            if descriptor.salt_size > 0 {
85                hasher.update(&descriptor.salt[..descriptor.salt_size as usize]);
86            }
87            hasher.update(descriptor.as_bytes());
88            Ok(Box::new(hasher.finish().bytes()))
89        }
90        _ => error!(EINVAL),
91    }
92}
93
94#[derive(Debug, FromPrimitive)]
95enum MetadataType {
96    MerkleTree = 1,
97    Descriptor = 2,
98    Signature = 3,
99}
100
101/// Per-file fsverity state stored in FsNode.
102#[derive(Debug, Default)]
103pub enum FsVerityState {
104    /// This file does not use fsverity.
105    #[default]
106    None,
107    /// The state when building the merkle-tree for this file.
108    ///
109    /// When in the building state, the file is not writable and further attempts to
110    /// ENABLE_VERITY will fail with EBUSY. It is possible for merkle-tree building to fail
111    /// (e.g. if the file is too large) but it is not possible to cancel this state manually.
112    Building,
113    /// This file uses fsverity.
114    ///
115    /// Files in this mode can never be opened as writable.
116    /// There is no way to disable fsverity once enabled outside of deleting the file.
117    FsVerity,
118}
119
120impl FsVerityState {
121    /// Returns an appropriate error if state is not writable.
122    pub fn check_writable(&self) -> Result<(), Errno> {
123        match self {
124            FsVerityState::None => Ok(()),
125            FsVerityState::Building => error!(ETXTBSY),
126            FsVerityState::FsVerity => error!(EACCES),
127        }
128    }
129}
130
131pub mod ioctl {
132
133    use crate::mm::{MemoryAccessor, MemoryAccessorExt};
134    use crate::task::CurrentTask;
135    use crate::vfs::fsverity::{
136        FsVerityState, HashAlgorithm, MetadataType, fsverity_descriptor_from_enable_arg,
137        fsverity_enable_arg, fsverity_measurement, fsverity_read_metadata_arg,
138    };
139    use crate::vfs::{FileObject, FileWriteGuardMode};
140    use num_traits::FromPrimitive;
141
142    use starnix_syscalls::{SUCCESS, SyscallResult};
143    use starnix_uapi::errors::Errno;
144    use starnix_uapi::user_address::{UserAddress, UserRef};
145    use starnix_uapi::{errno, error, uapi};
146    use zerocopy::IntoBytes;
147
148    /// ioctl handler for FS_IOC_ENABLE_VERITY.
149    pub fn enable(
150        task: &CurrentTask,
151        arg: UserAddress,
152        file: &FileObject,
153    ) -> Result<SyscallResult, Errno> {
154        if file.can_write() {
155            return error!(ETXTBSY);
156        }
157        let block_size = file.name.entry.node.fs().statfs(task)?.f_bsize as u32;
158        // Nb: Lock order is important here.
159        let args: fsverity_enable_arg = task.read_object(arg.into())?;
160        let mut descriptor = fsverity_descriptor_from_enable_arg(task, block_size, &args)?;
161        descriptor.data_size = file.node().fetch_and_refresh_info(task)?.size as u64;
162        // The "Exec" writeguard mode means 'no writers'.
163        let _mapping = file.name.clone().into_mapping(Some(FileWriteGuardMode::ExecMapping))?;
164        {
165            let mut fsverity = file.node().fsverity.lock();
166            match *fsverity {
167                FsVerityState::Building => return error!(EBUSY),
168                FsVerityState::FsVerity => return error!(EEXIST),
169                FsVerityState::None => *fsverity = FsVerityState::Building,
170            }
171        }
172        let result = file.node().enable_fsverity(task, &descriptor);
173        *file.node().fsverity.lock() =
174            if result.is_ok() { FsVerityState::FsVerity } else { FsVerityState::None };
175        result.map(|()| SUCCESS)
176    }
177
178    /// ioctl handler for FS_IOC_MEASURE_VERITY.
179    pub fn measure(
180        task: &CurrentTask,
181        arg: UserAddress,
182        file: &FileObject,
183    ) -> Result<SyscallResult, Errno> {
184        let header_ref = UserRef::<uapi::fsverity_digest>::new(arg);
185        let digest_addr = header_ref.next()?.addr();
186        let header = task.read_object(header_ref.clone())?;
187        match &*file.node().fsverity.lock() {
188            FsVerityState::FsVerity => {
189                let block_size = file.name.entry.node.fs().statfs(task)?.f_bsize as u32;
190                if !block_size.is_power_of_two() {
191                    return error!(EINVAL);
192                }
193                let descriptor =
194                    file.node().ops().get_fsverity_descriptor(block_size.ilog2() as u8)?;
195                let digest_algorithm = HashAlgorithm::from_u8(descriptor.hash_algorithm)
196                    .ok_or_else(|| errno!(EINVAL))?;
197                let required_size = match digest_algorithm {
198                    HashAlgorithm::SHA256 => 32,
199                    HashAlgorithm::SHA512 => 64,
200                };
201                if (header.digest_size as usize) < required_size {
202                    return error!(EOVERFLOW);
203                }
204                let output_header = uapi::fsverity_digest {
205                    digest_algorithm: descriptor.hash_algorithm as u16,
206                    digest_size: required_size as u16,
207                    ..Default::default()
208                };
209                task.write_object(header_ref, &output_header)?;
210                task.write_memory(digest_addr, &fsverity_measurement(&descriptor)?)?;
211                Ok(SUCCESS)
212            }
213            _ => error!(ENODATA),
214        }
215    }
216
217    /// ioctl handler for FS_IOC_READ_VERITY_METADATA.
218    pub fn read_metadata(
219        task: &CurrentTask,
220        arg: UserAddress,
221        file: &FileObject,
222    ) -> Result<SyscallResult, Errno> {
223        let arg: fsverity_read_metadata_arg = task.read_object(arg.into())?;
224        match &*file.node().fsverity.lock() {
225            FsVerityState::FsVerity => {
226                match MetadataType::from_u64(arg.metadata_type).ok_or_else(|| errno!(EINVAL))? {
227                    MetadataType::MerkleTree => {
228                        error!(EOPNOTSUPP)
229                    }
230                    MetadataType::Descriptor => {
231                        // TODO(b/314182708): Remove hardcoding of blocksize
232                        let descriptor = file.node().ops().get_fsverity_descriptor(12)?;
233                        task.write_memory(
234                            UserAddress::from(arg.buf_ptr).into(),
235                            &descriptor.as_bytes()
236                                [arg.offset as usize..(arg.offset + arg.length) as usize],
237                        )?;
238                        Ok(SUCCESS)
239                    }
240                    MetadataType::Signature => {
241                        error!(EOPNOTSUPP)
242                    }
243                }
244            }
245            _ => error!(ENODATA),
246        }
247    }
248}