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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ jobs:
env:
LITEBOX_DISABLE_NATIVE_FORK: "1"
run: |
./.github/tools/github_actions_run_cargo nextest -p litebox_runner_linux_userland --features litebox_runner_linux_userland/aarch64_copy_based_fork -E 'binary(run) and test(/fork/) and not test(/^deep_fork/)'
./.github/tools/github_actions_run_cargo nextest -p litebox_runner_linux_userland --features litebox_runner_linux_userland/aarch64_copy_based_fork -E 'binary(run) and test(/fork|hwcaps/) and not test(/^deep_fork/)'
# Nextest does not run doctests.
- run: |
./.github/tools/github_actions_run_cargo test --doc $AARCH64_CRATES
Expand Down
112 changes: 112 additions & 0 deletions litebox/src/arch/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,115 @@ pub const PSR_NZCV_MASK: u64 = 0b1111 << 28;
pub const PSR_SSBS_BIT: u64 = 1 << 12;
/// Data Independent Timing bit.
pub const PSR_DIT_BIT: u64 = 1 << 24;

bitflags::bitflags! {
/// AArch64 CPU features, named after Arm `FEAT_*` extensions.
///
/// Bit positions are LiteBox's own and must not be relied on to match any ABI encoding.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CpuFeatures: u64 {
const FP = 1 << 0;
/// `FEAT_AdvSIMD`.
const ASIMD = 1 << 1;
const AES = 1 << 2;
const PMULL = 1 << 3;
const SHA1 = 1 << 4;
const SHA256 = 1 << 5;
const CRC32 = 1 << 6;
const LSE = 1 << 7;
/// Half-precision scalar and vector arithmetic.
const FP16 = 1 << 8;
const RDM = 1 << 9;
const JSCVT = 1 << 10;
const FCMA = 1 << 11;
const LRCPC = 1 << 12;
/// `DC CVAP`.
const DPB = 1 << 13;
const SHA3 = 1 << 14;
const SM3 = 1 << 15;
const SM4 = 1 << 16;
const DOTPROD = 1 << 17;
const SHA512 = 1 << 18;
const FHM = 1 << 19;
/// Relaxed alignment for single-copy atomic accesses.
const LSE2 = 1 << 20;
const LRCPC2 = 1 << 21;
const FLAGM = 1 << 22;
const SB = 1 << 23;
/// `DC CVADP`.
const DPB2 = 1 << 24;
/// `AXFLAG` and `XAFLAG`.
const FLAGM2 = 1 << 25;
const FRINTTS = 1 << 26;
const I8MM = 1 << 27;
const BF16 = 1 << 28;
const DGH = 1 << 29;
/// `RNDR` and `RNDRRS`.
const RNG = 1 << 30;
const ECV = 1 << 31;
const AFP = 1 << 32;
const RPRES = 1 << 33;
/// `WFET` and `WFIT`.
const WFXT = 1 << 34;
const EBF16 = 1 << 35;
const CSSC = 1 << 36;
const RPRFM = 1 << 37;
const MOPS = 1 << 38;
const HBC = 1 << 39;
/// Adds the `PSTATE.DIT` bit.
const DIT = 1 << 40;
/// Adds the `PSTATE.SSBS` bit.
const SSBS = 1 << 41;
/// Pointer authentication with both address and generic keys.
const PAUTH = 1 << 42;
const SVE = 1 << 43;
const SME = 1 << 44;
const BTI = 1 << 45;
const MTE = 1 << 46;
}
}

