vmm/snapshot/
mod.rs

1// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Provides serialization and deserialization facilities and implements a persistent storage
5//! format for Firecracker state snapshots.
6//!
7//! The `Snapshot` API manages serialization and deserialization of collections of objects
8//! that implement the `serde` `Serialize`, `Deserialize` trait. Currently, we use
9//! [`bincode`](https://docs.rs/bincode/latest/bincode/) for performing the serialization.
10//!
11//! The snapshot format uses the following layout:
12//!
13//!  |-----------------------------|
14//!  |       64 bit magic_id       |
15//!  |-----------------------------|
16//!  |       version string        |
17//!  |-----------------------------|
18//!  |            State            |
19//!  |-----------------------------|
20//!  |        optional CRC64       |
21//!  |-----------------------------|
22//!
23//!
24//! The snapshot format uses a version value in the form of `MAJOR.MINOR.PATCH`. The version is
25//! provided by the library clients (it is not tied to this crate).
26pub mod crc;
27mod persist;
28use std::fmt::Debug;
29use std::io::{Read, Write};
30
31use bincode::config;
32use bincode::config::{Configuration, Fixint, Limit, LittleEndian};
33use bincode::error::{DecodeError, EncodeError};
34use crc64::crc64;
35use semver::Version;
36use serde::de::DeserializeOwned;
37use serde::{Deserialize, Serialize};
38
39use crate::persist::SNAPSHOT_VERSION;
40use crate::snapshot::crc::CRC64Writer;
41pub use crate::snapshot::persist::Persist;
42use crate::utils::mib_to_bytes;
43
44#[cfg(target_arch = "x86_64")]
45const SNAPSHOT_MAGIC_ID: u64 = 0x0710_1984_8664_0000u64;
46
47/// Constant bounding how much memory bincode may allocate during vmstate file deserialization
48const DESERIALIZATION_BYTES_LIMIT: usize = mib_to_bytes(10);
49
50const BINCODE_CONFIG: Configuration<LittleEndian, Fixint, Limit<DESERIALIZATION_BYTES_LIMIT>> =
51    config::standard()
52        .with_fixed_int_encoding()
53        .with_limit::<DESERIALIZATION_BYTES_LIMIT>()
54        .with_little_endian();
55
56#[cfg(target_arch = "aarch64")]
57const SNAPSHOT_MAGIC_ID: u64 = 0x0710_1984_AAAA_0000u64;
58
59/// Error definitions for the Snapshot API.
60#[derive(Debug, thiserror::Error, displaydoc::Display)]
61pub enum SnapshotError {
62    /// CRC64 validation failed
63    Crc64,
64    /// Invalid data version: {0}
65    InvalidFormatVersion(Version),
66    /// Magic value does not match arch: {0}
67    InvalidMagic(u64),
68    /// An error occured during bincode encoding: {0}
69    Encode(#[from] EncodeError),
70    /// An error occured during bincode decoding: {0}
71    Decode(#[from] DecodeError),
72    /// IO Error: {0}
73    Io(#[from] std::io::Error),
74}
75
76fn serialize<S: Serialize, W: Write>(data: &S, write: &mut W) -> Result<(), SnapshotError> {
77    bincode::serde::encode_into_std_write(data, write, BINCODE_CONFIG)
78        .map_err(SnapshotError::Encode)
79        .map(|_| ())
80}
81
82/// Firecracker snapshot header
83#[derive(Debug, Serialize, Deserialize)]
84struct SnapshotHdr {
85    /// magic value
86    magic: u64,
87    /// Snapshot data version
88    version: Version,
89}
90
91impl SnapshotHdr {
92    fn load(buf: &mut &[u8]) -> Result<Self, SnapshotError> {
93        let (hdr, bytes_read) = bincode::serde::decode_from_slice::<Self, _>(buf, BINCODE_CONFIG)?;
94
95        if hdr.magic != SNAPSHOT_MAGIC_ID {
96            return Err(SnapshotError::InvalidMagic(hdr.magic));
97        }
98
99        if hdr.version.major != SNAPSHOT_VERSION.major || hdr.version.minor > SNAPSHOT_VERSION.minor
100        {
101            return Err(SnapshotError::InvalidFormatVersion(hdr.version));
102        }
103
104        *buf = &buf[bytes_read..];
105
106        Ok(hdr)
107    }
108}
109
110/// Assumes the raw bytes stream read from the given [`Read`] instance is a snapshot file,
111/// and returns the version of it.
112pub fn get_format_version<R: Read>(reader: &mut R) -> Result<Version, SnapshotError> {
113    let hdr: SnapshotHdr = bincode::serde::decode_from_std_read(reader, BINCODE_CONFIG)?;
114    Ok(hdr.version)
115}
116
117/// Firecracker snapshot type
118///
119/// A type used to store and load Firecracker snapshots of a particular version
120#[derive(Debug, Serialize)]
121pub struct Snapshot<Data> {
122    header: SnapshotHdr,
123    /// The data stored int his [`Snapshot`]
124    pub data: Data,
125}
126
127impl<Data> Snapshot<Data> {
128    /// Constructs a new snapshot with the given `data`.
129    pub fn new(data: Data) -> Self {
130        Self {
131            header: SnapshotHdr {
132                magic: SNAPSHOT_MAGIC_ID,
133                version: SNAPSHOT_VERSION.clone(),
134            },
135            data,
136        }
137    }
138
139    /// Gets the version of this snapshot
140    pub fn version(&self) -> &Version {
141        &self.header.version
142    }
143}
144
145impl<Data: DeserializeOwned> Snapshot<Data> {
146    pub(crate) fn load_without_crc_check(mut buf: &[u8]) -> Result<Self, SnapshotError> {
147        let header = SnapshotHdr::load(&mut buf)?;
148        let data = bincode::serde::decode_from_slice(buf, BINCODE_CONFIG)?.0;
149        Ok(Self { header, data })
150    }
151
152    /// Loads a snapshot from the given [`Read`] instance, performing all validations
153    /// (CRC, snapshot magic value, snapshot version).
154    pub fn load<R: Read>(reader: &mut R) -> Result<Self, SnapshotError> {
155        // read_to_end internally right-sizes the buffer, so no reallocations due to growing buffers
156        // will happen.
157        let mut buf = Vec::new();
158        reader.read_to_end(&mut buf)?;
159        let snapshot = Self::load_without_crc_check(buf.as_slice())?;
160        let computed_checksum = crc64(0, buf.as_slice());
161        // When we read the entire file, we also read the checksum into the buffer. The CRC has the
162        // property that crc(0, buf.as_slice()) == 0 iff the last 8 bytes of buf are the checksum
163        // of all the preceeding bytes, and this is the property we are using here.
164        if computed_checksum != 0 {
165            return Err(SnapshotError::Crc64);
166        }
167        Ok(snapshot)
168    }
169}
170
171impl<Data: Serialize> Snapshot<Data> {
172    /// Saves `self` to the given [`Write`] instance, computing the CRC of the written data,
173    /// and then writing the CRC into the `Write` instance, too.
174    pub fn save<W: Write>(&self, writer: &mut W) -> Result<(), SnapshotError> {
175        let mut crc_writer = CRC64Writer::new(writer);
176        serialize(self, &mut crc_writer)?;
177        serialize(&crc_writer.checksum(), crc_writer.writer)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use crate::persist::MicrovmState;
185
186    #[test]
187    fn test_snapshot_restore() {
188        let state = MicrovmState::default();
189        let mut buf = Vec::new();
190
191        Snapshot::new(state).save(&mut buf).unwrap();
192        Snapshot::<MicrovmState>::load(&mut buf.as_slice()).unwrap();
193    }
194
195    #[test]
196    fn test_parse_version_from_file() {
197        let snapshot = Snapshot::new(42);
198
199        // Enough memory for the header, 1 byte and the CRC
200        let mut snapshot_data = vec![0u8; 100];
201
202        snapshot.save(&mut snapshot_data.as_mut_slice()).unwrap();
203
204        assert_eq!(
205            get_format_version(&mut snapshot_data.as_slice()).unwrap(),
206            SNAPSHOT_VERSION
207        );
208    }
209
210    #[test]
211    fn test_bad_reader() {
212        #[derive(Debug)]
213        struct BadReader;
214
215        impl Read for BadReader {
216            fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
217                Err(std::io::ErrorKind::InvalidInput.into())
218            }
219        }
220
221        let mut reader = BadReader {};
222
223        assert!(
224            matches!(Snapshot::<()>::load(&mut reader), Err(SnapshotError::Io(inner)) if inner.kind() == std::io::ErrorKind::InvalidInput)
225        );
226    }
227
228    #[test]
229    fn test_bad_magic() {
230        let mut data = vec![0u8; 100];
231
232        let snapshot = Snapshot::new(());
233        snapshot.save(&mut data.as_mut_slice()).unwrap();
234
235        // Writing dummy values in the first bytes of the snapshot data (we are on little-endian
236        // machines) should trigger an `Error::InvalidMagic` error.
237        data[0] = 0x01;
238        data[1] = 0x02;
239        data[2] = 0x03;
240        data[3] = 0x04;
241        data[4] = 0x42;
242        data[5] = 0x43;
243        data[6] = 0x44;
244        data[7] = 0x45;
245        assert!(matches!(
246            SnapshotHdr::load(&mut data.as_slice()),
247            Err(SnapshotError::InvalidMagic(0x4544_4342_0403_0201u64))
248        ));
249    }
250
251    #[test]
252    fn test_bad_crc() {
253        let mut data = vec![0u8; 100];
254
255        let snapshot = Snapshot::new(());
256        // Write the snapshot without CRC, so that when loading with CRC check, we'll read
257        // zeros for the CRC and fail.
258        serialize(&snapshot, &mut data.as_mut_slice()).unwrap();
259
260        assert!(matches!(
261            Snapshot::<()>::load(&mut std::io::Cursor::new(data.as_slice())),
262            Err(SnapshotError::Crc64)
263        ));
264    }
265
266    #[test]
267    fn test_bad_version() {
268        let mut data = vec![0u8; 100];
269
270        // Different major version: shouldn't work
271        let mut snapshot = Snapshot::new(());
272        snapshot.header.version.major = SNAPSHOT_VERSION.major + 1;
273        snapshot.save(&mut data.as_mut_slice()).unwrap();
274
275        assert!(matches!(
276            Snapshot::<()>::load_without_crc_check(data.as_slice()),
277            Err(SnapshotError::InvalidFormatVersion(v)) if v.major == SNAPSHOT_VERSION.major + 1
278        ));
279
280        //  minor > SNAPSHOT_VERSION.minor: shouldn't work
281        let mut snapshot = Snapshot::new(());
282        snapshot.header.version.minor = SNAPSHOT_VERSION.minor + 1;
283        snapshot.save(&mut data.as_mut_slice()).unwrap();
284        assert!(matches!(
285            Snapshot::<()>::load_without_crc_check(data.as_slice()),
286            Err(SnapshotError::InvalidFormatVersion(v)) if v.minor == SNAPSHOT_VERSION.minor + 1
287        ));
288
289        // But we can support minor versions smaller or equal to ours. We also support
290        // all patch versions within our supported major.minor version.
291        let snapshot = Snapshot::new(());
292        snapshot.save(&mut data.as_mut_slice()).unwrap();
293        Snapshot::<()>::load_without_crc_check(data.as_slice()).unwrap();
294
295        if SNAPSHOT_VERSION.minor != 0 {
296            let mut snapshot = Snapshot::new(());
297            snapshot.header.version.minor = SNAPSHOT_VERSION.minor - 1;
298            snapshot.save(&mut data.as_mut_slice()).unwrap();
299            Snapshot::<()>::load_without_crc_check(data.as_slice()).unwrap();
300        }
301
302        let mut snapshot = Snapshot::new(());
303        snapshot.header.version.patch = 0;
304        snapshot.save(&mut data.as_mut_slice()).unwrap();
305        Snapshot::<()>::load_without_crc_check(data.as_slice()).unwrap();
306
307        let mut snapshot = Snapshot::new(());
308        snapshot.header.version.patch = SNAPSHOT_VERSION.patch + 1;
309        snapshot.save(&mut data.as_mut_slice()).unwrap();
310        Snapshot::<()>::load_without_crc_check(data.as_slice()).unwrap();
311
312        let mut snapshot = Snapshot::new(());
313        snapshot.header.version.patch = 1024;
314        snapshot.save(&mut data.as_mut_slice()).unwrap();
315        Snapshot::<()>::load_without_crc_check(data.as_slice()).unwrap();
316    }
317}