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);
    }
}