impl CpuFeatures {
/// Features that use only X0-X30, SP, PC, NZCV, V0-V31, FPCR, and FPSR, and need no kernel
/// ABI. A host that reports one has enabled it for EL0, so preserving that state suffices.
pub const BASE_STATE_ONLY: Self = Self::FP
.union(Self::ASIMD)
.union(Self::AES)
.union(Self::PMULL)
.union(Self::SHA1)
.union(Self::SHA256)
.union(Self::CRC32)
.union(Self::LSE)
.union(Self::FP16)
.union(Self::RDM)
.union(Self::JSCVT)
.union(Self::FCMA)
.union(Self::LRCPC)
.union(Self::DPB)
.union(Self::SHA3)
.union(Self::SM3)
.union(Self::SM4)
.union(Self::DOTPROD)
.union(Self::SHA512)
.union(Self::FHM)
.union(Self::LSE2)
.union(Self::LRCPC2)
.union(Self::FLAGM)
.union(Self::SB)
.union(Self::DPB2)
.union(Self::FLAGM2)
.union(Self::FRINTTS)
.union(Self::I8MM)
.union(Self::BF16)
.union(Self::DGH)
.union(Self::RNG)
.union(Self::ECV)
.union(Self::AFP)
.union(Self::RPRES)
.union(Self::WFXT)
.union(Self::EBF16)
.union(Self::CSSC)
.union(Self::RPRFM)
.union(Self::MOPS)
.union(Self::HBC);
}
9 changes: 9 additions & 0 deletions litebox/src/platform/arch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ pub trait ArchSpecificProvider {
reg: &ArchSpecificRegister,
val: usize,
) -> Result<(), ArchSpecificError>;

/// AArch64 CPU features that guests may use: only those the host supports and whose
/// architectural state (registers, PSTATE bits, and PAC keys) the platform preserves across
/// syscalls, exceptions and signals, interrupts, and native fork. Shims must drop features
/// their own mechanisms do not preserve (e.g., pointer authentication under copy-based fork).
#[cfg(target_arch = "aarch64")]
fn get_guest_cpu_features(&self) -> crate::arch::aarch64::CpuFeatures {
crate::arch::aarch64::CpuFeatures::empty()
}
}

/// Architecture-specific registers.
Expand Down
193 changes: 193 additions & 0 deletions litebox_common_linux/src/hwcap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

//! Linux AArch64 `AT_HWCAP` and `AT_HWCAP2` encoding of [`CpuFeatures`].

use litebox::arch::aarch64::CpuFeatures;

// Bit positions from Linux uapi `arch/arm64/include/uapi/asm/hwcap.h`.
const HWCAP_FP: u64 = 1 << 0;
const HWCAP_ASIMD: u64 = 1 << 1;
const HWCAP_AES: u64 = 1 << 3;
const HWCAP_PMULL: u64 = 1 << 4;
const HWCAP_SHA1: u64 = 1 << 5;
const HWCAP_SHA2: u64 = 1 << 6;
const HWCAP_CRC32: u64 = 1 << 7;
const HWCAP_ATOMICS: u64 = 1 << 8;
const HWCAP_FPHP: u64 = 1 << 9;
const HWCAP_ASIMDHP: u64 = 1 << 10;
const HWCAP_ASIMDRDM: u64 = 1 << 12;
const HWCAP_JSCVT: u64 = 1 << 13;
const HWCAP_FCMA: u64 = 1 << 14;
const HWCAP_LRCPC: u64 = 1 << 15;
const HWCAP_DCPOP: u64 = 1 << 16;
const HWCAP_SHA3: u64 = 1 << 17;
const HWCAP_SM3: u64 = 1 << 18;
const HWCAP_SM4: u64 = 1 << 19;
const HWCAP_ASIMDDP: u64 = 1 << 20;
const HWCAP_SHA512: u64 = 1 << 21;
const HWCAP_SVE: u64 = 1 << 22;
const HWCAP_ASIMDFHM: u64 = 1 << 23;
const HWCAP_DIT: u64 = 1 << 24;
const HWCAP_USCAT: u64 = 1 << 25;
const HWCAP_ILRCPC: u64 = 1 << 26;
const HWCAP_FLAGM: u64 = 1 << 27;
const HWCAP_SSBS: u64 = 1 << 28;
const HWCAP_SB: u64 = 1 << 29;
const HWCAP_PACA: u64 = 1 << 30;
const HWCAP_PACG: u64 = 1 << 31;

