blob: fcda0f4b0d6425b5111e38a2bce0d93984abeb83 [file] [log] [blame] [edit]
// 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::{
logging::not_implemented,
mm::MemoryAccessorExt,
syscalls::{
errno, error, fsverity_descriptor, fsverity_enable_arg, fsverity_read_metadata_arg,
CurrentTask, Errno, UserAddress, FS_VERITY_HASH_ALG_SHA256, FS_VERITY_HASH_ALG_SHA512,
},
},
mundane::hash::{Digest, Hasher, Sha256, Sha512},
num_derive::FromPrimitive,
num_traits::FromPrimitive,
zerocopy::{AsBytes, FromBytes, FromZeroes},
};
#[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(fxbug.dev/302620572) Under linux, signatures are supported up to 16128 bytes.
// We don't support these currently.
not_implemented!("fsverity_enable signature");
return error!(ENOTSUP);
}
let salt = current_task
.mm
.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, FromZeroes)]
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. Descriptor is attached.
///
/// 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 { descriptor: fsverity_descriptor },
}
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::{
fs::{
fsverity::{
fsverity_descriptor_from_enable_arg, fsverity_enable_arg, fsverity_measurement,
fsverity_read_metadata_arg, FsVerityDigestHeader, FsVerityState, HashAlgorithm,
MetadataType,
},
FileObject, FileWriteGuardMode,
},
log_warn,
mm::{MemoryAccessor, MemoryAccessorExt},
syscalls::{SyscallResult, SUCCESS},
task::CurrentTask,
types::{errno, error, Errno, UserAddress, UserRef},
};
use num_traits::FromPrimitive;
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.mm.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 entry = &file.name.entry;
let mut fsverity = entry.node.fsverity.lock();
match *fsverity {
FsVerityState::Building => error!(EBUSY),
FsVerityState::FsVerity { .. } => error!(EEXIST),
FsVerityState::None => {
// Notify the filesystem that we are going to enable verity on this node.
// Note that this cannot be cancelled, but it may fail. In all cases we
// require end_enable_verity() to be called.
file.node().ops().begin_enable_fsverity()?;
let node = file.node().clone();
*fsverity = FsVerityState::Building;
std::thread::spawn(move || {
// Explicitly move write guard into this thread.
let _guard = guard;
// TODO(fxbug.dev/302620512): Generate merkle data, write to vmo.
let merkle_data: [u8; 0] = [];
let merkle_tree_size = match node.ops().set_fsverity_merkle_data(&merkle_data) {
Ok(()) => merkle_data.len(),
Err(error) => {
log_warn!("set_fsverity_merkle_data failed: {:?}", error);
0
}
};
// TODO(fxbug.dev/302620512): Set descriptor.root_hash.
// This hash is based on the contents "foo"
descriptor.root_hash[..32].copy_from_slice(&[
0xdf, 0xfd, 0xd9, 0x7c, 0xfb, 0xf2, 0x88, 0xa7, 0x29, 0xf6, 0xaf, 0x66,
0xf1, 0x2a, 0xc8, 0x88, 0x4f, 0xd7, 0x8d, 0xf3, 0xf1, 0x87, 0x6d, 0xcc,
0xc5, 0x8b, 0x5a, 0xb2, 0x36, 0x83, 0x9b, 0x49,
]);
let mut fsverity_state = node.fsverity.lock();
match node.ops().end_enable_fsverity(&descriptor, merkle_tree_size) {
Ok(()) => {
*fsverity_state = FsVerityState::FsVerity { descriptor };
}
Err(error) => {
log_warn!("enable_fsverity failed: {:?}", error);
}
}
});
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.mm.read_object(header_ref)?;
match &*file.node().fsverity.lock() {
FsVerityState::FsVerity { descriptor } => {
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.mm.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.mm.read_object(arg.into())?;
match &*file.node().fsverity.lock() {
FsVerityState::FsVerity { descriptor } => {
match MetadataType::from_u64(arg.metadata_type).ok_or_else(|| errno!(EINVAL))? {
MetadataType::MerkleTree => {
// TODO(fxbug.dev/302620512): Add support for reading back merkle data.
error!(EOPNOTSUPP)
}
MetadataType::Descriptor => {
task.mm.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),
}
}
}