Reland "swap: add userfaultfd wrapper"

origin:
https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3895235

userfaultfd enables applications to handle page faults on designated
memory area.

vmm-swap feature uses userfaultfd to catch page fault event and swap
in the guest memory from the swap file.

design document: go/tanooki-phase1-dd

BUG=b:215093219
TEST=cargo build --features=swap

Change-Id: I36ecfe9be988a4bc451f8edaf2ab48e25c6600f4
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4016142
Commit-Queue: Shin Kawamura <kawasin@google.com>
Reviewed-by: David Stevens <stevensd@chromium.org>
Reviewed-by: Dennis Kempin <denniskempin@google.com>
This commit is contained in:
Shintaro Kawamura 2022-09-14 14:18:14 +09:00 committed by crosvm LUCI
parent ba3e2fa852
commit 67390a05fb
6 changed files with 293 additions and 1 deletions

129
Cargo.lock generated
View file

@ -230,6 +230,25 @@ dependencies = [
"syn 1.0.103",
]
[[package]]
name = "bindgen"
version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote 1.0.21",
"regex",
"rustc-hash",
"shlex",
]
[[package]]
name = "bit_field"
version = "0.1.0"
@ -312,6 +331,15 @@ dependencies = [
"jobserver",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -332,6 +360,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "clang-sys"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -532,6 +571,7 @@ dependencies = [
"serde_json",
"serde_keyvalue",
"static_assertions",
"swap",
"sync",
"tempfile",
"terminal_size",
@ -606,7 +646,7 @@ version = "3.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865"
dependencies = [
"nix",
"nix 0.24.2",
"winapi",
]
@ -975,6 +1015,12 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "gpu_display"
version = "0.1.0"
@ -1172,6 +1218,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.126"
@ -1206,6 +1258,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "libloading"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libslirp-sys"
version = "4.2.1"
@ -1406,6 +1468,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset 0.6.5",
]
[[package]]
name = "nix"
version = "0.24.2"
@ -1497,6 +1572,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@ -1718,6 +1799,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rutabaga_gfx"
version = "0.1.0"
@ -1837,6 +1924,12 @@ dependencies = [
"syn 1.0.103",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "slab"
version = "0.4.7"
@ -1864,6 +1957,15 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "swap"
version = "0.1.0"
dependencies = [
"base",
"libc",
"userfaultfd",
]
[[package]]
name = "syn"
version = "0.11.11"
@ -2062,6 +2164,31 @@ dependencies = [
"usb_sys",
]
[[package]]
name = "userfaultfd"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fee2cdd3f8bdd0b98d7aa9ace35e7214a71888229d60c1cd1cd71b7c09c089d0"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"nix 0.23.1",
"thiserror",
"userfaultfd-sys",
]
[[package]]
name = "userfaultfd-sys"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cbcf2717fa856a7226499babbbccb07353ea2fc2b27defd38bd13b1227cc78"
dependencies = [
"bindgen",
"cc",
"cfg-if",
]
[[package]]
name = "uuid"
version = "0.8.2"

View file

@ -90,6 +90,7 @@ members = [
"resources",
"rutabaga_gfx",
"serde_keyvalue",
"swap",
"system_api",
"third_party/vmm_vhost",
"tpm2-sys",
@ -296,6 +297,7 @@ all-aarch64 = [
"panic-memfd",
"power-monitor-powerd",
"slirp",
"swap",
"tpm",
"vaapi",
"video-decoder",
@ -395,6 +397,9 @@ aarch64 = { path = "aarch64" }
minijail = "*" # provided by ebuild
p9 = "*"
[target.'cfg(target_os="linux")'.dependencies]
swap = { path = "swap", optional = true }
[target.'cfg(windows)'.dependencies]
anti_tamper = { path = "anti_tamper" }
cros_async = { path = "cros_async" }

10
swap/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "swap"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
edition = "2021"
[dependencies]
base = { path = "../base" }
libc = "*"
userfaultfd = "0.5.0"

11
swap/src/lib.rs Normal file
View file

@ -0,0 +1,11 @@
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! crate for the vmm-swap feature.
// TODO(kawasin): warn dead_code again after swap feature is done.
#![allow(dead_code)]
#![deny(missing_docs)]
mod userfaultfd;

138
swap/src/userfaultfd.rs Normal file
View file

@ -0,0 +1,138 @@
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Provides wrapper of userfaultfd crate for vmm-swap feature.
#![deny(missing_docs)]
use std::convert::From;
use std::os::unix::io::AsRawFd;
use base::AsRawDescriptor;
use base::RawDescriptor;
pub use userfaultfd::Error as UffdError;
pub use userfaultfd::Event as UffdEvent;
use userfaultfd::FeatureFlags;
use userfaultfd::IoctlFlags;
use userfaultfd::Result;
use userfaultfd::Uffd;
use userfaultfd::UffdBuilder;
/// Wrapper for [`userfaultfd::Uffd`] to be used in the vmm-swap feature.
///
/// # Safety
///
/// The userfaultfd operations (`UFFDIO_COPY` and `UFFDIO_ZEROPAGE`) looks unsafe since it fills a
/// memory content directly. But they actually are not unsafe operation but `UFFDIO_REGISTER` should
/// be the unsafe operation for Rust memory safety.
///
/// According to [the Rust document](https://doc.rust-lang.org/nomicon/uninitialized.html),
///
/// > All runtime-allocated memory in a Rust program begins its life as uninitialized.
///
/// The userfaultfd operations actually does not change/overwrite the existing memory contents but
/// they just setup the "uninitialized" pages. If the page was already initialized, the userfaultfd
/// operations fail and return EEXIST error (which is not documented unfortunately). So they
/// originally does not affect the Rust memory safety.
///
/// The "uninitialized" page in this context has 2 patterns:
///
/// 1. pages which is never touched or,
/// 2. pages which is never touched after MADV_REMOVE
///
/// Filling the (1) pages with any contents should not affect the Rust memory safety.
///
/// Filling the (2) pages potentially may break the memory used by Rust. But the safety should be
/// examined at `MADV_REMOVE` and `UFFDIO_REGISTER` timing.
pub struct Userfaultfd {
uffd: Uffd,
}
impl Userfaultfd {
/// Creates a new userfaultfd.
pub fn new() -> Result<Self> {
let uffd = UffdBuilder::new()
.close_on_exec(true)
.non_blocking(true)
.user_mode_only(false)
.require_features(
FeatureFlags::EVENT_FORK | FeatureFlags::MISSING_SHMEM | FeatureFlags::EVENT_REMOVE,
)
.create()?;
Ok(Self { uffd })
}
/// Register a range of memory to the userfaultfd.
///
/// After this registration, any page faults on the range will be caught by the userfaultfd.
///
/// # Arguments
///
/// * `addr` - the starting address of the range of memory.
/// * `len` - the length in bytes of the range of memory.
///
/// # Safety
///
/// [addr, addr+len) must lie within a [MemoryMapping](base::MemoryMapping), and that mapping
/// must live for the lifespan of the userfaultfd kernel object (which may be distinct from the
/// `Userfaultfd` rust object in this process).
pub unsafe fn register(&self, addr: usize, len: usize) -> Result<IoctlFlags> {
self.uffd.register(addr as *mut libc::c_void, len)
}
/// Initialize page(s) and fill it with zero.
///
/// # Arguments
///
/// * `addr` - the starting address of the page(s) to be initialzed with zero.
/// * `len` - the length in bytes of the page(s).
/// * `wake` - whether or not to unblock the faulting thread.
pub fn zero(&self, addr: usize, len: usize, wake: bool) -> Result<usize> {
// safe because zeroing untouched pages does not break the Rust memory safety since "All
// runtime-allocated memory in a Rust program begins its life as uninitialized."
// https://doc.rust-lang.org/nomicon/uninitialized.html
unsafe { self.uffd.zeropage(addr as *mut libc::c_void, len, wake) }
}
/// Copy the `data` to the page(s) starting from `addr`.
///
/// # Arguments
///
/// * `addr` - the starting address of the page(s) to be initialzed with data.
/// * `len` - the length in bytes of the page(s).
/// * `data` - the starting address of the content.
/// * `wake` - whether or not to unblock the faulting thread.
pub fn copy(&self, addr: usize, len: usize, data: *const u8, wake: bool) -> Result<usize> {
// safe because filling untouched pages with data does not break the Rust memory safety
// since "All runtime-allocated memory in a Rust program begins its life as uninitialized."
// https://doc.rust-lang.org/nomicon/uninitialized.html
unsafe {
self.uffd.copy(
data as *const libc::c_void,
addr as *mut libc::c_void,
len,
wake,
)
}
}
/// Read an event from the userfaultfd.
///
/// Return `None` immediately if no events is ready to read.
pub fn read_event(&self) -> Result<Option<UffdEvent>> {
self.uffd.read_event()
}
}
impl From<Uffd> for Userfaultfd {
fn from(uffd: Uffd) -> Self {
Self { uffd }
}
}
impl AsRawDescriptor for Userfaultfd {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.uffd.as_raw_fd()
}
}

View file

@ -71,6 +71,7 @@ WIN64_DISABLED_CRATES = [
"p9",
"qcow_utils",
"rutabaga_gralloc",
"swap",
"system_api_stub",
"tpm2-sys",
"tpm2",