#![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 compiler;
mod spec;
pub mod prop;
#[cfg(test)]
mod tests;
use vstd::prelude::*;
pub use crate::compiler::CompileError;
pub use crate::spec::{policy::*, syscall::*, cbpf::*};
verus! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidErrno,
InvalidArg(u32),
DuplicateArch,
UnsupportedNativeArch,
Compile(CompileError),
FilterTooLarge,
NoNewPrivsFailed(i32),
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 ArgCmp {
pub fn eq(arg: u32, val: u64) -> Self {
ArgCmp { arg, op: Compare::Eq, a: val, b: 0 }
}
pub fn ne(arg: u32, val: u64) -> Self {
ArgCmp { arg, op: Compare::Ne, a: val, b: 0 }
}
pub fn lt(arg: u32, val: u64) -> Self {
ArgCmp { arg, op: Compare::Lt, a: val, b: 0 }
}
pub fn le(arg: u32, val: u64) -> Self {
ArgCmp { arg, op: Compare::Le, a: val, b: 0 }
}
pub fn gt(arg: u32, val: u64) -> Self {
ArgCmp { arg, op: Compare::Gt, a: val, b: 0 }
}
pub fn ge(arg: u32, val: u64) -> Self {
ArgCmp { arg, op: Compare::Ge, a: val, b: 0 }
}
pub fn masked_eq(arg: u32, mask: u64, val: u64) -> Self {
ArgCmp { arg, op: Compare::MaskedEq, a: mask, b: val }
}
}
impl Action {
fn check(&self) -> (res: Result<(), Error>)
ensures (res is Ok) == self.wf()
{
match self {
Action::Errno(e) if (*e as u32) > Self::MAX_ERRNO => Err(Error::InvalidErrno),
_ => Ok(()),
}
}
}
impl Rule {
fn check(&self) -> (res: Result<(), Error>)
ensures (res is Ok) == self.wf()
{
self.action.check()?;
let mut i: usize = 0;
while i < self.conds.len()
invariant
self.action.wf(),
i <= self.conds@.len(),
forall |k: int| #![trigger self.conds@[k]]
0 <= k < i ==> self.conds@[k].arg < Self::ARG_COUNT_MAX,
decreases self.conds@.len() - i
{
if self.conds[i].arg >= Self::ARG_COUNT_MAX {
return Err(Error::InvalidArg(self.conds[i].arg));
}
i += 1;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[verifier::external_derive(Clone)]
pub struct Filter {
policy: Policy,
ctl_nnp: bool,
ctl_tsync: bool,
ctl_log: bool,
}
impl Filter {
pub closed spec fn wf(self) -> bool {
self.policy.wf()
}
pub closed spec fn policy(self) -> Policy {
self.policy
}
pub fn new(act_no_match: Action) -> (res: Result<Filter, Error>)
ensures res matches Ok(f) ==> {
&&& f.wf()
&&& f.policy().act_no_match == act_no_match
&&& f.policy().act_bad_arch == Action::KillThread
&&& f.policy().archs@.len() == 0
&&& f.policy().rules@.len() == 0
}
{
act_no_match.check()?;
Ok(Filter {
policy: Policy {
archs: Vec::new(),
rules: Vec::new(),
act_no_match,
act_bad_arch: Action::KillThread,
},
ctl_nnp: true,
ctl_tsync: false,
ctl_log: false,
})
}
pub fn new_native(act_no_match: Action) -> (res: Result<Filter, Error>)
ensures res matches Ok(f) ==> {
&&& f.wf()
&&& f.policy().act_no_match == act_no_match
&&& f.policy().act_bad_arch == Action::KillThread
&&& f.policy().archs@.len() == 1
&&& f.policy().rules@.len() == 0
}
{
let mut filter = Self::new(act_no_match)?;
filter.add_arch(Arch::native()?)?;
Ok(filter)
}
pub fn add_arch(&mut self, arch: Arch) -> (res: Result<(), Error>)
requires old(self).wf()
ensures
final(self).wf(),
final(self).policy().act_no_match == old(self).policy().act_no_match,
final(self).policy().act_bad_arch == old(self).policy().act_bad_arch,
final(self).policy().rules == old(self).policy().rules,
res is Ok ==> final(self).policy().archs@ == old(self).policy().archs@.push(arch),
res is Err ==> final(self).policy() == old(self).policy(),
{
let mut i: usize = 0;
while i < self.policy.archs.len()
invariant
self.wf(),
i <= self.policy.archs@.len(),
forall |k: int| #![trigger self.policy.archs@[k]]
0 <= k < i ==> self.policy.archs@[k] != arch,
decreases self.policy.archs@.len() - i
{
if self.policy.archs[i] == arch {
return Err(Error::DuplicateArch);
}
i += 1;
}
let ghost prev = self.policy.archs@;
self.policy.archs.push(arch);
proof {
assert forall |k: int, l: int| 0 <= k < l < self.policy.archs@.len()
implies #[trigger] self.policy.archs@[k] != #[trigger] self.policy.archs@[l] by {
if l < prev.len() {
assert(prev[k] != prev[l]);
} else {
assert(prev[k] != arch);
}
}
}
Ok(())
}
pub fn add_rule(&mut self, action: Action, syscall: Syscall, conds: Vec<ArgCmp>)
-> (res: Result<(), Error>)
requires old(self).wf()
ensures
final(self).wf(),
final(self).policy().archs == old(self).policy().archs,
final(self).policy().act_no_match == old(self).policy().act_no_match,
final(self).policy().act_bad_arch == old(self).policy().act_bad_arch,
res is Ok ==>
final(self).policy().rules@
== old(self).policy().rules@.push(Rule { action, syscall, conds }),
res is Err ==> final(self).policy() == old(self).policy(),
{
let rule = Rule { action, syscall, conds };
rule.check()?;
let ghost prev = self.policy.rules@;
self.policy.rules.push(rule);
proof {
assert forall |k: int| 0 <= k < self.policy.rules@.len()
implies #[trigger] self.policy.rules@[k].wf() by {
if k < prev.len() {
assert(prev[k].wf());
}
}
}
Ok(())
}
pub fn on_bad_arch(&mut self, act: Action) -> (res: Result<(), Error>)
requires old(self).wf()
ensures
final(self).wf(),
final(self).policy().archs == old(self).policy().archs,
final(self).policy().rules == old(self).policy().rules,
final(self).policy().act_no_match == old(self).policy().act_no_match,
res is Ok ==> final(self).policy().act_bad_arch == act,
res is Err ==> final(self).policy() == old(self).policy(),
{
act.check()?;
self.policy.act_bad_arch = act;
Ok(())
}
pub fn allow_new_privileges(&mut self)
ensures
final(self).wf() == old(self).wf(),
final(self).policy() == old(self).policy(),
{
self.ctl_nnp = false;
}
pub fn enable_thread_sync(&mut self)
ensures
final(self).wf() == old(self).wf(),
final(self).policy() == old(self).policy(),
{
self.ctl_tsync = true;
}
pub fn request_audit_logging(&mut self)
ensures
final(self).wf() == old(self).wf(),
final(self).policy() == old(self).policy(),
{
self.ctl_log = true;
}
}
#[cfg(target_os = "linux")]
impl Error {
#[verifier::external_body]
fn errno() -> i32 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
}
#[cfg(target_os = "linux")]
impl Filter {
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
}
#[verifier::external_body]
pub fn install(&self) -> Result<(), Error>
requires self.wf()
{
let program = match self.policy.to_cbpf() {
Ok(program) => program,
Err(err) => return Err(Error::Compile(err)),
};
if program.instrs.len() > 4096 {
return Err(Error::FilterTooLarge);
}
if self.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(self.filter_flags());
if rc != 0 {
let errno = if rc < 0 { Error::errno() } else { libc::ESRCH };
return Err(Error::InstallFailed(errno));
}
Ok(())
}
}
}