#![doc = include_str!("../README.md")]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(unused_must_use)]
#![deny(dangling_pointers_from_locals)]
#![deny(dangling_pointers_from_temporaries)]
#![warn(unnameable_types)]
#![warn(unreachable_pub)]
#![warn(clippy::undocumented_unsafe_blocks)]
mod asm;
mod check;
mod compiler;
mod macros;
mod spec;
pub mod prop;
use vstd::prelude::*;
pub use crate::compiler::CompileError;
pub use crate::check::CheckError;
pub use crate::spec::{policy::*, syscall::*, expr::*};
pub use crate::spec::cbpf;
pub use crate::macros::{ToExpr, ToCond};
verus! {
#[verifier::external_derive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("failed to validate the policy")]
Check(#[source] CheckError),
#[error("failed to compile the policy")]
Compile(#[source] CompileError),
#[error("policy has no architectures enabled")]
NoArch,
#[error("native architecture is not supported")]
UnsupportedNativeArch,
#[error("filter exceeds the kernel's 4096-instruction limit")]
FilterTooLarge,
#[error("failed to set no_new_privs: {}", std::io::Error::from_raw_os_error(*.0))]
NoNewPrivsFailed(i32),
#[error("failed to install the seccomp filter: {}", std::io::Error::from_raw_os_error(*.0))]
InstallFailed(i32),
}
impl Arch {
pub fn native() -> Result<Arch, Error> {
if cfg!(all(target_arch = "x86_64", target_pointer_width = "64")) {
Ok(Arch::X86_64)
} else if cfg!(target_arch = "aarch64") {
Ok(Arch::Aarch64)
} else if cfg!(target_arch = "x86") {
Ok(Arch::X86)
} else if cfg!(target_arch = "arm") {
Ok(Arch::Arm)
} else {
Err(Error::UnsupportedNativeArch)
}
}
}
impl Policy {
pub fn new(act_no_match: Action) -> (res: Result<Policy, Error>)
ensures res matches Ok(p) ==> {
&&& p.wf()
&&& p.act_no_match == act_no_match
&&& p.act_bad_arch == Action::KillThread
&&& p.archs@.len() == 0
&&& p.rules@.len() == 0
}
{
if let Err(err) = act_no_match.check() {
return Err(Error::Check(err));
}
Ok(Policy {
archs: Vec::new(),
rules: Vec::new(),
act_no_match,
act_bad_arch: Action::KillThread,
})
}
pub fn new_native(act_no_match: Action) -> (res: Result<Policy, Error>)
ensures res matches Ok(p) ==> {
&&& p.wf()
&&& p.act_no_match == act_no_match
&&& p.act_bad_arch == Action::KillThread
&&& p.archs@.len() == 1
&&& p.rules@.len() == 0
}
{
let mut policy = Self::new(act_no_match)?;
policy.add_arch(Arch::native()?)?;
Ok(policy)
}
pub fn add_arch(&mut self, arch: Arch) -> (res: Result<(), Error>)
requires old(self).wf()
ensures
final(self).wf(),
final(self).act_no_match == old(self).act_no_match,
final(self).act_bad_arch == old(self).act_bad_arch,
final(self).rules == old(self).rules,
old(self).archs@.contains(arch) ==> res is Ok && final(self).archs@ == old(self).archs@,
res is Ok && !old(self).archs@.contains(arch) ==> final(self).archs@ == old(self).archs@.push(arch),
res is Err ==> final(self).archs@ == old(self).archs@,
{
let mut i: usize = 0;
while i < self.archs.len()
invariant
self.wf(),
i <= self.archs@.len(),
forall |k: int| #![trigger self.archs@[k]]
0 <= k < i ==> self.archs@[k] != arch,
decreases self.archs@.len() - i
{
if self.archs[i] == arch {
proof { assert(self.archs@[i as int] == arch); }
return Ok(());
}
i += 1;
}
proof { assert(!self.archs@.contains(arch)); }
let ghost prev = self.archs@;
self.archs.push(arch);
let mut r: usize = 0;
while r < self.rules.len()
invariant
old(self).wf(),
!prev.contains(arch),
prev == old(self).archs@,
self.archs@ == prev.push(arch),
self.rules == old(self).rules,
self.act_no_match == old(self).act_no_match,
self.act_bad_arch == old(self).act_bad_arch,
r <= self.rules@.len(),
forall |k: int| 0 <= k < r ==>
#[trigger] self.rules@[k].wf(self.archs@),
decreases self.rules@.len() - r
{
if let Err(err) = self.rules[r].check(self.archs.as_slice()) {
self.archs.pop();
proof { assert(self.archs@ =~= prev); }
return Err(Error::Check(err));
}
r += 1;
}
proof {
assert forall |k: int, l: int| 0 <= k < l < self.archs@.len()
implies #[trigger] self.archs@[k] != #[trigger] self.archs@[l] by {
if l < prev.len() {
assert(prev[k] != prev[l]);
} else {
assert(prev[k] != arch);
}
}
}
Ok(())
}
pub fn add(&mut self, rule: Rule) -> (res: Result<(), Error>)
requires old(self).wf()
ensures
final(self).wf(),
final(self).archs == old(self).archs,
final(self).act_no_match == old(self).act_no_match,
final(self).act_bad_arch == old(self).act_bad_arch,
res is Ok ==> final(self).rules@ == old(self).rules@.push(rule),
res is Err ==> *final(self) == *old(self),
{
if let Err(err) = rule.check(self.archs.as_slice()) {
return Err(Error::Check(err));
}
let ghost prev = self.rules@;
self.rules.push(rule);
proof {
assert forall |k: int| 0 <= k < self.rules@.len()
implies #[trigger] self.rules@[k].wf(self.archs@) by {
if k < prev.len() {
assert(prev[k].wf(self.archs@));
}
}
}
Ok(())
}
pub fn on_bad_arch(&mut self, act: Action) -> (res: Result<(), Error>)
requires old(self).wf()
ensures
final(self).wf(),
final(self).archs == old(self).archs,
final(self).rules == old(self).rules,
final(self).act_no_match == old(self).act_no_match,
res is Ok ==> final(self).act_bad_arch == act,
res is Err ==> *final(self) == *old(self),
{
if let Err(err) = act.check() {
return Err(Error::Check(err));
}
self.act_bad_arch = act;
Ok(())
}
}
#[cfg(target_os = "linux")]
impl Error {
#[verifier::external_body]
fn errno() -> i32 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct InstallFlags {
pub ctl_nnp: bool,
pub ctl_tsync: bool,
pub ctl_log: bool,
}
impl Default for InstallFlags {
fn default() -> InstallFlags {
InstallFlags { ctl_nnp: true, ctl_tsync: false, ctl_log: false }
}
}
#[cfg(target_os = "linux")]
impl InstallFlags {
const FLAG_TSYNC: u64 = 1 << 0;
const FLAG_LOG: u64 = 1 << 1;
fn filter_flags(&self) -> u64 {
let mut flags: u64 = 0;
if self.ctl_tsync {
flags |= Self::FLAG_TSYNC;
}
if self.ctl_log {
flags |= Self::FLAG_LOG;
}
flags
}
}
#[cfg(target_os = "linux")]
impl Policy {
pub fn install(&self) -> Result<(), Error> {
self.install_with_flags(InstallFlags::default())
}
#[verifier::external_body]
pub fn install_with_flags(&self, flags: InstallFlags) -> Result<(), Error> {
if self.archs.is_empty() {
return Err(Error::NoArch);
}
if let Err(err) = self.check() {
return Err(Error::Check(err));
}
let program = match self.to_cbpf() {
Ok(program) => program,
Err(err) => return Err(Error::Compile(err)),
};
if program.instrs.len() > 4096 {
return Err(Error::FilterTooLarge);
}
if flags.ctl_nnp {
let rc = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };
if rc != 0 {
return Err(Error::NoNewPrivsFailed(Error::errno()));
}
}
let raw = program.assemble();
let rc = raw.install_with_flags(flags.filter_flags());
if rc != 0 {
let errno = if rc < 0 { Error::errno() } else { libc::ESRCH };
return Err(Error::InstallFailed(errno));
}
Ok(())
}
}
}