blob: 90c1f12cd0e927394ad266196984f1f547f8f7e4 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implement snapshot device functionality.
use std::convert::TryInto;
use std::fs::{metadata, File, OpenOptions};
use std::os::unix::fs::FileTypeExt;
use std::path::Path;
use anyhow::{Context, Result};
use libc::{self, c_int, c_ulong, c_void, loff_t};
use log::{error, info};
use sys_util::{ioctl_io_nr, ioctl_ior_nr, ioctl_iow_nr, ioctl_iowr_nr};
use crate::hiberutil::HibernateError;
const SNAPSHOT_PATH: &str = "/dev/snapshot";
#[repr(C)]
#[repr(packed)]
#[derive(Copy, Clone)]
pub struct UswsuspKeyBlob {
blob_len: u32,
blob: [u8; 512],
nonce: [u8; 16],
}
impl UswsuspKeyBlob {
pub fn deserialize(bytes: &[u8]) -> Self {
// This is safe because the structure is repr(C), packed, and made only
// of primitives.
unsafe {
let result: UswsuspKeyBlob = std::ptr::read_unaligned(
bytes[0..::std::mem::size_of::<UswsuspKeyBlob>()].as_ptr() as *const _,
);
result
}
}
pub fn serialize(&self) -> &[u8] {
// This is safe because the structure is repr(C), packed, and made only
// of primitives.
unsafe {
::std::slice::from_raw_parts(
(self as *const UswsuspKeyBlob) as *const u8,
::std::mem::size_of::<UswsuspKeyBlob>(),
)
}
}
}
// Ideally we'd be able to just derive Default for vectors with >32 elements,
// but alas, here we are.
impl Default for UswsuspKeyBlob {
fn default() -> Self {
Self {
blob_len: Default::default(),
blob: [0u8; 512],
nonce: Default::default(),
}
}
}
#[repr(C)]
#[repr(packed)]
#[derive(Copy, Clone, Default)]
pub struct UswsuspUserKey {
meta_size: i64,
key_len: u32,
key: [u8; 16],
pad: u32,
}
impl UswsuspUserKey {
pub fn new_from_u8_slice(key: &[u8]) -> Self {
UswsuspUserKey {
meta_size: 0,
key_len: 16,
key: key
.try_into()
.expect("UswsuspUserKey with incorrect length"),
pad: 0,
}
}
}
// Define snapshot device ioctl numbers.
const SNAPSHOT_IOC_MAGIC: u32 = '3' as u32;
ioctl_io_nr!(SNAPSHOT_FREEZE, SNAPSHOT_IOC_MAGIC, 1);
ioctl_io_nr!(SNAPSHOT_UNFREEZE, SNAPSHOT_IOC_MAGIC, 2);
ioctl_io_nr!(SNAPSHOT_ATOMIC_RESTORE, SNAPSHOT_IOC_MAGIC, 4);
ioctl_ior_nr!(SNAPSHOT_GET_IMAGE_SIZE, SNAPSHOT_IOC_MAGIC, 14, u64);
ioctl_io_nr!(SNAPSHOT_PLATFORM_SUPPORT, SNAPSHOT_IOC_MAGIC, 15);
ioctl_io_nr!(SNAPSHOT_POWER_OFF, SNAPSHOT_IOC_MAGIC, 16);
ioctl_iow_nr!(SNAPSHOT_CREATE_IMAGE, SNAPSHOT_IOC_MAGIC, 17, u32);
ioctl_iowr_nr!(
SNAPSHOT_ENABLE_ENCRYPTION,
SNAPSHOT_IOC_MAGIC,
21,
UswsuspKeyBlob
);
ioctl_iowr_nr!(
SNAPSHOT_SET_USER_KEY,
SNAPSHOT_IOC_MAGIC,
22,
UswsuspUserKey
);
const FREEZE: u64 = SNAPSHOT_FREEZE();
const UNFREEZE: u64 = SNAPSHOT_UNFREEZE();
const ATOMIC_RESTORE: u64 = SNAPSHOT_ATOMIC_RESTORE();
const GET_IMAGE_SIZE: u64 = SNAPSHOT_GET_IMAGE_SIZE();
const PLATFORM_SUPPORT: u64 = SNAPSHOT_PLATFORM_SUPPORT();
const POWER_OFF: u64 = SNAPSHOT_POWER_OFF();
const CREATE_IMAGE: u64 = SNAPSHOT_CREATE_IMAGE();
const ENABLE_ENCRYPTION: u64 = SNAPSHOT_ENABLE_ENCRYPTION();
const SET_USER_KEY: u64 = SNAPSHOT_SET_USER_KEY();
/// The SnapshotDevice is mostly a group of method functions that send ioctls to
/// an open snapshot device file descriptor.
pub struct SnapshotDevice {
pub file: File,
}
/// Define the possible modes in which to open the snapshot device.
pub enum SnapshotMode {
Read,
Write,
}
impl SnapshotDevice {
/// Open the snapshot device and return a new object.
pub fn new(mode: SnapshotMode) -> Result<SnapshotDevice> {
if !Path::new(SNAPSHOT_PATH).exists() {
return Err(HibernateError::SnapshotError(format!(
"Snapshot device {} does not exist",
SNAPSHOT_PATH
)))
.context("Failed to open snapshot device");
}
let snapshot_meta =
metadata(SNAPSHOT_PATH).context("Failed to get file metadata for snapshot device")?;
if !snapshot_meta.file_type().is_char_device() {
return Err(HibernateError::SnapshotError(format!(
"Snapshot device {} is not a character device",
SNAPSHOT_PATH
)))
.context("Failed to open snapshot device");
}
let mut file = OpenOptions::new();
let file = match mode {
SnapshotMode::Read => file.read(true).write(false),
SnapshotMode::Write => file.read(false).write(true),
};
let file = file
.open(SNAPSHOT_PATH)
.context("Failed to open snapshot device")?;
Ok(SnapshotDevice { file })
}
/// Freeze userspace, stopping all userspace processes except this one.
pub fn freeze_userspace(&mut self) -> Result<FrozenUserspaceTicket> {
// This is safe because the ioctl doesn't modify memory in a way that
// violates Rust's guarantees.
unsafe {
self.simple_ioctl(FREEZE, "FREEZE")?;
}
Ok(FrozenUserspaceTicket { snap_dev: self })
}
/// Unfreeze userspace, resuming all other previously frozen userspace
/// processes.
pub fn unfreeze_userspace(&mut self) -> Result<()> {
// This is safe because the ioctl doesn't modify memory in a way that
// violates Rust's guarantees.
unsafe { self.simple_ioctl(UNFREEZE, "UNFREEZE") }
}
/// Ask the kernel to create its hibernate snapshot. Returns a boolean
/// indicating whether this process is executing in suspend (true) or resume
/// (false). Like setjmp(), this function effectively returns twice: once
/// after the snapshot image is created (true), and again when the
/// hibernated image is restored (false).
pub fn atomic_snapshot(&mut self) -> Result<bool> {
let mut in_suspend: c_int = 0;
// This is safe because the ioctl modifies a u32 sized integer, which
// we have preinitialized and passed in.
unsafe {
self.ioctl_with_mut_ptr(
CREATE_IMAGE,
"CREATE_IMAGE",
&mut in_suspend as *mut c_int as *mut c_void,
)?;
}
Ok(in_suspend != 0)
}
/// Jump into the fully loaded resume image. On success, this does not
/// return, as it launches into the resumed image.
pub fn atomic_restore(&mut self) -> Result<()> {
// This is safe because either the entire world will stop executing,
// or nothing happens, preserving Rust's guarantees.
unsafe { self.simple_ioctl(ATOMIC_RESTORE, "ATOMIC_RESTORE") }
}
/// Return the size of the recently snapshotted hibernate image in bytes.
pub fn get_image_size(&mut self) -> Result<loff_t> {
let mut image_size: loff_t = 0;
// This is safe because the ioctl modifies an loff_t sized integer,
// we are passing down.
unsafe {
self.ioctl_with_mut_ptr(
GET_IMAGE_SIZE,
"GET_IMAGE_SIZE",
&mut image_size as *mut loff_t as *mut c_void,
)?;
}
Ok(image_size)
}
/// Indicate to the kernel whether or not to power down into "platform" mode
/// (which is only meaningful on Intel systems, and means S4).
pub fn set_platform_mode(&mut self, use_platform_mode: bool) -> Result<()> {
let param_ulong: c_ulong = use_platform_mode as c_ulong;
unsafe {
let rc = sys_util::ioctl_with_val(&self.file, PLATFORM_SUPPORT, param_ulong);
self.evaluate_ioctl_return("PLATFORM_SUPPORT", rc)
}
}
/// Power down the system.
pub fn power_off(&mut self) -> Result<()> {
// This is safe because powering the system off does not violate any
// Rust guarantees.
unsafe { self.simple_ioctl(POWER_OFF, "POWER_OFF") }
}
/// Get the encryption key from the kernel.
pub fn get_key_blob(&mut self) -> Result<UswsuspKeyBlob> {
let mut blob = UswsuspKeyBlob::default();
// The parameter may be either read or written to based on whether the
// snap device was opened with read or write. The API assumes both.
unsafe {
self.ioctl_with_mut_ptr(
ENABLE_ENCRYPTION,
"ENABLE_ENCRYPTION",
&mut blob as *mut UswsuspKeyBlob as *mut c_void,
)?;
}
Ok(blob)
}
/// Hand the encryption key to the kernel. This is actually the same ioctl
/// as the get call, but only one is successful depending on if the snapdev
/// was opened with read or write permission.
pub fn set_key_blob(&mut self, blob: &UswsuspKeyBlob) -> Result<()> {
unsafe {
self.ioctl_with_ptr(
ENABLE_ENCRYPTION,
"ENABLE_ENCRYPTION",
blob as *const UswsuspKeyBlob as *const c_void,
)
}
}
/// Set the user key for hibernate data. Returns the metadata size
/// from the kernel on success.
pub fn set_user_key(&mut self, key: &UswsuspUserKey) -> Result<i64> {
let mut key_copy = *key;
unsafe {
self.ioctl_with_mut_ptr(
SET_USER_KEY,
"SET_USER_KEY",
&mut key_copy as *mut UswsuspUserKey as *mut c_void,
)?;
}
Ok(key_copy.meta_size)
}
/// Helper function to send an ioctl with no parameter and return a result.
/// # Safety
///
/// The caller must ensure that the actions the ioctl performs uphold
/// Rust's memory safety guarantees. In this case, no parameter is being
/// passed, so those guarantees would mostly be concerned with larger
/// address space layout or memory model side effects.
unsafe fn simple_ioctl(&mut self, ioctl: c_ulong, name: &str) -> Result<()> {
let rc = sys_util::ioctl(&self.file, ioctl);
self.evaluate_ioctl_return(name, rc)
}
/// Helper function to send an ioctl and return a Result
/// # Safety
///
/// The caller must ensure that the actions the ioctl performs uphold
/// Rust's memory safety guarantees. Specifically
unsafe fn ioctl_with_ptr(
&mut self,
ioctl: c_ulong,
name: &str,
param: *const c_void,
) -> Result<()> {
let rc = sys_util::ioctl_with_ptr(&self.file, ioctl, param);
self.evaluate_ioctl_return(name, rc)
}
/// Helper function to send an ioctl and return a Result
/// # Safety
///
/// The caller must ensure that the actions the ioctl performs uphold
/// Rust's memory safety guarantees. Specifically
unsafe fn ioctl_with_mut_ptr(
&mut self,
ioctl: c_ulong,
name: &str,
param: *mut c_void,
) -> Result<()> {
let rc = sys_util::ioctl_with_mut_ptr(&self.file, ioctl, param);
self.evaluate_ioctl_return(name, rc)
}
fn evaluate_ioctl_return(&mut self, name: &str, rc: c_int) -> Result<()> {
if rc < 0 {
return Err(HibernateError::SnapshotIoctlError(
name.to_string(),
sys_util::Error::last(),
))
.context("Failed to execute ioctl on snapshot device");
}
Ok(())
}
}
/// A structure that wraps the SnapshotDevice, and unfreezes userspace when
/// dropped.
pub struct FrozenUserspaceTicket<'a> {
snap_dev: &'a mut SnapshotDevice,
}
impl Drop for FrozenUserspaceTicket<'_> {
fn drop(&mut self) {
info!("Unfreezing userspace");
if let Err(e) = self.snap_dev.unfreeze_userspace() {
error!("Failed to unfreeze userspace: {}", e);
}
}
}
impl<'a> AsMut<SnapshotDevice> for FrozenUserspaceTicket<'a> {
fn as_mut(&mut self) -> &mut SnapshotDevice {
self.snap_dev
}
}