1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
use crate::backend::{bpf::*, condition::SeccompCondition, Error, Result};
/// Rule that a filter attempts to match for a syscall.
///
/// If all conditions match then rule gets matched.
/// A syscall can have many rules associated. If either of them matches, the `match_action` of the
/// [`SeccompFilter`] is triggered.
///
/// [`SeccompFilter`]: struct.SeccompFilter.html
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SeccompRule {
/// Conditions of rule that need to match in order for the rule to get matched.
conditions: Vec<SeccompCondition>,
}
impl SeccompRule {
/// Creates a new rule. Rules with 0 conditions are not allowed.
///
/// # Arguments
///
/// * `conditions` - Vector of [`SeccompCondition`]s that the syscall must match.
///
/// # Example
///
/// ```
/// use seccompiler::{SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, SeccompRule};
///
/// let rule = SeccompRule::new(vec![
/// SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 1).unwrap(),
/// SeccompCondition::new(1, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 1).unwrap(),
/// ])
/// .unwrap();
/// ```
///
/// [`SeccompCondition`]: struct.SeccompCondition.html
pub fn new(conditions: Vec<SeccompCondition>) -> Result<Self> {
let instance = Self { conditions };
instance.validate()?;
Ok(instance)
}
/// Performs semantic checks on the SeccompRule.
fn validate(&self) -> Result<()> {
// Rules with no conditions are not allowed. Syscalls mappings to empty rule vectors are to
// be used instead, for matching only on the syscall number.
if self.conditions.is_empty() {
return Err(Error::EmptyRule);
}
Ok(())
}
/// Appends a condition of the rule to an accumulator.
///
/// The length of the rule and offset to the next rule are updated.
///
/// # Arguments
///
/// * `condition` - The condition added to the rule.
/// * `accumulator` - Accumulator of BPF statements that compose the BPF program.
/// * `rule_len` - Number of conditions in the rule.
/// * `offset` - Offset (in number of BPF statements) to the next rule.
fn append_condition(
condition: SeccompCondition,
accumulator: &mut Vec<Vec<sock_filter>>,
offset: &mut u8,
) {
// Tries to detect whether prepending the current condition will produce an unjumpable
// offset (since BPF conditional jumps are a maximum of 255 instructions, which is
// u8::MAX).
if offset.checked_add(CONDITION_MAX_LEN + 1).is_none() {
// If that is the case, three additional helper jumps are prepended and the offset
// is reset to 1.
//
// - The first jump continues the evaluation of the condition chain by jumping to
// the next condition or the action of the rule if the last condition was matched.
// - The second, jumps out of the rule, to the next rule or the default action of
// the filter in case of the last rule in the rule chain of a syscall.
// - The third jumps out of the rule chain of the syscall, to the rule chain of the
// next syscall number to be checked or the default action of the filter in the
// case of the last rule chain.
let helper_jumps = vec![
bpf_stmt(BPF_JMP | BPF_JA, 2),
bpf_stmt(BPF_JMP | BPF_JA, u32::from(*offset) + 1),
bpf_stmt(BPF_JMP | BPF_JA, u32::from(*offset) + 1),
];
accumulator.push(helper_jumps);
*offset = 1;
}
let condition = condition.into_bpf(*offset);
// Safe to unwrap since we checked that offset + `CONDITION_MAX_LEN` does not overflow.
*offset += u8::try_from(condition.len()).unwrap();
accumulator.push(condition);
}
}
impl From<SeccompRule> for BpfProgram {
fn from(rule: SeccompRule) -> Self {
// Each rule starts with 2 jump statements:
// * The first jump enters the rule, attempting a match.
// * The second one jumps out of the rule, into the next rule of the syscall or to the
// default action if none of the rules were matched.
// Rule is built backwards, because SeccompConditions need to know the jump offset to the
// next rule, when compiled to BPF.
let mut accumulator = Vec::with_capacity(
rule.conditions
.len()
// Realistically, this overflow should never happen.
// If the nr of statements ever overflows `usize`, the rust vector allocation would
// anyway fail.
.checked_mul(CONDITION_MAX_LEN as usize)
.unwrap(),
);
let mut offset = 1;
// Conditions are translated into BPF statements and prepended to the rule.
rule.conditions.into_iter().for_each(|condition| {
SeccompRule::append_condition(condition, &mut accumulator, &mut offset)
});
// The two initial jump statements are prepended to the rule.
accumulator.push(vec![
bpf_stmt(BPF_JMP | BPF_JA, 1),
bpf_stmt(BPF_JMP | BPF_JA, u32::from(offset) + 1),
]);
// Finally, builds the translated rule by reversing and consuming the accumulator.
let mut result = Vec::new();
accumulator
.into_iter()
.rev()
.for_each(|mut instructions| result.append(&mut instructions));
result
}
}
#[cfg(test)]
mod tests {
use super::SeccompRule;
use crate::backend::bpf::*;
use crate::backend::{
Error, SeccompCmpArgLen as ArgLen, SeccompCmpOp::*, SeccompCondition as Cond,
};
#[test]
fn test_validate_rule() {
assert_eq!(SeccompRule::new(vec![]).unwrap_err(), Error::EmptyRule);
}
// Checks that rule gets translated correctly into BPF statements.
#[test]
fn test_rule_bpf_output() {
let rule = SeccompRule::new(vec![
Cond::new(0, ArgLen::Dword, Eq, 1).unwrap(),
Cond::new(2, ArgLen::Qword, MaskedEq(0b1010), 14).unwrap(),
])
.unwrap();
let (msb_offset, lsb_offset) = { (4, 0) };
// Builds hardcoded BPF instructions.
let instructions = vec![
bpf_stmt(BPF_JMP | BPF_JA, 1), // Start evaluating the rule.
bpf_stmt(BPF_JMP | BPF_JA, 10), // Jump to the next rule.
bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32 + msb_offset),
bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 6),
bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32 + lsb_offset),
bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0b1010),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 14 & 0b1010, 0, 3),
bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + lsb_offset),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1),
];
// In a filter, these instructions would follow:
// RET match_action
// OTHER RULES...
// RET mismatch_action. (if the syscall number matched and then all rules fail to match)
// RET default action. (if no syscall number matched)
// Compares translated rule with hardcoded BPF instructions.
let bpfprog: BpfProgram = rule.into();
assert_eq!(bpfprog, instructions);
}
// Checks that rule with too many conditions gets translated correctly into BPF statements
// using three helper jumps.
#[test]
fn test_rule_many_conditions_bpf_output() {
// Builds rule.
let mut conditions = Vec::with_capacity(43);
for _ in 0..42 {
conditions.push(Cond::new(0, ArgLen::Qword, MaskedEq(0), 0).unwrap());
}
conditions.push(Cond::new(0, ArgLen::Qword, Eq, 0).unwrap());
let rule = SeccompRule::new(conditions).unwrap();
let (msb_offset, lsb_offset) = { (4, 0) };
// Builds hardcoded BPF instructions.
let mut instructions = vec![
bpf_stmt(BPF_JMP | BPF_JA, 1), // Start evaluating the rule.
bpf_stmt(BPF_JMP | BPF_JA, 6), // Jump to the next rule. Actually to a helper jump.
bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + msb_offset),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 3),
bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + lsb_offset),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1),
bpf_stmt(BPF_JMP | BPF_JA, 2),
bpf_stmt(BPF_JMP | BPF_JA, 254),
bpf_stmt(BPF_JMP | BPF_JA, 254),
];
let mut offset = 253;
for _ in 0..42 {
offset -= 6;
// Add the rest of the `MaskedEq` conditions.
instructions.append(&mut vec![
bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + msb_offset),
bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, offset + 3),
bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + lsb_offset),
bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, offset),
]);
}
// Compares translated rule with hardcoded BPF instructions.
let bpfprog: BpfProgram = rule.into();
assert_eq!(bpfprog, instructions);
}
}