1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! UEFI Event Logging subsystem.

use crate::platform::logger::BootInfo;
use crate::platform::logger::UefiEvent;
use crate::platform::logger::UefiLogger;
use crate::UefiDevice;
use guestmem::GuestMemory;
use guestmem::GuestMemoryError;
use inspect::Inspect;
use std::fmt::Debug;
use thiserror::Error;
use zerocopy::FromBytes;

#[derive(Debug, Error)]
pub enum EventLogError {
    #[error("error converting bytes")]
    ConvertBytes,
    #[error("could not access guest memory")]
    Memory(#[source] GuestMemoryError),
    #[error("invalid event channel data size")]
    EventChannelDataSize,
    #[error("invalid boot event size")]
    BootEventSize,
    #[error("invalid event size")]
    EventSize,
    #[error("no boot events present in log")]
    NoBootEvents,
}

#[derive(Inspect)]
pub struct EventLogServices {
    #[inspect(skip)]
    logger: Box<dyn UefiLogger>,
}

impl EventLogServices {
    pub fn new(logger: Box<dyn UefiLogger>) -> EventLogServices {
        EventLogServices { logger }
    }

    pub fn reset(&mut self) {
        // Nothing to do.
    }

    fn event_log_flush_inner(&mut self, gpa: u64, gm: &GuestMemory) -> Result<(), EventLogError> {
        use uefi_specs::hyperv::bios_event_log::BiosEventChannel;
        use uefi_specs::hyperv::bios_event_log::EfiEventDescriptor;
        use uefi_specs::hyperv::boot_bios_log::BootDeviceStatus;
        use uefi_specs::hyperv::boot_bios_log::BootEventDeviceEntry;

        let event_channel = gm
            .read_plain::<BiosEventChannel>(gpa)
            .map_err(EventLogError::Memory)?;

        // Limit max size, UEFI does not log many events
        const EVENT_CHANNEL_MAX_DATA_SIZE: u32 = 16 * 1024;

        // Sanity check data size
        if event_channel.data_size < size_of::<EfiEventDescriptor>() as u32
            || event_channel.data_size > EVENT_CHANNEL_MAX_DATA_SIZE
        {
            return Err(EventLogError::EventChannelDataSize);
        }

        // read channel data
        let mut event_data = vec![0; event_channel.data_size as usize];
        gm.read_at(gpa + size_of::<BiosEventChannel>() as u64, &mut event_data)
            .map_err(EventLogError::Memory)?;
        let mut event_data = event_data.as_slice();

        // Merge the boot events together, aggregating an arbitrary subset of
        // the available diagnostics information.
        //
        // TODO: determine if we really want to merge events in this way instead
        // of just logging them individually.
        let mut boot_succeeded = false;
        let mut no_boot_devices = false;
        let mut secure_boot_failure = None;
        let mut last_boot_event = None;

        while !event_data.is_empty() {
            let desc = EfiEventDescriptor::read_from_prefix(event_data)
                .ok_or(EventLogError::ConvertBytes)?;

            let data = event_data
                .get(desc.header_size as usize..)
                .ok_or(EventLogError::EventSize)?
                .get(..desc.data_size as usize)
                .ok_or(EventLogError::EventSize)?;

            // Advance to the next event.
            event_data = &event_data[(desc.header_size + desc.data_size) as usize..];

            match desc.event_id {
                uefi_specs::hyperv::boot_bios_log::BOOT_DEVICE_EVENT_ID => {
                    let boot_entry = BootEventDeviceEntry::read_from_prefix(data)
                        .ok_or(EventLogError::BootEventSize)?;

                    tracing::debug!(?boot_entry, "boot log entry");

                    match boot_entry.status {
                        BootDeviceStatus::BOOT_DEVICE_OS_LOADED => boot_succeeded = true,
                        BootDeviceStatus::BOOT_DEVICE_NO_DEVICES => no_boot_devices = true,
                        _ if boot_entry.status.get_boot_device_status_group()
                            == BootDeviceStatus::SECURE_BOOT_FAILED.0 =>
                        {
                            secure_boot_failure = Some(boot_entry.status);
                        }
                        _ => {}
                    }

                    last_boot_event = Some(boot_entry);
                }
                id => {
                    tracelimit::warn_ratelimited!(id, "unsupported uefi event log id");
                }
            }
        }

        let last_boot_event = last_boot_event.ok_or(EventLogError::NoBootEvents)?;
        let boot_info = BootInfo {
            secure_boot_succeeded: secure_boot_failure.is_none(),
        };

        // Don't log the secure boot failure code twice if it's the reason for
        // the boot failure.
        let secure_boot_error = if secure_boot_failure != Some(last_boot_event.status) {
            secure_boot_failure.map(tracing::field::debug)
        } else {
            None
        };

        let event = if no_boot_devices {
            tracelimit::info_ratelimited!("uefi boot: no boot devices");
            UefiEvent::NoBootDevice
        } else if boot_succeeded {
            tracelimit::info_ratelimited!(secure_boot_error, "uefi boot: success");
            UefiEvent::BootSuccess(boot_info)
        } else {
            tracelimit::info_ratelimited!(
                error = ?last_boot_event.status,
                extended_status = ?last_boot_event.extended_status,
                secure_boot_error,
                "uefi boot: failure",
            );
            UefiEvent::BootFailure(boot_info)
        };
        self.logger.log_event(event);
        Ok(())
    }
}

impl UefiDevice {
    /// Reads guest memory and logs the boot status to the host.
    pub(crate) fn event_log_flush(&mut self, data: u32) {
        if let Err(err) = self
            .service
            .event_log
            .event_log_flush_inner(data.into(), &self.gm)
        {
            tracelimit::error_ratelimited!(
                error = &err as &dyn std::error::Error,
                "event log flush error"
            );
        }
    }
}

mod save_restore {
    use super::*;
    use vmcore::save_restore::NoSavedState;
    use vmcore::save_restore::RestoreError;
    use vmcore::save_restore::SaveError;
    use vmcore::save_restore::SaveRestore;

    impl SaveRestore for EventLogServices {
        type SavedState = NoSavedState;

        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
            Ok(NoSavedState)
        }

        fn restore(&mut self, NoSavedState: Self::SavedState) -> Result<(), RestoreError> {
            Ok(())
        }
    }
}