const HWCAP2_DCPODP: u64 = 1 << 0;
const HWCAP2_FLAGM2: u64 = 1 << 7;
const HWCAP2_FRINT: u64 = 1 << 8;
const HWCAP2_I8MM: u64 = 1 << 13;
const HWCAP2_BF16: u64 = 1 << 14;
const HWCAP2_DGH: u64 = 1 << 15;
const HWCAP2_RNG: u64 = 1 << 16;
const HWCAP2_BTI: u64 = 1 << 17;
const HWCAP2_MTE: u64 = 1 << 18;
const HWCAP2_ECV: u64 = 1 << 19;
const HWCAP2_AFP: u64 = 1 << 20;
const HWCAP2_RPRES: u64 = 1 << 21;
const HWCAP2_SME: u64 = 1 << 23;
const HWCAP2_WFXT: u64 = 1 << 31;
const HWCAP2_EBF16: u64 = 1 << 32;
const HWCAP2_CSSC: u64 = 1 << 34;
const HWCAP2_RPRFM: u64 = 1 << 35;
const HWCAP2_MOPS: u64 = 1 << 43;
const HWCAP2_HBC: u64 = 1 << 44;

/// A feature with several bits is present only if all of them are. Unmapped: `HWCAP_EVTSTRM` and
/// `HWCAP_CPUID` are kernel services, not CPU features; SVE/SME sub-features and `HWCAP2_MTE3`
/// have no [`CpuFeatures`] flag.
const FEATURE_HWCAPS: &[(CpuFeatures, u64, u64)] = &[
(CpuFeatures::FP, HWCAP_FP, 0),
(CpuFeatures::ASIMD, HWCAP_ASIMD, 0),
(CpuFeatures::AES, HWCAP_AES, 0),
(CpuFeatures::PMULL, HWCAP_PMULL, 0),
(CpuFeatures::SHA1, HWCAP_SHA1, 0),
(CpuFeatures::SHA256, HWCAP_SHA2, 0),
(CpuFeatures::CRC32, HWCAP_CRC32, 0),
(CpuFeatures::LSE, HWCAP_ATOMICS, 0),
(CpuFeatures::FP16, HWCAP_FPHP | HWCAP_ASIMDHP, 0),
(CpuFeatures::RDM, HWCAP_ASIMDRDM, 0),
(CpuFeatures::JSCVT, HWCAP_JSCVT, 0),
(CpuFeatures::FCMA, HWCAP_FCMA, 0),
(CpuFeatures::LRCPC, HWCAP_LRCPC, 0),
(CpuFeatures::DPB, HWCAP_DCPOP, 0),
(CpuFeatures::SHA3, HWCAP_SHA3, 0),
(CpuFeatures::SM3, HWCAP_SM3, 0),
(CpuFeatures::SM4, HWCAP_SM4, 0),
(CpuFeatures::DOTPROD, HWCAP_ASIMDDP, 0),
(CpuFeatures::SHA512, HWCAP_SHA512, 0),
(CpuFeatures::SVE, HWCAP_SVE, 0),
(CpuFeatures::FHM, HWCAP_ASIMDFHM, 0),
(CpuFeatures::DIT, HWCAP_DIT, 0),
(CpuFeatures::LSE2, HWCAP_USCAT, 0),
(CpuFeatures::LRCPC2, HWCAP_ILRCPC, 0),
(CpuFeatures::FLAGM, HWCAP_FLAGM, 0),
(CpuFeatures::SSBS, HWCAP_SSBS, 0),
(CpuFeatures::SB, HWCAP_SB, 0),
(CpuFeatures::PAUTH, HWCAP_PACA | HWCAP_PACG, 0),
(CpuFeatures::DPB2, 0, HWCAP2_DCPODP),
(CpuFeatures::FLAGM2, 0, HWCAP2_FLAGM2),
(CpuFeatures::FRINTTS, 0, HWCAP2_FRINT),
(CpuFeatures::I8MM, 0, HWCAP2_I8MM),
(CpuFeatures::BF16, 0, HWCAP2_BF16),
(CpuFeatures::DGH, 0, HWCAP2_DGH),
(CpuFeatures::RNG, 0, HWCAP2_RNG),
(CpuFeatures::BTI, 0, HWCAP2_BTI),
(CpuFeatures::MTE, 0, HWCAP2_MTE),
(CpuFeatures::ECV, 0, HWCAP2_ECV),
(CpuFeatures::AFP, 0, HWCAP2_AFP),
(CpuFeatures::RPRES, 0, HWCAP2_RPRES),
(CpuFeatures::SME, 0, HWCAP2_SME),
(CpuFeatures::WFXT, 0, HWCAP2_WFXT),
(CpuFeatures::EBF16, 0, HWCAP2_EBF16),
(CpuFeatures::CSSC, 0, HWCAP2_CSSC),
(CpuFeatures::RPRFM, 0, HWCAP2_RPRFM),
(CpuFeatures::MOPS, 0, HWCAP2_MOPS),
(CpuFeatures::HBC, 0, HWCAP2_HBC),
];

