Skip to main content

ebpf/
converter.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use linux_uapi::sock_filter;
6use std::collections::HashMap;
7
8use crate::EbpfError;
9use crate::EbpfError::*;
10use crate::api::{
11    BPF_A, BPF_ABS, BPF_ADD, BPF_ALU, BPF_ALU64, BPF_AND, BPF_B, BPF_DIV, BPF_EXIT, BPF_H, BPF_IMM,
12    BPF_IND, BPF_JA, BPF_JEQ, BPF_JGE, BPF_JGT, BPF_JLE, BPF_JLT, BPF_JMP, BPF_JMP32, BPF_JNE,
13    BPF_JSET, BPF_K, BPF_LD, BPF_LDX, BPF_LEN, BPF_LSH, BPF_MEM, BPF_MISC, BPF_MOD, BPF_MOV,
14    BPF_MSH, BPF_MUL, BPF_NEG, BPF_OR, BPF_RET, BPF_RSH, BPF_ST, BPF_STX, BPF_SUB, BPF_TAX,
15    BPF_TXA, BPF_W, BPF_X, BPF_XOR, EbpfInstruction,
16};
17use crate::program::{
18    BpfProgramContext, EbpfProgram, ProgramArgument, StaticHelperSet, link_program,
19};
20use crate::verifier::{
21    CallingContext, NullVerifierLogger, Type, VerifiedEbpfProgram, verify_program,
22};
23
24const CBPF_WORD_SIZE: u32 = 4;
25
26// cBPF supports 16 words for scratch memory.
27const CBPF_SCRATCH_SIZE: u32 = 16;
28
29pub enum CbpfLenInstruction {
30    Static { len: i32 },
31    ContextField { offset: i16 },
32}
33
34pub struct CbpfConfig {
35    pub len: CbpfLenInstruction,
36    pub allow_msh: bool,
37}
38
39// These are accessors for bits in an BPF/EBPF instruction.
40// Instructions are encoded in one byte.  The first 3 LSB represent
41// the operation, and the other bits represent various modifiers.
42// Brief comments are given to indicate what the functions broadly
43// represent, but for the gory detail, consult a detailed guide to
44// BPF, like the one at https://docs.kernel.org/bpf/instruction-set.html
45
46/// The bpf_class is the instruction type.(e.g., load/store/jump/ALU).
47pub fn bpf_class(filter: &sock_filter) -> u8 {
48    (filter.code & 0x07) as u8
49}
50
51/// The bpf_size is the 4th and 5th bit of load and store
52/// instructions.  It indicates the bit width of the load / store
53/// target (8, 16, 32, 64 bits).
54pub fn bpf_size(filter: &sock_filter) -> u8 {
55    (filter.code & 0x18) as u8
56}
57
58/// The addressing mode is the most significant three bits of load and
59/// store instructions.  They indicate whether the instrution accesses a
60/// constant, accesses from memory, or accesses from memory atomically.
61pub fn bpf_addressing_mode(filter: &sock_filter) -> u8 {
62    (filter.code & 0xe0) as u8
63}
64
65/// Modifiers for jumps and alu operations.  For example, a jump can
66/// be jeq, jtl, etc.  An ALU operation can be plus, minus, divide,
67/// etc.
68fn bpf_op(filter: &sock_filter) -> u8 {
69    (filter.code & 0xf0) as u8
70}
71
72/// The source for the operation (either a register or an immediate).
73fn bpf_src(filter: &sock_filter) -> u8 {
74    (filter.code & 0x08) as u8
75}
76
77/// Similar to bpf_src, but also allows BPF_A - used for RET.
78fn bpf_rval(filter: &sock_filter) -> u8 {
79    (filter.code & 0x18) as u8
80}
81
82/// Returns offset for the scratch memory with the specified address.
83fn cbpf_scratch_offset(addr: u32) -> Result<i16, EbpfError> {
84    if addr < CBPF_SCRATCH_SIZE {
85        Ok((-(CBPF_SCRATCH_SIZE as i16) + addr as i16) * CBPF_WORD_SIZE as i16)
86    } else {
87        Err(EbpfError::InvalidCbpfScratchOffset(addr))
88    }
89}
90
91/// Transforms a program in classic BPF (cbpf, as stored in struct
92/// sock_filter) to extended BPF (stored as `EbpfInstruction`).
93/// The bpf_code parameter is kept as an array for easy transfer
94/// via FFI.  This currently only allows the subset of BPF permitted
95/// by seccomp(2).
96fn cbpf_to_ebpf(
97    bpf_code: &[sock_filter],
98    config: &CbpfConfig,
99) -> Result<Vec<EbpfInstruction>, EbpfError> {
100    // There are only two BPF registers, A and X. There are 10
101    // EBPF registers, numbered 0-9.  We map between the two as
102    // follows:
103
104    // R0: Mapped to A.
105    const REG_A: u8 = 0;
106
107    // R1: Incoming argument pointing at the packet context (e.g. `sk_buff`). Moved to R6.
108    const REG_ARG1: u8 = 1;
109
110    // R6: Pointer to the program context. Initially passed as the first argument. Implicitly
111    //     used by eBPF when executing the legacy packet access instructions (`BPF_LD | BPF_ABS`
112    //     and `BPF_LD | BPF_IND`).
113    const REG_CONTEXT: u8 = 6;
114
115    // R7: Temp register used in the `BPF_MSH` implementation.
116    const REG_TMP: u8 = 7;
117
118    // R9: Mapped to X
119    const REG_X: u8 = 9;
120
121    // R10: Const stack pointer. cBFP scratch memory (16 words) is stored on top of the stack.
122    const REG_STACK: u8 = 10;
123
124    // Map from jump targets in the cbpf to a list of jump instructions in the epbf that target
125    // it. When you figure out what the offset of the target is in the ebpf, you need to patch the
126    // jump instructions to target it correctly.
127    let mut to_be_patched: HashMap<usize, Vec<usize>> = HashMap::new();
128
129    let mut ebpf_code: Vec<EbpfInstruction> = vec![];
130    ebpf_code.reserve(bpf_code.len() * 2 + 2);
131
132    // Save the arguments to registers that won't get clobbered by `BPF_LD`.
133    ebpf_code.push(EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, REG_CONTEXT, REG_ARG1, 0, 0));
134
135    // Reset A to 0. This is necessary in case one of the load operation exits prematurely.
136    ebpf_code.push(EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, REG_A, 0, 0, 0));
137
138    for (i, bpf_instruction) in bpf_code.iter().enumerate() {
139        // Update instructions processed previously that jump to the current one.
140        if let Some((_, entries)) = to_be_patched.remove_entry(&i) {
141            for index in entries {
142                let offset = (ebpf_code.len() - index - 1) as i16;
143                ebpf_code[index].set_offset(offset);
144            }
145        }
146
147        // Helper to queue a new entry into `to_be_patched`.
148        let mut prep_patch = |cbpf_offset: usize, ebpf_source: usize| -> Result<(), EbpfError> {
149            let cbpf_target = i + 1 + cbpf_offset;
150            if cbpf_target >= bpf_code.len() {
151                return Err(EbpfError::InvalidCbpfJumpOffset(cbpf_offset as u32));
152            }
153            to_be_patched.entry(cbpf_target).or_insert_with(Vec::new).push(ebpf_source);
154            Ok(())
155        };
156
157        match bpf_class(bpf_instruction) {
158            BPF_ALU => match bpf_op(bpf_instruction) {
159                op @ (BPF_ADD | BPF_SUB | BPF_MUL | BPF_DIV | BPF_MOD | BPF_AND | BPF_OR
160                | BPF_XOR | BPF_LSH | BPF_RSH) => {
161                    if bpf_src(bpf_instruction) == BPF_K {
162                        // Division and remainder by 0 are rejected.
163                        if (op == BPF_DIV || op == BPF_MOD) && bpf_instruction.k == 0 {
164                            return Err(EbpfError::ProgramVerifyError("Division by 0".to_string()));
165                        }
166                        // LSH and RSH by more than 31 are rejected.
167                        if (op == BPF_LSH || op == BPF_RSH) && bpf_instruction.k >= 32 {
168                            return Err(EbpfError::ProgramVerifyError(
169                                "Shift by 32 or more".to_string(),
170                            ));
171                        }
172                        ebpf_code.push(EbpfInstruction::new(
173                            bpf_instruction.code as u8,
174                            REG_A,
175                            0,
176                            0,
177                            bpf_instruction.k as i32,
178                        ));
179                    } else {
180                        // Division and remainder by 0 must stop the execution and return 0.
181                        if op == BPF_DIV || op == BPF_MOD {
182                            // If X != 0, skip 1 instruction.
183                            ebpf_code.push(EbpfInstruction::new(
184                                BPF_JMP32 | BPF_JNE | BPF_K,
185                                REG_X,
186                                0,
187                                2,
188                                0,
189                            ));
190                            // Return 0.
191                            ebpf_code.push(EbpfInstruction::new(
192                                BPF_ALU | BPF_MOV | BPF_IMM,
193                                REG_A,
194                                0,
195                                0,
196                                0,
197                            ));
198                            ebpf_code.push(EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0));
199                        }
200
201                        ebpf_code.push(EbpfInstruction::new(
202                            bpf_instruction.code as u8,
203                            REG_A,
204                            REG_X,
205                            0,
206                            0,
207                        ));
208                    };
209                }
210                BPF_NEG => {
211                    ebpf_code.push(EbpfInstruction::new(BPF_ALU | BPF_NEG, REG_A, REG_A, 0, 0));
212                }
213                _ => return Err(InvalidCbpfInstruction(bpf_instruction.code)),
214            },
215            class @ (BPF_LD | BPF_LDX) => {
216                let dst_reg = if class == BPF_LDX { REG_X } else { REG_A };
217
218                let mode = bpf_addressing_mode(bpf_instruction);
219                let size = bpf_size(bpf_instruction);
220
221                // Half-word (`BPF_H`) and byte (`BPF_B`) loads are allowed only for `BPD_ABS` and
222                // `BPD_IND`. Also `BPD_ABS` and `BPD_IND` are not allowed with `BPD_LDX`.
223                // `BPF_LEN`, `BPF_IMM` and `BPF_MEM` loads should be word-sized (i.e. `BPF_W`).
224                // `BPF_MSH` is allowed only with `BPF_B` and `BPF_LDX`.
225                match (size, mode, class) {
226                    (BPF_H | BPF_B | BPF_W, BPF_ABS | BPF_IND, BPF_LD) => (),
227                    (BPF_W, BPF_LEN | BPF_IMM | BPF_MEM, BPF_LD | BPF_LDX) => (),
228                    (BPF_B, BPF_MSH, BPF_LDX) if config.allow_msh => (),
229                    _ => return Err(InvalidCbpfInstruction(bpf_instruction.code)),
230                };
231
232                let k = bpf_instruction.k;
233
234                match mode {
235                    BPF_ABS => {
236                        ebpf_code.push(EbpfInstruction::new(
237                            BPF_LD | BPF_ABS | size,
238                            REG_A,
239                            0,
240                            0,
241                            k as i32,
242                        ));
243                    }
244                    BPF_IND => {
245                        ebpf_code.push(EbpfInstruction::new(
246                            BPF_LD | BPF_IND | size,
247                            REG_A,
248                            REG_X,
249                            0,
250                            k as i32,
251                        ));
252                    }
253                    BPF_IMM => {
254                        let imm = k as i32;
255                        ebpf_code.push(EbpfInstruction::new(
256                            BPF_ALU | BPF_MOV | BPF_K,
257                            dst_reg,
258                            0,
259                            0,
260                            imm,
261                        ));
262                    }
263                    BPF_MEM => {
264                        // cBPF's scratch memory is stored in the stack referenced by R10.
265                        let offset = cbpf_scratch_offset(k)?;
266                        ebpf_code.push(EbpfInstruction::new(
267                            BPF_LDX | BPF_MEM,
268                            dst_reg,
269                            REG_STACK,
270                            offset,
271                            0,
272                        ));
273                    }
274                    BPF_LEN => {
275                        ebpf_code.push(match config.len {
276                            CbpfLenInstruction::Static { len } => {
277                                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, dst_reg, 0, 0, len)
278                            }
279                            CbpfLenInstruction::ContextField { offset } => EbpfInstruction::new(
280                                BPF_LDX | BPF_MEM | BPF_W,
281                                dst_reg,
282                                REG_CONTEXT,
283                                offset,
284                                0,
285                            ),
286                        });
287                    }
288                    BPF_MSH => {
289                        // `BPF_MSH` loads `4 * (P[k:1] & 0xf)`, which translates to 6 instructions.
290                        ebpf_code.extend_from_slice(&[
291                            // mov TMP, A
292                            EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_X, REG_TMP, REG_A, 0, 0),
293                            // ldpb [k]
294                            EbpfInstruction::new(BPF_LD | BPF_ABS | BPF_B, REG_A, 0, 0, k as i32),
295                            // and A, 0xf
296                            EbpfInstruction::new(BPF_ALU | BPF_AND | BPF_K, REG_A, 0, 0, 0x0f),
297                            // mul A, 4
298                            EbpfInstruction::new(BPF_ALU | BPF_MUL | BPF_K, REG_A, 0, 0, 4),
299                            // mov X, A
300                            EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_X, REG_X, REG_A, 0, 0),
301                            // mov A, TMP
302                            EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_X, REG_A, REG_TMP, 0, 0),
303                        ]);
304                    }
305                    _ => return Err(InvalidCbpfInstruction(bpf_instruction.code)),
306                }
307            }
308            BPF_JMP => {
309                match bpf_op(bpf_instruction) {
310                    BPF_JA => {
311                        ebpf_code.push(EbpfInstruction::new(BPF_JMP | BPF_JA, 0, 0, -1, 0));
312                        prep_patch(bpf_instruction.k as usize, ebpf_code.len() - 1)?;
313                    }
314                    op @ (BPF_JGT | BPF_JGE | BPF_JEQ | BPF_JSET) => {
315                        // In cBPF, JMPs have a jump-if-true and jump-if-false branch. eBPF only
316                        // has jump-if-true. In most cases only one of the two branches actually
317                        // jumps (the other one is set to 0). In these cases the instruction can
318                        // be translated to 1 eBPF instruction. Otherwise two instructions are
319                        // produced in the output.
320
321                        let src = bpf_src(bpf_instruction);
322                        let sock_filter { k, jt, jf, .. } = *bpf_instruction;
323                        let (src_reg, imm) = if src == BPF_K { (0, k as i32) } else { (REG_X, 0) };
324
325                        // When jumping only for the false case we can negate the comparison
326                        // operator to achieve the same effect with a single jump-if-true eBPF
327                        // instruction. That doesn't work for `BPF_JSET`. It is handled below
328                        // using 2 instructions.
329                        if jt == 0 && op != BPF_JSET {
330                            let op = match op {
331                                BPF_JGT => BPF_JLE,
332                                BPF_JGE => BPF_JLT,
333                                BPF_JEQ => BPF_JNE,
334                                _ => panic!("Unexpected operation: {op:?}"),
335                            };
336
337                            ebpf_code.push(EbpfInstruction::new(
338                                BPF_JMP32 | op | src,
339                                REG_A,
340                                src_reg,
341                                -1,
342                                imm,
343                            ));
344                            prep_patch(jf as usize, ebpf_code.len() - 1)?;
345                        } else {
346                            // Jump if true.
347                            ebpf_code.push(EbpfInstruction::new(
348                                BPF_JMP32 | op | src,
349                                REG_A,
350                                src_reg,
351                                -1,
352                                imm,
353                            ));
354                            prep_patch(jt as usize, ebpf_code.len() - 1)?;
355
356                            // Jump if false. Jumps with 0 offset are no-op and can be omitted.
357                            if jf > 0 {
358                                ebpf_code.push(EbpfInstruction::new(BPF_JMP | BPF_JA, 0, 0, -1, 0));
359                                prep_patch(jf as usize, ebpf_code.len() - 1)?;
360                            }
361                        }
362                    }
363                    _ => return Err(InvalidCbpfInstruction(bpf_instruction.code)),
364                }
365            }
366            BPF_MISC => match bpf_op(bpf_instruction) {
367                BPF_TAX => {
368                    ebpf_code.push(EbpfInstruction::new(
369                        BPF_ALU | BPF_MOV | BPF_X,
370                        REG_X,
371                        REG_A,
372                        0,
373                        0,
374                    ));
375                }
376                BPF_TXA => {
377                    ebpf_code.push(EbpfInstruction::new(
378                        BPF_ALU | BPF_MOV | BPF_X,
379                        REG_A,
380                        REG_X,
381                        0,
382                        0,
383                    ));
384                }
385                _ => return Err(InvalidCbpfInstruction(bpf_instruction.code)),
386            },
387
388            class @ (BPF_ST | BPF_STX) => {
389                if bpf_addressing_mode(bpf_instruction) != 0 || bpf_size(bpf_instruction) != 0 {
390                    return Err(InvalidCbpfInstruction(bpf_instruction.code));
391                }
392
393                // cBPF's scratch memory is stored in the stack referenced by R10.
394                let src_reg = if class == BPF_STX { REG_X } else { REG_A };
395                let offset = cbpf_scratch_offset(bpf_instruction.k)?;
396                ebpf_code.push(EbpfInstruction::new(
397                    BPF_STX | BPF_MEM | BPF_W,
398                    REG_STACK,
399                    src_reg,
400                    offset,
401                    0,
402                ));
403            }
404            BPF_RET => {
405                match bpf_rval(bpf_instruction) {
406                    BPF_K => {
407                        // We're returning a particular value instead of the contents of the
408                        // return register, so load that value into the return register.
409                        let imm = bpf_instruction.k as i32;
410                        ebpf_code.push(EbpfInstruction::new(
411                            BPF_ALU | BPF_MOV | BPF_IMM,
412                            REG_A,
413                            0,
414                            0,
415                            imm,
416                        ));
417                    }
418                    BPF_A => (),
419                    _ => return Err(InvalidCbpfInstruction(bpf_instruction.code)),
420                };
421
422                ebpf_code.push(EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0));
423            }
424            _ => return Err(InvalidCbpfInstruction(bpf_instruction.code)),
425        }
426    }
427
428    assert!(to_be_patched.is_empty());
429
430    Ok(ebpf_code)
431}
432
433/// Instantiates an EbpfProgram given a cbpf original that will work with a packet of the
434/// specified type.
435pub fn convert_and_verify_cbpf(
436    bpf_code: &[sock_filter],
437    packet_type: Type,
438    config: &CbpfConfig,
439) -> Result<VerifiedEbpfProgram, EbpfError> {
440    let context = CallingContext {
441        maps: vec![],
442        helpers: HashMap::new(),
443        args: vec![packet_type.clone()],
444        packet_type: Some(packet_type),
445    };
446    let ebpf_code = cbpf_to_ebpf(bpf_code, config)?;
447    verify_program(ebpf_code, context, &mut NullVerifierLogger)
448}
449
450/// Converts, verifies and links a cBPF program for execution in the specified context.
451pub fn convert_and_link_cbpf<C: BpfProgramContext + StaticHelperSet>(
452    bpf_code: &[sock_filter],
453) -> Result<EbpfProgram<C>, EbpfError> {
454    let verified = convert_and_verify_cbpf(
455        bpf_code,
456        <C as BpfProgramContext>::Packet::get_type().clone(),
457        C::CBPF_CONFIG,
458    )?;
459    link_program(&verified, vec![])
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465    use crate::{MemoryId, NoMap, empty_static_helper_set};
466    use linux_uapi::{
467        AUDIT_ARCH_AARCH64, AUDIT_ARCH_X86_64, SECCOMP_RET_ALLOW, SECCOMP_RET_TRAP, seccomp_data,
468        sock_filter,
469    };
470    use std::mem::offset_of;
471    use std::sync::LazyLock;
472    use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
473
474    pub const TEST_CBPF_CONFIG: CbpfConfig = CbpfConfig {
475        len: CbpfLenInstruction::Static { len: size_of::<seccomp_data>() as i32 },
476        allow_msh: true,
477    };
478
479    #[test]
480    fn test_cbpf_to_ebpf() {
481        // Jump to the next instruction.
482        assert_eq!(
483            cbpf_to_ebpf(
484                &vec![
485                    sock_filter { code: (BPF_JMP | BPF_JA) as u16, jt: 0, jf: 0, k: 0 },
486                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
487                ],
488                &TEST_CBPF_CONFIG
489            ),
490            Ok(vec![
491                EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, 6, 1, 0, 0),
492                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, 0),
493                EbpfInstruction::new(BPF_JMP | BPF_JA, 0, 0, 0, 0),
494                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
495            ]),
496        );
497
498        // Jump after last instruction.
499        assert_eq!(
500            cbpf_to_ebpf(
501                &vec![
502                    sock_filter { code: (BPF_JMP | BPF_JA) as u16, jt: 0, jf: 0, k: 1 },
503                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
504                ],
505                &TEST_CBPF_CONFIG
506            ),
507            Err(EbpfError::InvalidCbpfJumpOffset(1)),
508        );
509
510        // Jump out of bounds.
511        assert_eq!(
512            cbpf_to_ebpf(
513                &vec![sock_filter { code: (BPF_JMP | BPF_JA) as u16, jt: 0, jf: 0, k: 0xffffffff }],
514                &TEST_CBPF_CONFIG
515            ),
516            Err(EbpfError::InvalidCbpfJumpOffset(0xffffffff)),
517        );
518
519        // BPF_JNE is allowed only in eBPF.
520        assert_eq!(
521            cbpf_to_ebpf(
522                &vec![
523                    sock_filter { code: (BPF_JMP | BPF_JNE) as u16, jt: 0, jf: 0, k: 0 },
524                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
525                ],
526                &TEST_CBPF_CONFIG
527            ),
528            Err(EbpfError::InvalidCbpfInstruction((BPF_JMP | BPF_JNE) as u16)),
529        );
530
531        // BPF_JEQ is supported in BPF.
532        assert_eq!(
533            cbpf_to_ebpf(
534                &vec![
535                    sock_filter { code: (BPF_JMP | BPF_JEQ) as u16, jt: 1, jf: 0, k: 0 },
536                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
537                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
538                ],
539                &TEST_CBPF_CONFIG
540            ),
541            Ok(vec![
542                EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, 6, 1, 0, 0),
543                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, 0),
544                EbpfInstruction::new(BPF_JMP32 | BPF_JEQ, 0, 0, 1, 0),
545                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
546                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
547            ]),
548        );
549
550        // Make sure the jump is translated correctly when the jump target produces 2 instructions.
551        assert_eq!(
552            cbpf_to_ebpf(
553                &vec![
554                    sock_filter { code: (BPF_JMP | BPF_JA) as u16, jt: 0, jf: 0, k: 0 },
555                    sock_filter { code: (BPF_RET | BPF_K) as u16, jt: 0, jf: 0, k: 1 },
556                ],
557                &TEST_CBPF_CONFIG
558            ),
559            Ok(vec![
560                EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, 6, 1, 0, 0),
561                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, 0),
562                EbpfInstruction::new(BPF_JMP | BPF_JA, 0, 0, 0, 0),
563                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_IMM, 0, 0, 0, 1),
564                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
565            ]),
566        );
567
568        // BPF_MEM access.
569        assert_eq!(
570            cbpf_to_ebpf(
571                &vec![
572                    sock_filter { code: (BPF_LD | BPF_MEM) as u16, jt: 0, jf: 0, k: 0 },
573                    sock_filter { code: (BPF_LDX | BPF_MEM) as u16, jt: 0, jf: 0, k: 15 },
574                    sock_filter { code: BPF_ST as u16, jt: 0, jf: 0, k: 0 },
575                    sock_filter { code: BPF_STX as u16, jt: 0, jf: 0, k: 15 },
576                ],
577                &TEST_CBPF_CONFIG
578            ),
579            Ok(vec![
580                EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, 6, 1, 0, 0),
581                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, 0),
582                EbpfInstruction::new(BPF_LDX | BPF_MEM | BPF_W, 0, 10, -64, 0),
583                EbpfInstruction::new(BPF_LDX | BPF_MEM | BPF_W, 9, 10, -4, 0),
584                EbpfInstruction::new(BPF_STX | BPF_MEM | BPF_W, 10, 0, -64, 0),
585                EbpfInstruction::new(BPF_STX | BPF_MEM | BPF_W, 10, 9, -4, 0),
586            ]),
587        );
588
589        // BPF_MEM access out of bounds.
590        assert_eq!(
591            cbpf_to_ebpf(
592                &vec![sock_filter { code: (BPF_LD | BPF_MEM) as u16, jt: 0, jf: 0, k: 17 }],
593                &TEST_CBPF_CONFIG
594            ),
595            Err(EbpfError::InvalidCbpfScratchOffset(17)),
596        );
597    }
598
599    const BPF_ALU_ADD_K: u16 = (BPF_ALU | BPF_ADD | BPF_K) as u16;
600    const BPF_ALU_SUB_K: u16 = (BPF_ALU | BPF_SUB | BPF_K) as u16;
601    const BPF_ALU_MUL_K: u16 = (BPF_ALU | BPF_MUL | BPF_K) as u16;
602    const BPF_ALU_DIV_K: u16 = (BPF_ALU | BPF_DIV | BPF_K) as u16;
603    const BPF_ALU_AND_K: u16 = (BPF_ALU | BPF_AND | BPF_K) as u16;
604    const BPF_ALU_OR_K: u16 = (BPF_ALU | BPF_OR | BPF_K) as u16;
605    const BPF_ALU_XOR_K: u16 = (BPF_ALU | BPF_XOR | BPF_K) as u16;
606    const BPF_ALU_LSH_K: u16 = (BPF_ALU | BPF_LSH | BPF_K) as u16;
607    const BPF_ALU_RSH_K: u16 = (BPF_ALU | BPF_RSH | BPF_K) as u16;
608
609    const BPF_ALU_OR_X: u16 = (BPF_ALU | BPF_OR | BPF_X) as u16;
610
611    const BPF_LD_W_ABS: u16 = (BPF_LD | BPF_ABS | BPF_W) as u16;
612    const BPF_LD_W_MEM: u16 = (BPF_LD | BPF_MEM | BPF_W) as u16;
613    const BPF_JEQ_K: u16 = (BPF_JMP | BPF_JEQ | BPF_K) as u16;
614    const BPF_JSET_K: u16 = (BPF_JMP | BPF_JSET | BPF_K) as u16;
615    const BPF_RET_K: u16 = (BPF_RET | BPF_K) as u16;
616    const BPF_RET_A: u16 = (BPF_RET | BPF_A) as u16;
617    const BPF_ST_REG: u16 = BPF_ST as u16;
618    const BPF_MISC_TAX: u16 = (BPF_MISC | BPF_TAX) as u16;
619
620    struct TestProgramContext {}
621
622    impl BpfProgramContext for TestProgramContext {
623        type RunContext<'a> = ();
624        type Packet<'a> = &'a seccomp_data;
625        type Map = NoMap;
626        const CBPF_CONFIG: &'static CbpfConfig = &TEST_CBPF_CONFIG;
627    }
628
629    empty_static_helper_set!(TestProgramContext);
630
631    static SECCOMP_DATA_TYPE: LazyLock<Type> = LazyLock::new(|| Type::PtrToMemory {
632        id: MemoryId::new(),
633        offset: 0.into(),
634        buffer_size: 0,
635    });
636
637    impl ProgramArgument for &'_ seccomp_data {
638        fn get_type() -> &'static Type {
639            &*SECCOMP_DATA_TYPE
640        }
641    }
642
643    fn with_prg_assert_result(
644        prg: &EbpfProgram<TestProgramContext>,
645        mut data: seccomp_data,
646        result: u32,
647        msg: &str,
648    ) {
649        let return_value = prg.run(&mut (), &mut data);
650        assert_eq!(return_value, result as u64, "{}: filter return value is {}", msg, return_value);
651    }
652
653    #[test]
654    fn test_filter_with_dw_load() {
655        let test_prg = [
656            // Check data.arch
657            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 4 },
658            sock_filter { code: BPF_JEQ_K, jt: 1, jf: 0, k: AUDIT_ARCH_X86_64 },
659            // Return 1 if arch is wrong
660            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: 1 },
661            // Load data.nr (the syscall number)
662            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 0 },
663            // Always allow 41
664            sock_filter { code: BPF_JEQ_K, jt: 0, jf: 1, k: 41 },
665            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW },
666            // Don't allow 115
667            sock_filter { code: BPF_JEQ_K, jt: 0, jf: 1, k: 115 },
668            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_TRAP },
669            // For other syscalls, check the args
670            // A common hack to deal with 64-bit numbers in BPF: deal
671            // with 32 bits at a time.
672            // First, Load arg0's most significant 32 bits in M[0]
673            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 16 },
674            sock_filter { code: BPF_ST_REG, jt: 0, jf: 0, k: 0 },
675            // Load arg0's least significant 32 bits into M[1]
676            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 20 },
677            sock_filter { code: BPF_ST_REG, jt: 0, jf: 0, k: 1 },
678            // JSET is A & k.  Check the first 32 bits.  If the test
679            // is successful, jump, otherwise, check the next 32 bits.
680            sock_filter { code: BPF_LD_W_MEM, jt: 0, jf: 0, k: 0 },
681            sock_filter { code: BPF_JSET_K, jt: 2, jf: 0, k: 4294967295 },
682            sock_filter { code: BPF_LD_W_MEM, jt: 0, jf: 0, k: 1 },
683            sock_filter { code: BPF_JSET_K, jt: 0, jf: 1, k: 4294967292 },
684            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_TRAP },
685            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW },
686        ];
687
688        let prg =
689            convert_and_link_cbpf::<TestProgramContext>(&test_prg).expect("Error parsing program");
690
691        with_prg_assert_result(
692            &prg,
693            seccomp_data { arch: AUDIT_ARCH_AARCH64, ..Default::default() },
694            1,
695            "Did not reject incorrect arch",
696        );
697
698        with_prg_assert_result(
699            &prg,
700            seccomp_data { arch: AUDIT_ARCH_X86_64, nr: 41, ..Default::default() },
701            SECCOMP_RET_ALLOW,
702            "Did not pass simple RET_ALLOW",
703        );
704
705        with_prg_assert_result(
706            &prg,
707            seccomp_data {
708                arch: AUDIT_ARCH_X86_64,
709                nr: 100,
710                args: [0xFF00000000, 0, 0, 0, 0, 0],
711                ..Default::default()
712            },
713            SECCOMP_RET_TRAP,
714            "Did not treat load of first 32 bits correctly",
715        );
716
717        with_prg_assert_result(
718            &prg,
719            seccomp_data {
720                arch: AUDIT_ARCH_X86_64,
721                nr: 100,
722                args: [0x4, 0, 0, 0, 0, 0],
723                ..Default::default()
724            },
725            SECCOMP_RET_TRAP,
726            "Did not correctly reject load of second 32 bits",
727        );
728
729        with_prg_assert_result(
730            &prg,
731            seccomp_data {
732                arch: AUDIT_ARCH_X86_64,
733                nr: 100,
734                args: [0x0, 0, 0, 0, 0, 0],
735                ..Default::default()
736            },
737            SECCOMP_RET_ALLOW,
738            "Did not correctly accept load of second 32 bits",
739        );
740    }
741
742    #[test]
743    fn test_alu_insns() {
744        {
745            let test_prg = [
746                // Load data.nr (the syscall number)
747                sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 0 }, // = 1, 11
748                // Do some math.
749                sock_filter { code: BPF_ALU_ADD_K, jt: 0, jf: 0, k: 3 }, // = 4, 14
750                sock_filter { code: BPF_ALU_SUB_K, jt: 0, jf: 0, k: 2 }, // = 2, 12
751                sock_filter { code: BPF_MISC_TAX, jt: 0, jf: 0, k: 0 },  // 2, 12 -> X
752                sock_filter { code: BPF_ALU_MUL_K, jt: 0, jf: 0, k: 8 }, // = 16, 96
753                sock_filter { code: BPF_ALU_DIV_K, jt: 0, jf: 0, k: 2 }, // = 8, 48
754                sock_filter { code: BPF_ALU_AND_K, jt: 0, jf: 0, k: 15 }, // = 8, 0
755                sock_filter { code: BPF_ALU_OR_K, jt: 0, jf: 0, k: 16 }, // = 24, 16
756                sock_filter { code: BPF_ALU_XOR_K, jt: 0, jf: 0, k: 7 }, // = 31, 23
757                sock_filter { code: BPF_ALU_LSH_K, jt: 0, jf: 0, k: 2 }, // = 124, 92
758                sock_filter { code: BPF_ALU_OR_X, jt: 0, jf: 0, k: 1 },  // = 127, 92
759                sock_filter { code: BPF_ALU_RSH_K, jt: 0, jf: 0, k: 1 }, // = 63, 46
760                sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
761            ];
762
763            let prg = convert_and_link_cbpf::<TestProgramContext>(&test_prg)
764                .expect("Error parsing program");
765
766            with_prg_assert_result(
767                &prg,
768                seccomp_data { nr: 1, ..Default::default() },
769                63,
770                "BPF math does not work",
771            );
772
773            with_prg_assert_result(
774                &prg,
775                seccomp_data { nr: 11, ..Default::default() },
776                46,
777                "BPF math does not work",
778            );
779        }
780
781        {
782            // Negative numbers simple check
783            let test_prg = [
784                // Load data.nr (the syscall number)
785                sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 0 }, // = -1
786                sock_filter { code: BPF_ALU_SUB_K, jt: 0, jf: 0, k: 2 }, // = -3
787                sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
788            ];
789
790            let prg = convert_and_link_cbpf::<TestProgramContext>(&test_prg)
791                .expect("Error parsing program");
792
793            with_prg_assert_result(
794                &prg,
795                seccomp_data { nr: -1, ..Default::default() },
796                u32::MAX - 2,
797                "BPF math does not work",
798            );
799        }
800    }
801
802    // Test BPF_MSH cBPF instruction.
803    #[test]
804    fn test_ld_msh() {
805        let test_prg = [
806            // X <- 4 * (P[0] & 0xf)
807            sock_filter { code: (BPF_LDX | BPF_MSH | BPF_B) as u16, jt: 0, jf: 0, k: 0 },
808            // A <- X
809            sock_filter { code: (BPF_MISC | BPF_TXA) as u16, jt: 0, jf: 0, k: 0 },
810            // ret A
811            sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
812        ];
813
814        let prg =
815            convert_and_link_cbpf::<TestProgramContext>(&test_prg).expect("Error parsing program");
816
817        for i in [0x00, 0x01, 0x07, 0x15, 0xff].iter() {
818            with_prg_assert_result(
819                &prg,
820                seccomp_data { nr: *i, ..Default::default() },
821                4 * (*i & 0xf) as u32,
822                "BPF math does not work",
823            )
824        }
825    }
826
827    #[test]
828    fn test_static_packet_len() {
829        let test_prg = [
830            // A <- packet_len
831            sock_filter { code: (BPF_LD | BPF_LEN | BPF_W) as u16, jt: 0, jf: 0, k: 0 },
832            // ret A
833            sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
834        ];
835
836        let prg =
837            convert_and_link_cbpf::<TestProgramContext>(&test_prg).expect("Error parsing program");
838
839        let data = seccomp_data::default();
840        assert_eq!(prg.run(&mut (), &data), size_of::<seccomp_data>() as u64);
841    }
842
843    // A packet used by `test_variable_packet_len()` below to verify the case when the packet
844    // length is stored as a struct field.
845    #[repr(C)]
846    #[derive(Debug, Default, IntoBytes, Immutable, KnownLayout, FromBytes)]
847    struct VariableLengthPacket {
848        foo: u32,
849        len: i32,
850        bar: u64,
851    }
852
853    static VARIABLE_LENGTH_PACKET_TYPE: LazyLock<Type> = LazyLock::new(|| Type::PtrToMemory {
854        id: MemoryId::new(),
855        offset: 0.into(),
856        buffer_size: size_of::<VariableLengthPacket>() as u64,
857    });
858
859    impl ProgramArgument for &'_ VariableLengthPacket {
860        fn get_type() -> &'static Type {
861            &*VARIABLE_LENGTH_PACKET_TYPE
862        }
863    }
864
865    pub const VARIABLE_LENGTH_CBPF_CONFIG: CbpfConfig = CbpfConfig {
866        len: CbpfLenInstruction::ContextField {
867            offset: offset_of!(VariableLengthPacket, len) as i16,
868        },
869        allow_msh: true,
870    };
871
872    struct VariableLengthPacketContext {}
873
874    impl BpfProgramContext for VariableLengthPacketContext {
875        type RunContext<'a> = ();
876        type Packet<'a> = &'a VariableLengthPacket;
877        type Map = NoMap;
878        const CBPF_CONFIG: &'static CbpfConfig = &VARIABLE_LENGTH_CBPF_CONFIG;
879    }
880
881    empty_static_helper_set!(VariableLengthPacketContext);
882
883    #[test]
884    fn test_variable_packet_len() {
885        let test_prg = [
886            // A <- packet_len
887            sock_filter { code: (BPF_LD | BPF_LEN | BPF_W) as u16, jt: 0, jf: 0, k: 0 },
888            // ret A
889            sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
890        ];
891
892        let prg = convert_and_link_cbpf::<VariableLengthPacketContext>(&test_prg)
893            .expect("Error parsing program");
894        let data = VariableLengthPacket { len: 42, ..VariableLengthPacket::default() };
895        assert_eq!(prg.run(&mut (), &data), data.len as u64);
896    }
897}