diff options
author | Ludovic Barman <ludovicb@google.com> | 2024-01-05 11:20:59 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2024-01-05 11:20:59 +0000 |
commit | 26a65d048fce377a7160653f408988ed455dbf76 (patch) | |
tree | 7c3c342073022878b3bd37e25a235d7dfb0bd488 | |
parent | 499e4718b98512eaa9309c40bb5da0eab288d01f (diff) | |
parent | 91342e8f170110f827df7603c7de9778ee888150 (diff) | |
download | userfaultfd-26a65d048fce377a7160653f408988ed455dbf76.tar.gz |
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/userfaultfd/+/2878120
Change-Id: I27e96e5ccd1f3e4411592c84fcd091ae807f88fc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | .github/dependabot.yml | 16 | ||||
-rw-r--r-- | .github/workflows/rust.yml | 36 | ||||
-rw-r--r-- | Android.bp | 4 | ||||
-rwxr-xr-x | CHANGELOG.md | 13 | ||||
-rw-r--r-- | Cargo.toml | 22 | ||||
-rw-r--r-- | Cargo.toml.orig | 13 | ||||
-rw-r--r-- | METADATA | 21 | ||||
-rw-r--r-- | cargo_embargo.json | 3 | ||||
-rw-r--r-- | examples/manpage.rs | 7 | ||||
-rw-r--r-- | src/builder.rs | 62 | ||||
-rw-r--r-- | src/error.rs | 6 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/raw.rs | 15 |
14 files changed, 179 insertions, 50 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index c054044..054ab4e 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "cb2cd6d359b715e24144242e4580679897268ffd" + "sha1": "afcbd90872e7fa0fca2c416e786e4d62e948bced" }, "path_in_vcs": "" }
\ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8923421 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/userfaultfd-sys" # Location of package manifests + schedule: + interval: "weekly" + diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7631965..1e212ec 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,24 +9,50 @@ on: jobs: build: - runs-on: ubuntu-latest + # ubuntu-latest runs a recent kernel with /dev/userfaultfd support whereas + # ubuntu-20.04 has a 5.15 kernel. We run the job in both, so we can test + # both paths for creating the file descriptor, i.e. /dev/userfaultfd ioctl + # and userfaultfd syscall. + runs-on: ${{ matrix.runner }} + strategy: + matrix: + runner: [ ubuntu-latest, ubuntu-20.04 ] steps: - uses: actions/checkout@v2 + + # Keep this step, so that we can check that the Linux kernel is the one we + # expect, depending on the runner kernel. + - name: Check Linux version + run: uname -r + + # /dev/userfaultfd is only present on ubuntu-latest. + - name: Setup access to /dev/userfaultfd + if: ${{ matrix.runner == 'ubuntu-latest' }} + run: sudo setfacl -m u:${USER}:rw /dev/userfaultfd + - name: Build run: cargo build --verbose - # The github ubuntu-latest is now on linux 5.11 kernel, - # so we can test the crate with support for each of the - # kernel featuresets: - - name: Run tests (Linux 4.11 support) run: cargo test --verbose - name: Run tests (Linux 4.14 support) run: cargo test --verbose --features linux4_14 + - name: Run tests (Linux 5.7 support) + if: ${{ matrix.runner == 'ubuntu-latest' }} run: cargo test --verbose --features linux5_7 + # On ubuntu-20.04 runner we need to make sure we have the proper kernel + # headers for building the correct bindings + - name: Run tests (Linux 5.7 support) + if: ${{ matrix.runner == 'ubuntu-20.04' }} + run: + sudo apt update && + sudo apt install -y linux-headers-5.11.0-25-generic && + export LINUX_HEADERS=/usr/src/linux-headers-5.11.0-25-generic && + cargo test --verbose --features linux5_7 + audit: runs-on: ubuntu-latest @@ -29,12 +29,12 @@ rust_library { host_supported: true, crate_name: "userfaultfd", cargo_env_compat: true, - cargo_pkg_version: "0.5.1", + cargo_pkg_version: "0.7.0", srcs: ["src/lib.rs"], edition: "2018", features: ["default"], rustlibs: [ - "libbitflags-1.3.2", + "libbitflags", "libcfg_if", "liblibc", "libnix", diff --git a/CHANGELOG.md b/CHANGELOG.md index b2621e9..b0797ad 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ ### Unreleased - Added `Uffd::read_events` that can read multiple events from the userfaultfd file descriptor. +- Updated `bitflags` dependency to `2.2.1`. +- Use `/dev/userfaultfd` as the default API for creating userfaultfd file descriptors. + + Since Linux 5.11 a process can select if it wants to handle page faults triggered in kernel space + or not. Under this mechanism, processes that wish to handle those, need to have `CAP_SYS_PTRACE` + capability. `CAP_SYS_PTRACE` allows a process to do much more than create userfault fds, so with + 6.1 Linux introduces `/dev/userfaultfd`, a special character device that allows creating + userfault file descriptors using the `USERFAULTFD_IOC_NEW` `ioctl`. Access to this device is + granted via file system permissions and does not require `CAP_SYS_PTRACE` to handle kernel + triggered page faults. + + We now default to using `/dev/userfaultfd` for creating the descriptors and only if that file is + not present, we fall back to using the syscall. ### 0.3.1 (2021-02-17) @@ -12,7 +12,7 @@ [package] edition = "2018" name = "userfaultfd" -version = "0.5.1" +version = "0.7.0" authors = ["The Wasmtime Project Developers"] description = "Rust bindings for the Linux userfaultfd functionality" readme = "README.md" @@ -20,7 +20,7 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/bytecodealliance/userfaultfd-rs" [dependencies.bitflags] -version = "1.0" +version = "2.2.1" [dependencies.cfg-if] version = "^1.0.0" @@ -29,15 +29,27 @@ version = "^1.0.0" version = "0.2.65" [dependencies.nix] -version = "0.26" +version = "0.27" +features = ["ioctl"] [dependencies.thiserror] version = "1.0.4" [dependencies.userfaultfd-sys] -version = "^0.4.0" +version = "^0.5.0" + +[dev-dependencies.nix] +version = "0.27" +features = [ + "poll", + "mman", + "feature", +] [features] default = [] -linux4_14 = ["userfaultfd-sys/linux4_14"] +linux4_14 = [ + "userfaultfd-sys/linux4_14", + "nix/process", +] linux5_7 = ["userfaultfd-sys/linux5_7"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 04c8ca4..aa3d40c 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "userfaultfd" -version = "0.5.1" +version = "0.7.0" authors = ["The Wasmtime Project Developers"] edition = "2018" license = "MIT OR Apache-2.0" @@ -9,14 +9,17 @@ repository = "https://github.com/bytecodealliance/userfaultfd-rs" readme = "README.md" [dependencies] -bitflags = "1.0" +bitflags = "2.2.1" cfg-if = "^1.0.0" libc = "0.2.65" -nix = "0.26" +nix = { version = "0.27", features = ["ioctl"] } thiserror = "1.0.4" -userfaultfd-sys = { path = "userfaultfd-sys", version = "^0.4.0" } +userfaultfd-sys = { path = "userfaultfd-sys", version = "^0.5.0" } + +[dev-dependencies] +nix = { version = "0.27", features = ["poll", "mman", "feature"] } [features] default = [] -linux4_14 = ["userfaultfd-sys/linux4_14"] +linux4_14 = ["userfaultfd-sys/linux4_14", "nix/process"] linux5_7 = ["userfaultfd-sys/linux5_7"] @@ -1,23 +1,20 @@ # This project was upgraded with external_updater. # Usage: tools/external_updater/updater.sh update rust/crates/userfaultfd -# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md name: "userfaultfd" description: "Rust bindings for the Linux userfaultfd functionality" third_party { - url { - type: HOMEPAGE - value: "https://crates.io/crates/userfaultfd" - } - url { - type: ARCHIVE - value: "https://static.crates.io/crates/userfaultfd/userfaultfd-0.5.1.crate" - } - version: "0.5.1" license_type: NOTICE last_upgrade_date { year: 2023 - month: 3 - day: 30 + month: 12 + day: 16 + } + homepage: "https://crates.io/crates/userfaultfd" + identifier { + type: "Archive" + value: "https://static.crates.io/crates/userfaultfd/userfaultfd-0.7.0.crate" + version: "0.7.0" } } diff --git a/cargo_embargo.json b/cargo_embargo.json index 99df43f..cb908d7 100644 --- a/cargo_embargo.json +++ b/cargo_embargo.json @@ -1,6 +1,3 @@ { - "module_name_overrides": { - "libbitflags": "libbitflags-1.3.2" - }, "run_cargo": false } diff --git a/examples/manpage.rs b/examples/manpage.rs index 29cccd0..1de593e 100644 --- a/examples/manpage.rs +++ b/examples/manpage.rs @@ -3,7 +3,6 @@ use libc::{self, c_void}; use nix::poll::{poll, PollFd, PollFlags}; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::unistd::{sysconf, SysconfVar}; -use std::os::unix::io::AsRawFd; use std::{convert::TryInto, env}; use userfaultfd::{Event, Uffd, UffdBuilder}; @@ -18,7 +17,7 @@ fn fault_handler_thread(uffd: Uffd) { page_size.try_into().unwrap(), ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, - -1, + None::<std::os::fd::BorrowedFd>, 0, ) .expect("mmap") @@ -30,7 +29,7 @@ fn fault_handler_thread(uffd: Uffd) { loop { // See what poll() tells us about the userfaultfd - let pollfd = PollFd::new(uffd.as_raw_fd(), PollFlags::POLLIN); + let pollfd = PollFd::new(&uffd, PollFlags::POLLIN); let nready = poll(&mut [pollfd], -1).expect("poll"); println!("\nfault_handler_thread():"); @@ -101,7 +100,7 @@ fn main() { len.try_into().unwrap(), ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, - -1, + None::<std::os::fd::BorrowedFd>, 0, ) .expect("mmap") diff --git a/src/builder.rs b/src/builder.rs index b89efb4..a200148 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,11 +3,17 @@ use crate::raw; use crate::{IoctlFlags, Uffd}; use bitflags::bitflags; use nix::errno::Errno; +use std::fs::{File, OpenOptions}; +use std::io::ErrorKind; +use std::os::fd::AsRawFd; + +const UFFD_DEVICE_PATH: &str = "/dev/userfaultfd"; cfg_if::cfg_if! { if #[cfg(any(feature = "linux5_7", feature = "linux4_14"))] { bitflags! { /// Used with `UffdBuilder` to determine which features are available in the current kernel. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct FeatureFlags: u64 { const PAGEFAULT_FLAG_WP = raw::UFFD_FEATURE_PAGEFAULT_FLAG_WP; const EVENT_FORK = raw::UFFD_FEATURE_EVENT_FORK; @@ -23,6 +29,7 @@ cfg_if::cfg_if! { } else { bitflags! { /// Used with `UffdBuilder` to determine which features are available in the current kernel. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct FeatureFlags: u64 { const PAGEFAULT_FLAG_WP = raw::UFFD_FEATURE_PAGEFAULT_FLAG_WP; const EVENT_FORK = raw::UFFD_FEATURE_EVENT_FORK; @@ -113,6 +120,47 @@ impl UffdBuilder { self } + fn uffd_from_dev(&self, file: &mut File, flags: i32) -> Result<Uffd> { + match unsafe { raw::new_uffd(file.as_raw_fd(), flags) } { + Err(err) => Err(err.into()), + Ok(fd) => Ok(Uffd { fd }), + } + } + + fn uffd_from_syscall(&self, flags: i32) -> Result<Uffd> { + let fd = match Errno::result(unsafe { raw::userfaultfd(flags) }) { + Ok(fd) => fd, + // setting the USER_MODE_ONLY flag on kernel pre-5.11 causes it to return EINVAL. + // If the user asks for the flag, we first try with it set, and if kernel gives + // EINVAL we try again without the flag set. + Err(Errno::EINVAL) if self.user_mode_only => Errno::result(unsafe { + raw::userfaultfd(flags & !raw::UFFD_USER_MODE_ONLY as i32) + })?, + Err(e) => return Err(e.into()), + }; + + // Wrap the fd up so that a failure in this function body closes it with the drop. + Ok(Uffd { fd }) + } + + // Try to get a UFFD file descriptor using `/dev/userfaultfd`. If that fails + // fall back to calling the system call. + fn open_file_descriptor(&self, flags: i32) -> Result<Uffd> { + // If `/dev/userfaultfd` exists we'll try to get the file descriptor from it. If the file + // doesn't exist we will fall back to calling the system call. This means, that if the + // device exists but the calling process does not have access rights to it, this will fail, + // i.e. we will not fall back to calling the system call. + match OpenOptions::new() + .read(true) + .write(true) + .open(UFFD_DEVICE_PATH) + { + Ok(mut file) => self.uffd_from_dev(&mut file, flags), + Err(err) if err.kind() == ErrorKind::NotFound => self.uffd_from_syscall(flags), + Err(err) => Err(Error::OpenDevUserfaultfd(err)), + } + } + /// Create a `Uffd` object with the current settings of this builder. pub fn create(&self) -> Result<Uffd> { // first do the syscall to get the file descriptor @@ -128,19 +176,7 @@ impl UffdBuilder { flags |= raw::UFFD_USER_MODE_ONLY as i32; } - let fd = match Errno::result(unsafe { raw::userfaultfd(flags) }) { - Ok(fd) => fd, - // setting the USER_MODE_ONLY flag on kernel pre-5.11 causes it to return EINVAL. - // If the user asks for the flag, we first try with it set, and if kernel gives - // EINVAL we try again without the flag set. - Err(Errno::EINVAL) if self.user_mode_only => Errno::result(unsafe { - raw::userfaultfd(flags & !raw::UFFD_USER_MODE_ONLY as i32) - })?, - Err(e) => return Err(e.into()), - }; - - // Wrap the fd up so that a failure in this function body closes it with the drop. - let uffd = Uffd { fd }; + let uffd = self.open_file_descriptor(flags)?; // then do the UFFDIO_API ioctl to set up and ensure features and other ioctls are available let mut api = raw::uffdio_api { diff --git a/src/error.rs b/src/error.rs index 5cd8926..f66806a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use std::io; + use crate::IoctlFlags; use nix::errno::Errno; use thiserror::Error; @@ -47,6 +49,10 @@ pub enum Error { /// Zeropage ioctl failure with `errno` value. #[error("Zeropage failed: {0}")] ZeropageFailed(Errno), + + /// Could not open /dev/userfaultfd even though it exists + #[error("Error accessing /dev/userfaultfd: {0}")] + OpenDevUserfaultfd(io::Error), } impl From<nix::Error> for Error { @@ -21,6 +21,7 @@ use libc::{self, c_void}; use nix::errno::Errno; use nix::unistd::read; use std::mem; +use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; /// Represents an opaque buffer where userfaultfd events are stored. @@ -53,6 +54,12 @@ impl Drop for Uffd { } } +impl AsFd for Uffd { + fn as_fd(&self) -> BorrowedFd<'_> { + unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } + } +} + impl AsRawFd for Uffd { fn as_raw_fd(&self) -> RawFd { self.fd @@ -73,6 +80,7 @@ impl FromRawFd for Uffd { bitflags! { /// The registration mode used when registering an address range with `Uffd`. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct RegisterMode: u64 { /// Registers the range for missing page faults. const MISSING = raw::UFFDIO_REGISTER_MODE_MISSING; @@ -359,6 +367,7 @@ impl Uffd { bitflags! { /// Used with `UffdBuilder` and `Uffd::register()` to determine which operations are available. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct IoctlFlags: u64 { const REGISTER = 1 << raw::_UFFDIO_REGISTER; const UNREGISTER = 1 << raw::_UFFDIO_UNREGISTER; @@ -23,3 +23,18 @@ nix::ioctl_readwrite!( _UFFDIO_WRITEPROTECT, uffdio_writeprotect ); + +// ioctls for /dev/userfaultfd + +// This is the `/dev/userfaultfd` ioctl() from creating a new userfault file descriptor. +// It is a "bad" ioctl in the sense that it is defined as an _IOC: +// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/userfaultfd.h#L17, +// aka `nix::ioctl_none`, however it does receive an integer argument: +// https://elixir.bootlin.com/linux/latest/source/fs/userfaultfd.c#L2186. That is the same argument +// that the userfaultfd() system call receives. +nix::ioctl_write_int_bad!( + /// Create a new userfault file descriptor from the `/dev/userfaultfd` + /// device. This receives the same arguments as the userfaultfd system call. + new_uffd, + nix::request_code_none!(USERFAULTFD_IOC, 0x00) +); |