/// Linux `AT_HWCAP` and `AT_HWCAP2` auxiliary vector values.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct LinuxHwcaps {
pub hwcap: u64,
pub hwcap2: u64,
}

impl LinuxHwcaps {
#[must_use]
pub fn from_cpu_features(features: CpuFeatures) -> Self {
let mut hwcaps = Self::default();
for &(feature, hwcap, hwcap2) in FEATURE_HWCAPS {
if features.contains(feature) {
hwcaps.hwcap |= hwcap;
hwcaps.hwcap2 |= hwcap2;
}
}
hwcaps
}

/// Drops bits that encode no [`CpuFeatures`] flag.
#[must_use]
pub fn to_cpu_features(self) -> CpuFeatures {
FEATURE_HWCAPS
.iter()
.filter(|&&(_, hwcap, hwcap2)| {
self.hwcap & hwcap == hwcap && self.hwcap2 & hwcap2 == hwcap2
})
.map(|&(feature, _, _)| feature)
.collect()
}
}

#[cfg(test)]
mod tests {
use super::{FEATURE_HWCAPS, HWCAP_FPHP, HWCAP_PACA, LinuxHwcaps};
use litebox::arch::aarch64::CpuFeatures;

#[test]
fn feature_hwcaps_table_is_consistent() {
let mut seen_features = CpuFeatures::empty();
let mut seen = LinuxHwcaps::default();
for &(feature, hwcap, hwcap2) in FEATURE_HWCAPS {
assert!(
!seen_features.intersects(feature),
"{feature:?} is mapped twice"
);
assert!(hwcap | hwcap2 != 0, "{feature:?} has no Linux bits");
assert_eq!(seen.hwcap & hwcap, 0, "{feature:?} reuses an AT_HWCAP bit");
assert_eq!(
seen.hwcap2 & hwcap2,
0,
"{feature:?} reuses an AT_HWCAP2 bit"
);
seen_features |= feature;
seen.hwcap |= hwcap;
seen.hwcap2 |= hwcap2;
}
assert_eq!(seen_features, CpuFeatures::all());
assert_eq!(
LinuxHwcaps {
hwcap: HWCAP_PACA | HWCAP_FPHP,
hwcap2: 0,
}
.to_cpu_features(),
CpuFeatures::empty()
);
}

#[test]
fn feature_hwcaps_match_linux_uapi() {
// Literal bit positions from Linux uapi `hwcap.h`, independent of the constants above.
let features = CpuFeatures::AES | CpuFeatures::FP16 | CpuFeatures::PAUTH | CpuFeatures::HBC;
let hwcaps = LinuxHwcaps {
hwcap: 1 << 3 | 1 << 9 | 1 << 10 | 1 << 30 | 1 << 31,
hwcap2: 1 << 44,
};
assert_eq!(LinuxHwcaps::from_cpu_features(features), hwcaps);
assert_eq!(hwcaps.to_cpu_features(), features);
}
}
2 changes: 2 additions & 0 deletions litebox_common_linux/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use zerocopy::{FromBytes, Immutable, IntoBytes};
use crate::signal::SigSet;

