use vstd::prelude::*;
use super::syscall::*;
verus! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Structural)]
pub enum Arch { X86, X86_64, Arm, Aarch64 }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Structural)]
pub enum Action {
KillProcess,
KillThread,
Trap(u16),
Errno(u16),
Trace(u16),
Log,
Allow,
Notify,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Structural)]
pub enum Compare { Ne, Lt, Le, Eq, Ge, Gt, MaskedEq }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Structural)]
pub struct ArgCmp {
pub arg: u32,
pub op: Compare,
pub a: u64,
pub b: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[verifier::external_derive(Clone)]
pub struct Rule {
pub action: Action,
pub syscall: Syscall,
pub conds: Vec<ArgCmp>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[verifier::external_derive(Clone)]
pub struct Policy {
pub archs: Vec<Arch>,
pub rules: Vec<Rule>,
pub act_no_match: Action,
pub act_bad_arch: Action,
}
impl Action {
pub const MAX_ERRNO: u32 = 4095;
pub open spec fn wf(self) -> bool {
match self {
Action::Errno(e) => (e as u32) <= Self::MAX_ERRNO,
_ => true,
}
}
pub open spec fn precedence(self) -> nat {
match self {
Action::KillProcess => 7,
Action::KillThread => 6,
Action::Trap(_) => 5,
Action::Errno(_) => 4,
Action::Notify => 3,
Action::Trace(_) => 2,
Action::Log => 1,
Action::Allow => 0,
}
}
}
impl Rule {
pub const ARG_COUNT_MAX: u32 = 6;
pub open spec fn wf(self) -> bool {
&&& self.action.wf()
&&& forall |i: int| #![trigger self.conds@[i]]
0 <= i < self.conds@.len() ==> self.conds@[i].arg < Self::ARG_COUNT_MAX
}
}
impl Policy {
pub open spec fn wf(self) -> bool {
&&& self.act_no_match.wf()
&&& self.act_bad_arch.wf()
&&& forall |i: int, j: int| #![trigger self.archs@[i], self.archs@[j]]
0 <= i < j < self.archs@.len() ==> self.archs@[i] != self.archs@[j]
&&& forall |i: int| #![trigger self.rules@[i]]
0 <= i < self.rules@.len() ==> self.rules@[i].wf()
}
}
}
verus! {
pub struct Event {
pub arch: u32,
pub nr: i32,
pub args: Seq<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Structural)]
pub enum SyscallMatch {
Exact,
Mux,
None,
}
impl Arch {
pub const TOKEN_X86: u32 = 0x4000_0003;
pub const TOKEN_X86_64: u32 = 0xC000_003E;
pub const TOKEN_ARM: u32 = 0x4000_0028;
pub const TOKEN_AARCH64: u32 = 0xC000_00B7;
pub open spec fn mask(self) -> u64 {
if self == Arch::X86_64 || self == Arch::Aarch64 {
u64::MAX
} else {
0xFFFF_FFFF
}
}
pub open spec fn token(self) -> u32 {
match self {
Arch::X86 => Self::TOKEN_X86,
Arch::X86_64 => Self::TOKEN_X86_64,
Arch::Arm => Self::TOKEN_ARM,
Arch::Aarch64 => Self::TOKEN_AARCH64,
}
}
}
impl Event {
pub open spec fn matches_syscall(self, arch: Arch, name: Syscall) -> SyscallMatch {
if name.nr(arch) == Some(self.nr) {
SyscallMatch::Exact
} else if arch == Arch::X86 && {
||| Syscall::Socketcall.nr(arch) == Some(self.nr)
&& name.to_socketcall_arg() == Some(self.args[0] & arch.mask())
||| Syscall::Ipc.nr(arch) == Some(self.nr)
&& name.to_ipc_arg() == Some(self.args[0] & arch.mask())
} {
SyscallMatch::Mux
} else {
SyscallMatch::None
}
}
pub open spec fn parse(data: &[u8]) -> Option<Event> {
if data@.len() != 64 {
None
} else {
Some(Event {
nr: ((data@[0] as u32) | (data@[1] as u32) << 8 | (data@[2] as u32) << 16 | (data@[3] as u32) << 24) as i32,
arch: (data@[4] as u32) | (data@[5] as u32) << 8 | (data@[6] as u32) << 16 | (data@[7] as u32) << 24,
args: Seq::new(
Rule::ARG_COUNT_MAX as nat,
|k: int| (data@[16 + 8 * k] as u64)
| (data@[17 + 8 * k] as u64) << 8
| (data@[18 + 8 * k] as u64) << 16
| (data@[19 + 8 * k] as u64) << 24
| (data@[20 + 8 * k] as u64) << 32
| (data@[21 + 8 * k] as u64) << 40
| (data@[22 + 8 * k] as u64) << 48
| (data@[23 + 8 * k] as u64) << 56,
),
})
}
}
}
impl Action {
pub const RET_ACTION: u32 = 0xffff_0000;
pub const RET_DATA: u32 = 0x0000_ffff;
pub const RET_KILL_PROCESS: u32 = 0x8000_0000;
pub const RET_KILL_THREAD: u32 = 0x0000_0000;
pub const RET_TRAP: u32 = 0x0003_0000;
pub const RET_ERRNO: u32 = 0x0005_0000;
pub const RET_USER_NOTIF: u32 = 0x7fc0_0000;
pub const RET_TRACE: u32 = 0x7ff0_0000;
pub const RET_LOG: u32 = 0x7ffc_0000;
pub const RET_ALLOW: u32 = 0x7fff_0000;
pub open spec fn to_ret(&self) -> u32 {
match self {
Action::KillProcess => Self::RET_KILL_PROCESS,
Action::KillThread => Self::RET_KILL_THREAD,
Action::Trap(data) => Self::RET_TRAP | *data as u32,
Action::Errno(data) => Self::RET_ERRNO | *data as u32,
Action::Trace(data) => Self::RET_TRACE | *data as u32,
Action::Log => Self::RET_LOG,
Action::Allow => Self::RET_ALLOW,
Action::Notify => Self::RET_USER_NOTIF,
}
}
}
impl ArgCmp {
pub open spec fn eval(self, arch: Arch, args: Seq<u64>) -> bool {
let x = args[self.arg as int] & arch.mask();
let a = self.a & arch.mask();
let b = self.b & arch.mask();
match self.op {
Compare::Ne => x != a,
Compare::Lt => x < a,
Compare::Le => x <= a,
Compare::Eq => x == a,
Compare::Ge => x >= a,
Compare::Gt => x > a,
Compare::MaskedEq => x & a == b & a,
}
}
}
impl Rule {
pub open spec fn eval(self, arch: Arch, ev: Event) -> bool {
let conds_hold = forall |i: int| #![trigger self.conds@[i]]
0 <= i < self.conds@.len() ==> self.conds@[i].eval(arch, ev.args);
match ev.matches_syscall(arch, self.syscall) {
SyscallMatch::Exact => conds_hold,
SyscallMatch::Mux => self.conds@.len() == 0,
SyscallMatch::None => false,
}
}
}
impl Policy {
pub open spec fn is_active_arch(self, arch: Arch, ev: Event) -> bool {
&&& self.archs@.contains(arch)
&&& ev.arch == arch.token()
&&& arch == Arch::X86_64 ==> ev.nr & 0x40000000 == 0
}
pub open spec fn eval(self, ev: Event, act: Action) -> bool
recommends self.wf(),
{
||| act == self.act_bad_arch && forall |a: Arch| !self.is_active_arch(a, ev)
||| exists |a: Arch, i: int| {
&&& self.is_active_arch(a, ev)
&&& 0 <= i < self.rules@.len()
&&& #[trigger] self.rules@[i].eval(a, ev)
&&& self.rules@[i].action == act
&&& forall |j: int| #![trigger self.rules@[j]]
0 <= j < self.rules@.len() && j != i && self.rules@[j].eval(a, ev)
==> {
||| self.rules@[j].action.precedence() < act.precedence()
||| j < i && self.rules@[j].action.precedence() == act.precedence()
}
}
||| {
&&& act == self.act_no_match
&&& exists |a: Arch| #[trigger] self.is_active_arch(a, ev)
&&& forall |a: Arch, i: int|
self.is_active_arch(a, ev) && 0 <= i < self.rules@.len()
==> !#[trigger] self.rules@[i].eval(a, ev)
}
}
}
}