blob: 850b4cf4a862f333bf403e519edbe595df0cf624 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Splits the hibernate image into header and data. Splitting is needed in
//! order to make image preloading work properly. The way the kernel does resume
//! is to read in the header pages, then attempt to allocate all the space it
//! needs for the hibernate image all at once. (This is a good idea because for
//! any page it gets in this big allocation that's also used by the hibernated
//! kernel, data can be restored directly to the correct pfn without a second
//! copy.) What this means is if you try to preload the hibernate image into
//! memory before feeding the header portion into the kernel, the kernel's giant
//! allocation attempt ends up failing or taking the system down. The splitter
//! allows us to save and restore the header portion separately (and
//! unencrypted), so that we can feed the header portion in early. This lets the
//! kernel have its big chunk, and then we can soak up the rest of memory
//! preloading. The header portion is ok to save unencrypted because it's
//! essentially just a page list. The kernel validates its contents, and we also
//! verify its hash to detect tampering.
use std::convert::TryInto;
use std::io::{Error as IoError, ErrorKind, Read, Write};
use anyhow::{Context, Result};
use libc::utsname;
use log::debug;
use openssl::hash::{Hasher, MessageDigest};
use crate::hibermeta::{HibernateMetadata, META_HASH_SIZE};
use crate::hiberutil::{get_page_size, HibernateError};
/// A machine with 32GB RAM has 8M PFNs. Half of that times 8 bytes per PFN is
/// 32MB.
pub const HIBER_HEADER_MAX_SIZE: i64 = (1024 * 1024 * 32) + 4096;
/// Define the swsusp_info header created by the kernel at the start of each
/// hibernate image. Use this to figure out how many header pages there are.
#[repr(C)]
struct SwSuspInfo {
uts: utsname,
version_code: u32,
num_physpages: usize,
cpus: u32,
image_pages: usize,
pages: usize,
size: usize,
}
/// An image splitter is a generic object that implements the Write trait. It
/// will divert writes first into the header file, and then the data file.
pub struct ImageSplitter<'a> {
header_file: &'a mut dyn Write,
data_file: &'a mut dyn Write,
metadata: &'a mut HibernateMetadata,
page_size: usize,
meta_pages: usize,
pages_done: usize,
hasher: Hasher,
}
/// The ImageSplitter routes an initial set of writes to a header file, then
/// routes the main data to the data file. It uses the first sector's write to
/// determine where to make the split. It also computes the hash of the header
/// portion as it goes by, and saves that hash into the metadata.
impl<'a> ImageSplitter<'a> {
/// Create a new image splitter, given pointers to the header destination
/// and data file destination. The metadata is also received as a place to
/// store the header hash.
pub fn new(
header_file: &'a mut dyn Write,
data_file: &'a mut dyn Write,
metadata: &'a mut HibernateMetadata,
) -> ImageSplitter<'a> {
Self {
header_file,
data_file,
metadata,
page_size: get_page_size(),
meta_pages: 0,
pages_done: 0,
hasher: Hasher::new(MessageDigest::sha256()).unwrap(),
}
}
/// Helper function to write contents to the header file, snarfing out the
/// header data size on the way down. Any remainder is passed to the data
/// file.
fn write_header(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let page_size = self.page_size;
let length = buf.len();
// The write is always expected to be at least a page, and an even
// multiple of a page.
assert!(length >= page_size);
assert!((length & (page_size - 1)) == 0);
// If this is the first page, snarf the header out of the buffer to
// figure out the number of metadata pages.
if self.pages_done == 0 {
assert!(self.meta_pages == 0);
self.meta_pages = get_meta_page_count(buf, page_size)?;
// Save the non-data page count in the official metadata too.
self.metadata.pagemap_pages =
self.meta_pages.try_into().expect("Too many metadata pages");
}
// Write out to the header if that's not done yet.
let mut offset = 0;
if self.pages_done < self.meta_pages {
// Send either the rest of the metadata or the rest of this buffer.
let mut meta_size = (self.meta_pages - self.pages_done) * self.page_size;
if meta_size > length {
meta_size = length;
}
self.hasher.update(&buf[..meta_size]).unwrap();
let bytes_written = self.header_file.write(&buf[..meta_size])?;
// Assert that the write did not cross into data territory, only sidled
// up to it.
assert!((self.pages_done + (bytes_written / page_size)) <= self.meta_pages);
assert!((bytes_written & (page_size - 1)) == 0);
self.pages_done += bytes_written / page_size;
// If the header just finished, finalize the hash of it and save it into
// the metadata.
if self.pages_done == self.meta_pages {
self.metadata
.header_hash
.copy_from_slice(&self.hasher.finish().unwrap());
}
if bytes_written == length {
return Ok(bytes_written);
}
offset = bytes_written;
}
// Write down to the data file. If this is the first byte of data, save
// it off to the metadata too.
if self.pages_done == self.meta_pages {
self.metadata.first_data_byte = buf[offset];
}
// Send the rest of the write down to the data file.
let bytes_written = self.data_file.write(&buf[offset..])?;
assert!((bytes_written & (page_size - 1)) == 0);
self.pages_done += bytes_written / page_size;
offset += bytes_written;
Ok(offset)
}
}
impl Write for ImageSplitter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
// Just forward the write on down if the header's already been split
// off. This will be the hot path. The comparison is strictly greater
// than because we also need to slurp the first data byte from the first
// data page.
if (self.meta_pages != 0) && (self.pages_done > self.meta_pages) {
return self.data_file.write(buf);
}
self.write_header(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.header_file.flush()?;
self.data_file.flush()
}
}
/// An ImageJoiner is the opposite of an ImageSplitter. It implements Read,
/// stitching together the header contents followed by the data contents.
pub struct ImageJoiner<'a> {
header_file: &'a mut dyn Read,
data_file: &'a mut dyn Read,
page_size: usize,
meta_pages: usize,
pages_done: usize,
hasher: Hasher,
header_hash: Vec<u8>,
}
impl<'a> ImageJoiner<'a> {
/// Create a new ImageJoiner, a single object implementing the Read trait
/// that will stitch together the header, followed by the data portion.
pub fn new(header_file: &'a mut dyn Read, data_file: &'a mut dyn Read) -> ImageJoiner<'a> {
Self {
header_file,
data_file,
page_size: get_page_size(),
meta_pages: 0,
pages_done: 0,
hasher: Hasher::new(MessageDigest::sha256()).unwrap(),
header_hash: vec![],
}
}
/// Returns the computed hash of the header region, which the caller will
/// compare to what's in the private metadata (once that's decrypted and
/// available).
pub fn get_header_hash(&self, hash: &mut [u8; META_HASH_SIZE]) -> Result<usize> {
if self.header_hash.len() != META_HASH_SIZE {
return Err(HibernateError::HeaderIncomplete())
.context("The header is invalid or has not yet been read");
}
hash.copy_from_slice(&self.header_hash[..]);
Ok(self.meta_pages)
}
/// Helper function to read contents from the header file, snarfing out the
/// header data size on the way. Any remaining reads are fed from the data
/// file.
fn read_header(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let page_size = self.page_size;
let mut offset = 0usize;
let length = buf.len();
// The read better be at least a page, and an even multiple of a page.
assert!(length >= page_size);
assert!((length & (page_size - 1)) == 0);
// If this is the first read, do a single page read to snarf out the
// header. This also ensures the header isn't over-read.
if self.pages_done == 0 {
assert!(self.meta_pages == 0);
let bytes_read = self.header_file.read(&mut buf[..page_size])?;
assert!(bytes_read == page_size);
self.hasher.update(&buf[..bytes_read]).unwrap();
offset += bytes_read;
self.pages_done += bytes_read / page_size;
self.meta_pages = get_meta_page_count(buf, page_size)?;
}
// Read the rest of the header file (or at least the rest of the buffer).
let mut meta_size = (self.meta_pages - self.pages_done) * page_size;
if meta_size > (length - offset) {
meta_size = length - offset;
}
let end = offset + meta_size;
let bytes_read = self.header_file.read(&mut buf[offset..end])?;
assert!((bytes_read & (page_size - 1)) == 0);
let read_end = offset + bytes_read;
self.hasher.update(&buf[offset..read_end]).unwrap();
self.pages_done += bytes_read / page_size;
assert!(self.pages_done <= self.meta_pages);
// Save the hash locally if this was the last header page. The caller
// will eventually ask for it once the private metadata is unlocked.
if self.pages_done == self.meta_pages {
let hash_slice: &[u8] = &self.hasher.finish().unwrap();
self.header_hash = hash_slice.to_vec();
}
offset += bytes_read;
if offset == length {
return Ok(offset);
}
// Send the remaining read down to the data file.
let bytes_read = self.data_file.read(&mut buf[offset..])?;
Ok(offset + bytes_read)
}
}
impl Read for ImageJoiner<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// Just forward the read on down if the header's already been split off.
// This will be the hot path.
if (self.meta_pages != 0) && (self.pages_done >= self.meta_pages) {
return self.data_file.read(buf);
}
self.read_header(buf)
}
}
/// Read the header out of the first page of the hibernate image. Returns the
/// number of metadata pages on success.
fn get_meta_page_count(buf: &[u8], page_size: usize) -> std::io::Result<usize> {
assert!(buf.len() >= page_size);
// Assert that libc didn't somehow change the size of the utsname header
// while we were sleeping, which would ruin the other structure member
// offsets.
assert!(std::mem::size_of::<utsname>() == (65 * 6));
// This is safe because the buffer is larger than the structure size, and
// the types in the struct are all basic.
let header: SwSuspInfo = unsafe {
std::ptr::read_unaligned(buf[0..std::mem::size_of::<SwSuspInfo>()].as_ptr() as *const _)
};
let meta_pages = header.pages - header.image_pages;
debug!(
"Image has {:x?} image pages, {:x?} header pages",
header.image_pages, meta_pages
);
if header.pages < header.image_pages {
return Err(IoError::new(
ErrorKind::InvalidInput,
format!(
"Header pages {:x} < image_pages {:x}",
header.pages, header.image_pages
),
));
}
// TODO: Validate additional fields in the structure in case the kernel
// changes the format, like num_physpages.
if (meta_pages * page_size) as i64 > HIBER_HEADER_MAX_SIZE {
return Err(IoError::new(
ErrorKind::InvalidInput,
format!(
"Too many header pages. Pages {:x}, image_pages {:x}",
header.pages, header.image_pages
),
));
}
if meta_pages < 2 {
return Err(IoError::new(
ErrorKind::InvalidInput,
format!(
"Too few header pages. Pages {:x}, image_pages {:x}",
header.pages, header.image_pages
),
));
}
Ok(meta_pages)
}