blob: f2f2ffc3e62d261b580cc8f8af5c4536d9d2b00c [file] [log] [blame]
// Copyright 2024 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::device::kobject::{Device, DeviceMetadata};
use crate::device::DeviceMode;
use crate::fs::sysfs::{BlockDeviceDirectory, BlockDeviceInfo};
use crate::mm::{MemoryAccessor, MemoryAccessorExt, ProtectionFlags};
use crate::task::CurrentTask;
use crate::vfs::buffers::VecOutputBuffer;
use crate::vfs::{
default_ioctl, fileops_impl_dataless, fileops_impl_seekable, fileops_impl_seekless, FileHandle,
FileObject, FileOps, FsNode, FsString, OutputBuffer,
};
use bitflags::bitflags;
use fsverity_merkle::{FsVerityHasher, FsVerityHasherOptions};
use fuchsia_zircon::Vmo;
use linux_uapi::DM_UUID_LEN;
use mundane::hash::{Digest, Hasher, Sha256, Sha512};
use starnix_logging::{log_trace, track_stub};
use starnix_sync::{DeviceOpen, FileOpsCore, LockBefore, Locked, Mutex, Unlocked};
use starnix_syscalls::{SyscallArg, SyscallResult, SUCCESS};
use starnix_uapi::device_type::{DeviceType, DEVICE_MAPPER_MAJOR, LOOP_MAJOR};
use starnix_uapi::errors::Errno;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::user_address::{UserCString, UserRef};
use starnix_uapi::{
errno, error, uapi, DM_ACTIVE_PRESENT_FLAG, DM_BUFFER_FULL_FLAG, DM_DEV_ARM_POLL,
DM_DEV_CREATE, DM_DEV_REMOVE, DM_DEV_RENAME, DM_DEV_SET_GEOMETRY, DM_DEV_STATUS,
DM_DEV_SUSPEND, DM_DEV_WAIT, DM_GET_TARGET_VERSION, DM_IMA_MEASUREMENT_FLAG,
DM_INACTIVE_PRESENT_FLAG, DM_LIST_DEVICES, DM_LIST_VERSIONS, DM_MAX_TYPE_NAME, DM_NAME_LEN,
DM_NAME_LIST_FLAG_DOESNT_HAVE_UUID, DM_NAME_LIST_FLAG_HAS_UUID, DM_READONLY_FLAG,
DM_REMOVE_ALL, DM_STATUS_TABLE_FLAG, DM_SUSPEND_FLAG, DM_TABLE_CLEAR, DM_TABLE_DEPS,
DM_TABLE_LOAD, DM_TABLE_STATUS, DM_TARGET_MSG, DM_UEVENT_GENERATED_FLAG, DM_VERSION,
DM_VERSION_MAJOR, DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL,
};
use std::collections::btree_map::{BTreeMap, Entry};
use std::ops::Sub;
use std::sync::Arc;
const SECTOR_SIZE: u64 = 512;
// The value of the data_size field in the output dm_ioctl struct when no data is returned as per
// Linux 6.6.15.
const DATA_SIZE: u32 = 305;
// Observed version values for the dm-verity target as per Linux 6.6.15.
const DM_VERITY_VERSION_MAJOR: u32 = 1;
const DM_VERITY_VERSION_MINOR: u32 = 9;
const DM_VERITY_VERSION_PATCHLEVEL: u32 = 0;
bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct DeviceMapperFlags: u32 {
const ACTIVE_PRESENT = DM_ACTIVE_PRESENT_FLAG;
const INACTIVE_PRESENT = DM_INACTIVE_PRESENT_FLAG;
const READONLY = DM_READONLY_FLAG;
const SUSPEND = DM_SUSPEND_FLAG;
const UEVENT_GENERATED = DM_UEVENT_GENERATED_FLAG;
const BUFFER_FULL = DM_BUFFER_FULL_FLAG;
const STATUS_TABLE = DM_STATUS_TABLE_FLAG;
const IMA_MEASUREMENT = DM_IMA_MEASUREMENT_FLAG;
const DM_NAME_LIST_HAS_UUID = DM_NAME_LIST_FLAG_HAS_UUID;
const DM_NAME_LIST_NO_UUID = DM_NAME_LIST_FLAG_DOESNT_HAVE_UUID;
}
}
pub fn device_mapper_init(current_task: &CurrentTask) {
let kernel = current_task.kernel();
kernel
.device_registry
.register_major(DEVICE_MAPPER_MAJOR, get_or_create_dm_device, DeviceMode::Block)
.expect("dm device register failed.");
}
#[derive(Debug, Default)]
pub struct DeviceMapperRegistry {
devices: Mutex<BTreeMap<u32, Arc<DmDevice>>>,
}
impl DeviceMapperRegistry {
/// Looks up a dm-device based on strictly one of the following: name, uuid, or dev number.
fn get(&self, io: &uapi::dm_ioctl) -> Result<Arc<DmDevice>, Errno> {
if io.name != [0; DM_NAME_LEN as usize] {
if io.uuid != [0; DM_UUID_LEN as usize] || io.dev > 0 {
return error!(EINVAL);
} else {
return self.get_by_name(io.name);
}
}
if io.uuid != [0; DM_UUID_LEN as usize] {
if io.dev > 0 {
return error!(EINVAL);
} else {
return self.get_by_uuid(io.uuid);
}
}
let dev_minor = ((io.dev >> 12 & 0xffffff00) | (io.dev & 0xff)) as u32;
match self.devices.lock().entry(dev_minor) {
Entry::Occupied(e) => Ok(e.get().clone()),
Entry::Vacant(_) => return error!(ENODEV),
}
}
fn get_by_name(
&self,
name: [std::ffi::c_char; DM_NAME_LEN as usize],
) -> Result<Arc<DmDevice>, Errno> {
let devices = self.devices.lock();
let entry = devices.iter().find(|(_, device)| {
let state = device.state.lock();
state.name == name
});
if let Some((_, device)) = entry {
Ok(device.clone())
} else {
Err(errno!(ENODEV))
}
}
fn get_by_uuid(
&self,
uuid: [std::ffi::c_char; DM_UUID_LEN as usize],
) -> Result<Arc<DmDevice>, Errno> {
let devices = self.devices.lock();
let entry = devices.iter().find(|(_, device)| {
let state = device.state.lock();
state.uuid == uuid
});
if let Some((_, device)) = entry {
Ok(device.clone())
} else {
Err(errno!(ENODEV))
}
}
fn get_or_create_by_minor<L>(
&self,
locked: &mut Locked<'_, L>,
current_task: &CurrentTask,
minor: u32,
) -> Arc<DmDevice>
where
L: LockBefore<FileOpsCore>,
{
self.devices
.lock()
.entry(minor)
.or_insert_with(|| DmDevice::new(locked, current_task, minor))
.clone()
}
/// Finds a free minor number in the DeviceMapperRegistry. Returns that minor number along with
/// a new DmDevice associated with that minor number.
fn find<L>(
&self,
locked: &mut Locked<'_, L>,
current_task: &CurrentTask,
) -> Result<Arc<DmDevice>, Errno>
where
L: LockBefore<FileOpsCore>,
{
let mut devices = self.devices.lock();
for minor in 0..u32::MAX {
match devices.entry(minor) {
Entry::Vacant(e) => {
let device = DmDevice::new(locked, current_task, minor);
e.insert(device.clone());
return Ok(device);
}
Entry::Occupied(_) => {}
}
}
Err(errno!(ENODEV))
}
/// Removes `device` from both the Device and DeviceMapper registries.
fn remove<L>(
&self,
locked: &mut Locked<'_, L>,
current_task: &CurrentTask,
devices: &mut BTreeMap<u32, Arc<DmDevice>>,
minor: u32,
k_device: &Option<Device>,
) -> Result<(), Errno>
where
L: LockBefore<FileOpsCore>,
{
match devices.entry(minor) {
Entry::Vacant(_) => Err(errno!(ENODEV)),
Entry::Occupied(e) => {
e.remove();
let kernel = current_task.kernel();
let registry = &kernel.device_registry;
if let Some(dev) = &k_device {
registry.remove_device(locked, current_task, dev.clone());
} else {
return Err(errno!(EINVAL));
}
Ok(())
}
}
}
}
#[derive(Debug, Default)]
pub struct DmDevice {
number: DeviceType,
state: Mutex<DmDeviceState>,
}
impl DmDevice {
fn new<L>(locked: &mut Locked<'_, L>, current_task: &CurrentTask, minor: u32) -> Arc<Self>
where
L: LockBefore<FileOpsCore>,
{
let kernel = current_task.kernel();
let registry = &kernel.device_registry;
let dm_device_name = FsString::from(format!("dm-{minor}"));
let virtual_block_class =
registry.get_or_create_class("block".into(), registry.virtual_bus());
let device = Arc::new(Self {
number: DeviceType::new(DEVICE_MAPPER_MAJOR, minor),
..Default::default()
});
let device_weak = Arc::<DmDevice>::downgrade(&device);
let k_device = registry.add_device(
locked,
current_task,
dm_device_name.as_ref(),
DeviceMetadata::new(
dm_device_name.clone(),
DeviceType::new(DEVICE_MAPPER_MAJOR, minor),
DeviceMode::Block,
),
virtual_block_class,
move |dev| BlockDeviceDirectory::new(dev, device_weak.clone()),
);
{
let mut state = device.state.lock();
state.set_k_device(k_device);
}
device
}
fn create_file_ops(self: &Arc<Self>) -> Box<dyn FileOps> {
let mut state = self.state.lock();
state.open_count += 1;
Box::new(DmDeviceFile { device: self.clone() })
}
}
impl BlockDeviceInfo for DmDevice {
fn size(&self) -> Result<usize, Errno> {
let state = self.state.lock();
if !state.suspended {
if let Some(active_table) = &state.active_table {
Ok(active_table.size())
} else {
Ok(0)
}
} else {
Ok(0)
}
}
}
struct DmDeviceFile {
device: Arc<DmDevice>,
}
fn verify_read(
buffer: &VecOutputBuffer,
args: &mut VerityTargetParams,
offset: usize,
) -> Result<(), Errno> {
let (hasher, digest_size) = match args.base_args.hash_algorithm.as_str() {
"sha256" => {
let hasher = FsVerityHasher::Sha256(FsVerityHasherOptions::new_dmverity(
hex::decode(args.base_args.salt.clone()).unwrap(),
args.base_args.hash_block_size as usize,
));
(hasher, <Sha256 as Hasher>::Digest::DIGEST_LEN)
}
"sha512" => {
let hasher = FsVerityHasher::Sha512(FsVerityHasherOptions::new_dmverity(
hex::decode(args.base_args.salt.clone()).unwrap(),
args.base_args.hash_block_size as usize,
));
(hasher, <Sha512 as Hasher>::Digest::DIGEST_LEN)
}
_ => return Err(errno!(ENOTSUP)),
};
let leaf_nodes: Vec<&[u8]> = args.hash_device.chunks(digest_size).collect();
let mut leaf_nodes_offset = offset;
for b in buffer.data().chunks(args.base_args.hash_block_size as usize) {
if hasher.hash_block(b)
!= leaf_nodes[leaf_nodes_offset / args.base_args.hash_block_size as usize]
{
args.corrupted = true;
return Err(errno!(EINVAL));
}
leaf_nodes_offset += args.base_args.hash_block_size as usize;
}
Ok(())
}
impl FileOps for DmDeviceFile {
fileops_impl_seekable!();
fn write(
&self,
_locked: &mut starnix_sync::Locked<'_, starnix_sync::WriteOps>,
_file: &crate::vfs::FileObject,
_current_task: &crate::task::CurrentTask,
_offset: usize,
_data: &mut dyn crate::vfs::buffers::InputBuffer,
) -> Result<usize, starnix_uapi::errors::Errno> {
starnix_uapi::error!(ENOTSUP)
}
fn read(
&self,
locked: &mut Locked<'_, FileOpsCore>,
_file: &FileObject,
current_task: &CurrentTask,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
let device = &self.device;
let mut state = device.state.lock();
if state.suspended {
track_stub!(TODO("https://fxbug.dev/338241090"), "Defer io for suspended devices.");
return Ok(0);
}
if let Some(active_table) = &mut state.active_table {
let mut bytes_read = 0;
let to_read = std::cmp::min(
data.available(),
active_table.size().checked_sub(offset).ok_or_else(|| errno!(EINVAL))?,
);
let mut buffer = VecOutputBuffer::new(to_read);
if active_table.targets.len() > 1 {
track_stub!(
TODO("https://fxbug.dev/339701082"),
"Support reads for multiple targets."
);
return Err(errno!(ENOTSUP));
}
let target = &mut active_table.targets[0];
let start = (target.sector_start * SECTOR_SIZE) as usize;
debug_assert!(start == 0);
let size = (target.length * SECTOR_SIZE) as usize;
if offset >= start && offset < start + size {
match &mut target.target_type {
TargetType::Verity(args) => {
if to_read % args.base_args.hash_block_size as usize != 0 {
return error!(EINVAL);
}
let read = args.block_device.read_at(
locked,
current_task,
offset - start,
&mut buffer,
)?;
bytes_read += read;
verify_read(&buffer, args, offset - start)?;
}
}
}
let read = data.write_all(buffer.data())?;
debug_assert!(read == bytes_read);
Ok(bytes_read)
} else {
Ok(0)
}
}
fn get_vmo(
&self,
_file: &FileObject,
current_task: &CurrentTask,
length: Option<usize>,
prot: ProtectionFlags,
) -> Result<Arc<Vmo>, Errno> {
let device = &self.device;
let state = device.state.lock();
if state.suspended {
track_stub!(TODO("https://fxbug.dev/338241090"), "Defer io for suspended devices.");
return Err(errno!(EINVAL));
}
if let Some(active_table) = &state.active_table {
if active_table.targets.len() > 1 {
track_stub!(
TODO("https://fxbug.dev/339701082"),
"Support pager-backed vmos for multiple targets."
);
return Err(errno!(ENOTSUP));
}
match &active_table.targets[0].target_type {
TargetType::Verity(args) => args.block_device.get_vmo(current_task, length, prot),
}
} else {
Err(errno!(EINVAL))
}
}
fn close(&self, _file: &FileObject, _current_task: &CurrentTask) {
let mut state = self.device.state.lock();
state.open_count -= 1;
}
}
#[derive(Debug)]
struct DmDeviceState {
version: [u32; 3],
target_count: u32,
open_count: u64,
name: [std::ffi::c_char; 128],
uuid: [std::ffi::c_char; 129],
active_table: Option<DmDeviceTable>,
inactive_table: Option<DmDeviceTable>,
flags: DeviceMapperFlags,
suspended: bool,
k_device: Option<Device>,
}
impl Default for DmDeviceState {
fn default() -> Self {
DmDeviceState {
version: [0; 3],
name: [0 as std::ffi::c_char; 128],
uuid: [0 as std::ffi::c_char; 129],
target_count: 0,
open_count: 0,
active_table: None,
inactive_table: None,
flags: DeviceMapperFlags::empty(),
suspended: false,
k_device: None,
}
}
}
impl DmDeviceState {
fn set_version(&mut self) {
self.version = [DM_VERSION_MAJOR, DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL];
}
fn set_name(&mut self, name: [std::ffi::c_char; 128]) {
self.name = name;
}
fn set_uuid(&mut self, uuid: [std::ffi::c_char; 129]) {
self.uuid = uuid;
}
fn set_inactive_table(&mut self, inactive_table: DmDeviceTable) {
self.inactive_table = Some(inactive_table);
}
fn resume(&mut self) {
if let Some(inactive_table) = self.inactive_table.take() {
self.active_table = Some(inactive_table);
}
self.suspended = false;
}
fn remove(&mut self) {
self.active_table.take();
self.suspended = false;
}
fn set_target_count(&mut self, target_count: u32) {
self.target_count = target_count;
}
fn get_target_count(&self) -> u32 {
if let Some(_) = self.active_table {
self.target_count
} else {
0
}
}
fn add_flags(&mut self, flags: DeviceMapperFlags) {
self.flags |= flags;
}
fn get_flags(&self) -> DeviceMapperFlags {
let mut flags = DeviceMapperFlags::empty();
if let Some(active_table) = &self.active_table {
flags |= DeviceMapperFlags::ACTIVE_PRESENT;
if active_table.readonly {
flags |= DeviceMapperFlags::READONLY;
}
}
if let Some(_) = &self.inactive_table {
flags |= DeviceMapperFlags::INACTIVE_PRESENT;
}
if self.suspended {
flags |= DeviceMapperFlags::SUSPEND;
}
flags
}
fn suspend(&mut self) {
self.suspended = true;
}
fn set_k_device(&mut self, k_device: Device) {
self.k_device = Some(k_device);
}
}
#[derive(Debug, Clone)]
struct DmDeviceTarget {
sector_start: u64,
length: u64,
status: i32,
name: [std::ffi::c_char; DM_MAX_TYPE_NAME as usize],
target_type: TargetType,
}
#[derive(Debug, Default, Clone)]
pub struct DmDeviceTable {
targets: Vec<DmDeviceTarget>,
readonly: bool,
}
impl DmDeviceTable {
fn size(&self) -> usize {
let mut size = 0;
for target in &self.targets {
size += (SECTOR_SIZE * target.length) as usize;
}
size
}
}
struct DeviceMapper {
registry: Arc<DeviceMapperRegistry>,
}
impl DeviceMapper {
pub fn new(registry: Arc<DeviceMapperRegistry>) -> Self {
Self { registry: registry }
}
}
#[derive(Debug, Clone)]
enum TargetType {
Verity(VerityTargetParams),
}
#[derive(Debug, Clone)]
struct VerityTargetParams {
base_args: VerityTargetBaseArgs,
optional_args: VerityTargetOptionalArgs,
block_device: FileHandle,
hash_device: Vec<u8>,
corrupted: bool,
}
impl VerityTargetParams {
fn parameter_string(&self) -> String {
let base_string = format!(
"{} {} {} {:?} {:?} {:?} {:?} {} {} {}",
self.base_args.version,
self.base_args.block_device_path,
self.base_args.hash_device_path,
self.base_args.data_block_size,
self.base_args.hash_block_size,
self.base_args.num_data_blocks,
self.base_args.hash_start_block,
self.base_args.hash_algorithm,
self.base_args.root_digest,
self.base_args.salt
);
let mut optional_arg_count = 0;
let mut optional_string = String::new();
if self.optional_args.ignore_zero_blocks {
optional_arg_count += 1;
optional_string.push_str(" ignore_zero_blocks");
}
if self.optional_args.restart_on_corruption {
optional_arg_count += 1;
optional_string.push_str(" restart on corruption");
}
if optional_arg_count > 0 {
return format!("{base_string} {optional_arg_count}{optional_string}");
} else {
base_string
}
}
}
#[derive(Debug, Default, Clone)]
struct VerityTargetOptionalArgs {
ignore_zero_blocks: bool,
restart_on_corruption: bool,
}
#[derive(Debug, Clone)]
struct VerityTargetBaseArgs {
version: String,
block_device_path: String,
hash_device_path: String,
data_block_size: u64,
hash_block_size: u64,
num_data_blocks: u64,
hash_start_block: u64,
hash_algorithm: String,
root_digest: String,
salt: String,
}
// Returns the FileHandle and minor number of the device found at `device path` formatted as
// either /dev/loop# of MAJOR:MINOR
fn open_device(
locked: &mut Locked<'_, Unlocked>,
current_task: &CurrentTask,
device_path: String,
) -> Result<(u64, FileHandle), Errno> {
let device_path_vec: Vec<&str> = device_path.split(":").collect();
if device_path_vec.len() == 1 {
let dev = current_task.open_file(locked, device_path.as_str().into(), OpenFlags::RDONLY)?;
let loop_device_vec: Vec<&str> = device_path.split("loop").collect();
let minor = loop_device_vec[1].parse::<u64>().unwrap();
Ok((minor, dev))
} else {
let minor = device_path_vec[1].parse::<u64>().unwrap();
let dev = current_task.open_file(
locked,
format!("/dev/loop{minor}").as_str().into(),
OpenFlags::RDONLY,
)?;
Ok((minor, dev))
}
}
fn size_of_merkle_tree_preceding_leaf_nodes(
leaf_nodes_size: u64,
hash_size: u64,
base_args: &VerityTargetBaseArgs,
) -> u64 {
let mut total_size = 0;
let mut data_size = leaf_nodes_size;
while data_size > base_args.hash_block_size {
let num_hashes = data_size.div_ceil(base_args.hash_block_size);
let hashes_per_block = base_args.hash_block_size.div_ceil(hash_size);
let hash_blocks = num_hashes.div_ceil(hashes_per_block);
data_size = hash_blocks * base_args.hash_block_size;
total_size += data_size;
}
total_size
}
// Parse the parameter string into a TargetType.
fn parse_parameter_string(
locked: &mut Locked<'_, Unlocked>,
current_task: &CurrentTask,
target_type: &str,
parameter_str: String,
) -> Result<TargetType, Errno> {
match target_type {
"verity" => {
let v: Vec<&str> = parameter_str.split(" ").collect();
let mut base_args = VerityTargetBaseArgs {
version: String::from(v[0]),
block_device_path: String::from(v[1]),
hash_device_path: String::from(v[2]),
data_block_size: v[3].parse::<u64>().unwrap(),
hash_block_size: v[4].parse::<u64>().unwrap(),
num_data_blocks: v[5].parse::<u64>().unwrap(),
hash_start_block: v[6].parse::<u64>().unwrap(),
hash_algorithm: String::from(v[7]),
root_digest: String::from(v[8]),
salt: String::from(v[9]),
};
let mut optional_args = VerityTargetOptionalArgs { ..Default::default() };
if v.len() > 10 {
let num_optional_args = v[10].parse::<u64>().unwrap();
if num_optional_args > 2 {
return error!(ENOTSUP);
}
for i in 0..num_optional_args {
if String::from(v[11 + i as usize]) == "ignore_zero_blocks" {
optional_args.ignore_zero_blocks = true;
} else if String::from(v[11 + i as usize]) == "restart_on_corruption" {
track_stub!(
TODO("https://fxbug.dev/338243823"),
"Support restart on corruption."
);
optional_args.restart_on_corruption = true;
} else {
return error!(ENOTSUP);
}
}
}
let (minor, block_device) =
open_device(locked, current_task, base_args.block_device_path)?;
base_args.block_device_path = format!("{LOOP_MAJOR}:{minor}");
let (minor, hash_device) = if base_args.hash_device_path == base_args.block_device_path
{
(minor, block_device.clone())
} else {
open_device(locked, current_task, base_args.hash_device_path)?
};
base_args.hash_device_path = format!("{LOOP_MAJOR}:{minor}");
let hash_size: u64 = match base_args.hash_algorithm.as_str() {
"sha256" => <Sha256 as Hasher>::Digest::DIGEST_LEN as u64,
"sha512" => <Sha512 as Hasher>::Digest::DIGEST_LEN as u64,
_ => return Err(errno!(ENOTSUP)),
};
debug_assert!(base_args.hash_block_size > 0);
let data_size = base_args.num_data_blocks * base_args.data_block_size;
let num_hashes = data_size.div_ceil(base_args.hash_block_size);
let hashes_per_block = base_args.hash_block_size.div_ceil(hash_size);
let hash_blocks = num_hashes.div_ceil(hashes_per_block);
let leaf_nodes_size = hash_blocks * base_args.hash_block_size;
let mut buffer = VecOutputBuffer::new(leaf_nodes_size as usize);
let offset = base_args.hash_start_block * base_args.hash_block_size
+ size_of_merkle_tree_preceding_leaf_nodes(leaf_nodes_size, hash_size, &base_args);
let bytes_read =
hash_device.read_at(locked, current_task, offset as usize, &mut buffer)?;
debug_assert!(bytes_read == leaf_nodes_size as usize);
Ok(TargetType::Verity(VerityTargetParams {
base_args: base_args,
optional_args: optional_args,
block_device,
hash_device: buffer.into(),
corrupted: false,
}))
}
_ => error!(ENOTSUP),
}
}
fn check_version_compatibility(major: u32, minor: u32) -> Result<(), Errno> {
// The version field of the input dm-ioctl struct should represent the version of the interface
// that the client was compiled with. The major number must match the kernel's, the minor
// number is backwards compatible, and the patchlevel is forwards and backwards compatible.
if major != DM_VERSION_MAJOR || minor > DM_VERSION_MINOR {
return error!(EINVAL);
}
return Ok(());
}
impl FileOps for DeviceMapper {
fileops_impl_seekless!();
fileops_impl_dataless!();
fn ioctl(
&self,
locked: &mut Locked<'_, Unlocked>,
file: &FileObject,
current_task: &CurrentTask,
request: u32,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let user_info = UserRef::<uapi::dm_ioctl>::from(arg);
let info_addr: starnix_uapi::user_address::UserAddress = user_info.addr();
let info = current_task.read_object(user_info)?;
let flags = DeviceMapperFlags::from_bits_truncate(info.flags);
match request {
DM_DEV_CREATE => {
// Expect name and version to be set. This should not fail if uuid is not set.
if info.name == [0; DM_NAME_LEN as usize] || info.version == [0; 3] {
return error!(EINVAL);
}
let dm_device = self.registry.find(locked, current_task)?;
let mut state = dm_device.state.lock();
check_version_compatibility(info.version[0], info.version[1])?;
state.set_version();
state.set_name(info.name);
state.set_uuid(info.uuid);
let i = uapi::dm_ioctl {
name: state.name,
version: state.version,
uuid: state.uuid,
dev: dm_device.number.bits(),
data_size: DATA_SIZE,
data_start: 0,
..Default::default()
};
log_trace!("DM_DEV_CREATE returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
DM_TABLE_LOAD => {
let mut start_addr = info_addr + info.data_start;
let mut num_targets = 0;
let dm_device = self.registry.get(&info)?;
let mut state = dm_device.state.lock();
let mut table = DmDeviceTable { ..Default::default() };
if flags.contains(DeviceMapperFlags::READONLY) {
table.readonly = true;
state.add_flags(DeviceMapperFlags::READONLY);
}
if info.target_count > 1 {
track_stub!(TODO("https://fxbug.dev/339701082"), "Support multiple targets.");
return Err(errno!(ENOTSUP));
}
track_stub!(
TODO("https://fxbug.dev/338245544"),
"Make sure targets are contiguous and non-overlapping"
);
while num_targets < info.target_count {
let target_ref = UserRef::<uapi::dm_target_spec>::new(start_addr);
let target = current_task.read_object(target_ref)?;
let parameter_cstring = UserCString::new(target_ref.next().addr());
let parameters = current_task.read_c_string_to_vec(
parameter_cstring,
target.next as usize - std::mem::size_of::<uapi::dm_target_spec>(),
)?;
let target_type_addr = start_addr
.checked_add(
2 * std::mem::size_of::<u64>()
+ std::mem::size_of::<u32>()
+ std::mem::size_of::<i32>(),
)
.ok_or_else(|| errno!(EINVAL))?;
let target_type_cstring = UserCString::new(target_type_addr);
let target_type = current_task
.read_c_string_to_vec(target_type_cstring, DM_MAX_TYPE_NAME as usize)?;
let device_target = DmDeviceTarget {
sector_start: target.sector_start,
length: target.length,
status: target.status,
name: target.target_type,
target_type: parse_parameter_string(
locked,
current_task,
&target_type.to_string(),
parameters.to_string(),
)?,
};
table.targets.push(device_target);
num_targets += 1;
debug_assert!(target.next % 8 == 0);
start_addr = start_addr
.checked_add(target.next as usize)
.ok_or_else(|| errno!(EINVAL))?;
}
// Update the metadata of the dm device
state.set_inactive_table(table);
state.set_target_count(num_targets);
let i = uapi::dm_ioctl {
name: state.name,
version: state.version,
uuid: state.uuid,
dev: dm_device.number.bits(),
data_size: DATA_SIZE,
data_start: 0,
flags: state.get_flags().bits(),
target_count: state.get_target_count(),
..Default::default()
};
log_trace!("DM_TABLE_LOAD returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
DM_DEV_SUSPEND => {
let dm_device = self.registry.get(&info)?;
let mut state = dm_device.state.lock();
if flags.contains(DeviceMapperFlags::SUSPEND) {
state.suspend();
} else {
state.resume();
}
let mut out_flags = state.get_flags();
if !flags.contains(DeviceMapperFlags::SUSPEND) {
out_flags |= DeviceMapperFlags::UEVENT_GENERATED;
}
let i = uapi::dm_ioctl {
name: state.name,
version: state.version,
uuid: state.uuid,
dev: dm_device.number.bits(),
data_size: DATA_SIZE,
data_start: 0,
flags: out_flags.bits(),
target_count: state.get_target_count(),
..Default::default()
};
log_trace!("DM_DEV_SUSPEND returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
DM_DEV_STATUS => {
let dm_device = self.registry.get(&info)?;
let state = dm_device.state.lock();
let i = uapi::dm_ioctl {
name: state.name,
version: state.version,
uuid: state.uuid,
dev: dm_device.number.bits(),
data_size: DATA_SIZE,
data_start: 0,
flags: state.get_flags().bits(),
target_count: state.get_target_count(),
..Default::default()
};
log_trace!("DM_DEV_STATUS returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
DM_DEV_REMOVE => {
let dm_device = self.registry.get(&info)?;
let mut devices = self.registry.devices.lock();
let mut state = dm_device.state.lock();
if state.open_count > 0 {
return error!(ENOTSUP);
}
self.registry.remove(
locked,
current_task,
&mut devices,
dm_device.number.minor(),
&state.k_device,
)?;
state.remove();
let i = uapi::dm_ioctl {
name: state.name,
version: state.version,
uuid: state.uuid,
dev: dm_device.number.bits(),
data_size: DATA_SIZE,
data_start: 0,
flags: (state.get_flags() | DeviceMapperFlags::UEVENT_GENERATED).bits(),
target_count: state.get_target_count(),
..Default::default()
};
log_trace!("DM_DEV_REMOVE returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
DM_LIST_DEVICES => {
if flags.contains(DeviceMapperFlags::DM_NAME_LIST_HAS_UUID)
|| flags.contains(DeviceMapperFlags::DM_NAME_LIST_NO_UUID)
{
return error!(ENOTSUP);
}
let mut name_list_addr = user_info.next().addr();
let mut total_size = std::mem::size_of::<uapi::dm_ioctl>() as u32;
for (_, device) in self.registry.devices.lock().iter() {
let state = device.state.lock();
let dm_name_list = UserRef::<uapi::dm_name_list>::new(name_list_addr);
let name = state.name.iter().map(|v| *v as u8).collect::<Vec<u8>>();
let name_c_str = std::ffi::CStr::from_bytes_until_nul(name.as_slice())
.map_err(|_| errno!(EINVAL))?;
let mut name_vec_with_nul = name_c_str.to_bytes_with_nul().to_vec();
let mut size = (std::mem::size_of::<uapi::dm_name_list>() - 4
+ name_vec_with_nul.len()) as u32;
let mut padding = 0;
if size % 8 != 0 {
padding = 8 - (size % 8);
size += padding;
};
// For the event_nr and flags.
size += 8;
let name_list = uapi::dm_name_list {
dev: device.number.bits(),
next: size,
..Default::default()
};
if total_size + size > info.data_size {
let i = uapi::dm_ioctl {
data_size: DATA_SIZE,
data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
flags: DeviceMapperFlags::BUFFER_FULL.bits(),
..Default::default()
};
log_trace!("DM_LIST_DEVICES returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
return Ok(SUCCESS);
}
total_size += size;
let mut name_addr = dm_name_list.next().addr();
name_addr = name_addr.sub(4 as usize);
current_task.write_object(dm_name_list, &name_list)?;
name_vec_with_nul.extend(vec![0; padding as usize + 8]);
current_task.write_memory(name_addr, name_vec_with_nul.as_slice())?;
name_list_addr =
name_list_addr.checked_add(size as usize).ok_or_else(|| errno!(EINVAL))?;
}
let i = uapi::dm_ioctl {
data_size: total_size,
data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
..Default::default()
};
log_trace!("DM_LIST_DEVICE returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
DM_LIST_VERSIONS => {
let version_list_addr = user_info.next().addr();
let dm_versions_list = UserRef::<uapi::dm_target_versions>::new(version_list_addr);
let name_c_str =
std::ffi::CString::new(String::from("verity")).map_err(|_| errno!(EINVAL))?;
let mut name_vec_with_nul = name_c_str.as_bytes_with_nul().to_vec();
let mut size = (std::mem::size_of::<uapi::dm_target_versions>()
+ name_vec_with_nul.len()) as u32;
let mut padding = 0;
if size % 8 != 0 {
padding = 8 - (size % 8);
size += padding;
};
name_vec_with_nul.extend(vec![0; padding as usize]);
if std::mem::size_of::<uapi::dm_ioctl>() as u32 + size > info.data_size {
let i = uapi::dm_ioctl {
data_size: DATA_SIZE,
data_start: 0,
flags: DeviceMapperFlags::BUFFER_FULL.bits(),
..Default::default()
};
log_trace!("DM_LIST_VERSIONS returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
return Ok(SUCCESS);
}
let target_versions = uapi::dm_target_versions {
next: size,
version: [
DM_VERITY_VERSION_MAJOR,
DM_VERITY_VERSION_MINOR,
DM_VERITY_VERSION_PATCHLEVEL,
],
..Default::default()
};
let name_addr = dm_versions_list.next().addr();
current_task.write_object(dm_versions_list, &target_versions)?;
current_task.write_memory(name_addr, name_vec_with_nul.as_slice())?;
let i = uapi::dm_ioctl {
data_size: std::mem::size_of::<uapi::dm_ioctl>() as u32 + size,
data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
..Default::default()
};
log_trace!("DM_LIST_VERSIONS returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
DM_TABLE_STATUS => {
let dm_device = self.registry.get(&info)?;
let state = dm_device.state.lock();
let mut total_data_size = 0;
let mut data_padding = 0;
let mut target_spec_addr = user_info.next().addr();
let mut out_flags = DeviceMapperFlags::empty();
let space_for_data = info.data_size as usize
- std::cmp::min(info.data_size as usize, std::mem::size_of::<uapi::dm_ioctl>());
if let Some(active_table) = &state.active_table {
for target in &active_table.targets {
let target_spec_info =
UserRef::<uapi::dm_target_spec>::new(target_spec_addr);
let mut data_size = std::mem::size_of::<uapi::dm_target_spec>();
if total_data_size + data_size - data_padding >= space_for_data {
out_flags |= DeviceMapperFlags::BUFFER_FULL;
break;
}
let data_addr = target_spec_info.next().addr();
if flags.contains(DeviceMapperFlags::STATUS_TABLE) {
match &target.target_type {
TargetType::Verity(args) => {
let param_str = std::ffi::CString::new(args.parameter_string())
.map_err(|_| errno!(EINVAL))?;
let mut args_bytes = param_str.into_bytes_with_nul();
if args_bytes.len() % 8 != 0 {
data_padding = 8 - args_bytes.len() % 8;
args_bytes.extend(vec![0 as u8; data_padding]);
}
data_size += args_bytes.len();
if total_data_size + data_size - data_padding >= space_for_data
{
out_flags |= DeviceMapperFlags::BUFFER_FULL;
break;
}
current_task.write_memory(data_addr, args_bytes.as_slice())?;
}
}
} else if flags.contains(DeviceMapperFlags::IMA_MEASUREMENT) {
// Linux 6.6.15 does not currently support IMA for dm-verity.
return Err(errno!(ENOTSUP));
} else {
match &target.target_type {
TargetType::Verity(args) => {
let status = if args.corrupted { "C" } else { "V" };
let status_c_str = std::ffi::CString::new(String::from(status))
.map_err(|_| errno!(EINVAL))?;
let mut status_bytes = status_c_str.into_bytes_with_nul();
if status_bytes.len() % 8 != 0 {
data_padding = 8 - status_bytes.len() % 8;
status_bytes.extend(vec![0 as u8; data_padding]);
}
data_size += status_bytes.len();
if total_data_size + data_size - data_padding >= space_for_data
{
out_flags |= DeviceMapperFlags::BUFFER_FULL;
break;
}
current_task
.write_memory(data_addr, status_bytes.as_slice())?;
}
}
}
total_data_size += data_size;
let target_spec = uapi::dm_target_spec {
sector_start: target.sector_start,
length: target.length,
status: target.status,
target_type: target.name,
next: total_data_size as u32,
};
current_task.write_object(target_spec_info, &target_spec)?;
target_spec_addr = target_spec_addr
.checked_add(data_size)
.ok_or_else(|| errno!(EINVAL))?;
}
} else {
let i = uapi::dm_ioctl {
name: state.name,
version: state.version,
uuid: state.uuid,
dev: dm_device.number.bits(),
data_size: DATA_SIZE,
data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
flags: state.get_flags().bits(),
..Default::default()
};
log_trace!("DM_TABLE_STATUS returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
return Ok(SUCCESS);
}
// Linux removes the size of the data padding when calculating the data size field
// returned.
let total_size = if out_flags.contains(DeviceMapperFlags::BUFFER_FULL) {
DATA_SIZE
} else {
(total_data_size + std::mem::size_of::<uapi::dm_ioctl>() - data_padding) as u32
};
out_flags |= state.get_flags();
let i = uapi::dm_ioctl {
name: state.name,
version: state.version,
uuid: state.uuid,
dev: dm_device.number.bits(),
data_size: total_size,
data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
flags: out_flags.bits(),
..Default::default()
};
log_trace!("DM_TABLE_STATUS returned dm_ioctl: {:?}", i);
current_task.write_object(user_info, &i)?;
Ok(SUCCESS)
}
// These dm ioctls are not used by Android
DM_VERSION
| DM_DEV_RENAME
| DM_DEV_WAIT
| DM_TABLE_CLEAR
| DM_TABLE_DEPS
| DM_REMOVE_ALL
| DM_TARGET_MSG
| DM_DEV_SET_GEOMETRY
| DM_DEV_ARM_POLL
| DM_GET_TARGET_VERSION => return error!(ENOTSUP),
_ => default_ioctl(file, current_task, request, arg),
}
}
}
pub fn create_device_mapper(
_locked: &mut Locked<'_, DeviceOpen>,
current_task: &CurrentTask,
_id: DeviceType,
_node: &FsNode,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
Ok(Box::new(DeviceMapper::new(current_task.kernel().device_mapper_registry.clone())))
}
fn get_or_create_dm_device(
locked: &mut Locked<'_, DeviceOpen>,
current_task: &CurrentTask,
id: DeviceType,
_node: &FsNode,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
Ok(current_task
.kernel()
.device_mapper_registry
.get_or_create_by_minor(locked, current_task, id.minor())
.create_file_ops())
}