| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::{mm::MemoryAccessorExt, task::CurrentTask}; |
| use mundane::hash::{Digest, Hasher, Sha256, Sha512}; |
| use num_derive::FromPrimitive; |
| use num_traits::FromPrimitive; |
| use starnix_logging::track_stub; |
| use starnix_uapi::{ |
| errno, error, errors::Errno, fsverity_descriptor, fsverity_enable_arg, |
| fsverity_read_metadata_arg, user_address::UserAddress, FS_VERITY_HASH_ALG_SHA256, |
| FS_VERITY_HASH_ALG_SHA512, |
| }; |
| use zerocopy::{AsBytes, FromBytes, FromZeros, NoCell}; |
| |
| #[derive(Copy, Clone, Debug, Eq, FromPrimitive, PartialEq)] |
| enum HashAlgorithm { |
| SHA256 = FS_VERITY_HASH_ALG_SHA256 as isize, |
| SHA512 = FS_VERITY_HASH_ALG_SHA512 as isize, |
| } |
| |
| /// Create a new fsverity_descriptor from the ioctl `enable_verity`. |
| /// Copies salt and signature from the given user tasks memory space. |
| fn fsverity_descriptor_from_enable_arg( |
| current_task: &CurrentTask, |
| filesystem_block_size: u32, |
| src: &fsverity_enable_arg, |
| ) -> Result<fsverity_descriptor, Errno> { |
| if src.version != 1 { |
| return error!(EINVAL); |
| } |
| // block_size must be a power of 2 in [1024,min(page_size, filesystem_block_size)]. |
| if src.block_size.count_ones() != 1 |
| || src.block_size < 1024 |
| || src.block_size > std::cmp::min(4096, filesystem_block_size) |
| { |
| return error!(EINVAL); |
| } |
| if src.hash_algorithm != FS_VERITY_HASH_ALG_SHA256 |
| && src.hash_algorithm != FS_VERITY_HASH_ALG_SHA512 |
| { |
| return error!(ENOTSUP); |
| } |
| if src.salt_size > 32 { |
| return error!(EINVAL); |
| } |
| if src.sig_size > 0 { |
| // TODO(https://fxbug.dev/302620572) Under linux, signatures are supported up to 16128 bytes. |
| // We don't support these currently. |
| track_stub!(TODO("https://fxbug.dev/302620572"), "fsverity_enable signature"); |
| return error!(ENOTSUP); |
| } |
| let salt = |
| current_task.read_memory_to_vec(UserAddress::from(src.salt_ptr), src.salt_size as usize)?; |
| let mut desc = fsverity_descriptor { |
| version: 1, |
| hash_algorithm: HashAlgorithm::from_u32(src.hash_algorithm).ok_or_else(|| errno!(EINVAL))? |
| as u8, |
| salt_size: salt.len() as u8, |
| log_blocksize: (u32::BITS - 1 - src.block_size.leading_zeros()) as u8, |
| ..Default::default() |
| }; |
| desc.salt[..src.salt_size as usize].clone_from_slice(salt.as_slice()); |
| Ok(desc) |
| } |
| |
| /// An fsverity 'measurement' is a hash of a descriptor that contains the root hash. |
| /// This ensures that the result captures file size, hash type and other relevant parameters. |
| fn fsverity_measurement(descriptor: &fsverity_descriptor) -> Result<Box<[u8]>, Errno> { |
| match descriptor.hash_algorithm.into() { |
| FS_VERITY_HASH_ALG_SHA256 => { |
| let mut hasher = Sha256::default(); |
| if descriptor.salt_size > 0 { |
| hasher.update(&descriptor.salt[..descriptor.salt_size as usize]); |
| } |
| hasher.update(descriptor.as_bytes()); |
| Ok(Box::new(hasher.finish().bytes())) |
| } |
| FS_VERITY_HASH_ALG_SHA512 => { |
| let mut hasher = Sha512::default(); |
| if descriptor.salt_size > 0 { |
| hasher.update(&descriptor.salt[..descriptor.salt_size as usize]); |
| } |
| hasher.update(descriptor.as_bytes()); |
| Ok(Box::new(hasher.finish().bytes())) |
| } |
| _ => { |
| error!(EINVAL) |
| } |
| } |
| } |
| |
| #[derive(FromBytes, FromZeros, NoCell)] |
| struct FsVerityDigestHeader { |
| digest_algorithm: u16, |
| digest_size: u16, |
| } |
| |
| #[derive(Debug, FromPrimitive)] |
| enum MetadataType { |
| MerkleTree = 1, |
| Descriptor = 2, |
| Signature = 3, |
| } |
| |
| /// Per-file fsverity state stored in FsNode. |
| #[derive(Debug)] |
| pub enum FsVerityState { |
| /// This file does not use fsverity. |
| None, |
| /// The state when building the merkle-tree for this file. |
| /// |
| /// When in the building state, the file is not writable and further attempts to |
| /// ENABLE_VERITY will fail with EBUSY. It is possible for merkle-tree building to fail |
| /// (e.g. if the file is too large) but it is not possible to cancel this state manually. |
| Building, |
| /// This file uses fsverity. |
| /// |
| /// Files in this mode can never be opened as writable. |
| /// There is no way to disable fsverity once enabled outside of deleting the file. |
| FsVerity, |
| } |
| |
| impl FsVerityState { |
| /// Returns an appropriate error if state is not writable. |
| pub fn check_writable(&self) -> Result<(), Errno> { |
| match self { |
| FsVerityState::None => Ok(()), |
| FsVerityState::Building => error!(ETXTBSY), |
| FsVerityState::FsVerity => error!(EACCES), |
| } |
| } |
| } |
| |
| pub mod ioctl { |
| |
| use crate::{ |
| mm::{MemoryAccessor, MemoryAccessorExt}, |
| task::CurrentTask, |
| vfs::{ |
| fsverity::{ |
| fsverity_descriptor_from_enable_arg, fsverity_enable_arg, fsverity_measurement, |
| fsverity_read_metadata_arg, FsVerityDigestHeader, FsVerityState, HashAlgorithm, |
| MetadataType, |
| }, |
| FileObject, FileWriteGuardMode, |
| }, |
| }; |
| use num_traits::FromPrimitive; |
| use starnix_syscalls::{SyscallResult, SUCCESS}; |
| use starnix_uapi::{ |
| errno, error, |
| errors::Errno, |
| user_address::{UserAddress, UserRef}, |
| }; |
| use zerocopy::AsBytes; |
| |
| /// ioctl handler for FS_IOC_ENABLE_VERITY. |
| pub fn enable( |
| task: &CurrentTask, |
| arg: UserAddress, |
| file: &FileObject, |
| ) -> Result<SyscallResult, Errno> { |
| if file.can_write() { |
| return error!(ETXTBSY); |
| } |
| let block_size = file.name.entry.node.fs().statfs(task)?.f_bsize as u32; |
| // Nb: Lock order is important here. |
| let args: fsverity_enable_arg = task.read_object(arg.into())?; |
| let mut descriptor = fsverity_descriptor_from_enable_arg(task, block_size, &args)?; |
| descriptor.data_size = file.node().refresh_info(task)?.size as u64; |
| // The "Exec" writeguard mode means 'no writers'. |
| let _guard = file.node().create_write_guard(FileWriteGuardMode::Exec)?; |
| let mut fsverity = file.node().fsverity.lock(); |
| match *fsverity { |
| FsVerityState::Building => error!(EBUSY), |
| FsVerityState::FsVerity => error!(EEXIST), |
| FsVerityState::None => { |
| *fsverity = FsVerityState::Building; |
| file.node().ops().enable_fsverity(&descriptor)?; |
| *fsverity = FsVerityState::FsVerity; |
| Ok(SUCCESS) |
| } |
| } |
| } |
| |
| /// ioctl handler for FS_IOC_MEASURE_VERITY. |
| pub fn measure( |
| task: &CurrentTask, |
| arg: UserAddress, |
| file: &FileObject, |
| ) -> Result<SyscallResult, Errno> { |
| let header_ref = UserRef::<FsVerityDigestHeader>::new(arg); |
| let digest_addr = header_ref.next().addr(); |
| let header = task.read_object(header_ref)?; |
| match &*file.node().fsverity.lock() { |
| FsVerityState::FsVerity => { |
| let block_size = file.name.entry.node.fs().statfs(task)?.f_bsize as u32; |
| if !block_size.is_power_of_two() { |
| return error!(EINVAL); |
| } |
| let descriptor = |
| file.node().ops().get_fsverity_descriptor(block_size.ilog2() as u8)?; |
| let digest_algorithm = HashAlgorithm::from_u8(descriptor.hash_algorithm) |
| .ok_or_else(|| errno!(EINVAL))?; |
| if descriptor.hash_algorithm as u16 != header.digest_algorithm { |
| return error!(EINVAL); |
| } |
| let required_size = match digest_algorithm { |
| HashAlgorithm::SHA256 => 32, |
| HashAlgorithm::SHA512 => 64, |
| }; |
| if (header.digest_size as usize) < required_size { |
| return error!(EOVERFLOW); |
| } |
| task.write_memory(digest_addr, &fsverity_measurement(&descriptor)?)?; |
| Ok(SUCCESS) |
| } |
| _ => error!(ENODATA), |
| } |
| } |
| |
| /// ioctl handler for FS_IOC_READ_VERITY_METADATA. |
| pub fn read_metadata( |
| task: &CurrentTask, |
| arg: UserAddress, |
| file: &FileObject, |
| ) -> Result<SyscallResult, Errno> { |
| let arg: fsverity_read_metadata_arg = task.read_object(arg.into())?; |
| match &*file.node().fsverity.lock() { |
| FsVerityState::FsVerity => { |
| match MetadataType::from_u64(arg.metadata_type).ok_or_else(|| errno!(EINVAL))? { |
| MetadataType::MerkleTree => { |
| error!(EOPNOTSUPP) |
| } |
| MetadataType::Descriptor => { |
| // TODO(b/314182708): Remove hardcoding of blocksize |
| let descriptor = file.node().ops().get_fsverity_descriptor(12)?; |
| task.write_memory( |
| UserAddress::from(arg.buf_ptr).into(), |
| &descriptor.as_bytes() |
| [arg.offset as usize..(arg.offset + arg.length) as usize], |
| )?; |
| Ok(SUCCESS) |
| } |
| MetadataType::Signature => { |
| error!(EOPNOTSUPP) |
| } |
| } |
| } |
| _ => error!(ENODATA), |
| } |
| } |
| } |