bpfvm/
asm.rs

1/*
2 * Copyright © 2022, Steve Smith <tarkasteve@gmail.com>
3 *
4 * Permission to use, copy, modify, and/or distribute this software for
5 * any purpose with or without fee is hereby granted, provided that the
6 * above copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
11 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
13 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18use std::collections::HashMap;
19
20use libc::sock_filter;
21use crate::bpf::*;
22use crate::errors::{Error, Result};
23
24
25#[derive(Eq, PartialEq, Debug)]
26pub enum Operation<'a> {
27    Label(&'a str),
28    Load(Mode, u32),
29    LoadIdx(Mode, u32),
30    Store(Mode, u32),
31    StoreIdx(Mode, u32),
32    Alu(AluOp, Src, u32),
33    Return(Src, u32),
34    JumpTo(&'a str), // Special case JMP_JA
35    Jump(JmpOp, u32, Option<&'a str>, Option<&'a str>),
36}
37use Operation::*;
38
39type Program<'a> = [Operation<'a>];
40
41
42fn map_labels<'a>(prog: &'a Program) -> Result<HashMap<&'a str, usize>> {
43    // FIXME: If I get bored, convert this to an iter workflow.
44    let mut line = 0;
45    let mut labels: HashMap<&'a str, usize> = HashMap::new();
46    for op in prog {
47        match op {
48            Label(l) => {
49                labels.insert(l, line);
50            },
51            _ => {
52                line += 1;
53            }
54        }
55    }
56
57    Ok(labels)
58}
59
60fn jmp_calc(labels: &HashMap<&str, usize>, label: &Option<&str>, curr: usize) -> Result<usize> {
61
62    let jmp_off = match label {
63        Some(l) => {
64            let off = labels.get(l)
65                .ok_or(Error::UnknownLabelReference(l.to_string()))?;
66            *off - curr - 1
67        },
68        None => 0,
69    };
70    Ok(jmp_off)
71}
72
73fn to_sock_filter(linenum: usize, op: &Operation, labels: &HashMap<&str, usize>) -> Result<sock_filter> {
74    let sf = match op {
75        JumpTo(l) => {
76            let targetline = jmp_calc(labels, &Some(l), linenum)?;
77            bpf_jmp(JmpOp::JA, targetline as u32, 0, 0)
78        },
79        Jump(op, cmp, ltrue, lfalse) => {
80            let lt = jmp_calc(labels, ltrue, linenum)?;
81            let lf = jmp_calc(labels, lfalse, linenum)?;
82            bpf_jmp(*op, *cmp, lt as u8, lf as u8)
83        },
84        Load(mode, val) => bpf_ld(*mode, *val),
85        LoadIdx(mode, val) => bpf_ldx(*mode, *val),
86        Store(mode, val) => bpf_st(*mode, *val),
87        StoreIdx(mode, val) => bpf_stx(*mode, *val),
88        Alu(op, src, val) => bpf_alu(*op, *src, *val),
89        Return(src, val) => bpf_ret(*src, *val),
90        Label(l) => return Err(Error::UnknownLabelReference(l.to_string())),
91    };
92
93    Ok(sf)
94}
95
96
97pub fn compile(prog: &Program) -> Result<Vec<sock_filter>> {
98    let labels = map_labels(prog)?;
99
100    // TODO: We should probably do forward-only jump checks,
101    // etc. here.
102
103    let opcodes = prog.into_iter()
104        .filter(|op| !matches!(op, Label(_)))
105        .enumerate()
106        .map(|(linenum, op)| to_sock_filter(linenum, op, &labels))
107        .collect();
108
109    opcodes
110}
111
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use libc;
117    use test_log;
118    use crate::vm::{any_to_data, BpfVM};
119
120    #[test_log::test]
121    fn test_simple_jump() {
122        let asm = vec![
123            JumpTo("FAIL"),
124            Label("OK"), Return(Src::Const, 0),
125            Label("FAIL"), Return(Src::Const, 99),
126        ];
127        let prog = compile(&asm).unwrap();
128
129        let mut vm = BpfVM::new(&prog).unwrap();
130        let data = vec![];
131        let ret = vm.run(&data).unwrap();
132        assert!(ret == 99);
133    }
134
135    #[test_log::test]
136    fn test_ld_gt_ret() {
137        let asm = vec![
138            Load(Mode::IMM, 99),
139            Jump(JmpOp::JGT, 98, Some("ret_acc"), Some("ret999")),
140            // Should skip this one
141            Label("ret999"),
142            Load(Mode::IMM, 999),
143            Label("ret_acc"),
144            Return(Src::Acc, 0),
145        ];
146        let prog = compile(&asm).unwrap();
147
148        let mut vm = BpfVM::new(&prog).unwrap();
149        let data = vec![];
150        let ret = vm.run(&data).unwrap();
151        assert!(ret == 99);
152    }
153
154
155    // Complex case from Cosmopolitan pledge() impl:
156    //
157    // The family parameter of socket() must be one of:
158    //
159    //   - AF_INET  (0x02)
160    //   - AF_INET6 (0x0a)
161    //
162    // The type parameter of socket() will ignore:
163    //
164    //   - SOCK_CLOEXEC  (0x80000)
165    //   - SOCK_NONBLOCK (0x00800)
166    //
167    // The type parameter of socket() must be one of:
168    //
169    //   - SOCK_STREAM (0x01)
170    //   - SOCK_DGRAM  (0x02)
171    //
172    // The protocol parameter of socket() must be one of:
173    //
174    //   - 0
175    //   - IPPROTO_ICMP (0x01)
176    //   - IPPROTO_TCP  (0x06)
177    //   - IPPROTO_UDP  (0x11)
178    //
179    #[ignore]
180    #[test_log::test]
181    fn test_cosmo_socket_filter() {
182        use JmpOp::*;
183        use Mode::*;
184        use Src::*;
185        use crate::seccomp::FieldOffset::*;
186
187        let asm = vec![
188            Load(ABS, Syscall.offset()),
189            Jump(JEQ, libc::SYS_socket as u32, None, Some("REJECT")),
190
191            // The family parameter of socket() must be one of:
192            //
193            //   - AF_INET  (0x02)
194            //   - AF_INET6 (0x0a)
195            //
196            Load(ABS, ArgLower(0).offset()),
197            Jump(JEQ, libc::AF_INET as u32, Some("Type_Check"), None),
198            Jump(JEQ, libc::AF_INET6 as u32, Some("Type_Check"), Some("REJECT")),
199
200            // The type parameter of socket() will ignore:
201            //
202            //   - SOCK_CLOEXEC  (0x80000)
203            //   - SOCK_NONBLOCK (0x00800)
204            //
205            // The type parameter of socket() must be one of:
206            //
207            //   - SOCK_STREAM (0x01)
208            //   - SOCK_DGRAM  (0x02)
209            //
210            Label("Type_Check"),
211            Load(ABS, ArgLower(1).offset()),
212            Alu(AluOp::AND, Const, !0x80800),
213            Jump(JEQ, libc::SOCK_STREAM as u32, Some("Proto_Check"), None),
214            Jump(JEQ, libc::SOCK_DGRAM as u32, Some("Proto_Check"), Some("REJECT")),
215
216            // The protocol parameter of socket() must be one of:
217            //
218            //   - 0
219            //   - IPPROTO_ICMP (0x01)
220            //   - IPPROTO_TCP  (0x06)
221            //   - IPPROTO_UDP  (0x11)
222            //
223            Label("Proto_Check"),
224            Load(ABS, ArgLower(1).offset()),
225            Jump(JEQ, 0, Some("ALLOW"), None),
226            Jump(JEQ, libc::IPPROTO_ICMP as u32, Some("ALLOW"), None),
227            Jump(JEQ, libc::IPPROTO_TCP as u32, Some("ALLOW"), None),
228            Jump(JEQ, libc::IPPROTO_UDP as u32, Some("ALLOW"), None),
229
230            JumpTo("REJECT"),
231
232            Label("ALLOW"),
233            Return(Const, 0),
234
235            Label("REJECT"),
236            Return(Const, 99),
237        ];
238        let prog = compile(&asm).unwrap();
239        let mut vm = BpfVM::new(&prog).unwrap();
240
241        let sc_data = libc::seccomp_data {
242            nr: libc::SYS_open as i32,
243            arch: 0,
244            instruction_pointer: 0,
245            args: [0; 6],
246        };
247        let data = any_to_data(&sc_data);
248        let ret = vm.run(&data).unwrap();
249        assert!(ret == 99);
250
251        let sc_data = libc::seccomp_data {
252            nr: libc::SYS_socket as i32,
253            arch: 0,
254            instruction_pointer: 0,
255            args: [
256                libc::AF_INET as u64,
257                (libc::SOCK_STREAM | libc::SOCK_NONBLOCK) as u64,
258                libc::IPPROTO_TCP as u64,
259                0, 0, 0
260            ],
261        };
262        let data = any_to_data(&sc_data);
263        let ret = vm.run(&data).unwrap();
264        assert!(ret == 0);
265    }
266
267}