Skip to main content

virtfw_varstore/mm/
variable.rs

1//!
2//! EfiSmmVariableProtocol reimplementation
3//!
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::fmt::Display;
7use core::mem::size_of;
8use log::{debug, error};
9use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
10
11use uefi::data_types::FromStrError;
12use uefi::{CString16, Error, Status};
13use virtfw_libefi::efivar::types::{EfiVar, EfiVarAttr, EfiVarId};
14use virtfw_libefi::guids::RawGuid;
15
16use crate::mm::core::MmCoreHeader;
17use crate::mm::util;
18use crate::store::EfiVarStore;
19
20const GET_VARIABLE: u64 = 1;
21const GET_NEXT_VARIABLE_NAME: u64 = 2;
22const SET_VARIABLE: u64 = 3;
23const QUERY_VARIABLE_INFO: u64 = 4;
24const READY_TO_BOOT: u64 = 5;
25const EXIT_BOOT_SERVICE: u64 = 6;
26const LOCK_VARIABLE: u64 = 8;
27const GET_PAYLOAD_SIZE: u64 = 11;
28
29// SMM_VARIABLE_COMMUNICATE_HEADER
30#[repr(C)]
31#[derive(Debug, FromBytes, IntoBytes, Immutable, KnownLayout)]
32pub struct MmVariableHeader {
33    pub function: u64,
34    pub status: u64,
35}
36
37impl MmVariableHeader {
38    pub fn from_data(data: &[u8]) -> Result<(Self, &[u8]), Error<&'static str>> {
39        Self::read_from_prefix(data).or(Err(Error::new(
40            Status::BAD_BUFFER_SIZE,
41            "read MmVariableHeader",
42        )))
43    }
44
45    pub fn new(nr: u64, status: Option<Status>) -> Self {
46        Self {
47            function: nr,
48            status: status.unwrap_or(Status::SUCCESS).0 as u64,
49        }
50    }
51}
52
53// SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE
54#[repr(C, packed)]
55#[derive(Debug, FromBytes, IntoBytes, Immutable, KnownLayout)]
56struct MmVariableAccess {
57    guid: RawGuid,
58    data_size: u64,
59    name_size: u64,
60    attributes: EfiVarAttr,
61    // Name
62    // Data
63}
64
65impl MmVariableAccess {
66    fn from_data(data: &[u8]) -> Result<(Self, &[u8]), Error<&'static str>> {
67        Self::read_from_prefix(data).or(Err(Error::new(
68            Status::BAD_BUFFER_SIZE,
69            "read MmVariableAccess",
70        )))
71    }
72}
73
74// SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME
75#[repr(C, packed)]
76#[derive(Debug, FromBytes, IntoBytes, Immutable, KnownLayout)]
77struct MmNextVariable {
78    guid: RawGuid,
79    name_size: u64,
80    // Name
81}
82
83impl MmNextVariable {
84    fn from_data(data: &[u8]) -> Result<(Self, &[u8]), Error<&'static str>> {
85        Self::read_from_prefix(data).or(Err(Error::new(
86            Status::BAD_BUFFER_SIZE,
87            "read MmNextVariable",
88        )))
89    }
90}
91
92// SMM_VARIABLE_COMMUNICATE_QUERY_VARIABLE_INFO
93#[repr(C, packed)]
94#[derive(Debug, FromBytes, IntoBytes, Immutable, KnownLayout)]
95struct MmVariableInfo {
96    max_storage_size: u64,
97    free_storage_size: u64,
98    max_variable_size: u64,
99    attributes: u32,
100}
101
102// SMM_VARIABLE_COMMUNICATE_GET_PAYLOAD_SIZE
103#[repr(C, packed)]
104#[derive(Debug, FromBytes, IntoBytes, Immutable, KnownLayout)]
105struct MmGetPayloadSize {
106    payload_size: u64,
107}
108
109// parsed request
110#[derive(Debug, Eq, PartialEq)]
111pub enum MmVariableParsedRequest {
112    GetVariable(EfiVarId),
113    GetNextVariableName(EfiVarId),
114    SetVariable(EfiVar),
115    QueryVariableInfo,
116    ReadyToBoot,
117    ExitBootService,
118    LockVariable(EfiVarId),
119    GetPayloadSize,
120    Unknown { function: u64 },
121}
122
123impl MmVariableParsedRequest {
124    fn parse_access_id(body: &[u8]) -> Result<EfiVarId, Error<&'static str>> {
125        let (access, namedata) = MmVariableAccess::from_data(body)?;
126        let ns = access.name_size as usize;
127        let Some(slice) = &namedata.get(0..ns) else {
128            return Err(Error::new(Status::BAD_BUFFER_SIZE, "invalid name size"));
129        };
130        let name = util::cstr16_from_bytes_with_nul(slice)?;
131        let guid = access.guid.into();
132        let id = EfiVarId::new(String::from(name), guid);
133        Ok(id)
134    }
135
136    fn parse_access_var(body: &[u8]) -> Result<EfiVar, Error<&'static str>> {
137        let (access, namedata) = MmVariableAccess::from_data(body)?;
138        let ns = access.name_size as usize;
139        let ds = access.data_size as usize;
140        let Some(slice) = &namedata.get(0..ns) else {
141            return Err(Error::new(Status::BAD_BUFFER_SIZE, "invalid name size"));
142        };
143        let name = util::cstr16_from_bytes_with_nul(slice)?;
144        let Some(data) = &namedata.get(ns..ns + ds) else {
145            return Err(Error::new(Status::BAD_BUFFER_SIZE, "invalid data size"));
146        };
147        let guid = access.guid.into();
148        let id = EfiVarId::new(String::from(name), guid);
149        let var = EfiVar::new_from_slice(id, access.attributes, data);
150        Ok(var)
151    }
152
153    fn parse_next(body: &[u8]) -> Result<EfiVarId, Error<&'static str>> {
154        let (next, namebytes) = MmNextVariable::from_data(body)?;
155        let ns = next.name_size as usize;
156        let Some(slice) = &namebytes.get(0..ns) else {
157            return Err(Error::new(Status::BAD_BUFFER_SIZE, "invalid name size"));
158        };
159        let name = util::cstr16_from_bytes_until_nul(slice)?;
160        let guid = next.guid.into();
161        let id = EfiVarId::new(String::from(name), guid);
162        Ok(id)
163    }
164
165    pub fn new(function: u64, body: &[u8]) -> Result<Self, Error<&'static str>> {
166        let req = match function {
167            GET_VARIABLE => {
168                let id = MmVariableParsedRequest::parse_access_id(body)?;
169                MmVariableParsedRequest::GetVariable(id)
170            }
171            GET_NEXT_VARIABLE_NAME | LOCK_VARIABLE => {
172                let id = MmVariableParsedRequest::parse_next(body)?;
173                if function == GET_NEXT_VARIABLE_NAME {
174                    MmVariableParsedRequest::GetNextVariableName(id)
175                } else {
176                    MmVariableParsedRequest::LockVariable(id)
177                }
178            }
179            SET_VARIABLE => {
180                let var = MmVariableParsedRequest::parse_access_var(body)?;
181                MmVariableParsedRequest::SetVariable(var)
182            }
183            QUERY_VARIABLE_INFO => MmVariableParsedRequest::QueryVariableInfo,
184            READY_TO_BOOT => MmVariableParsedRequest::ReadyToBoot,
185            EXIT_BOOT_SERVICE => MmVariableParsedRequest::ExitBootService,
186            GET_PAYLOAD_SIZE => MmVariableParsedRequest::GetPayloadSize,
187            _ => MmVariableParsedRequest::Unknown { function },
188        };
189        Ok(req)
190    }
191
192    // MmVariableAccess (name only)
193    fn serialize_access_id(id: &EfiVarId) -> Result<Vec<u8>, FromStrError> {
194        let name = CString16::try_from(id.name())?;
195        let access = MmVariableAccess {
196            guid: id.guid().into(),
197            attributes: EfiVarAttr::new(),
198            name_size: name.num_bytes() as u64,
199            data_size: 0,
200        };
201        let mut blob = Vec::new();
202        blob.extend_from_slice(access.as_bytes());
203        blob.extend_from_slice(name.as_bytes());
204        Ok(blob)
205    }
206
207    // MmVariableAccess (name+data)
208    fn serialize_access_var(var: &EfiVar) -> Result<Vec<u8>, FromStrError> {
209        let name = CString16::try_from(var.name())?;
210        let access = MmVariableAccess {
211            guid: var.guid().into(),
212            attributes: var.attr(),
213            name_size: name.num_bytes() as u64,
214            data_size: var.data().len() as u64,
215        };
216        let mut blob = Vec::new();
217        blob.extend_from_slice(access.as_bytes());
218        blob.extend_from_slice(name.as_bytes());
219        blob.extend_from_slice(var.data().as_bytes());
220        Ok(blob)
221    }
222
223    // MmNextVariable
224    fn serialize_next(id: &EfiVarId) -> Result<Vec<u8>, FromStrError> {
225        let name = CString16::try_from(id.name())?;
226        let next = MmNextVariable {
227            guid: id.guid().into(),
228            name_size: name.num_bytes() as u64,
229        };
230        let mut blob = Vec::new();
231        blob.extend_from_slice(next.as_bytes());
232        blob.extend_from_slice(name.as_bytes());
233        Ok(blob)
234    }
235}
236
237impl TryFrom<&MmVariableParsedRequest> for Vec<u8> {
238    type Error = FromStrError;
239    fn try_from(value: &MmVariableParsedRequest) -> Result<Vec<u8>, FromStrError> {
240        let (function, data) = match value {
241            MmVariableParsedRequest::GetVariable(id) => (
242                GET_VARIABLE,
243                MmVariableParsedRequest::serialize_access_id(id)?,
244            ),
245            MmVariableParsedRequest::SetVariable(var) => (
246                SET_VARIABLE,
247                MmVariableParsedRequest::serialize_access_var(var)?,
248            ),
249            MmVariableParsedRequest::GetNextVariableName(id) => (
250                GET_NEXT_VARIABLE_NAME,
251                MmVariableParsedRequest::serialize_next(id)?,
252            ),
253            MmVariableParsedRequest::LockVariable(id) => {
254                (LOCK_VARIABLE, MmVariableParsedRequest::serialize_next(id)?)
255            }
256            MmVariableParsedRequest::QueryVariableInfo => (QUERY_VARIABLE_INFO, Vec::new()),
257            MmVariableParsedRequest::ReadyToBoot => (READY_TO_BOOT, Vec::new()),
258            MmVariableParsedRequest::ExitBootService => (EXIT_BOOT_SERVICE, Vec::new()),
259            MmVariableParsedRequest::GetPayloadSize => (GET_PAYLOAD_SIZE, Vec::new()),
260            MmVariableParsedRequest::Unknown { function } => (*function, Vec::new()),
261        };
262
263        let hdr = MmVariableHeader::new(function, None);
264        let mut blob = Vec::new();
265        blob.extend_from_slice(hdr.as_bytes());
266        blob.extend_from_slice(&data);
267        Ok(blob)
268    }
269}
270
271impl Display for MmVariableParsedRequest {
272    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
273        match self {
274            MmVariableParsedRequest::GetVariable(id) => {
275                write!(f, "request/var: get-variable {}", id.name())
276            }
277            MmVariableParsedRequest::GetNextVariableName(id) => {
278                write!(f, "request/var: get-next-variable-name {}", id.name())
279            }
280            MmVariableParsedRequest::SetVariable(var) => {
281                write!(
282                    f,
283                    "request/var: set-variable {}  {}  {} data bytes",
284                    var.name(),
285                    var.attr(),
286                    var.data().len()
287                )
288            }
289            MmVariableParsedRequest::QueryVariableInfo => {
290                write!(f, "request/var: query-variable-info")
291            }
292            MmVariableParsedRequest::ReadyToBoot => {
293                write!(f, "request/var: ready-to-boot")
294            }
295            MmVariableParsedRequest::ExitBootService => {
296                write!(f, "request/var: exit-boot-service")
297            }
298            MmVariableParsedRequest::LockVariable(id) => {
299                write!(f, "request/var: lock-variable {}", id.name())
300            }
301            MmVariableParsedRequest::GetPayloadSize => {
302                write!(f, "request/var: get-payload-size")
303            }
304            MmVariableParsedRequest::Unknown { function } => {
305                write!(f, "request/var: unknown function #{function}")
306            }
307        }
308    }
309}
310
311// request state
312#[derive(Debug)]
313pub struct MmVariableRequest<'a> {
314    pub data: &'a [u8],
315    pub function: u64,
316    pub parsed: Option<MmVariableParsedRequest>,
317}
318
319impl<'a> MmVariableRequest<'a> {
320    pub fn new(data: &'a [u8]) -> Result<Self, Error<&'static str>> {
321        let (header, body) = MmVariableHeader::from_data(data)?;
322        let req = match MmVariableParsedRequest::new(header.function, body) {
323            Ok(req) => {
324                debug!("variable/req/ok: {req}");
325                Some(req)
326            }
327            Err(e) => {
328                debug!("variable/req/err: {e}");
329                None
330            }
331        };
332        Ok(MmVariableRequest {
333            data,
334            function: header.function,
335            parsed: req,
336        })
337    }
338
339    fn response(&self, status: Status, reply: Option<&[u8]>) -> Vec<u8> {
340        let mut status = status;
341
342        let limit = self.data.len() - core::mem::size_of::<MmVariableHeader>();
343        if let Some(r) = reply {
344            if limit < r.len() {
345                status = Status::BUFFER_TOO_SMALL;
346            }
347        }
348
349        debug!(
350            "variable/rsp: {}, {} data byte(s) ({} available)",
351            status,
352            if let Some(r) = reply { r.len() } else { 0 },
353            limit,
354        );
355
356        let hdr = MmVariableHeader::new(self.function, Some(status));
357        let mut rsp = hdr.as_bytes().to_vec();
358        if let Some(r) = reply {
359            if limit < r.len() {
360                rsp.extend_from_slice(&r[..limit]);
361            } else {
362                rsp.extend_from_slice(r);
363            }
364        }
365
366        rsp
367    }
368
369    fn get_variable(&self, store: &EfiVarStore, id: &EfiVarId) -> Vec<u8> {
370        let res = store.get(id);
371        if let Err(e) = res {
372            debug!("variable/get/error: {e}");
373            return self.response(e.status(), None);
374        }
375        let var = res.unwrap();
376
377        let reply = MmVariableParsedRequest::serialize_access_var(var);
378        match reply {
379            Err(_) => self.response(Status::DEVICE_ERROR, None),
380            Ok(r) => self.response(Status::SUCCESS, Some(&r)),
381        }
382    }
383
384    fn get_next_variable_name(&self, store: &EfiVarStore, id: &EfiVarId) -> Vec<u8> {
385        let res = store.get_next(id);
386        if let Err(e) = res {
387            return self.response(e.status(), None);
388        }
389        let next_id = res.unwrap();
390        debug!("variable/next: {} -> {}", id.name(), next_id.name());
391
392        let reply = MmVariableParsedRequest::serialize_next(next_id);
393        match reply {
394            Err(_) => self.response(Status::DEVICE_ERROR, None),
395            Ok(r) => self.response(Status::SUCCESS, Some(&r)),
396        }
397    }
398
399    fn set_variable(&self, store: &mut EfiVarStore, var: &EfiVar) -> Vec<u8> {
400        let res = store.set(var.clone());
401        if let Err(e) = res {
402            debug!("variable/set/error: {e}");
403            return self.response(e.status(), None);
404        }
405        self.response(Status::SUCCESS, None)
406    }
407
408    fn query_variable_info(&self, _store: &EfiVarStore) -> Vec<u8> {
409        let reply = MmVariableInfo {
410            max_storage_size: 256 * 1024, // qemu default
411            free_storage_size: 0,         // FIXME
412            max_variable_size: 64 * 1024, // qemu default
413            attributes: 0,
414        };
415        self.response(Status::SUCCESS, Some(reply.as_bytes()))
416    }
417
418    fn lock_variable(&self, store: &mut EfiVarStore, id: &EfiVarId) -> Vec<u8> {
419        let res = store.lock(id.name(), id.guid());
420        if let Err(e) = res {
421            debug!("variable/lock/error: {e}");
422            return self.response(e.status(), None);
423        }
424        self.response(Status::SUCCESS, None)
425    }
426
427    fn ready_to_boot(&self, store: &mut EfiVarStore) -> Vec<u8> {
428        store.ready_to_boot();
429        self.response(Status::SUCCESS, None)
430    }
431
432    fn exit_boot_service(&self, store: &mut EfiVarStore) -> Vec<u8> {
433        store.exit_boot_service();
434        self.response(Status::SUCCESS, None)
435    }
436
437    fn get_payload_size(&self) -> Vec<u8> {
438        let mut payload_size = 64 * 1024; // qemu max
439        payload_size -= size_of::<MmCoreHeader>() as u64;
440        payload_size -= size_of::<MmVariableHeader>() as u64;
441        let reply = MmGetPayloadSize { payload_size };
442        self.response(Status::SUCCESS, Some(reply.as_bytes()))
443    }
444
445    fn process(&self, store: &mut EfiVarStore) -> Vec<u8> {
446        if self.parsed.is_none() {
447            return self.response(Status::INVALID_PARAMETER, None);
448        }
449        let parsed = self.parsed.as_ref().unwrap();
450        match parsed {
451            MmVariableParsedRequest::GetVariable(id) => self.get_variable(store, id),
452            MmVariableParsedRequest::GetNextVariableName(id) => {
453                self.get_next_variable_name(store, id)
454            }
455            MmVariableParsedRequest::SetVariable(var) => self.set_variable(store, var),
456            MmVariableParsedRequest::QueryVariableInfo => self.query_variable_info(store),
457            MmVariableParsedRequest::ReadyToBoot => self.ready_to_boot(store),
458            MmVariableParsedRequest::ExitBootService => self.exit_boot_service(store),
459            MmVariableParsedRequest::LockVariable(id) => self.lock_variable(store, id),
460            MmVariableParsedRequest::GetPayloadSize => self.get_payload_size(),
461            _ => self.response(Status::INVALID_PARAMETER, None),
462        }
463    }
464}
465
466pub fn variable_request(store: &mut EfiVarStore, req: &[u8]) -> Vec<u8> {
467    let res = MmVariableRequest::new(req);
468    if let Err(e) = res.as_ref() {
469        error!("{e}");
470        return Vec::new();
471    }
472
473    let vreq = res.unwrap();
474    vreq.process(store)
475}