Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,32 @@ concurrency:
cancel-in-progress: true

jobs:
# The image of test-e2e-qemu too: one image, one migration.
lint-and-test:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 60
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# gcc's 32-bit libraries, which test-i686 links with.
- run: |
sudo apt-get update
sudo apt-get install --yes gcc-multilib

- run: make setup

# After setup: the key covers installed toolchains.
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2

# Every check but the boots.
- run: make -k lint test
- run: make -k lint test test-i686

# QEMU 9.0 and newer fault unaligned loads from Device memory as arm64 does, which the
# aarch64 guests check; Ubuntu 24.04 has QEMU 8.2.
test-e2e-qemu:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 120
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -57,23 +65,24 @@ jobs:
# profile and logs errors; the builds take about a minute without it.
- run: make setup

# Kernel RPMs and DriverSample.efi. Saved only on success, as run.sh reuses
# DriverSample.efi unchecked.
# The guests' packages and each architecture's DriverSample.efi, keyed also on the
# apt packages and the runner image (this file) that build DriverSample. Saved only
# on success, as run.sh reuses DriverSample.efi unchecked.
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
target/qemu-test/kernel-rpms
target/qemu-test/driver-sample
key: qemu-inputs-${{ hashFiles('tests/qemu/kernel.sha256', 'tests/qemu/build-driver-sample.sh') }}
target/qemu-test/packages
target/qemu-test/*/driver-sample
key: qemu-inputs-${{ hashFiles('tests/qemu/guest-packages-*', 'tests/qemu/build-driver-sample.sh', 'tests/qemu/ubuntu-packages', '.github/workflows/checks.yml') }}

- run: make test-e2e-qemu
- run: make -k test-e2e-qemu

- if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: qemu-test-logs
path: |
target/qemu-test/*/*/*.log
target/qemu-test/*/executables
target/qemu-test/*/*/*/*.log
target/qemu-test/*/*/executables
if-no-files-found: ignore
retention-days: 14
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 29 additions & 17 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# The checks of uefisettings. They need GNU make and bash, rustup while
# RUST_TOOLCHAIN is set, as it is by default, and for test-e2e-qemu an x86_64
# host with /dev/kvm and what tests/qemu/README.md lists:
# RUST_TOOLCHAIN is set, as it is by default, for test-i686 an x86 host with
# gcc's 32-bit libraries, which README.md names, and for test-e2e-qemu an
# x86_64 host with /dev/kvm and what tests/qemu/README.md lists:
# make every check of CHECKS, stopping at the first that fails
# make -k every check of CHECKS, even after one fails
# make <check>... the named checks, e.g. make clippy-aarch64 doc
# make lint the checks of LINTS
# make lint test every check but test-e2e-qemu
# make lint test test-i686
# every check but test-e2e-qemu
# make RUST_TOOLCHAIN= the checks on the cargo of PATH
# make setup installs, through rustup, the toolchain, components and
# targets that the checks use
Expand All @@ -27,10 +29,10 @@
#
# A CI host runs make setup && make -k under timeout, as nothing bounds the time
# that cargo takes: timeout -k 60 <seconds> bash -c 'make setup && make -k'. It
# may keep target/qemu-test/kernel-rpms and target/qemu-test/driver-sample
# between runs: tests/qemu/fetch-kernel.sh downloads only the RPMs that are
# may keep target/qemu-test/packages and target/qemu-test/*/driver-sample
# between runs: tests/qemu/fetch-packages.sh downloads only the packages that are
# missing there or fail their sha256 check, and tests/qemu/run.sh builds
# DriverSample only when it is missing, so delete target/qemu-test/driver-sample
# DriverSample only when it is missing, so delete target/qemu-test/*/driver-sample
# after a change to build-driver-sample.sh.

SHELL := bash
Expand Down Expand Up @@ -58,14 +60,16 @@ export RUSTFLAGS := -D warnings
unexport CARGO_ENCODED_RUSTFLAGS

# The targets that the checks build for, comma-separated as rustup takes them.
TARGETS := x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-unknown-linux-musl,x86_64-unknown-uefi
TARGETS := x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,i686-unknown-linux-gnu,x86_64-unknown-linux-musl,x86_64-unknown-uefi,i686-unknown-linux-musl,i686-unknown-uefi,aarch64-unknown-linux-musl,aarch64-unknown-uefi
comma := ,

