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        _ => {
91            error!(EINVAL)
92        }
93    }
94}
95
96#[derive(Debug, FromPrimitive)]
97enum MetadataType {
98    MerkleTree = 1,
99    Descriptor = 2,
100    Signature = 3,
101}
102
103/// Per-file fsverity state stored in FsNode.
104#[derive(Debug)]
105pub enum FsVerityState {
106    /// This file does not use fsverity.
107    None,
108    /// The state when building the merkle-tree for this file.
109    ///
110    /// When in the building state, the file is not writable and further attempts to
111    /// ENABLE_VERITY will fail with EBUSY. It is possible for merkle-tree building to fail
112    /// (e.g. if the file is too large) but it is not possible to cancel this state manually.
113    Building,
114    /// This file uses fsverity.
115    ///
116    /// Files in this mode can never be opened as writable.
117    /// There is no way to disable fsverity once enabled outside of deleting the file.
118    FsVerity,
119}
120
121impl FsVerityState {
122    /// Returns an appropriate error if state is not writable.
123    pub fn check_writable(&self) -> Result<(), Errno> {
124        match self {
125            FsVerityState::None => Ok(()),
126            FsVerityState::Building => error!(ETXTBSY),
127            FsVerityState::FsVerity => error!(EACCES),
128        }
129    }
130}
131
132pub mod ioctl {
133
134    use crate::mm::{MemoryAccessor, MemoryAccessorExt};
135    use crate::task::CurrentTask;
136    use crate::vfs::fsverity::{
137        FsVerityState, HashAlgorithm, MetadataType, fsverity_descriptor_from_enable_arg,
138        fsverity_enable_arg, fsverity_measurement, fsverity_read_metadata_arg,
139    };
140    use crate::vfs::{FileObject, FileWriteGuardMode};
141    use num_traits::FromPrimitive;
142    use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked};
143    use starnix_syscalls::{SUCCESS, SyscallResult};
144    use starnix_uapi::errors::Errno;
145    use starnix_uapi::user_address::{UserAddress, UserRef};
146    use starnix_uapi::{errno, error, uapi};
147    use zerocopy::IntoBytes;
148
149    /// ioctl handler for FS_IOC_ENABLE_VERITY.
150    pub fn enable<L>(
151        locked: &mut Locked<L>,
152        task: &CurrentTask,
153        arg: UserAddress,
154        file: &FileObject,
155    ) -> Result<SyscallResult, Errno>
156    where
157        L: LockEqualOrBefore<FileOpsCore>,
158    {
159        if file.can_write() {
160            return error!(ETXTBSY);
161        }
162        let block_size = file.name.entry.node.fs().statfs(locked, task)?.f_bsize as u32;
163        // Nb: Lock order is important here.
164        let args: fsverity_enable_arg = task.read_object(arg.into())?;
165        let mut descriptor = fsverity_descriptor_from_enable_arg(task, block_size, &args)?;
166        descriptor.data_size = file.node().fetch_and_refresh_info(locked, task)?.size as u64;
167        // The "Exec" writeguard mode means 'no writers'.
168        let _mapping = file.name.clone().into_mapping(Some(FileWriteGuardMode::ExecMapping))?;
169        let mut fsverity = file.node().fsverity.lock();
170        match *fsverity {
171            FsVerityState::Building => error!(EBUSY),
172            FsVerityState::FsVerity => error!(EEXIST),
173            FsVerityState::None => {
174                *fsverity = FsVerityState::Building;
175                file.node().ops().enable_fsverity(&descriptor)?;
176                *fsverity = FsVerityState::FsVerity;
177                Ok(SUCCESS)
178            }
179        }
180    }
181
182    /// ioctl handler for FS_IOC_MEASURE_VERITY.
183    pub fn measure<L>(
184        locked: &mut Locked<L>,
185        task: &CurrentTask,
186        arg: UserAddress,
187        file: &FileObject,
188    ) -> Result<SyscallResult, Errno>
189    where
190        L: LockEqualOrBefore<FileOpsCore>,
191    {
192        let header_ref = UserRef::<uapi::fsverity_digest>::new(arg);
193        let digest_addr = header_ref.next()?.addr();
194        let header = task.read_object(header_ref.clone())?;
195        match &*file.node().fsverity.lock() {
196            FsVerityState::FsVerity => {
197                let block_size = file.name.entry.node.fs().statfs(locked, task)?.f_bsize as u32;
198                if !block_size.is_power_of_two() {
199                    return error!(EINVAL);
200                }
201                let descriptor =
202                    file.node().ops().get_fsverity_descriptor(block_size.ilog2() as u8)?;
203                let digest_algorithm = HashAlgorithm::from_u8(descriptor.hash_algorithm)
204                    .ok_or_else(|| errno!(EINVAL))?;
205                let required_size = match digest_algorithm {
206                    HashAlgorithm::SHA256 => 32,
207                    HashAlgorithm::SHA512 => 64,
208                };
209                if (header.digest_size as usize) < required_size {
210                    return error!(EOVERFLOW);
211                }
212                let output_header = uapi::fsverity_digest {
213                    digest_algorithm: descriptor.hash_algorithm as u16,
214                    digest_size: required_size as u16,
215                    ..Default::default()
216                };
217                task.write_object(header_ref, &output_header)?;
218                task.write_memory(digest_addr, &fsverity_measurement(&descriptor)?)?;
219                Ok(SUCCESS)
220            }
221            _ => error!(ENODATA),
222        }
223    }
224
225    /// ioctl handler for FS_IOC_READ_VERITY_METADATA.
226    pub fn read_metadata(
227        task: &CurrentTask,
228        arg: UserAddress,
229        file: &FileObject,
230    ) -> Result<SyscallResult, Errno> {
231        let arg: fsverity_read_metadata_arg = task.read_object(arg.into())?;
232        match &*file.node().fsverity.lock() {
233            FsVerityState::FsVerity => {
234                match MetadataType::from_u64(arg.metadata_type).ok_or_else(|| errno!(EINVAL))? {
235                    MetadataType::MerkleTree => {
236                        error!(EOPNOTSUPP)
237                    }
238                    MetadataType::Descriptor => {
239                        // TODO(b/314182708): Remove hardcoding of blocksize
240                        let descriptor = file.node().ops().get_fsverity_descriptor(12)?;
241                        task.write_memory(
242                            UserAddress::from(arg.buf_ptr).into(),
243                            &descriptor.as_bytes()
244                                [arg.offset as usize..(arg.offset + arg.length) as usize],
245                        )?;
246                        Ok(SUCCESS)
247                    }
248                    MetadataType::Signature => {
249                        error!(EOPNOTSUPP)
250                    }
251                }
252            }
253            _ => error!(ENODATA),
254        }
255    }
256}