pub mod errno;
#[cfg(target_arch = "aarch64")]
pub mod hwcap;
pub mod loader;
pub mod mm;
pub mod program_startup;
Expand Down
37 changes: 25 additions & 12 deletions litebox_platform_linux_userland/src/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1300,19 +1300,13 @@ switch_to_guest_stage_x16_fixup:
sub x4, x4, #{SVC_FRAME_BYTES}
mov sp, x4

// `msr nzcv` writes only PSTATE 31:28. SSBS and DIT are carried in
// `PtRegs::pstate` (see `copy_signal_context`) but are knowingly NOT
// restored here: writing them needs `msr ssbs`/`msr dit`, which are
// ARMv8.0 optional / ARMv8.4 respectively, so unconditionally emitting
// them would raise this crate's hardware floor. A guest that sets SSBS or
// DIT therefore loses them across a syscall: both are hardening hints with
// no architectural effect on results. (The generic `rt_sigreturn` path
// does restore them for free by writing the whole SPSR; see
// `resume_frame::prepare`.)
// Syscall entry saves only NZCV into `PtRegs::pstate`, and this path restores only NZCV,
// so SSBS/DIT are not preserved across syscalls: a resume that rebuilds PSTATE from
// `PtRegs::pstate` (`resume_frame::prepare`) clears them. Saving and restoring them needs
// `mrs`/`msr` of SSBS and DIT (optional ARMv8.0 / ARMv8.4), which would raise this crate's
// hardware floor, so guests are not offered them (see `get_guest_cpu_features`).
//
// TODO: restore SSBS/DIT for guests that set them, gated on a runtime
// feature probe (HWCAP_SSBS / HWCAP2_DIT) so the hardware floor stays at
// ARMv8.0. Only worth doing once a guest actually depends on them.
// TODO: save and restore SSBS/DIT on the syscall path, gated on HWCAP_SSBS/HWCAP_DIT.
ldr x1, [x0, #264]
msr nzcv, x1
ldr x1, [x0, #8]
Expand Down Expand Up @@ -1449,6 +1443,25 @@ impl litebox::platform::ArchSpecificProvider for LinuxUserland {
_ => Err(litebox::platform::ArchSpecificError::RegisterUnsupported),
}
}
fn get_guest_cpu_features(&self) -> litebox::arch::aarch64::CpuFeatures {
use litebox::arch::aarch64::CpuFeatures;

// SAFETY: getauxval only reads this process's auxiliary vector. It makes no syscall, so
// it also works under the seccomp filter.
let (hwcap, hwcap2) = unsafe {
(
libc::getauxval(libc::AT_HWCAP),
libc::getauxval(libc::AT_HWCAP2),
)
};
let host = litebox_common_linux::hwcap::LinuxHwcaps { hwcap, hwcap2 }.to_cpu_features();
// PAUTH is kept: PAC keys are per host process and inherited by native fork. Excluded:
// - DIT and SSBS: the syscall path saves and restores only NZCV (see
// `switch_to_guest_via_outbound_stub`).
// - SVE and SME: only FP/SIMD vector state is saved.
// - BTI and MTE: there is no PROT_BTI/PROT_MTE page-attribute support.
host & (CpuFeatures::BASE_STATE_ONLY | CpuFeatures::PAUTH)
}
}

impl litebox::platform::GuestVectorStateProvider for LinuxUserland {
Expand Down
Loading
Loading