# CI runs lint test and test-e2e-qemu: a new check joins LINTS or gets a CI job.
LINTS := fmt clippy-x86_64 clippy-aarch64 clippy-uefi doc
CHECKS := $(LINTS) test test-e2e-qemu
# CI runs lint test test-i686 and test-e2e-qemu: a new check joins LINTS or
# QEMU_TESTS or CI runs it too.
LINTS := fmt clippy-x86_64 clippy-aarch64 clippy-i686 clippy-uefi doc
QEMU_TESTS := test-e2e-qemu-x86_64 test-e2e-qemu-i686 test-e2e-qemu-aarch64
CHECKS := $(LINTS) test test-i686 test-e2e-qemu

.PHONY: check lint help setup toolchain $(CHECKS)
.PHONY: check lint help setup toolchain $(CHECKS) $(QEMU_TESTS)

check: $(CHECKS)

Expand All @@ -75,7 +79,8 @@ help:
@echo "checks: $(CHECKS)"
@echo "make runs every check; make <check>... runs the named ones."
@echo "make lint runs $(LINTS)."
@echo "make lint test runs every check but test-e2e-qemu."
@echo "make test-e2e-qemu runs $(QEMU_TESTS)."
@echo "make lint test test-i686 runs every check but test-e2e-qemu."
@echo "make -k runs every check, even after one fails."
@echo "make -n <check> prints the commands of a check without running them."
@echo "make RUST_TOOLCHAIN= runs the checks on the cargo of PATH."
Expand All @@ -101,7 +106,7 @@ toolchain:
fmt: toolchain
cargo fmt --check

clippy-x86_64 clippy-aarch64: clippy-%: toolchain
clippy-x86_64 clippy-aarch64 clippy-i686: clippy-%: toolchain
cargo clippy --locked --workspace --all-targets --all-features --target $*-unknown-linux-gnu -- -D warnings

# publish-hiidb, the one crate built for UEFI.
Expand All @@ -111,10 +116,17 @@ clippy-uefi: toolchain
test: toolchain
scripts/assert-tests-ran.sh cargo test --locked

# The tests on 32-bit glibc, where off_t is signed and has 32 bits; this
# host runs them.
test-i686: toolchain
scripts/assert-tests-ran.sh cargo test --locked --target i686-unknown-linux-gnu

doc: toolchain
RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps --document-private-items --locked

# Boots the scenarios of tests/qemu/scenarios.tsv under KVM, with logs in
# target/qemu-test/<UTC time>/; see tests/qemu/README.md.
test-e2e-qemu: toolchain
tests/qemu/run.sh
test-e2e-qemu: $(QEMU_TESTS)

# Boots the scenarios of tests/qemu/scenarios.tsv on one architecture, with logs in
# target/qemu-test/<architecture>/<UTC time>/; see tests/qemu/README.md.
$(QEMU_TESTS): test-e2e-qemu-%: toolchain
tests/qemu/run.sh $*
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ mv lib.rs thrift/rust/uefisettings_spellings_db_thrift/uefisettings_spellings_db

## Checks

The `Makefile` in the root of the repository runs the checks that a change should pass: the lints (`make lint`: rustfmt, clippy for x86_64, aarch64 and UEFI, and the docs), the tests (`make test`) and the QEMU tests (`make test-e2e-qemu`).
The `Makefile` in the root of the repository runs the checks that a change should pass: the lints (`make lint`: rustfmt, clippy for x86_64, aarch64, i686 and UEFI, and the docs), the tests (`make test`, and `make test-i686` on 32-bit glibc) and the QEMU tests (`make test-e2e-qemu`).

The checks need GNU make and bash, and rustup unless `make RUST_TOOLCHAIN=` runs them on the cargo of `PATH`.

