use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::io::Read;
use std::result;
use crate::backend::{
Error as BackendError, SeccompAction, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition,
SeccompFilter, SeccompRule, TargetArch,
};
use crate::syscall_table::SyscallTable;
use serde::de::{self, Error as _, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Backend(BackendError),
SerdeJson(serde_json::Error),
SyscallName(String, TargetArch),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use self::Error::*;
match self {
Backend(error) => Some(error),
SerdeJson(error) => Some(error),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
Backend(error) => write!(f, "{}", error),
SerdeJson(error) => {
write!(f, "Error parsing Json: {}", error)
}
SyscallName(syscall_name, arch) => write!(
f,
"Invalid syscall name: {} for given arch: {:?}.",
syscall_name, arch
),
}
}
}
pub(crate) struct JsonFilterMap(pub HashMap<String, JsonFilter>);
impl<'de> Deserialize<'de> for JsonFilterMap {
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct JsonFilterMapVisitor;
impl<'d> Visitor<'d> for JsonFilterMapVisitor {
type Value = HashMap<String, JsonFilter>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {
f.write_str("a map of filters")
}
fn visit_map<M>(self, mut access: M) -> result::Result<Self::Value, M::Error>
where
M: MapAccess<'d>,
{
let mut values = Self::Value::with_capacity(access.size_hint().unwrap_or(0));
while let Some((key, value)) = access.next_entry()? {
if values.insert(key, value).is_some() {
return Err(M::Error::custom("duplicate filter key"));
};
}
Ok(values)
}
}
Ok(JsonFilterMap(
deserializer.deserialize_map(JsonFilterMapVisitor)?,
))
}
}
#[derive(PartialEq, Debug, Clone)]
struct JsonComment;
impl<'de> Deserialize<'de> for JsonComment {
fn deserialize<D>(deserializer: D) -> std::result::Result<JsonComment, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?;
Ok(JsonComment {})
}
}
#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct JsonCondition {
#[serde(rename = "index")]
arg_index: u8,
#[serde(rename = "type")]
arg_len: SeccompCmpArgLen,
#[serde(rename = "op")]
operator: SeccompCmpOp,
#[serde(rename = "val")]
value: u64,
comment: Option<JsonComment>,
}
impl TryFrom<JsonCondition> for SeccompCondition {
type Error = Error;
fn try_from(json_cond: JsonCondition) -> Result<Self> {
SeccompCondition::new(
json_cond.arg_index,
json_cond.arg_len,
json_cond.operator,
json_cond.value,
)
.map_err(Error::Backend)
}
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(deny_unknown_fields)]
pub(crate) struct JsonRule {
syscall: String,
#[serde(rename = "args")]
conditions: Option<Vec<JsonCondition>>,
comment: Option<JsonComment>,
}
#[derive(Deserialize, PartialEq, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub(crate) struct JsonFilter {
#[serde(alias = "default_action")]
mismatch_action: SeccompAction,
#[serde(alias = "filter_action")]
match_action: SeccompAction,
#[serde(rename = "filter")]
rules: Vec<JsonRule>,
}
pub(crate) struct JsonCompiler {
arch: TargetArch,
syscall_table: SyscallTable,
}
impl JsonCompiler {
pub fn new(arch: TargetArch) -> Self {
Self {
arch,
syscall_table: SyscallTable::new(arch),
}
}
pub fn compile<R: Read>(&self, reader: R) -> Result<HashMap<String, SeccompFilter>> {
let filters: JsonFilterMap = serde_json::from_reader(reader).map_err(Error::SerdeJson)?;
let filters = filters.0;
let mut bpf_map: HashMap<String, SeccompFilter> = HashMap::with_capacity(filters.len());
for (name, filter) in filters.into_iter() {
bpf_map.insert(name, self.make_seccomp_filter(filter)?);
}
Ok(bpf_map)
}
fn make_seccomp_filter(&self, filter: JsonFilter) -> Result<SeccompFilter> {
let mut rule_map: BTreeMap<i64, Vec<SeccompRule>> = BTreeMap::new();
for json_rule in filter.rules {
let syscall_name = json_rule.syscall;
let syscall_nr = self
.syscall_table
.get_syscall_nr(&syscall_name)
.ok_or_else(|| Error::SyscallName(syscall_name.clone(), self.arch))?;
let rule_accumulator = rule_map.entry(syscall_nr).or_insert_with(Vec::new);
if let Some(conditions) = json_rule.conditions {
let mut seccomp_conditions = Vec::with_capacity(conditions.len());
for condition in conditions {
seccomp_conditions.push(condition.try_into()?);
}
rule_accumulator
.push(SeccompRule::new(seccomp_conditions).map_err(Error::Backend)?);
}
}
SeccompFilter::new(
rule_map,
filter.mismatch_action,
filter.match_action,
self.arch,
)
.map_err(Error::Backend)
}
}
#[cfg(test)]
mod tests {
use super::{Error, JsonCompiler, JsonCondition, JsonFilter, JsonRule};
use crate::backend::{
Error as BackendError, SeccompAction, SeccompCmpArgLen, SeccompCmpArgLen::*, SeccompCmpOp,
SeccompCmpOp::*, SeccompCondition as Cond, SeccompFilter, SeccompRule,
};
use std::collections::HashMap;
use std::convert::TryInto;
use std::env::consts::ARCH;
impl JsonFilter {
pub fn new(
mismatch_action: SeccompAction,
match_action: SeccompAction,
rules: Vec<JsonRule>,
) -> JsonFilter {
JsonFilter {
mismatch_action,
match_action,
rules,
}
}
}
impl JsonRule {
pub fn new(syscall: String, conditions: Option<Vec<JsonCondition>>) -> JsonRule {
JsonRule {
syscall,
conditions,
comment: None,
}
}
}
impl JsonCondition {
pub fn new(
arg_index: u8,
arg_len: SeccompCmpArgLen,
operator: SeccompCmpOp,
value: u64,
) -> Self {
Self {
arg_index,
arg_len,
operator,
value,
comment: None,
}
}
}
#[test]
fn test_make_seccomp_filter() {
let compiler = JsonCompiler::new(ARCH.try_into().unwrap());
let wrong_syscall_name_filter = JsonFilter::new(
SeccompAction::Trap,
SeccompAction::Allow,
vec![JsonRule::new("wrong_syscall".to_string(), None)],
);
assert!(matches!(
compiler
.make_seccomp_filter(wrong_syscall_name_filter)
.unwrap_err(),
Error::SyscallName(_, _)
));
let wrong_arg_index_filter = JsonFilter::new(
SeccompAction::Allow,
SeccompAction::Trap,
vec![JsonRule::new(
"futex".to_string(),
Some(vec![JsonCondition::new(8, Dword, Le, 65)]),
)],
);
assert!(matches!(
compiler
.make_seccomp_filter(wrong_arg_index_filter)
.unwrap_err(),
Error::Backend(BackendError::InvalidArgumentNumber)
));
let empty_rule_filter = JsonFilter::new(
SeccompAction::Allow,
SeccompAction::Trap,
vec![JsonRule::new("read".to_string(), Some(vec![]))],
);
assert!(matches!(
compiler.make_seccomp_filter(empty_rule_filter).unwrap_err(),
Error::Backend(BackendError::EmptyRule)
));
let wrong_syscall_name_filter = JsonFilter::new(
SeccompAction::Allow,
SeccompAction::Allow,
vec![JsonRule::new("read".to_string(), None)],
);
assert!(matches!(
compiler
.make_seccomp_filter(wrong_syscall_name_filter)
.unwrap_err(),
Error::Backend(BackendError::IdenticalActions)
));
let filter = JsonFilter::new(
SeccompAction::Trap,
SeccompAction::Allow,
vec![
JsonRule::new("read".to_string(), None),
JsonRule::new(
"futex".to_string(),
Some(vec![
JsonCondition::new(2, Dword, Le, 65),
JsonCondition::new(1, Qword, Ne, 80),
]),
),
JsonRule::new(
"futex".to_string(),
Some(vec![
JsonCondition::new(3, Qword, Gt, 65),
JsonCondition::new(1, Qword, Lt, 80),
]),
),
JsonRule::new(
"futex".to_string(),
Some(vec![JsonCondition::new(3, Qword, Ge, 65)]),
),
JsonRule::new(
"ioctl".to_string(),
Some(vec![JsonCondition::new(3, Dword, MaskedEq(100), 65)]),
),
],
);
let seccomp_filter = SeccompFilter::new(
vec![
(
compiler.syscall_table.get_syscall_nr("read").unwrap(),
vec![],
),
(
compiler.syscall_table.get_syscall_nr("futex").unwrap(),
vec![
SeccompRule::new(vec![
Cond::new(2, Dword, Le, 65).unwrap(),
Cond::new(1, Qword, Ne, 80).unwrap(),
])
.unwrap(),
SeccompRule::new(vec![
Cond::new(3, Qword, Gt, 65).unwrap(),
Cond::new(1, Qword, Lt, 80).unwrap(),
])
.unwrap(),
SeccompRule::new(vec![Cond::new(3, Qword, Ge, 65).unwrap()]).unwrap(),
],
),
(
compiler.syscall_table.get_syscall_nr("ioctl").unwrap(),
vec![
SeccompRule::new(vec![Cond::new(3, Dword, MaskedEq(100), 65).unwrap()])
.unwrap(),
],
),
]
.into_iter()
.collect(),
SeccompAction::Trap,
SeccompAction::Allow,
ARCH.try_into().unwrap(),
)
.unwrap();
assert_eq!(
compiler.make_seccomp_filter(filter).unwrap(),
seccomp_filter
);
}
#[allow(clippy::useless_asref)]
#[test]
fn test_compile() {
let compiler = JsonCompiler::new(ARCH.try_into().unwrap());
{
let json_input = "";
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = "hjkln";
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = "[]";
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = "{1}";
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"{"a": {}}"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"{"a": {"match_action": "allow", "mismatch_action":"log"}}"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input =
r#"{"a": {"match_action": "allow", "mismatch_action":"log", "filters": []}}"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input =
r#"{"a": {"match_action": "allow", "mismatch_action":"logs", "filter": []}}"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"{
"a": {
"match_action": "allow",
"mismatch_action":"log",
"filter_action": "trap",
"filter": []
}
}"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input =
r#"{"a": {"match_action": "allow", "mismatch_action":"errno", "filter": []}}"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"
{
"thread_2": {
"mismatch_action": "trap",
"match_action": "allow",
"filter": [
{
"syscall": "ioctl",
"args": [
{
"index": 3,
"type": "qword",
"op": "eq",
"val": 18446744073709551616
}
]
}
]
}
}
"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"
{
"thread_2": {
"mismatch_action": "trap",
"match_action": "allow",
"filter": [
{
"syscall": "ioctl",
"args": [
{
"index": 3,
"type": "qword",
"op": "eq",
"val": -1846
}
]
}
]
}
}
"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"
{
"thread_2": {
"mismatch_action": "trap",
"match_action": "allow",
"filter": [
{
"syscall": "ioctl",
"args": [
{
"index": 3,
"type": "qword",
"op": "eq",
"val": 1846.4
}
]
}
]
}
}
"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"
{
"thread_2": {
"mismatch_action": "trap",
"match_action": "allow",
"filter": [
{
"syscall": "ioctl",
"args": [
{
"index": 3,
"type": "qword",
"op": "eq",
"val": 14,
"comment": 15
}
]
}
]
}
}
"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
let json_input = r#"
{
"thread_1": {
"mismatch_action": "trap",
"match_action": "allow",
"filter": []
},
"thread_1": {
"mismatch_action": "trap",
"match_action": "allow",
"filter": []
}
}
"#;
assert!(compiler.compile(json_input.as_bytes()).is_err());
}
{
let json_input = "{}";
assert_eq!(compiler.compile(json_input.as_bytes()).unwrap().len(), 0);
let json_input =
r#"{"a": {"match_action": "allow", "mismatch_action":"log", "filter": []}}"#;
assert!(compiler.compile(json_input.as_bytes()).is_ok());
let json_input = r#"{
"a": {
"default_action":"log",
"filter_action": "allow",
"filter": []
}
}"#;
let filter_with_aliases = compiler.compile(json_input.as_bytes()).unwrap();
let json_input = r#"{
"a": {
"mismatch_action":"log",
"match_action": "allow",
"filter": []
}
}"#;
let filter_without_aliases = compiler.compile(json_input.as_bytes()).unwrap();
assert_eq!(
filter_with_aliases.get("a").unwrap(),
filter_without_aliases.get("a").unwrap()
);
let json_input = r#"{
"a": {
"default_action":"log",
"filter_action": "allow",
"filter": []
}
}"#;
let filter_without_aliases = compiler.compile(json_input.as_bytes()).unwrap();
assert_eq!(
filter_with_aliases.get("a").unwrap(),
filter_without_aliases.get("a").unwrap()
);
let json_input = r#"
{
"thread_1": {
"mismatch_action": {
"errno": 12
},
"match_action": "allow",
"filter": [
{
"syscall": "openat"
},
{
"syscall": "close"
},
{
"syscall": "read"
},
{
"syscall": "futex",
"args": [
{
"index": 2,
"type": "dword",
"op": "le",
"val": 65
},
{
"index": 1,
"type": "qword",
"op": "ne",
"val": 80
}
]
},
{
"syscall": "futex",
"args": [
{
"index": 3,
"type": "qword",
"op": "gt",
"val": 65
},
{
"index": 1,
"type": "qword",
"op": "lt",
"val": 80
}
]
},
{
"syscall": "futex",
"args": [
{
"index": 3,
"type": "qword",
"op": "ge",
"val": 65,
"comment": "dummy comment"
}
]
},
{
"syscall": "ioctl",
"args": [
{
"index": 3,
"type": "dword",
"op": {
"masked_eq": 100
},
"val": 65
}
]
}
]
},
"thread_2": {
"mismatch_action": "trap",
"match_action": "allow",
"filter": [
{
"syscall": "ioctl",
"comment": "dummy comment",
"args": [
{
"index": 3,
"type": "dword",
"op": "eq",
"val": 65,
"comment": "dummy comment"
}
]
}
]
}
}
"#;
let mut filters = HashMap::new();
filters.insert(
"thread_1".to_string(),
SeccompFilter::new(
vec![
(libc::SYS_openat, vec![]),
(libc::SYS_close, vec![]),
(libc::SYS_read, vec![]),
(
libc::SYS_futex,
vec![
SeccompRule::new(vec![
Cond::new(2, Dword, Le, 65).unwrap(),
Cond::new(1, Qword, Ne, 80).unwrap(),
])
.unwrap(),
SeccompRule::new(vec![
Cond::new(3, Qword, Gt, 65).unwrap(),
Cond::new(1, Qword, Lt, 80).unwrap(),
])
.unwrap(),
SeccompRule::new(vec![Cond::new(3, Qword, Ge, 65).unwrap()])
.unwrap(),
],
),
(
libc::SYS_ioctl,
vec![SeccompRule::new(vec![
Cond::new(3, Dword, MaskedEq(100), 65).unwrap()
])
.unwrap()],
),
]
.into_iter()
.collect(),
SeccompAction::Errno(12),
SeccompAction::Allow,
ARCH.try_into().unwrap(),
)
.unwrap(),
);
filters.insert(
"thread_2".to_string(),
SeccompFilter::new(
vec![(
libc::SYS_ioctl,
vec![SeccompRule::new(vec![Cond::new(3, Dword, Eq, 65).unwrap()]).unwrap()],
)]
.into_iter()
.collect(),
SeccompAction::Trap,
SeccompAction::Allow,
ARCH.try_into().unwrap(),
)
.unwrap(),
);
let mut v1: Vec<_> = filters.into_iter().collect();
v1.sort_by(|x, y| x.0.cmp(&y.0));
let mut v2: Vec<_> = compiler
.compile(json_input.as_bytes())
.unwrap()
.into_iter()
.collect();
v2.sort_by(|x, y| x.0.cmp(&y.0));
assert_eq!(v1, v2);
}
}
}