mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
rust/pv: Refactor pv crate
Big refactoring patch of the pv crate. The main reason behind this refactoring is to simplify testing and maintaining the pv crate while keeping OpenSSL/libcurl dependencies optional. Using crate features increases the number of targets that have to be tested. This refactoring eliminates the use of features by splitting the functionality of pv into a use OpenSSL and no-use-OpenSSL crate. Split off some code from the pv crate into a pv_core crate. pv requires pv_core and reexports all symbols. pv_base contains all code from former pv that does not use OpenSSL or libcurl functionalities. The refactored pv crate contains functionalities to generate requests and validate host key documents. All features from pv are dropped as they are not needed anymore and to streamline the codebase for easier use and testing. While at it fix some documentation issues. Users (pvsecret & pvapconfig) have next to no code change, besides the different import of the crate. Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
48539596ef
commit
9b51b8b882
@@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
/// Result type for this crate
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
/// Error cases for this crate
|
||||
#[allow(missing_docs)]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
#[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))]
|
||||
#[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))]
|
||||
Uv {
|
||||
rc: u16,
|
||||
rrc: u16,
|
||||
msg: &'static str,
|
||||
},
|
||||
|
||||
#[error("{0}")]
|
||||
Specification(String),
|
||||
|
||||
#[error("Cannot {ty} {ctx} at `{path}`")]
|
||||
FileIo {
|
||||
ty: FileIoErrorType,
|
||||
ctx: String,
|
||||
path: String,
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("Cannot {ty} `{path}`")]
|
||||
FileAccess {
|
||||
ty: FileAccessErrorType,
|
||||
path: String,
|
||||
source: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("Cannot encode secrets (Too many secrets)")]
|
||||
ManySecrets,
|
||||
|
||||
#[error("Cannot decode secret list")]
|
||||
InvSecretList(#[source] std::io::Error),
|
||||
|
||||
#[error("Input does not contain an add-secret request")]
|
||||
NoAsrcb,
|
||||
|
||||
// errors from other crates
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
ParseInt(#[from] std::num::ParseIntError),
|
||||
}
|
||||
|
||||
/// Error cases for I/O operations
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum FileIoErrorType {
|
||||
#[error("read")]
|
||||
Read,
|
||||
#[error("write")]
|
||||
Write,
|
||||
}
|
||||
|
||||
/// Error cases for accessing files
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum FileAccessErrorType {
|
||||
#[error("open")]
|
||||
Open,
|
||||
#[error("create")]
|
||||
Create,
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
#![deny(missing_docs)]
|
||||
#![allow(unused)]
|
||||
//! pv_core - basic library for pv-tools
|
||||
//!
|
||||
//! This library is intened to be used by tools and libraries that
|
||||
//! are used for creating and managing IBM Secure Execution guests.
|
||||
//! `pv_core` provides abstraction layers for secure memory management,
|
||||
//! logging, and accessing the uvdevice.
|
||||
//!
|
||||
//! It does not provide any cryptographic operations through OpenSSL.
|
||||
//! For this use `pv` which reexports all symbos from this crate.
|
||||
mod error;
|
||||
mod log;
|
||||
mod macros;
|
||||
mod utils;
|
||||
mod uvdevice;
|
||||
mod uvsecret;
|
||||
|
||||
pub use crate::log::PvLogger;
|
||||
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
|
||||
|
||||
/// Miscellaneous functions and definitions
|
||||
pub mod misc {
|
||||
pub use crate::utils::pv_guest_bit_set;
|
||||
pub use crate::utils::{create_file, open_file, read_exact_file, read_file, write_file};
|
||||
pub use crate::utils::{memeq, parse_hex, to_u16, to_u32, try_parse_u128, try_parse_u64};
|
||||
pub use crate::utils::{read, write};
|
||||
pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64};
|
||||
}
|
||||
|
||||
/// Definitions and functions for interacting with the Ultravisor
|
||||
pub mod uv {
|
||||
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
|
||||
pub use crate::uvdevice::secret::{ListableSecretType, SecretEntry, SecretList};
|
||||
pub use crate::uvdevice::{
|
||||
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
|
||||
};
|
||||
}
|
||||
|
||||
/// Functionalities to verify UV requests
|
||||
pub mod request {
|
||||
/// Functionalities for reading add-secret requests
|
||||
pub mod uvsecret {
|
||||
pub use crate::uvsecret::AddSecretMagic;
|
||||
pub use crate::uvsecret::UserDataType;
|
||||
}
|
||||
|
||||
/// Version number of the request in system-endian
|
||||
pub type RequestVersion = u32;
|
||||
/// Request magic value
|
||||
///
|
||||
/// The first 8 byte of a request providing an identifier of the request type
|
||||
/// for programs
|
||||
pub type RequestMagic = [u8; 8];
|
||||
/// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified
|
||||
/// (binary) data.
|
||||
pub trait MagicValue<const N: usize> {
|
||||
/// Magic value as byte array
|
||||
const MAGIC: [u8; N];
|
||||
/// Test whether the given slice starts with the magic value.
|
||||
fn starts_with_magic(v: &[u8]) -> bool {
|
||||
if v.len() < Self::MAGIC.len() {
|
||||
return false;
|
||||
}
|
||||
crate::misc::memeq(&v[..Self::MAGIC.len()], &Self::MAGIC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides cargo version Info about this crate.
|
||||
///
|
||||
/// Produces `pv_core-crate <version>`
|
||||
pub const fn crate_info() -> &'static str {
|
||||
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
|
||||
// Internal definitions/ imports
|
||||
const PAGESIZE: usize = 0x1000;
|
||||
use ::utils::assert_size;
|
||||
use ::utils::static_assert;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// stuff pv_core and pv share. Not intended for other users
|
||||
pub mod for_pv {
|
||||
pub use crate::uvdevice::secret::ser_gsid;
|
||||
pub use crate::uvdevice::secret::SECRET_ID_SIZE;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use log::{self, Level, LevelFilter, Log, Metadata, Record};
|
||||
|
||||
/// A simple Logger that prints to stderr if the verbosity level is high enough.
|
||||
/// Prints log-level for Debug+Trace
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct PvLogger;
|
||||
|
||||
fn to_level(verbosity: u8) -> LevelFilter {
|
||||
match verbosity {
|
||||
// Error and Warn on by default
|
||||
0 => LevelFilter::Warn,
|
||||
1 => LevelFilter::Info,
|
||||
2 => LevelFilter::Debug,
|
||||
_ => LevelFilter::Trace,
|
||||
}
|
||||
}
|
||||
|
||||
impl PvLogger {
|
||||
/// Set self as the logger for this application.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error is returned if a logger has already been set.
|
||||
pub fn start(&'static self, verbosity: u8) -> Result<(), log::SetLoggerError> {
|
||||
log::set_logger(self).map(|()| log::set_max_level(to_level(verbosity)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Log for PvLogger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
if record.level() > Level::Info {
|
||||
eprintln!("{}: {}", record.level(), record.args());
|
||||
} else {
|
||||
eprintln!("{}", record.args());
|
||||
}
|
||||
}
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
macro_rules! path_to_str {
|
||||
($path: expr) => {
|
||||
$path.as_ref().to_str().unwrap_or("no UTF-8 path")
|
||||
};
|
||||
}
|
||||
pub(crate) use path_to_str;
|
||||
|
||||
macro_rules! file_error {
|
||||
($ty: tt, $ctx: expr, $path:expr, $src: expr) => {
|
||||
$crate::Error::FileIo {
|
||||
ty: $crate::FileIoErrorType::$ty,
|
||||
ctx: $ctx.to_string(),
|
||||
path: $path.to_string(),
|
||||
source: $src,
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use file_error;
|
||||
|
||||
macro_rules! bail_spec {
|
||||
($str: expr) => {
|
||||
return Err($crate::Error::Specification($str.to_string()))
|
||||
};
|
||||
}
|
||||
pub(crate) use bail_spec;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! file_acc_error {
|
||||
($ty: tt, $path:expr, $src: expr) => {
|
||||
$crate::Error::FileAccess {
|
||||
ty: $crate::FileAccessErrorType::$ty,
|
||||
path: $path.to_string(),
|
||||
source: $src,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
use crate::{
|
||||
macros::{bail_spec, file_error, path_to_str},
|
||||
Error, FileAccessErrorType, FileIoErrorType, Result,
|
||||
};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, U64};
|
||||
|
||||
/// Trait that describes bitflags, represented by `T`.
|
||||
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
|
||||
/// Set the specified bit to one.
|
||||
/// # Panics
|
||||
///Panics if bit is >= 64
|
||||
fn set_bit(&mut self, bit: u8);
|
||||
/// Set the specified bit to zero.
|
||||
/// # Panics
|
||||
///Panics if bit is >= 64
|
||||
fn unset_bit(&mut self, bit: u8);
|
||||
/// Test if the specified bit is set.
|
||||
/// # Panics
|
||||
///Panics if bit is >= 64
|
||||
fn is_set(&self, bit: u8) -> bool;
|
||||
}
|
||||
|
||||
/// Bitflags in MSB0 ordering
|
||||
///
|
||||
/// Wraps an u64 to set/get individual bits
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
|
||||
pub struct Msb0Flags64(U64<BigEndian>);
|
||||
impl Flags<u64> for Msb0Flags64 {
|
||||
#[track_caller]
|
||||
fn set_bit(&mut self, bit: u8) {
|
||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||
let mut v = self.0.get();
|
||||
v |= 1 << (63 - bit);
|
||||
self.0.set(v)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn unset_bit(&mut self, bit: u8) {
|
||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||
let mut v = self.0.get();
|
||||
v &= !(1 << (63 - bit));
|
||||
self.0.set(v)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn is_set(&self, bit: u8) -> bool {
|
||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||
self.0.get() & (1 << (63 - bit)) > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Msb0Flags64 {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&u64> for Msb0Flags64 {
|
||||
fn from(value: &u64) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Bitflags in LSB0 ordering
|
||||
///
|
||||
/// Wraps an u64 to set/get individual bits
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
|
||||
pub struct Lsb0Flags64(U64<BigEndian>);
|
||||
impl Flags<u64> for Lsb0Flags64 {
|
||||
#[track_caller]
|
||||
fn set_bit(&mut self, bit: u8) {
|
||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||
let mut v = self.0.get();
|
||||
v |= 1 << bit;
|
||||
self.0.set(v)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn unset_bit(&mut self, bit: u8) {
|
||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||
let mut v = self.0.get();
|
||||
v &= !(1 << bit);
|
||||
self.0.set(v)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn is_set(&self, bit: u8) -> bool {
|
||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||
self.0.get() & (1 << bit) > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Lsb0Flags64 {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&u64> for Lsb0Flags64 {
|
||||
fn from(value: &u64) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to convert a BE hex string into a 128 unsigned integer
|
||||
/// The hexstring must contain 32chars of hexdigits
|
||||
///
|
||||
/// * `hex_str` - string to convert can be prepended with "0x"
|
||||
/// * `ctx` - Error context string in case of an error
|
||||
/// ```rust
|
||||
/// # use std::error::Error;
|
||||
/// # use pv_core::misc::try_parse_u128;
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let hex = "11223344556677889900aabbccddeeff";
|
||||
/// try_parse_u128(&hex, "The test")?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// If `hex_string` is not a 32 byte hex string an Error appears
|
||||
pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> {
|
||||
let hex_str = if hex_str.starts_with("0x") {
|
||||
hex_str.split_at(2).1
|
||||
} else {
|
||||
hex_str
|
||||
};
|
||||
if hex_str.len() != 32 {
|
||||
bail_spec!(format!(
|
||||
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
|
||||
));
|
||||
}
|
||||
parse_hex(hex_str).try_into().map_err(|_| {
|
||||
Error::Specification(format!(
|
||||
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to convert a BE hex string into a 64 unsigned integer
|
||||
/// The hexstring must *NOT* contain 16 chars of hexdigits, but
|
||||
/// 16 chars at most.
|
||||
///
|
||||
/// * `hex_str` - string to convert can be prepended with "0x"
|
||||
/// * `ctx` - Error context string in case of an error
|
||||
/// ```rust
|
||||
/// # use std::error::Error;
|
||||
/// # use pv_core::misc::try_parse_u64;
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let hex = "1234567890abcdef";
|
||||
/// try_parse_u64(&hex, "The test")?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// If `hex_string` is not a 32 byte hex string an Error appears
|
||||
pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result<u64> {
|
||||
let hex_str = if hex_str.starts_with("0x") {
|
||||
hex_str.split_at(2).1
|
||||
} else {
|
||||
hex_str
|
||||
};
|
||||
if hex_str.len() > 16 {
|
||||
bail_spec!(format!(
|
||||
"{ctx} hexstring {hex_str} must be max 16 chars long"
|
||||
));
|
||||
}
|
||||
Ok(u64::from_str_radix(hex_str, 16)?)
|
||||
}
|
||||
|
||||
/// Open a file.
|
||||
///
|
||||
/// Wraps [`File::open`]
|
||||
///
|
||||
/// * `path` - Path to file
|
||||
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||
File::open(&path).map_err(|e| Error::FileAccess {
|
||||
ty: FileAccessErrorType::Open,
|
||||
path: path_to_str!(path).to_string(),
|
||||
source: e,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a file.
|
||||
///
|
||||
/// Wraps [`File::create`]
|
||||
///
|
||||
/// * `path` - Path to file
|
||||
pub fn create_file<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||
File::create(&path).map_err(|e| Error::FileAccess {
|
||||
ty: FileAccessErrorType::Create,
|
||||
path: path_to_str!(path).to_string(),
|
||||
source: e,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read exactly COUNT bytes into a buffer and return it.
|
||||
///
|
||||
/// * `path` - Path to file
|
||||
/// * `ctx` - Error context string in case of an error
|
||||
///
|
||||
/// # Errors
|
||||
/// If this function encounters an "end of file" before completely filling
|
||||
/// the buffer, it returns an error. The contents of `buf` are unspecified in this case.
|
||||
///
|
||||
/// If aber so hast du mmmeny other read error is encountered then this function immediately
|
||||
/// returns. The contents of `buf` are unspecified in this case.
|
||||
///
|
||||
/// If this function returns an error, it is unspecified how many bytes it
|
||||
/// has read, but it will never read more than would be necessary to
|
||||
/// completely fill the buffer.
|
||||
pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
|
||||
path: P,
|
||||
ctx: &str,
|
||||
) -> Result<[u8; COUNT]> {
|
||||
let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess {
|
||||
ty: crate::FileAccessErrorType::Open,
|
||||
path: path_to_str!(path).to_string(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
if f.metadata()?.len() as usize != COUNT {
|
||||
bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long"));
|
||||
}
|
||||
|
||||
let mut buf = [0; COUNT];
|
||||
f.read_exact(&mut buf)
|
||||
.map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Read content from a file and add context in case of an error
|
||||
///
|
||||
/// * `path` - Path to file
|
||||
/// * `ctx` - Error context string in case of an error
|
||||
///
|
||||
///
|
||||
/// # Errors
|
||||
/// Passes through any kind of error `std::fs::read` produces
|
||||
pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
|
||||
std::fs::read(&path).map_err(|e| {
|
||||
file_error!(
|
||||
Read,
|
||||
ctx,
|
||||
path.as_ref().to_str().unwrap_or("no UTF-8 path"),
|
||||
e
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads all content from a [`std::io::Read`] and add context in case of an error
|
||||
///
|
||||
/// * `path` - Path to file
|
||||
/// * `ctx` - Error context string in case of an error
|
||||
///
|
||||
///
|
||||
/// # Errors
|
||||
/// Passes through any kind of error `std::fs::read` produces
|
||||
pub fn read<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
|
||||
let mut buf = vec![];
|
||||
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
|
||||
ty: FileIoErrorType::Write,
|
||||
ctx: ctx.to_string(),
|
||||
path: path.to_string(),
|
||||
source: e,
|
||||
})?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// write content to a file and add context in case of an error
|
||||
///
|
||||
/// * `path` - Path to file
|
||||
/// * `ctx` - Error context string in case of an error
|
||||
///
|
||||
///
|
||||
/// # Errors
|
||||
/// Passes through any kind of error `std::fs::write` produces
|
||||
pub fn write_file<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> Result<()> {
|
||||
std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo {
|
||||
ty: FileIoErrorType::Write,
|
||||
ctx: ctx.to_string(),
|
||||
path: path.to_string(),
|
||||
source: e,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write content to a [`std::io::Write`] and add context in case of an error
|
||||
///
|
||||
/// * `path` - Path to file
|
||||
/// * `ctx` - Error context string in case of an error
|
||||
///
|
||||
///
|
||||
/// # Errors
|
||||
/// Passes through any kind of error `std::fs::write` produces
|
||||
pub fn write<D: AsRef<[u8]>, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> {
|
||||
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
|
||||
ty: FileIoErrorType::Write,
|
||||
ctx: ctx.to_string(),
|
||||
path: path.to_string(),
|
||||
source: e,
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! usize_to_ui {
|
||||
($(#[$attr:meta])* => $t: ident, $name:ident) => {
|
||||
///Converts an [`usize`] to an [`
|
||||
$(#[$attr])*
|
||||
///`] if possible
|
||||
pub fn $name(u: usize) -> Option<$t> {
|
||||
if u > $t::MAX as usize {
|
||||
None
|
||||
} else {
|
||||
Some(u as $t)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
usize_to_ui! {
|
||||
#[doc = r"u32"]
|
||||
=> u32, to_u32}
|
||||
usize_to_ui! {
|
||||
#[doc = r"u16"]
|
||||
=> u16, to_u16}
|
||||
|
||||
/// Test if both slices contain the exact same bytes.
|
||||
///
|
||||
/// Do not use this to compare cryptographic values (i.e. hashes)
|
||||
pub fn memeq(lhs: &[u8], rhs: &[u8]) -> bool {
|
||||
let size = lhs.len();
|
||||
|
||||
size == rhs.len()
|
||||
&& unsafe {
|
||||
let l = lhs as *const _ as _;
|
||||
let r = rhs as *const _ as _;
|
||||
(l as usize) == (r as usize) || libc::memcmp(l, r, size) == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the hexstring into a byte vector.
|
||||
///
|
||||
/// Stops if the end or until a non hex chat is found
|
||||
pub fn parse_hex(hex_str: &str) -> Vec<u8> {
|
||||
let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b {
|
||||
b'0'..=b'9' => Some(b - b'0'),
|
||||
b'a'..=b'f' => Some(b - b'a' + 10),
|
||||
b'A'..=b'F' => Some(b - b'A' + 10),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
|
||||
bytes.push(h << 4 | l)
|
||||
}
|
||||
bytes
|
||||
}
|
||||
/// Report if the `prot_virt_guest` sysfs entry is one.
|
||||
///
|
||||
/// If the entry does not exist returns false.
|
||||
///
|
||||
/// for non-s390-architectures:
|
||||
/// Returns always false
|
||||
/// A non-s390 system cannot be a secure execution guest.
|
||||
#[allow(unreachable_code)]
|
||||
pub fn pv_guest_bit_set() -> bool {
|
||||
#[cfg(not(target_arch = "s390x"))]
|
||||
return false;
|
||||
//s390 branch
|
||||
let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]);
|
||||
let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0);
|
||||
v == 1
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn msb_flags() {
|
||||
let v = 17;
|
||||
let v_flag: Msb0Flags64 = v.into();
|
||||
assert_eq!(v, v_flag.0.get());
|
||||
|
||||
let mut v: Msb0Flags64 = 4.into();
|
||||
v.unset_bit(61);
|
||||
assert_eq!(v.0.get(), 0);
|
||||
v.set_bit(61);
|
||||
assert_eq!(4, v.0.get());
|
||||
|
||||
let mut v = Msb0Flags64::default();
|
||||
v.set_bit(0);
|
||||
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
v.set_bit(0);
|
||||
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
v.set_bit(1);
|
||||
assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
v.set_bit(2);
|
||||
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
v.set_bit(3);
|
||||
assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
|
||||
v.unset_bit(3);
|
||||
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
v.unset_bit(3);
|
||||
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
|
||||
v.set_bit(16);
|
||||
assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn msb_flags_set_panic() {
|
||||
Msb0Flags64::default().set_bit(64)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn msb_flags_unset_panic() {
|
||||
Msb0Flags64::default().unset_bit(64)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsb_flags() {
|
||||
let v = 17;
|
||||
let v_flag: Lsb0Flags64 = v.into();
|
||||
assert_eq!(v, v_flag.0.get());
|
||||
|
||||
let mut v: Lsb0Flags64 = 4.into();
|
||||
v.unset_bit(2);
|
||||
assert_eq!(v.0.get(), 0);
|
||||
v.set_bit(2);
|
||||
assert_eq!(4, v.0.get());
|
||||
|
||||
let mut v = Lsb0Flags64::default();
|
||||
v.set_bit(0);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
|
||||
v.set_bit(0);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
|
||||
v.set_bit(1);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes());
|
||||
v.set_bit(2);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
||||
v.set_bit(3);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes());
|
||||
|
||||
v.unset_bit(3);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
||||
v.unset_bit(3);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
||||
|
||||
v.set_bit(16);
|
||||
assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn lsb_flags_set_panic() {
|
||||
Lsb0Flags64::default().set_bit(64)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn lsb_flags_unset_panic() {
|
||||
Lsb0Flags64::default().unset_bit(64)
|
||||
}
|
||||
#[test]
|
||||
fn parse_hex() {
|
||||
let s = "123456acbef0";
|
||||
let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
||||
assert_eq!(super::parse_hex(s), exp);
|
||||
|
||||
let s = "00123456acbef0";
|
||||
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
||||
assert_eq!(super::parse_hex(s), exp);
|
||||
|
||||
let s = "00123456acbef0ii90";
|
||||
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
||||
assert_eq!(super::parse_hex(s), exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_u32() {
|
||||
assert_eq!(Some(17), super::to_u32(17));
|
||||
assert_eq!(Some(0), super::to_u32(0));
|
||||
assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize));
|
||||
assert_eq!(None, super::to_u32(u32::MAX as usize + 1));
|
||||
assert_eq!(None, super::to_u32(usize::MAX));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_u128() {
|
||||
assert!(matches!(
|
||||
try_parse_u128("123456", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("-1234", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("0011223344556677889900aabbccddeeff", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("dd11223344556677889900aabbccddeeff", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("-1223344556677889900aabbccddeeff", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
try_parse_u128("0x123456", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("-0x1234", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("0x0011223344556677889900aabbccddeeff", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
try_parse_u128("0x-1223344556677889900aabbccddeeff", ""),
|
||||
Err(Error::Specification(_))
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
[
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||
0xee, 0xff
|
||||
],
|
||||
try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
[
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||
0xee, 0xff
|
||||
],
|
||||
try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
[
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||
0xee, 0xff
|
||||
],
|
||||
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
[
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||
0xee, 0xff
|
||||
],
|
||||
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memeq() {
|
||||
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
|
||||
let b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1];
|
||||
let c = [0, 0, 1, 2, 3, 4];
|
||||
|
||||
assert!(super::memeq(&a, &a));
|
||||
assert!(super::memeq(&a, &a.clone()));
|
||||
assert!(!super::memeq(&b, &a));
|
||||
assert!(!super::memeq(&b, &c));
|
||||
assert!(!super::memeq(&b, &[]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::FileAccessErrorType;
|
||||
use crate::{Error, Result};
|
||||
use libc::c_ulong;
|
||||
use log::debug;
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fs::File,
|
||||
os::unix::prelude::{AsRawFd, RawFd},
|
||||
};
|
||||
|
||||
#[cfg(not(test))]
|
||||
use ::libc::ioctl;
|
||||
#[cfg(test)]
|
||||
use test::mock_libc::ioctl;
|
||||
|
||||
/// Contains the rust representation of asm/uvdevice.h
|
||||
/// from kernel version: 6.5 verify
|
||||
mod ffi;
|
||||
mod info;
|
||||
mod test;
|
||||
pub use ffi::uv_ioctl;
|
||||
pub mod secret;
|
||||
|
||||
pub use info::UvDeviceInfo;
|
||||
#[allow(dead_code)] //TODO rm when pv learns attestation
|
||||
pub type AttestationUserData = [u8; ffi::UVIO_ATT_USER_DATA_LEN];
|
||||
|
||||
///Configuration Unique Id of the Secure Execution guest
|
||||
pub type ConfigUid = [u8; ffi::UVIO_ATT_UID_LEN];
|
||||
|
||||
/// Bitflags as used by the Ultravisor in MSB0 ordering
|
||||
///
|
||||
/// Wraps an u64 to set/get individual bits
|
||||
pub type UvFlags = crate::misc::Msb0Flags64;
|
||||
|
||||
/// Fire an ioctl.
|
||||
///
|
||||
/// # Safety:
|
||||
/// Raw fd must point to an open file
|
||||
fn ioctl_raw(raw_fd: RawFd, cmd: c_ulong, cb: &mut IoctlCb) -> Result<()> {
|
||||
debug!("calling unsafe fn wrapper uv::ioctl_raw with {raw_fd:#x?}, {cmd:#x?}, {cb:?}");
|
||||
|
||||
let rc;
|
||||
|
||||
// Get the raw pointer and do an ioctl.
|
||||
//
|
||||
// SAFETY: the passed pointer points to a valid memory region that
|
||||
// contains the expected C-struct. The struct outlives this function.
|
||||
unsafe {
|
||||
rc = ioctl(raw_fd, cmd, cb.as_ptr_mut());
|
||||
}
|
||||
|
||||
debug!("ioctl resulted with {cb:?}");
|
||||
match rc {
|
||||
0 => Ok(()),
|
||||
//NOTE io::Error handles all errnos ioctl uses
|
||||
_ => Err(std::io::Error::last_os_error().into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts UV return codes into human readable error messages
|
||||
fn rc_fmt<C: UvCmd>(rc: u16, rrc: u16, cmd: &mut C) -> &'static str {
|
||||
let s = match (rc, rrc) {
|
||||
(0x0000, _) => Some("invalid rc"),
|
||||
(0x0002, _) => Some("invalid UV command"),
|
||||
(0x0005, _) => Some("request has an invalid size"),
|
||||
(0x0030, _) => Some("home address space control bit has R-bit set to one"),
|
||||
(0x0031, _) => Some("access exception"),
|
||||
(0x0032, _) => Some("request contains virtual address translating to an invalid address"),
|
||||
(UvDevice::RC_MORE_DATA, _) => unreachable!("This is no Error!!!!"),
|
||||
(UvDevice::RC_SUCCESS, _) => unreachable!("This is no Error!!!!"),
|
||||
|
||||
_ => cmd.rc_fmt(rc, rrc),
|
||||
};
|
||||
s.unwrap_or("unexpected error-code")
|
||||
}
|
||||
|
||||
/// Ultravisor Command.
|
||||
pub trait UvCmd {
|
||||
/// Returns the uvdevice IOCTL command that his command uses.
|
||||
///
|
||||
/// # Returns
|
||||
/// The IOCTL cmd for this UvCmd usually sth like `uv_ioctl!(CMD_NR)`
|
||||
fn cmd(&self) -> u64;
|
||||
/// Converts UV return codes into human readable error messages
|
||||
///
|
||||
/// no need to handle `0x0000, 0x0001, 0x0002, 0x0005, 0x0030, 0x0031, 0x0032, 0x0100`
|
||||
fn rc_fmt(&self, rc: u16, rrc: u16) -> Option<&'static str>;
|
||||
/// Returns data used by this command if available.
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UvDevice`] IOCTL control block.
|
||||
#[derive(Debug)]
|
||||
struct IoctlCb(ffi::uvio_ioctl_cb);
|
||||
impl IoctlCb {
|
||||
fn new(data: Option<&mut [u8]>) -> Result<Self> {
|
||||
let (data_raw, data_size) = match data {
|
||||
Some(data) => (
|
||||
data.as_mut_ptr(),
|
||||
data.len()
|
||||
.try_into()
|
||||
.map_err(|_| Error::Specification("passed data too large".to_string()))?,
|
||||
),
|
||||
None => (std::ptr::null_mut(), 0),
|
||||
};
|
||||
|
||||
Ok(Self(ffi::uvio_ioctl_cb {
|
||||
flags: 0,
|
||||
uv_rc: 0,
|
||||
uv_rrc: 0,
|
||||
argument_addr: data_raw as u64,
|
||||
argument_len: data_size,
|
||||
reserved14: [0; 44],
|
||||
}))
|
||||
}
|
||||
|
||||
fn rc(&self) -> u16 {
|
||||
self.0.uv_rc
|
||||
}
|
||||
|
||||
fn rrc(&self) -> u16 {
|
||||
self.0.uv_rrc
|
||||
}
|
||||
|
||||
fn as_ptr_mut(&mut self) -> *mut ffi::uvio_ioctl_cb {
|
||||
&mut self.0 as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
/// The Ultravisor has two codes that represent a successful execution.
|
||||
/// These are represented by this enum.
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UvcSuccess {
|
||||
/// Command executed successfully
|
||||
RC_SUCCESS = UvDevice::RC_SUCCESS,
|
||||
/// Command executed successfully, but there is more data available and the buffer was to small
|
||||
/// to hold it all. The returned data is still valid.
|
||||
RC_MORE_DATA = UvDevice::RC_MORE_DATA,
|
||||
}
|
||||
|
||||
/// The UvDevice is a (virtual) device on s390 machines to send Ultravisor commands from userspace.
|
||||
pub struct UvDevice(File);
|
||||
|
||||
impl UvDevice {
|
||||
const RC_SUCCESS: u16 = 0x0001;
|
||||
const RC_MORE_DATA: u16 = 0x0100;
|
||||
const PATH: &'static str = "/dev/uv";
|
||||
|
||||
/// IOCTL number for the info UVC
|
||||
pub const INFO_NR: u8 = ffi::UVIO_IOCTL_UVDEV_INFO_NR;
|
||||
/// IOCTL number for the attestation UVC
|
||||
pub const ATTESTATION_NR: u8 = ffi::UVIO_IOCTL_ATT_NR;
|
||||
/// IOCTL number for the add secret UVC
|
||||
pub const ADD_SECRET_NR: u8 = ffi::UVIO_IOCTL_ADD_SECRET_NR;
|
||||
/// IOCTL number for the list secret UVC
|
||||
pub const LIST_SECRET_NR: u8 = ffi::UVIO_IOCTL_LIST_SECRETS_NR;
|
||||
/// IOCTL number for the lock ksecret UVC
|
||||
pub const LOCK_SECRET_NR: u8 = ffi::UVIO_IOCTL_LOCK_SECRETS_NR;
|
||||
/// Maximum length for add-secret requests
|
||||
pub const ADD_SECRET_MAX_LEN: usize = ffi::UVIO_ADD_SECRET_MAX_LEN;
|
||||
/// Size of the buffer for list secret requests
|
||||
pub const LIST_SECRETS_LEN: usize = ffi::UVIO_LIST_SECRETS_LEN;
|
||||
|
||||
/// Open the uvdevice located at `/dev/uv`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the device file cannot be opened.
|
||||
pub fn open() -> Result<Self> {
|
||||
Ok(Self(
|
||||
std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(UvDevice::PATH)
|
||||
.map_err(|e| Error::FileAccess {
|
||||
ty: FileAccessErrorType::Open,
|
||||
path: (UvDevice::PATH).to_string(),
|
||||
source: e,
|
||||
})?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Send an Ultravisor Command via this uvdevice.
|
||||
///
|
||||
/// This works by sending an IOCTL to the uvdevice.
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the IOCTL fails or the Ultravisor does not report
|
||||
/// a success.
|
||||
/// # Returns
|
||||
/// [`UvcSuccess`] if the UVC ececuted successfully
|
||||
pub fn send_cmd<C: UvCmd>(&self, cmd: &mut C) -> Result<UvcSuccess> {
|
||||
let mut cb = IoctlCb::new(cmd.data())?;
|
||||
ioctl_raw(self.0.as_raw_fd(), cmd.cmd(), &mut cb)?;
|
||||
|
||||
match (cb.rc(), cb.rrc()) {
|
||||
(Self::RC_SUCCESS, _) => Ok(UvcSuccess::RC_SUCCESS),
|
||||
(Self::RC_MORE_DATA, _) => Ok(UvcSuccess::RC_MORE_DATA),
|
||||
(rc, rrc) => Err(Error::Uv {
|
||||
rc,
|
||||
rrc,
|
||||
msg: rc_fmt(rc, rrc, cmd),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::{assert_size, static_assert};
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
||||
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
|
||||
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
|
||||
pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000;
|
||||
pub const UVIO_ADD_SECRET_MAX_LEN: usize = 0x100000;
|
||||
pub const UVIO_LIST_SECRETS_LEN: usize = 0x1000;
|
||||
|
||||
// equal to ascii 'u'
|
||||
pub const UVIO_TYPE_UVC: u8 = 117u8;
|
||||
|
||||
pub const UVIO_IOCTL_UVDEV_INFO_NR: u8 = 0;
|
||||
pub const UVIO_IOCTL_ATT_NR: u8 = 1;
|
||||
pub const UVIO_IOCTL_ADD_SECRET_NR: u8 = 2;
|
||||
pub const UVIO_IOCTL_LIST_SECRETS_NR: u8 = 3;
|
||||
pub const UVIO_IOCTL_LOCK_SECRETS_NR: u8 = 4;
|
||||
|
||||
/// Uvdevice IOCTL control block
|
||||
/// Programs can use this struct to communicate with the uvdevice via IOCTLs
|
||||
/// `argument_{addr,len}` specifies in/out data depending on the request
|
||||
///
|
||||
/// 'uv_rc' and `uv_rrc` are the response and reason response codes from the
|
||||
/// Ultravisor.
|
||||
///
|
||||
/// `flags` is currently unused and to be set zero
|
||||
///
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct uvio_ioctl_cb {
|
||||
pub flags: u32,
|
||||
pub uv_rc: u16,
|
||||
pub uv_rrc: u16,
|
||||
pub argument_addr: u64,
|
||||
pub argument_len: u32,
|
||||
pub reserved14: [u8; 44usize],
|
||||
}
|
||||
assert_size!(uvio_ioctl_cb, 0x40);
|
||||
|
||||
/// Information of supported functions by the uvdevice
|
||||
///
|
||||
/// * `supp_uvio_cmds` - supported IOCTLs by this device
|
||||
/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL
|
||||
///
|
||||
/// UVIO request to get information about supported request types by this
|
||||
/// uvdevice and the Ultravisor.
|
||||
/// Everything is output. Bits are in LSB0 ordering.
|
||||
/// If the bit is set in both, `supp_uvio_cmds` and `supp_uv_cmds`,
|
||||
/// the uvdevice and the Ultravisor support that call.
|
||||
///
|
||||
/// Note that bit 0 (UVIO_IOCTL_UVDEV_INFO_NR) is always zero for `supp_uv_cmds`
|
||||
/// as there is no corresponding UV-call.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
|
||||
pub struct uvio_uvdev_info {
|
||||
pub supp_uvio_cmds: u64,
|
||||
pub supp_uv_cmds: u64,
|
||||
}
|
||||
assert_size!(uvio_uvdev_info, 0x10);
|
||||
|
||||
pub const UVIO_ATT_USER_DATA_LEN: usize = 0x100;
|
||||
pub const UVIO_ATT_UID_LEN: usize = 0x10;
|
||||
|
||||
/// Request Attestation Measurement control block
|
||||
///
|
||||
/// The Attestation Request has two input and two outputs.
|
||||
/// ARCB and User Data are inputs for the UV.
|
||||
/// Measurement and Additional Data are outputs generated by UV.
|
||||
///
|
||||
/// The Attestation Request Control Block (ARCB) is a cryptographically verified
|
||||
/// and secured request to UV and User Data is some plaintext data which is
|
||||
/// going to be included in the Attestation Measurement calculation.
|
||||
///
|
||||
/// Measurement is a cryptographic measurement of the callers properties,
|
||||
/// optional data configured by the ARCB and the user data. If specified by the
|
||||
/// ARCB, UV will add some Additional Data to the measurement calculation.
|
||||
/// This Additional Data is then returned as well.
|
||||
///
|
||||
/// If the Retrieve Attestation Measurement UV facility is not present,
|
||||
/// UV will return invalid command rc.
|
||||
/// Obviously all numbers are in BIG-endian!
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes, FromBytes)]
|
||||
pub struct uvio_attest {
|
||||
pub arcb_addr: u64, //in
|
||||
pub meas_addr: u64, //out
|
||||
pub add_data_addr: u64, //out
|
||||
pub user_data: [u8; UVIO_ATT_USER_DATA_LEN], //in
|
||||
pub config_uid: [u8; UVIO_ATT_UID_LEN], //out
|
||||
pub arcb_len: u32,
|
||||
pub meas_len: u32,
|
||||
pub add_data_len: u32,
|
||||
pub user_data_len: u16,
|
||||
pub reserved136: u16,
|
||||
}
|
||||
assert_size!(uvio_attest, 0x138);
|
||||
|
||||
#[allow(dead_code)] //TODO rm when pv learns attestation
|
||||
impl uvio_attest {
|
||||
pub const ARCB_MAX_LEN: usize = UVIO_ATT_ARCB_MAX_LEN;
|
||||
pub const MEASUREMENT_MAX_LEN: usize = UVIO_ATT_MEASUREMENT_MAX_LEN;
|
||||
pub const ADDITIONAL_MAX_LEN: usize = UVIO_ATT_ADDITIONAL_MAX_LEN;
|
||||
}
|
||||
|
||||
/// corresponds to the UV_IOCTL macro
|
||||
pub const fn uv_ioctl(nr: u8) -> u64 {
|
||||
iowr(UVIO_TYPE_UVC, nr, std::mem::size_of::<uvio_ioctl_cb>())
|
||||
}
|
||||
static_assert!(uv_ioctl(UVIO_IOCTL_ATT_NR) == 0xc0407501);
|
||||
|
||||
/// corresponds to the __IOWR macro
|
||||
const fn iowr(ty: u8, nr: u8, size: usize) -> u64 {
|
||||
// constants and calculation from linux: asm-generic/ioctl.h
|
||||
const _IOC_WRITE: u32 = 1;
|
||||
const _IOC_READ: u32 = 2;
|
||||
const _IOC_NRSHIFT: u32 = 0;
|
||||
const _IOC_TYPESHIFT: u32 = 8;
|
||||
const _IOC_SIZESHIFT: u32 = 16;
|
||||
const _IOC_DIRSHIFT: u32 = 30;
|
||||
((_IOC_READ | _IOC_WRITE) as u64) << _IOC_DIRSHIFT
|
||||
| ((ty as u64) << _IOC_TYPESHIFT)
|
||||
| ((nr as u64) << _IOC_NRSHIFT)
|
||||
| ((size as u64) << _IOC_SIZESHIFT)
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::ffi::uvio_uvdev_info;
|
||||
use crate::{
|
||||
misc::{Flags, Lsb0Flags64},
|
||||
uv::{uv_ioctl, UvCmd, UvDevice},
|
||||
Result,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
||||
/// Information of supported functions by the uvdevice
|
||||
///
|
||||
/// * `supp_uvio_cmds` - supported IOCTLs by this device
|
||||
/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL
|
||||
///
|
||||
/// UVIO request to get information about supported request types by this
|
||||
/// uvdevice and the Ultravisor.
|
||||
/// Everything is output.
|
||||
/// If the bit is set in both, `supp_uvio_cmds` and `supp_uv_cmds`,
|
||||
/// the uvdevice and the Ultravisor support that call.
|
||||
///
|
||||
/// Note that bit 0 ([`UvDevice::INFO_NR`]) is always zero for `supp_uv_cmds`
|
||||
/// as there is no corresponding UV-call.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct UvDeviceInfo {
|
||||
supp_uvio_cmds: Lsb0Flags64,
|
||||
supp_uv_cmds: Option<Lsb0Flags64>,
|
||||
}
|
||||
|
||||
impl UvDeviceInfo {
|
||||
/// Get information from the uvdevice.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the ioctl fails and the error code is not
|
||||
/// [`libc::ENOTTY`].
|
||||
/// `ENOTTY` is most likely because the uvdevice does not support the info IOCTL.
|
||||
/// In that case one can safely assume that the device only supports the Attestation IOCTL.
|
||||
/// Therefore this is what this function returns IOCTL support for Attestation and _Data not
|
||||
/// available_ for the UV Attestation facility.
|
||||
/// To check if the Ultravisor supports the Attestation call check at
|
||||
/// `/sys/firmware/uv/query/facilities` and check for bit 28 (Msb0 ordering!)
|
||||
pub fn get(uv: &UvDevice) -> Result<Self> {
|
||||
let mut cmd = uvio_uvdev_info::new_zeroed();
|
||||
match uv.send_cmd(&mut cmd) {
|
||||
Ok(_) => Ok(cmd.into()),
|
||||
Err(crate::Error::Io(e)) if e.raw_os_error() == Some(libc::ENOTTY) => Ok(Self {
|
||||
supp_uvio_cmds: (UvDevice::ATTESTATION_NR as u64).into(),
|
||||
supp_uv_cmds: None,
|
||||
}),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<uvio_uvdev_info> for UvDeviceInfo {
|
||||
fn from(value: uvio_uvdev_info) -> Self {
|
||||
Self {
|
||||
supp_uvio_cmds: value.supp_uvio_cmds.into(),
|
||||
supp_uv_cmds: Some(value.supp_uv_cmds.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UvCmd for uvio_uvdev_info {
|
||||
fn cmd(&self) -> u64 {
|
||||
uv_ioctl(UvDevice::INFO_NR)
|
||||
}
|
||||
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
Some(self.as_bytes_mut())
|
||||
}
|
||||
|
||||
fn rc_fmt(&self, _: u16, _: u16) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn nr_as_string(nr: u8) -> Option<&'static str> {
|
||||
match nr {
|
||||
UvDevice::INFO_NR => Some("Info"),
|
||||
UvDevice::ATTESTATION_NR => Some("Attestation"),
|
||||
UvDevice::ADD_SECRET_NR => Some("Add Secret"),
|
||||
UvDevice::LIST_SECRET_NR => Some("List Secrets"),
|
||||
UvDevice::LOCK_SECRET_NR => Some("Lock Secret Store"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn print_uvdevice_cmd(nr: u8, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match nr_as_string(nr) {
|
||||
Some(s) => write!(f, "{s}"),
|
||||
None => write!(f, "Unknown ({nr})"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_flags(uv_cmds: &Lsb0Flags64, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let supp_cmds: Vec<_> = (0u8..64)
|
||||
.filter(|v| -> bool { uv_cmds.is_set(*v) })
|
||||
.enumerate()
|
||||
.collect();
|
||||
let num_supp_cmds = supp_cmds.len();
|
||||
if num_supp_cmds == 0 {
|
||||
println!("None");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (n, cmd) in supp_cmds {
|
||||
print_uvdevice_cmd(cmd, f)?;
|
||||
if n != num_supp_cmds - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
writeln!(f)
|
||||
}
|
||||
impl Display for UvDeviceInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "uvdevice supports:")?;
|
||||
parse_flags(&self.supp_uvio_cmds, f)?;
|
||||
writeln!(f, "Ultravisor-calls available:")?;
|
||||
match &self.supp_uv_cmds {
|
||||
Some(cmds) => parse_flags(cmds, f),
|
||||
None => writeln!(f, "Data not available"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
misc::to_u16,
|
||||
request::{uvsecret::AddSecretMagic, MagicValue},
|
||||
uv::{uv_ioctl, UvCmd, UvDevice},
|
||||
Error, Result, PAGESIZE,
|
||||
};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::{Cursor, Read, Seek, Write},
|
||||
slice::Iter,
|
||||
vec::IntoIter,
|
||||
};
|
||||
use zerocopy::{AsBytes, FromBytes, U16, U32};
|
||||
|
||||
/// _List Secrets_ Ultravisor command.
|
||||
///
|
||||
/// The List Secrets Ultravisor call is used to list the
|
||||
/// secrets that are in the secret store for the current SE-guest.
|
||||
pub struct ListCmd(Vec<u8>);
|
||||
impl ListCmd {
|
||||
fn with_size(size: usize) -> Self {
|
||||
Self(vec![0; size])
|
||||
}
|
||||
|
||||
/// Create a new list secrets command with a one page capacity
|
||||
pub fn new() -> Self {
|
||||
Self::with_size(PAGESIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ListCmd {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl UvCmd for ListCmd {
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
Some(self.0.as_mut_slice())
|
||||
}
|
||||
|
||||
fn cmd(&self) -> u64 {
|
||||
uv_ioctl(UvDevice::LIST_SECRET_NR)
|
||||
}
|
||||
|
||||
fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// _Add Secret_ Ultravisor command.
|
||||
///
|
||||
/// The Add Secret Ultravisor-call is used to add a secret
|
||||
/// to the secret store for the current SE-guest.
|
||||
pub struct AddCmd(Vec<u8>);
|
||||
|
||||
impl AddCmd {
|
||||
/// Create a new Add Secret command using the provided data.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the provided data does not start with the
|
||||
/// ['crate::AddSecretRequest'] magic Value.
|
||||
pub fn new<R: Read>(bin_add_secret_req: &mut R) -> Result<Self> {
|
||||
let mut data = Vec::with_capacity(PAGESIZE);
|
||||
bin_add_secret_req.read_to_end(&mut data)?;
|
||||
|
||||
if !AddSecretMagic::starts_with_magic(&data[..6]) {
|
||||
return Err(Error::NoAsrcb);
|
||||
}
|
||||
Ok(Self(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl UvCmd for AddCmd {
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
Some(&mut self.0)
|
||||
}
|
||||
|
||||
fn cmd(&self) -> u64 {
|
||||
uv_ioctl(UvDevice::ADD_SECRET_NR)
|
||||
}
|
||||
|
||||
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||
match rc {
|
||||
0x0101 => Some("not allowed to modify the secret store"),
|
||||
0x0102 => Some("secret store locked"),
|
||||
0x0103 => Some("access exception when accessing request control block"),
|
||||
0x0104 => Some("unsupported add secret version"),
|
||||
0x0105 => Some("invalid request size"),
|
||||
0x0106 => Some("invalid number of host-keys"),
|
||||
0x0107 => Some("unsupported flags specified"),
|
||||
0x0108 => Some("unable to decrypt the request"),
|
||||
0x0109 => Some("unsupported secret provided"),
|
||||
0x010a => Some("invalid length for the specified secret"),
|
||||
0x010b => Some("secret store full"),
|
||||
0x010c => Some("unable to add secret"),
|
||||
0x010d => Some("dump in progress, try again later"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// _Lock Secret Store_ Ultravisor command.
|
||||
///
|
||||
/// The Lock Secret Store Ultravisor-call is used to block
|
||||
/// all changes to the secret store. Upon successful
|
||||
/// completion of a Lock Secret Store Ultravisor-call, any
|
||||
/// request to modify the secret store will fail.
|
||||
pub struct LockCmd;
|
||||
impl UvCmd for LockCmd {
|
||||
fn cmd(&self) -> u64 {
|
||||
uv_ioctl(UvDevice::LOCK_SECRET_NR)
|
||||
}
|
||||
|
||||
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||
match rc {
|
||||
0x0101 => Some("not allowed to modify the secret store"),
|
||||
0x0102 => Some("secret store already locked"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List of secrets used to parse the [`crate::uv::ListCmd`] result.
|
||||
///
|
||||
/// The list should not hold more than 0xffffffff elements
|
||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct SecretList {
|
||||
total_num_secrets: usize,
|
||||
secrets: Vec<SecretEntry>,
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a SecretList {
|
||||
type Item = &'a SecretEntry;
|
||||
type IntoIter = Iter<'a, SecretEntry>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for SecretList {
|
||||
type Item = SecretEntry;
|
||||
type IntoIter = IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.secrets.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<SecretEntry> for SecretList {
|
||||
fn from_iter<T: IntoIterator<Item = SecretEntry>>(iter: T) -> Self {
|
||||
let secrets: Vec<_> = iter.into_iter().collect();
|
||||
let total_num_secrets = secrets.len() as u16;
|
||||
Self::new(total_num_secrets, secrets)
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretList {
|
||||
/// Creates a new SecretList.
|
||||
///
|
||||
/// The content of this list will very liekly not represent the status of the guest in the
|
||||
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
|
||||
pub fn new(total_num_secrets: u16, secrets: Vec<SecretEntry>) -> Self {
|
||||
Self {
|
||||
total_num_secrets: total_num_secrets as usize,
|
||||
secrets,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the slice.
|
||||
///
|
||||
/// The iterator yields all secret entries from start to end.
|
||||
pub fn iter(&self) -> Iter<'_, SecretEntry> {
|
||||
self.secrets.iter()
|
||||
}
|
||||
|
||||
/// Returns the length of this [`SecretList`].
|
||||
pub fn len(&self) -> usize {
|
||||
self.secrets.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`SecretList`] contains no [`SecretEntry`].
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.secrets.is_empty()
|
||||
}
|
||||
|
||||
/// Reports the number of secrets stored in UV.
|
||||
///
|
||||
/// This number may be not equal to the provided number of [`SecretEntry`]
|
||||
pub fn total_num_secrets(&self) -> usize {
|
||||
self.total_num_secrets
|
||||
}
|
||||
|
||||
/// Encodes the list in the same binary format the UV would do
|
||||
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
|
||||
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
|
||||
w.write_u16::<BigEndian>(num_s)?;
|
||||
w.write_u16::<BigEndian>(
|
||||
self.total_num_secrets
|
||||
.try_into()
|
||||
.map_err(|_| Error::ManySecrets)?,
|
||||
)?;
|
||||
w.write_all(&[0u8; 12])?;
|
||||
for secret in &self.secrets {
|
||||
w.write_all(secret.as_bytes())?;
|
||||
}
|
||||
w.flush().map_err(Error::Io)
|
||||
}
|
||||
|
||||
/// Decodes the list from the binary format of the UV into this internal representation
|
||||
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
|
||||
let num_s = r.read_u16::<BigEndian>()?;
|
||||
let total_num_secrets = r.read_u16::<BigEndian>()? as usize;
|
||||
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
|
||||
r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes
|
||||
let mut buf = [0u8; SecretEntry::STRUCT_SIZE];
|
||||
for _ in 0..num_s {
|
||||
r.read_exact(&mut buf)?;
|
||||
//cannot fail. buffer has the same size as the secret entry
|
||||
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
|
||||
v.push(secr);
|
||||
}
|
||||
Ok(Self {
|
||||
total_num_secrets,
|
||||
secrets: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ListCmd> for SecretList {
|
||||
type Error = Error;
|
||||
fn try_from(mut list: ListCmd) -> Result<SecretList> {
|
||||
SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SecretList {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
|
||||
if !self.secrets.is_empty() {
|
||||
writeln!(f)?;
|
||||
}
|
||||
for s in &self.secrets {
|
||||
writeln!(f, "{s}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn ser_u32<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
|
||||
ser.serialize_u32(v.get())
|
||||
}
|
||||
|
||||
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
|
||||
ser.serialize_u16(v.get())
|
||||
}
|
||||
|
||||
/// Secret types that can appear in a [`SecretList`]
|
||||
#[non_exhaustive]
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum ListableSecretType {
|
||||
/// Association Secret
|
||||
Association,
|
||||
/// Invalid secret type, that should never appear in a list
|
||||
///
|
||||
/// 0 is reserved
|
||||
/// 1 is Null secret, with no id and not listable
|
||||
Invalid(u16),
|
||||
/// Unknown secret type
|
||||
Unknown(u16),
|
||||
}
|
||||
impl ListableSecretType {
|
||||
const RESERVED_0: u16 = 0x0000;
|
||||
const NULL: u16 = 0x0001;
|
||||
const ASSOCIATION: u16 = 0x0002;
|
||||
}
|
||||
|
||||
impl Display for ListableSecretType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Association => write!(f, "Association"),
|
||||
Self::Invalid(n) => write!(f, "Invalid({n})"),
|
||||
Self::Unknown(n) => write!(f, "Unknown({n})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U16<BigEndian>> for ListableSecretType {
|
||||
fn from(value: U16<BigEndian>) -> Self {
|
||||
match value.get() {
|
||||
Self::RESERVED_0 => Self::Invalid(Self::RESERVED_0),
|
||||
Self::NULL => Self::Invalid(Self::NULL),
|
||||
Self::ASSOCIATION => ListableSecretType::Association,
|
||||
n => Self::Unknown(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ListableSecretType> for U16<BigEndian> {
|
||||
fn from(value: ListableSecretType) -> Self {
|
||||
match value {
|
||||
ListableSecretType::Association => ListableSecretType::ASSOCIATION,
|
||||
ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub const SECRET_ID_SIZE: usize = 32;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut s = String::with_capacity(32 * 2 + 2);
|
||||
s.push_str("0x");
|
||||
let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
|
||||
ser.serialize_str(&s)
|
||||
}
|
||||
|
||||
/// A secret in a [`SecretList`]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, PartialEq, Eq, AsBytes, FromBytes, Serialize)]
|
||||
pub struct SecretEntry {
|
||||
#[serde(serialize_with = "ser_u16")]
|
||||
index: U16<BigEndian>,
|
||||
#[serde(serialize_with = "ser_u16")]
|
||||
stype: U16<BigEndian>,
|
||||
#[serde(serialize_with = "ser_u32")]
|
||||
len: U32<BigEndian>,
|
||||
#[serde(skip)]
|
||||
res_8: u64,
|
||||
#[serde(serialize_with = "ser_gsid")]
|
||||
id: [u8; SECRET_ID_SIZE],
|
||||
}
|
||||
assert_size!(SecretEntry, SecretEntry::STRUCT_SIZE);
|
||||
|
||||
impl SecretEntry {
|
||||
const STRUCT_SIZE: usize = 0x30;
|
||||
|
||||
/// Create a new entry for a [`SecretList`].
|
||||
///
|
||||
/// The content of this entry will very liekly not represent the status of the guest in the
|
||||
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
|
||||
pub fn new(index: u16, stype: ListableSecretType, id: [u8; 32], secret_len: u32) -> Self {
|
||||
Self {
|
||||
index: index.into(),
|
||||
stype: stype.into(),
|
||||
len: secret_len.into(),
|
||||
res_8: 0,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index of this [`SecretEntry`].
|
||||
pub fn index(&self) -> u16 {
|
||||
self.index.get()
|
||||
}
|
||||
|
||||
/// Returns the secret type of this [`SecretEntry`].
|
||||
pub fn stype(&self) -> ListableSecretType {
|
||||
self.stype.into()
|
||||
}
|
||||
|
||||
/// Returns a reference to the id of this [`SecretEntry`].
|
||||
pub fn id(&self) -> &[u8] {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SecretEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let stype: ListableSecretType = self.stype.into();
|
||||
writeln!(f, "{} {}:", self.index, stype)?;
|
||||
write!(f, " ")?;
|
||||
for b in self.id {
|
||||
write!(f, "{b:02x}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
use std::io::{BufReader, BufWriter, Cursor};
|
||||
|
||||
#[test]
|
||||
fn dump_secret_entry() {
|
||||
const EXP: &[u8] = &[
|
||||
0x00, 0x01, 0x00, 0x02, //idx + type
|
||||
0x00, 0x00, 0x00, 0x20, //len
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
// id
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let s = SecretEntry {
|
||||
index: 1.into(),
|
||||
stype: 2.into(),
|
||||
len: 32.into(),
|
||||
res_8: 0,
|
||||
id: [0; 32],
|
||||
};
|
||||
|
||||
assert_eq!(s.as_bytes(), EXP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_list_dec() {
|
||||
let buf = [
|
||||
0x00u8, 0x01, // num secr stored
|
||||
0x01, 0x12, // total num secrets
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
|
||||
// secret
|
||||
0x00, 0x01, 0x00, 0x02, //idx + type
|
||||
0x00, 0x00, 0x00, 0x20, //len
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
// id
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let exp = SecretList {
|
||||
total_num_secrets: 0x112,
|
||||
secrets: vec![SecretEntry {
|
||||
index: 1.into(),
|
||||
stype: 2.into(),
|
||||
len: 32.into(),
|
||||
res_8: 0,
|
||||
id: [0; 32],
|
||||
}],
|
||||
};
|
||||
|
||||
let mut br = BufReader::new(Cursor::new(buf));
|
||||
let sl = SecretList::decode(&mut br).unwrap();
|
||||
assert_eq!(sl, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_list_enc() {
|
||||
const EXP: &[u8] = &[
|
||||
0x00, 0x01, // num secr stored
|
||||
0x01, 0x12, // total num secrets
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
|
||||
// secret
|
||||
0x00, 0x01, 0x00, 0x02, //idx + type
|
||||
0x00, 0x00, 0x00, 0x20, //len
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
// id
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let sl = SecretList {
|
||||
total_num_secrets: 0x112,
|
||||
secrets: vec![SecretEntry {
|
||||
index: 1.into(),
|
||||
stype: 2.into(),
|
||||
len: 32.into(),
|
||||
res_8: 0,
|
||||
id: [0; 32],
|
||||
}],
|
||||
};
|
||||
|
||||
let mut buf = [0u8; 0x40];
|
||||
{
|
||||
let mut bw = BufWriter::new(&mut buf[..]);
|
||||
sl.encode(&mut bw).unwrap();
|
||||
}
|
||||
println!("list: {sl:?}");
|
||||
assert_eq!(buf, EXP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::{
|
||||
os::unix::prelude::FromRawFd,
|
||||
sync::{Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
/// needed to serialize all tests as tests operate on static data required by the mock
|
||||
static ref TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||
/// exists to have a lazy static mod variable
|
||||
static ref IOCTL_MTX: Mutex<IoctlCtx> = Mutex::new(IoctlCtx::new());
|
||||
}
|
||||
|
||||
fn get_lock<T>(m: &'static Mutex<T>) -> MutexGuard<'static, T> {
|
||||
match m.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(poisoned) => poisoned.into_inner(),
|
||||
}
|
||||
}
|
||||
|
||||
struct IoctlCtx {
|
||||
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> i32 + Send + Sync>,
|
||||
exp_cmd: ::libc::c_ulong,
|
||||
called: bool,
|
||||
}
|
||||
|
||||
impl IoctlCtx {
|
||||
pub fn exp_cmd(&mut self, cmd: ::libc::c_ulong) -> &mut Self {
|
||||
self.exp_cmd = cmd;
|
||||
self
|
||||
}
|
||||
pub fn set_mdfy<F>(&mut self, mdfy: F) -> &mut Self
|
||||
where
|
||||
F: FnMut(&mut ffi::uvio_ioctl_cb) -> ::libc::c_int + 'static + Send + Sync,
|
||||
{
|
||||
self.modify = Box::new(mdfy);
|
||||
self
|
||||
}
|
||||
pub fn reset(&mut self) -> bool {
|
||||
let old = self.called;
|
||||
self.called = false;
|
||||
old
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
modify: Box::new(|_| -1),
|
||||
exp_cmd: 0,
|
||||
called: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mock_libc {
|
||||
use super::*;
|
||||
|
||||
pub unsafe fn ioctl(
|
||||
fd: ::libc::c_int,
|
||||
cmd: ::libc::c_ulong,
|
||||
data: *mut ffi::uvio_ioctl_cb,
|
||||
) -> ::libc::c_int {
|
||||
let mut ctx = get_lock(&IOCTL_MTX);
|
||||
assert!(!ctx.called, "IOCTL called more than once");
|
||||
ctx.called = true;
|
||||
|
||||
assert_eq!(cmd, ctx.exp_cmd, "IOCTL cmd mismatch");
|
||||
assert_eq!(fd, 17, "IOCTL fd mismatch");
|
||||
|
||||
let data_ref: &mut ffi::uvio_ioctl_cb = &mut *data;
|
||||
|
||||
(ctx.modify)(data_ref)
|
||||
}
|
||||
}
|
||||
|
||||
impl ffi::uvio_ioctl_cb {
|
||||
fn addr_eq(&self, exp: u64) -> &Self {
|
||||
assert_eq!(
|
||||
self.argument_addr, exp,
|
||||
"ioctl arg addr not eq: {} == {}",
|
||||
self.argument_addr, exp
|
||||
);
|
||||
self
|
||||
}
|
||||
fn size_eq(&self, exp: u32) -> &Self {
|
||||
assert_eq!(
|
||||
self.argument_len, exp,
|
||||
"ioctl arg len not eq: {} == {}",
|
||||
self.argument_len, exp
|
||||
);
|
||||
self
|
||||
}
|
||||
fn set_rc(&mut self, rc: u16) -> &mut Self {
|
||||
self.uv_rc = rc;
|
||||
self
|
||||
}
|
||||
fn set_rrc(&mut self, rrc: u16) -> &mut Self {
|
||||
self.uv_rrc = rrc;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_CMD: u64 = 17;
|
||||
struct TestCmd(Option<Vec<u8>>);
|
||||
impl UvCmd for TestCmd {
|
||||
fn cmd(&self) -> u64 {
|
||||
TEST_CMD
|
||||
}
|
||||
fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
match &mut self.0 {
|
||||
None => None,
|
||||
Some(d) => Some(d.as_mut_slice()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UvDevice {
|
||||
///use some random fd for `uvdevice` its OK, as the ioctl is mocked and never touches the passed file
|
||||
fn test_dev() -> Self {
|
||||
UvDevice(unsafe { std::fs::File::from_raw_fd(17) })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_fail() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let mut mock_cmd = TestCmd(None);
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|_| -1);
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert!(matches!(res, Err(Error::Io(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_simpleo() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let mut mock_cmd = TestCmd(None);
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| {
|
||||
cb.set_rc(1).addr_eq(0).size_eq(0);
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_simple_err() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let mut mock_cmd = TestCmd(None);
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| {
|
||||
cb.set_rc(17).set_rrc(3).addr_eq(0).size_eq(0);
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert!(matches!(res, Err(Error::Uv{rc, rrc, ..}) if rc == 17 && rrc == 3 ));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_write_data() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let cmd_data = vec![0u8; 32];
|
||||
let cmd_data_len = cmd_data.len();
|
||||
let data_addr = cmd_data.as_ptr() as u64;
|
||||
|
||||
let mut mock_cmd = TestCmd(Some(cmd_data));
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| {
|
||||
cb.set_rc(1).addr_eq(data_addr).size_eq(32);
|
||||
unsafe {
|
||||
::libc::memset(cb.argument_addr as *mut ::libc::c_void, 0x42, cmd_data_len);
|
||||
}
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_read_data() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let cmd_data = vec![42u8; 32];
|
||||
let cmd_data_len = cmd_data.len();
|
||||
let data_addr = cmd_data.as_ptr() as u64;
|
||||
let data_exp = cmd_data.clone();
|
||||
|
||||
let mut mock_cmd = TestCmd(Some(cmd_data));
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| {
|
||||
cb.set_rc(1).addr_eq(data_addr).size_eq(32);
|
||||
unsafe {
|
||||
let data = std::slice::from_raw_parts(cb.argument_addr as *const u8, cmd_data_len);
|
||||
assert_eq!(data, data_exp);
|
||||
}
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::{
|
||||
misc::to_u16,
|
||||
request::MagicValue,
|
||||
uv::{ListCmd, UvCmd},
|
||||
Error, Result,
|
||||
};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::{Cursor, Read, Seek, Write},
|
||||
};
|
||||
use zerocopy::{AsBytes, FromBytes, U16, U32};
|
||||
|
||||
/// The magic value used to identify an add-secret request`]
|
||||
///
|
||||
/// The magic value is ASCII:
|
||||
/// ```rust
|
||||
/// # use pv_core::request::uvsecret::AddSecretMagic;
|
||||
/// # use pv_core::request::MagicValue;
|
||||
/// # fn main() {
|
||||
/// # let magic =
|
||||
/// b"asrcbM"
|
||||
/// # ;
|
||||
/// # assert!(AddSecretMagic::starts_with_magic(magic));
|
||||
/// # }
|
||||
///```
|
||||
///
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, AsBytes)]
|
||||
pub struct AddSecretMagic {
|
||||
magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]
|
||||
tp: UserDataType,
|
||||
}
|
||||
|
||||
impl MagicValue<6> for AddSecretMagic {
|
||||
// "asrcbM"
|
||||
const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D];
|
||||
}
|
||||
|
||||
/// Types of (non architectured) user data for an add-secret request
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, zerocopy::AsBytes)]
|
||||
pub enum UserDataType {
|
||||
/// Marker that the request does not contain any user data
|
||||
Null = 0x0000,
|
||||
}
|
||||
|
||||
impl From<UserDataType> for AddSecretMagic {
|
||||
fn from(tp: UserDataType) -> Self {
|
||||
Self {
|
||||
magic: Self::MAGIC,
|
||||
tp,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user