Skip to content
Open
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
47 changes: 47 additions & 0 deletions .github/workflows/freebsd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: FreeBSD

permissions: {}

on:
push:
branches:
- main
pull_request:
merge_group:
branches:
- main

jobs:
freebsd:
name: FreeBSD unit tests
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Unit tests in a FreeBSD VM
uses: vmactions/freebsd-vm@77ed28d336d03fe19a3f4f7266c1d2c4714dd79d # v1.5.2
with:
usesh: true
cache-after-prepare: true
# system::time::tests::get_process_start_time compares the process
# start time against the wall clock, so the guest clock must be sane.
sync-time: true
prepare: |
pkg install -y rust git-tiny
run: |
set -e
rustc --version
cargo --version

# The unit tests must not run as root: src/su/context.rs says
# "this test is allowed to fail if run as root -- do not run unit
# tests as root", and the VM logs in as root by default.
pw useradd sudorstest -m
chown -R sudorstest .

# We skip a couple of tests which fail when running as root.
su -m sudorstest -c "cd $PWD && env HOME=/home/sudorstest cargo test --workspace --all-targets --release -- --skip group_as_non_root --skip test_secure_open_cookie_file --skip test_traverse_secure_open_positive"
39 changes: 35 additions & 4 deletions src/system/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,40 @@ impl ProcessCreateTime {
}
}

/// The current time on the same clock `Process::starting_time` reports.
///
/// This deliberately does NOT reuse `SystemTime::now`, which always reads
/// `CLOCK_BOOTTIME`. On FreeBSD the process start time comes from
/// `kinfo_proc.ki_start`, which the kernel exports as absolute wall clock,
/// while `CLOCK_BOOTTIME` is an alias for the uptime clock there -- so the
/// two are not comparable. `SystemTime::now` must stay on the monotonic
/// clock (it guards the credential cache timeout), hence the split.
#[cfg(test)]
pub(super) fn now() -> std::io::Result<ProcessCreateTime> {
let mut spec = MaybeUninit::<libc::timespec>::uninit();
// SAFETY: valid pointer is passed to clock_gettime
crate::cutils::cerr(unsafe {
libc::clock_gettime(
if cfg!(target_os = "freebsd") {
libc::CLOCK_REALTIME
} else {
libc::CLOCK_BOOTTIME
},
spec.as_mut_ptr(),
)
})?;
// SAFETY: The `libc::clock_gettime` will correctly initialize `spec`,
// otherwise it will return early with the `?` operator.
let spec = unsafe { spec.assume_init() };

// the below conversion is not as useless as clippy thinks, on 32bit systems
#[allow(clippy::useless_conversion)]
Ok(ProcessCreateTime::new(
spec.tv_sec.into(),
spec.tv_nsec.into(),
))
}

pub(super) fn encode(&self, target: &mut impl Write) -> std::io::Result<()> {
let secs = self.secs.to_ne_bytes();
let nsecs = self.nsecs.to_ne_bytes();
Expand Down Expand Up @@ -178,10 +212,7 @@ mod tests {
use crate::system::{Process, WithProcess};
let time = Process::starting_time(WithProcess::Current).unwrap();

let now = {
let super::SystemTime { secs, nsecs } = super::SystemTime::now().unwrap();
super::ProcessCreateTime { secs, nsecs }
};
let now = super::ProcessCreateTime::now().unwrap();

assert!(time.secs > now.secs - 24 * 60 * 60);
assert!(time < now);
Expand Down
Loading