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