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(InvalidCbpfInstruction(bpf_instruction.code));
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(InvalidCbpfInstruction(bpf_instruction.code));
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    Ok(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, PointerOrigin, 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_ALU | BPF_MOV | BPF_K, 9, 0, 0, 0),
494                EbpfInstruction::new(BPF_JMP | BPF_JA, 0, 0, 0, 0),
495                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
496            ]),
497        );
498
499        // Jump after last instruction.
500        assert_eq!(
501            cbpf_to_ebpf(
502                &vec![
503                    sock_filter { code: (BPF_JMP | BPF_JA) as u16, jt: 0, jf: 0, k: 1 },
504                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
505                ],
506                &TEST_CBPF_CONFIG
507            ),
508            Err(EbpfError::InvalidCbpfJumpOffset(1)),
509        );
510
511        // Jump out of bounds.
512        assert_eq!(
513            cbpf_to_ebpf(
514                &vec![sock_filter { code: (BPF_JMP | BPF_JA) as u16, jt: 0, jf: 0, k: 0xffffffff }],
515                &TEST_CBPF_CONFIG
516            ),
517            Err(EbpfError::InvalidCbpfJumpOffset(0xffffffff)),
518        );
519
520        // BPF_JNE is allowed only in eBPF.
521        assert_eq!(
522            cbpf_to_ebpf(
523                &vec![
524                    sock_filter { code: (BPF_JMP | BPF_JNE) as u16, jt: 0, jf: 0, k: 0 },
525                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
526                ],
527                &TEST_CBPF_CONFIG
528            ),
529            Err(EbpfError::InvalidCbpfInstruction((BPF_JMP | BPF_JNE) as u16)),
530        );
531
532        // BPF_JEQ is supported in BPF.
533        assert_eq!(
534            cbpf_to_ebpf(
535                &vec![
536                    sock_filter { code: (BPF_JMP | BPF_JEQ) as u16, jt: 1, jf: 0, k: 0 },
537                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
538                    sock_filter { code: (BPF_RET | BPF_A) as u16, jt: 0, jf: 0, k: 0 },
539                ],
540                &TEST_CBPF_CONFIG
541            ),
542            Ok(vec![
543                EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, 6, 1, 0, 0),
544                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, 0),
545                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 9, 0, 0, 0),
546                EbpfInstruction::new(BPF_JMP32 | BPF_JEQ, 0, 0, 1, 0),
547                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
548                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
549            ]),
550        );
551
552        // Make sure the jump is translated correctly when the jump target produces 2 instructions.
553        assert_eq!(
554            cbpf_to_ebpf(
555                &vec![
556                    sock_filter { code: (BPF_JMP | BPF_JA) as u16, jt: 0, jf: 0, k: 0 },
557                    sock_filter { code: (BPF_RET | BPF_K) as u16, jt: 0, jf: 0, k: 1 },
558                ],
559                &TEST_CBPF_CONFIG
560            ),
561            Ok(vec![
562                EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, 6, 1, 0, 0),
563                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, 0),
564                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 9, 0, 0, 0),
565                EbpfInstruction::new(BPF_JMP | BPF_JA, 0, 0, 0, 0),
566                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_IMM, 0, 0, 0, 1),
567                EbpfInstruction::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),
568            ]),
569        );
570
571        // BPF_MEM access.
572        assert_eq!(
573            cbpf_to_ebpf(
574                &vec![
575                    sock_filter { code: (BPF_LD | BPF_MEM) as u16, jt: 0, jf: 0, k: 0 },
576                    sock_filter { code: (BPF_LDX | BPF_MEM) as u16, jt: 0, jf: 0, k: 15 },
577                    sock_filter { code: BPF_ST as u16, jt: 0, jf: 0, k: 0 },
578                    sock_filter { code: BPF_STX as u16, jt: 0, jf: 0, k: 15 },
579                ],
580                &TEST_CBPF_CONFIG
581            ),
582            Ok(vec![
583                EbpfInstruction::new(BPF_ALU64 | BPF_MOV | BPF_X, 6, 1, 0, 0),
584                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 0, 0, 0, 0),
585                EbpfInstruction::new(BPF_ALU | BPF_MOV | BPF_K, 9, 0, 0, 0),
586                EbpfInstruction::new(BPF_LDX | BPF_MEM | BPF_W, 0, 10, -64, 0),
587                EbpfInstruction::new(BPF_LDX | BPF_MEM | BPF_W, 9, 10, -4, 0),
588                EbpfInstruction::new(BPF_STX | BPF_MEM | BPF_W, 10, 0, -64, 0),
589                EbpfInstruction::new(BPF_STX | BPF_MEM | BPF_W, 10, 9, -4, 0),
590            ]),
591        );
592
593        // BPF_MEM access out of bounds.
594        assert_eq!(
595            cbpf_to_ebpf(
596                &vec![sock_filter { code: (BPF_LD | BPF_MEM) as u16, jt: 0, jf: 0, k: 17 }],
597                &TEST_CBPF_CONFIG
598            ),
599            Err(EbpfError::InvalidCbpfScratchOffset(17)),
600        );
601    }
602
603    const BPF_ALU_ADD_K: u16 = (BPF_ALU | BPF_ADD | BPF_K) as u16;
604    const BPF_ALU_SUB_K: u16 = (BPF_ALU | BPF_SUB | BPF_K) as u16;
605    const BPF_ALU_MUL_K: u16 = (BPF_ALU | BPF_MUL | BPF_K) as u16;
606    const BPF_ALU_DIV_K: u16 = (BPF_ALU | BPF_DIV | BPF_K) as u16;
607    const BPF_ALU_AND_K: u16 = (BPF_ALU | BPF_AND | BPF_K) as u16;
608    const BPF_ALU_OR_K: u16 = (BPF_ALU | BPF_OR | BPF_K) as u16;
609    const BPF_ALU_XOR_K: u16 = (BPF_ALU | BPF_XOR | BPF_K) as u16;
610    const BPF_ALU_LSH_K: u16 = (BPF_ALU | BPF_LSH | BPF_K) as u16;
611    const BPF_ALU_RSH_K: u16 = (BPF_ALU | BPF_RSH | BPF_K) as u16;
612
613    const BPF_ALU_OR_X: u16 = (BPF_ALU | BPF_OR | BPF_X) as u16;
614
615    const BPF_LD_W_ABS: u16 = (BPF_LD | BPF_ABS | BPF_W) as u16;
616    const BPF_LD_W_MEM: u16 = (BPF_LD | BPF_MEM | BPF_W) as u16;
617    const BPF_JEQ_K: u16 = (BPF_JMP | BPF_JEQ | BPF_K) as u16;
618    const BPF_JSET_K: u16 = (BPF_JMP | BPF_JSET | BPF_K) as u16;
619    const BPF_RET_K: u16 = (BPF_RET | BPF_K) as u16;
620    const BPF_RET_A: u16 = (BPF_RET | BPF_A) as u16;
621    const BPF_ST_REG: u16 = BPF_ST as u16;
622    const BPF_MISC_TAX: u16 = (BPF_MISC | BPF_TAX) as u16;
623
624    struct TestProgramContext {}
625
626    impl BpfProgramContext for TestProgramContext {
627        type RunContext<'a> = ();
628        type Packet<'a> = &'a seccomp_data;
629        type Map = NoMap;
630        const CBPF_CONFIG: &'static CbpfConfig = &TEST_CBPF_CONFIG;
631    }
632
633    empty_static_helper_set!(TestProgramContext);
634
635    static SECCOMP_DATA_TYPE: LazyLock<Type> = LazyLock::new(|| Type::PtrToMemory {
636        id: MemoryId::new(),
637        offset: 0.into(),
638        buffer_size: 0,
639        origin: PointerOrigin::KernelReadOnly,
640    });
641
642    impl ProgramArgument for &'_ seccomp_data {
643        fn get_type() -> &'static Type {
644            &*SECCOMP_DATA_TYPE
645        }
646    }
647
648    fn with_prg_assert_result(
649        prg: &EbpfProgram<TestProgramContext>,
650        mut data: seccomp_data,
651        result: u32,
652        msg: &str,
653    ) {
654        let return_value = prg.run(&mut (), &mut data);
655        assert_eq!(return_value, result as u64, "{}: filter return value is {}", msg, return_value);
656    }
657
658    #[test]
659    fn test_filter_with_dw_load() {
660        let test_prg = [
661            // Check data.arch
662            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 4 },
663            sock_filter { code: BPF_JEQ_K, jt: 1, jf: 0, k: AUDIT_ARCH_X86_64 },
664            // Return 1 if arch is wrong
665            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: 1 },
666            // Load data.nr (the syscall number)
667            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 0 },
668            // Always allow 41
669            sock_filter { code: BPF_JEQ_K, jt: 0, jf: 1, k: 41 },
670            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW },
671            // Don't allow 115
672            sock_filter { code: BPF_JEQ_K, jt: 0, jf: 1, k: 115 },
673            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_TRAP },
674            // For other syscalls, check the args
675            // A common hack to deal with 64-bit numbers in BPF: deal
676            // with 32 bits at a time.
677            // First, Load arg0's most significant 32 bits in M[0]
678            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 16 },
679            sock_filter { code: BPF_ST_REG, jt: 0, jf: 0, k: 0 },
680            // Load arg0's least significant 32 bits into M[1]
681            sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 20 },
682            sock_filter { code: BPF_ST_REG, jt: 0, jf: 0, k: 1 },
683            // JSET is A & k.  Check the first 32 bits.  If the test
684            // is successful, jump, otherwise, check the next 32 bits.
685            sock_filter { code: BPF_LD_W_MEM, jt: 0, jf: 0, k: 0 },
686            sock_filter { code: BPF_JSET_K, jt: 2, jf: 0, k: 4294967295 },
687            sock_filter { code: BPF_LD_W_MEM, jt: 0, jf: 0, k: 1 },
688            sock_filter { code: BPF_JSET_K, jt: 0, jf: 1, k: 4294967292 },
689            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_TRAP },
690            sock_filter { code: BPF_RET_K, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW },
691        ];
692
693        let prg =
694            convert_and_link_cbpf::<TestProgramContext>(&test_prg).expect("Error parsing program");
695
696        with_prg_assert_result(
697            &prg,
698            seccomp_data { arch: AUDIT_ARCH_AARCH64, ..Default::default() },
699            1,
700            "Did not reject incorrect arch",
701        );
702
703        with_prg_assert_result(
704            &prg,
705            seccomp_data { arch: AUDIT_ARCH_X86_64, nr: 41, ..Default::default() },
706            SECCOMP_RET_ALLOW,
707            "Did not pass simple RET_ALLOW",
708        );
709
710        with_prg_assert_result(
711            &prg,
712            seccomp_data {
713                arch: AUDIT_ARCH_X86_64,
714                nr: 100,
715                args: [0xFF00000000, 0, 0, 0, 0, 0],
716                ..Default::default()
717            },
718            SECCOMP_RET_TRAP,
719            "Did not treat load of first 32 bits correctly",
720        );
721
722        with_prg_assert_result(
723            &prg,
724            seccomp_data {
725                arch: AUDIT_ARCH_X86_64,
726                nr: 100,
727                args: [0x4, 0, 0, 0, 0, 0],
728                ..Default::default()
729            },
730            SECCOMP_RET_TRAP,
731            "Did not correctly reject load of second 32 bits",
732        );
733
734        with_prg_assert_result(
735            &prg,
736            seccomp_data {
737                arch: AUDIT_ARCH_X86_64,
738                nr: 100,
739                args: [0x0, 0, 0, 0, 0, 0],
740                ..Default::default()
741            },
742            SECCOMP_RET_ALLOW,
743            "Did not correctly accept load of second 32 bits",
744        );
745    }
746
747    #[test]
748    fn test_alu_insns() {
749        {
750            let test_prg = [
751                // Load data.nr (the syscall number)
752                sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 0 }, // = 1, 11
753                // Do some math.
754                sock_filter { code: BPF_ALU_ADD_K, jt: 0, jf: 0, k: 3 }, // = 4, 14
755                sock_filter { code: BPF_ALU_SUB_K, jt: 0, jf: 0, k: 2 }, // = 2, 12
756                sock_filter { code: BPF_MISC_TAX, jt: 0, jf: 0, k: 0 },  // 2, 12 -> X
757                sock_filter { code: BPF_ALU_MUL_K, jt: 0, jf: 0, k: 8 }, // = 16, 96
758                sock_filter { code: BPF_ALU_DIV_K, jt: 0, jf: 0, k: 2 }, // = 8, 48
759                sock_filter { code: BPF_ALU_AND_K, jt: 0, jf: 0, k: 15 }, // = 8, 0
760                sock_filter { code: BPF_ALU_OR_K, jt: 0, jf: 0, k: 16 }, // = 24, 16
761                sock_filter { code: BPF_ALU_XOR_K, jt: 0, jf: 0, k: 7 }, // = 31, 23
762                sock_filter { code: BPF_ALU_LSH_K, jt: 0, jf: 0, k: 2 }, // = 124, 92
763                sock_filter { code: BPF_ALU_OR_X, jt: 0, jf: 0, k: 1 },  // = 127, 92
764                sock_filter { code: BPF_ALU_RSH_K, jt: 0, jf: 0, k: 1 }, // = 63, 46
765                sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
766            ];
767
768            let prg = convert_and_link_cbpf::<TestProgramContext>(&test_prg)
769                .expect("Error parsing program");
770
771            with_prg_assert_result(
772                &prg,
773                seccomp_data { nr: 1, ..Default::default() },
774                63,
775                "BPF math does not work",
776            );
777
778            with_prg_assert_result(
779                &prg,
780                seccomp_data { nr: 11, ..Default::default() },
781                46,
782                "BPF math does not work",
783            );
784        }
785
786        {
787            // Negative numbers simple check
788            let test_prg = [
789                // Load data.nr (the syscall number)
790                sock_filter { code: BPF_LD_W_ABS, jt: 0, jf: 0, k: 0 }, // = -1
791                sock_filter { code: BPF_ALU_SUB_K, jt: 0, jf: 0, k: 2 }, // = -3
792                sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
793            ];
794
795            let prg = convert_and_link_cbpf::<TestProgramContext>(&test_prg)
796                .expect("Error parsing program");
797
798            with_prg_assert_result(
799                &prg,
800                seccomp_data { nr: -1, ..Default::default() },
801                u32::MAX - 2,
802                "BPF math does not work",
803            );
804        }
805    }
806
807    // Test BPF_MSH cBPF instruction.
808    #[test]
809    fn test_ld_msh() {
810        let test_prg = [
811            // X <- 4 * (P[0] & 0xf)
812            sock_filter { code: (BPF_LDX | BPF_MSH | BPF_B) as u16, jt: 0, jf: 0, k: 0 },
813            // A <- X
814            sock_filter { code: (BPF_MISC | BPF_TXA) as u16, jt: 0, jf: 0, k: 0 },
815            // ret A
816            sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
817        ];
818
819        let prg =
820            convert_and_link_cbpf::<TestProgramContext>(&test_prg).expect("Error parsing program");
821
822        for i in [0x00, 0x01, 0x07, 0x15, 0xff].iter() {
823            with_prg_assert_result(
824                &prg,
825                seccomp_data { nr: *i, ..Default::default() },
826                4 * (*i & 0xf) as u32,
827                "BPF math does not work",
828            )
829        }
830    }
831
832    #[test]
833    fn test_static_packet_len() {
834        let test_prg = [
835            // A <- packet_len
836            sock_filter { code: (BPF_LD | BPF_LEN | BPF_W) as u16, jt: 0, jf: 0, k: 0 },
837            // ret A
838            sock_filter { code: BPF_RET_A, jt: 0, jf: 0, k: 0 },
839        ];
840
841        let prg =
842            convert_and_link_cbpf::<TestProgramContext>(&test_prg).expect("Error parsing program");
843
844        let data = seccomp_data::default();
845        assert_eq!(prg.run(&mut (), &data), size_of::<seccomp_data>() as u64);
846    }
847
848    // A packet used by `test_variable_packet_len()` below to verify the case when the packet
849    // length is stored as a struct field.
850    #[repr(C)]
851    #[derive(Debug, Default, IntoBytes, Immutable, KnownLayout, FromBytes)]
852    struct VariableLengthPacket {
853        foo: u32,
854        len: i32,
855        bar: u64,
856    }
857
858    static VARIABLE_LENGTH_PACKET_TYPE: LazyLock<Type> = LazyLock::new(|| Type::PtrToMemory {
859        id: MemoryId::new(),
860        offset: 0.into(),
861        buffer_size: size_of::<VariableLengthPacket>() as u64,
862        origin: PointerOrigin::KernelReadOnly,
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}