virtfw-varstore 0.2.0

efi variable store
Documentation
#![allow(dead_code)] // temporary

//!
//! [WIP] VarCheckPolicyLibMmiHandler + Edk2VariablePolicyProtocol reimplementation
//!
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, IntoBytes, Unalign};
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, Unaligned};

use uefi::{Error, Status};
use virtfw_libefi::efivar::types::EfiVarAttr;

use crate::mm::util;
use crate::store::EfiVarStore;

// ----- VarCheckPolicyLibMmiHandler structs -----

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_DUMP: u32 = 0x04;
const VAR_CHECK_POLICY_COMMAND_LOCK: u32 = 0x05;

// VAR_CHECK_POLICY_COMM_HEADER
#[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",
        )))
    }
}

// VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS
#[repr(C, packed)]
#[derive(Debug, IntoBytes, Immutable)]
struct MmVarCheckPolicyEnabled {
    state: u8,
}

// ----- Edk2VariablePolicyProtocol structs -----

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;

// VARIABLE_POLICY_ENTRY
#[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],
    // LockPolicy
    // Name
}

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",
        )))
    }
}

// VARIABLE_LOCK_ON_VAR_STATE_POLICY
#[repr(C, packed)]
#[derive(Debug, FromBytes)]
struct MmVariableLockOnVarState {
    namespace: [u8; 16],
    value: u8,
    _padding: u8,
    // Name
}

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",
        )))
    }
}

// ----- implementation structs -----

#[derive(Debug)]
pub enum VariablePolicyType {
    NoLock,
    LockNow,
    LockOnCreate,
    LockOnVarState {
        namespace: Guid,
        name: String,
        value: u8,
    },
}

#[derive(Debug)]
pub struct VariablePolicyEntry {
    pub namespace: Guid,
    pub name: String,
    pub min_size: u32,
    pub max_size: u32,
    pub attr_must_have: EfiVarAttr,
    pub attr_cant_have: EfiVarAttr,
    pub lock_policy_type: VariablePolicyType,
}

impl VariablePolicyEntry {
    pub fn new(data: &[u8]) -> Result<Self, Error<&'static str>> {
        let (policy, _) = MmVariablePolicyEntry::new(data)?;
        let pst = core::mem::size_of::<MmVariablePolicyEntry>();
        let nst = policy.offset_to_name as usize;
        let end = policy.size as usize;
        let name = util::cstr16_from_bytes_with_nul(&data[nst..end])?;
        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 (vstate, more) = MmVariableLockOnVarState::new(&data[pst..nst])?;
                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,
        })
    }
}

impl core::fmt::Display for VariablePolicyEntry {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "policy")?;
        write!(f, " name={}", self.name)?;
        write!(f, " size=0x{:x}-0x{:x}", self.min_size, self.max_size)?;
        write!(f, " must={}", self.attr_must_have)?;
        write!(f, " cant={}", self.attr_cant_have)?;
        write!(f, " type={:?}", self.lock_policy_type)?;
        Ok(())
    }
}

#[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 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 [TODO]");
                self.response(Status::SUCCESS, None)
            }
            VAR_CHECK_POLICY_COMMAND_IS_ENABLED => {
                debug!("policy/req: is-enabled [TODO]");
                let enabled = MmVarCheckPolicyEnabled {
                    state: 0, // disabled
                };
                self.response(Status::SUCCESS, Some(enabled.as_bytes()))
            }
            VAR_CHECK_POLICY_COMMAND_REGISTER => {
                debug!("policy/req: register [TODO]");
                let res = VariablePolicyEntry::new(self.body);
                if let Err(e) = res {
                    return self.response(e.status(), None);
                }
                let policy = res.unwrap();
                debug!("{}", policy);
                // TODO: register actually
                self.response(Status::SUCCESS, None)
            }
            VAR_CHECK_POLICY_COMMAND_LOCK => {
                debug!("policy/req: lock [TODO]");
                self.response(Status::SUCCESS, None)
            }
            _ => {
                debug!("policy/req: unsupported command 0x{:x}", cmd);
                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)
}