Expand All @@ -153,11 +153,13 @@ make setup # once: installs, through rustup, the Rust toolchain, compo
make # every check, stopping at the first that fails
make -k # every check, even after one fails
make <check>... # the named checks, e.g. make clippy-aarch64 doc
make lint test # every check but the QEMU tests
make lint test test-i686 # every check but the QEMU tests
make RUST_TOOLCHAIN= # the checks on the cargo of PATH
make help # lists the checks
```

`make test-i686` also needs an x86 host (x86_64 or i686), which runs the i686 test binaries, and gcc's 32-bit libraries (Ubuntu: `gcc-multilib`, or, next to the `gcc-aarch64-linux-gnu` of [`tests/qemu/ubuntu-packages`](tests/qemu/ubuntu-packages), which conflicts with it, `libc6-dev-i386` and `lib32gcc-<gcc version>-dev`; Fedora: `glibc-devel.i686`).

`make test-e2e-qemu`, the QEMU tests, also needs an x86_64 host with `/dev/kvm` and the packages that [`tests/qemu/README.md`](tests/qemu/README.md) lists.

GitHub Actions runs the checks on every pull request and every push to main, as [`.github/workflows/checks.yml`](.github/workflows/checks.yml) describes.
Expand Down
56 changes: 31 additions & 25 deletions src/lib/chattr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use std::fs::OpenOptions;
use std::os::raw::c_long;
use std::os::raw::c_uint;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::path::PathBuf;
Expand All @@ -26,24 +28,24 @@ const FS_IOC_GETFLAGS: u8 = 1;
const FS_IOC_SETFLAGS: u8 = 2;

// full list of flags https://github.com/torvalds/linux/blob/master/include/uapi/linux/fs.h#L239
pub const FS_IMMUTABLE_FL: i64 = 0x00000010; // Immutable file
pub const FS_IMMUTABLE_FL: c_uint = 0x00000010; // Immutable file

nix::ioctl_read!(
// The requests encode the size of a long, but the kernel reads and writes an unsigned
// int (ioctl_getflags() and ioctl_setflags() in fs/file_attr.c, Linux v6.17).
nix::ioctl_read_bad!(
ioctl_fs_get_attrs,
FS_IOC_MAGIC,
FS_IOC_GETFLAGS,
::std::os::raw::c_long
nix::request_code_read!(FS_IOC_MAGIC, FS_IOC_GETFLAGS, std::mem::size_of::<c_long>()),
c_uint
);

nix::ioctl_write_ptr!(
nix::ioctl_write_ptr_bad!(
ioctl_fs_set_attrs,
FS_IOC_MAGIC,
FS_IOC_SETFLAGS,
::std::os::raw::c_long
nix::request_code_write!(FS_IOC_MAGIC, FS_IOC_SETFLAGS, std::mem::size_of::<c_long>()),
c_uint
);

/// returns all extended fs attributes for the given path as a set of bit flags
pub fn get_all_attrs(path: impl AsRef<Path>) -> Result<i64> {
pub fn get_all_attrs(path: impl AsRef<Path>) -> Result<c_uint> {
let path = path.as_ref();
let file = OpenOptions::new()
.write(false)
Expand All @@ -54,21 +56,19 @@ pub fn get_all_attrs(path: impl AsRef<Path>) -> Result<i64> {
path.to_string_lossy()
))?;
let fd = file.as_raw_fd();
let mut attrs = 0i64;
// SAFETY c_long is guaranteed to be i32 or i64 (https://doc.rust-lang.org/std/os/raw/type.c_long.html)
// This tool is linux specific and do not support 32bit systems, so we can assume that it's always i64
let attrs_ptr = &mut attrs as *mut i64;

let mut attrs: c_uint = 0;
// SAFETY: fd stays open while file lives, and attrs is the unsigned int that the
// kernel writes.
unsafe {
ioctl_fs_get_attrs(fd, attrs_ptr).context("ioctl failed")?;
ioctl_fs_get_attrs(fd, &mut attrs).context("ioctl failed")?;
}

Ok(attrs)
}

/// writes extended fs attributes for the given path as a set of bit flags
/// all existing attributes will be overwritten
pub fn set_all_attrs(path: impl AsRef<Path>, attrs: i64) -> Result<()> {
pub fn set_all_attrs(path: impl AsRef<Path>, attrs: c_uint) -> Result<()> {
let path = path.as_ref();
let file = OpenOptions::new()
.write(false)
Expand All @@ -79,12 +79,10 @@ pub fn set_all_attrs(path: impl AsRef<Path>, attrs: i64) -> Result<()> {
path.to_string_lossy()
))?;
let fd = file.as_raw_fd();
let mut tmp = attrs;
// SAFETY c_long is guaranteed to be i32 or i64 (https://doc.rust-lang.org/std/os/raw/type.c_long.html)
// This tool is linux specific and do not support 32bit systems, so we can assume that it's always i64
let attrs_ptr = &mut tmp as *mut i64;
// SAFETY: fd stays open while file lives, and attrs is the unsigned int that the
// kernel reads.
unsafe {
ioctl_fs_set_attrs(fd, attrs_ptr).context("ioctl failed")?;
ioctl_fs_set_attrs(fd, &attrs).context("ioctl failed")?;
}
Ok(())
}
Expand Down Expand Up @@ -127,7 +125,7 @@ macro_rules! clear_attrs {

pub struct EfivarsImmutabilityGuard {
path: PathBuf,
attrs_before_writing: i64,
attrs_before_writing: c_uint,
}

impl EfivarsImmutabilityGuard {
Expand Down Expand Up @@ -180,7 +178,7 @@ pub mod tests {

use super::*;
//in the tests we expect that FS_NODUMP_FL is initially cleared, might be a weak assumption..
pub const FS_NODUMP_FL: i64 = 0x00000040; // do not dump file
pub const FS_NODUMP_FL: c_uint = 0x00000040; // do not dump file
#[test]
fn test_get_all_attrs() -> Result<()> {
let test_file = tempfile::NamedTempFile::new().expect("Failed to create test file");
Expand Down Expand Up @@ -230,14 +228,22 @@ pub mod tests {
get_all_attrs(test_file.path()).context("No error expected from get_attrs")?;
// checking that FS_NODUMP_FL is set
assert_eq!(attrs_after_set & FS_NODUMP_FL, FS_NODUMP_FL);
// checking that clearing a flag that no new file has or inherits, so that clearing it
// needs no capability, keeps FS_NODUMP_FL, the only flag that the owner may set on both
// ZFS and tmpfs
clear_attrs!(test_file.path(), FS_IMMUTABLE_FL)
.context("No error expected from clear_attrs")?;
let attrs_after_other_clear =
get_all_attrs(test_file.path()).context("No error expected from get_attrs")?;
assert_eq!(attrs_after_other_clear & FS_NODUMP_FL, FS_NODUMP_FL);
clear_attrs!(test_file.path(), FS_NODUMP_FL, FS_NODUMP_FL)
.context("No error expected from clear_attrs")?;
let attrs_after_clear =
get_all_attrs(test_file.path()).context("No error expected from get_attrs")?;
// checking that FS_NODUMP_FL is cleared
assert_eq!(attrs_after_clear & FS_NODUMP_FL, 0);
// checking that everything else is still the same
assert_eq!(attrs_after_set & !FS_NODUMP_FL, attrs & !FS_NODUMP_FL);
assert_eq!(attrs_after_clear & !FS_NODUMP_FL, attrs & !FS_NODUMP_FL);
Ok(())
}
}
9 changes: 5 additions & 4 deletions src/lib/hii/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,14 @@ fn copy_mapped_page(file: &File, offset: u64, dst: &mut [u8]) -> Result<usize> {
let offset_in_page = usize::try_from(offset % page_size as u64)?;
let len = (page_size - offset_in_page).min(dst.len());
let page_base = offset - offset_in_page as u64;
let map_base = libc::off_t::try_from(page_base)
.with_context(|| format!("Mapping offset 0x{page_base:x} does not fit off_t"))?;
// mmap64(), as off_t is signed and has 32 bits on most 32-bit glibc targets.
let map_base = libc::off64_t::try_from(page_base)
.with_context(|| format!("Mapping offset 0x{page_base:x} does not fit off64_t"))?;

// SAFETY: addr = NULL lets the kernel pick an unused address range, so the
// new PROT_READ mapping cannot alias any Rust allocation (including dst).
let mapped = unsafe {
libc::mmap(
libc::mmap64(
ptr::null_mut(),
page_size,
libc::PROT_READ,
Expand Down Expand Up @@ -356,7 +357,7 @@ mod tests {
}
}

/// The offset also fails the off_t check; the overflow error must win.
/// The offset also fails the off64_t check; the overflow error must win.
#[test]
fn read_mapped_rejects_overflowing_range() {
let (file, _) = mapped_fixture(16);
Expand Down
Loading