use alloc::string::String;
use alloc::vec::Vec;
use log::{debug, error};
use uguid::Guid;
use zerocopy::byteorder::little_endian::{U32, U64};
use zerocopy::{FromBytes, Immutable, IntoBytes, Unalign, Unaligned};
use uefi::{Error, Status};
use crate::mm::util;
use crate::policy::{VariablePolicyEntry, VariablePolicyType};
use crate::store::EfiVarStore;
const VAR_CHECK_POLICY_COMMAND_DISABLE: u32 = 0x01;
const VAR_CHECK_POLICY_COMMAND_IS_ENABLED: u32 = 0x02;
const VAR_CHECK_POLICY_COMMAND_REGISTER: u32 = 0x03;
const VAR_CHECK_POLICY_COMMAND_LOCK: u32 = 0x05;
#[repr(C, packed)]
#[derive(Debug, FromBytes, IntoBytes, Immutable, Unaligned, Clone)]
struct MmVarCheckPolicy {
signature: u32,
revision: u32,
command: Unalign<U32>,
result: Unalign<U64>,
}
impl MmVarCheckPolicy {
pub fn new(data: &[u8]) -> Result<(Self, &[u8]), Error<&'static str>> {
Self::read_from_prefix(data).or(Err(Error::new(
Status::BAD_BUFFER_SIZE,
"read MmVarCheckPolicy",
)))
}
}
#[repr(C, packed)]
#[derive(Debug, IntoBytes, Immutable)]
struct MmVarCheckPolicyEnabled {
state: u8,
}
const VARIABLE_POLICY_ENTRY_REVISION: u32 = 0x00010000;
const VARIABLE_POLICY_TYPE_NO_LOCK: u8 = 0;
const VARIABLE_POLICY_TYPE_LOCK_NOW: u8 = 1;
const VARIABLE_POLICY_TYPE_LOCK_ON_CREATE: u8 = 2;
const VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE: u8 = 3;
#[repr(C, packed)]
#[derive(Debug, FromBytes)]
struct MmVariablePolicyEntry {
version: u32,
size: u16,
offset_to_name: u16,
namespace: [u8; 16],
min_size: u32,
max_size: u32,
attr_must_have: u32,
attr_cant_have: u32,
lock_policy_type: u8,
_padding: [u8; 3],
}
impl MmVariablePolicyEntry {
pub fn new(data: &[u8]) -> Result<(Self, &[u8]), Error<&'static str>> {
Self::read_from_prefix(data).or(Err(Error::new(
Status::BAD_BUFFER_SIZE,
"read VariablePolicyEntry",
)))
}
}
#[repr(C, packed)]
#[derive(Debug, FromBytes)]
struct MmVariableLockOnVarState {
namespace: [u8; 16],
value: u8,
_padding: u8,
}
impl MmVariableLockOnVarState {
pub fn new(data: &[u8]) -> Result<(Self, &[u8]), Error<&'static str>> {
Self::read_from_prefix(data).or(Err(Error::new(
Status::BAD_BUFFER_SIZE,
"read VariableLockOnVarState",
)))
}
}
#[derive(Debug)]
pub struct MmVarPolicyRequest<'a> {
hdr: MmVarCheckPolicy,
pub body: &'a [u8],
}
impl<'a> MmVarPolicyRequest<'a> {
pub fn new(data: &'a [u8]) -> Result<Self, Error<&'static str>> {
let (hdr, body) = MmVarCheckPolicy::new(data)?;
Ok(MmVarPolicyRequest { hdr, body })
}
fn response(&self, status: Status, _reply: Option<&[u8]>) -> Vec<u8> {
debug!("policy/rsp: {status}");
let mut hdr = self.hdr.clone();
hdr.result.set((status.0 as u64).into());
let rsp = hdr.as_bytes().to_vec();
rsp
}
fn parse_entry(&self) -> Result<VariablePolicyEntry, Error<&'static str>> {
let (policy, _) = MmVariablePolicyEntry::new(self.body)?;
if policy.version != VARIABLE_POLICY_ENTRY_REVISION {
return Err(Error::new(Status::UNSUPPORTED, "unknown policy version"));
}
let pst = core::mem::size_of::<MmVariablePolicyEntry>();
let nst = policy.offset_to_name as usize;
let end = policy.size as usize;
let Some(slice) = &self.body.get(nst..end) else {
return Err(Error::new(Status::BAD_BUFFER_SIZE, "invalid name size"));
};
let name = util::cstr16_from_bytes_with_nul(slice)?;
let policy_type = match policy.lock_policy_type {
VARIABLE_POLICY_TYPE_NO_LOCK => VariablePolicyType::NoLock,
VARIABLE_POLICY_TYPE_LOCK_NOW => VariablePolicyType::LockNow,
VARIABLE_POLICY_TYPE_LOCK_ON_CREATE => VariablePolicyType::LockOnCreate,
VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE => {
let Some(slice) = &self.body.get(pst..nst) else {
return Err(Error::new(
Status::BAD_BUFFER_SIZE,
"invalid var state size",
));
};
let (vstate, more) = MmVariableLockOnVarState::new(slice)?;
let vname = util::cstr16_from_bytes_with_nul(more)?;
VariablePolicyType::LockOnVarState {
namespace: Guid::from_bytes(vstate.namespace),
name: String::from(vname),
value: vstate.value,
}
}
_ => {
return Err(Error::new(
Status::INVALID_PARAMETER,
"invalid variable policy type",
))
}
};
Ok(VariablePolicyEntry {
namespace: Guid::from_bytes(policy.namespace),
name: String::from(name),
min_size: policy.min_size,
max_size: policy.max_size,
attr_must_have: policy.attr_must_have.into(),
attr_cant_have: policy.attr_cant_have.into(),
lock_policy_type: policy_type,
})
}
fn process(&self, store: &mut EfiVarStore) -> Vec<u8> {
let cmd: u32 = self.hdr.command.get().into();
match cmd {
VAR_CHECK_POLICY_COMMAND_DISABLE => {
debug!("policy/req/disable");
self.response(Status::UNSUPPORTED, None)
}
VAR_CHECK_POLICY_COMMAND_IS_ENABLED => {
let enabled = MmVarCheckPolicyEnabled { state: 1 };
self.response(Status::SUCCESS, Some(enabled.as_bytes()))
}
VAR_CHECK_POLICY_COMMAND_REGISTER => {
let res = self.parse_entry();
if let Err(e) = res {
return self.response(e.status(), None);
}
let policy = res.unwrap();
debug!("policy/req/register: {policy}");
let res = store.policy_register(policy);
match res {
Err(e) => self.response(e.status(), None),
Ok(_) => self.response(Status::SUCCESS, None),
}
}
VAR_CHECK_POLICY_COMMAND_LOCK => {
debug!("policy/req/lock");
store.policy_lock();
self.response(Status::SUCCESS, None)
}
_ => {
debug!("policy/req: unsupported command 0x{cmd:x}");
self.response(Status::UNSUPPORTED, None)
}
}
}
}
pub fn policy_request(store: &mut EfiVarStore, req: &[u8]) -> Vec<u8> {
let res = MmVarPolicyRequest::new(req);
if let Err(e) = res.as_ref() {
error!("{e}");
return Vec::new();
}
let vreq = res.unwrap();
vreq.process(store)
}