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