authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-04-28 18:54:09+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-04-28 18:54:09+03:00
log6d48600ea099a0ac3e99a3970dd79f989f3f9b84
tree4fdf850229194701c2e748103f43c3596ad2047b
parent75c9936737a6ba991d4ef187ddc9d51bc0ad0998
parentee32d11252b5558fe156e40874c78fb3bfe38566
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #10717 from gh-fork-dump/seccomp-bits

Add Seccomp bits for linux

6 files changed, 1285 insertions(+), 1 deletions(-)

lib/std/elf.zig+6
...@@ -1482,6 +1482,12 @@ pub const EM = enum(u16) {...@@ -1482,6 +1482,12 @@ pub const EM = enum(u16) {
1482 /// Linux kernel bpf virtual machine1482 /// Linux kernel bpf virtual machine
1483 _BPF = 247,1483 _BPF = 247,
14841484
1485 /// C-SKY
1486 _CSKY = 252,
1487
1488 /// Fujitsu FR-V
1489 _FRV = 0x5441,
1490
1485 _,1491 _,
14861492
1487 pub fn toTargetCpuArch(em: EM) ?std.Target.Cpu.Arch {1493 pub fn toTargetCpuArch(em: EM) ?std.Target.Cpu.Arch {
lib/std/os/linux.zig+57
...@@ -91,6 +91,7 @@ pub const tls = @import("linux/tls.zig");...@@ -91,6 +91,7 @@ pub const tls = @import("linux/tls.zig");
91pub const pie = @import("linux/start_pie.zig");91pub const pie = @import("linux/start_pie.zig");
92pub const BPF = @import("linux/bpf.zig");92pub const BPF = @import("linux/bpf.zig");
93pub const IOCTL = @import("linux/ioctl.zig");93pub const IOCTL = @import("linux/ioctl.zig");
94pub const SECCOMP = @import("linux/seccomp.zig");
9495
95pub const MAP = struct {96pub const MAP = struct {
96 pub usingnamespace arch_bits.MAP;97 pub usingnamespace arch_bits.MAP;
...@@ -1691,6 +1692,10 @@ pub fn perf_event_open(...@@ -1691,6 +1692,10 @@ pub fn perf_event_open(
1691 );1692 );
1692}1693}
16931694
1695pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize {
1696 return syscall3(.seccomp, operation, flags, @ptrToInt(args));
1697}
1698
1694pub const E = switch (native_arch) {1699pub const E = switch (native_arch) {
1695 .mips, .mipsel => @import("linux/errno/mips.zig").E,1700 .mips, .mipsel => @import("linux/errno/mips.zig").E,
1696 .sparc, .sparcel, .sparcv9 => @import("linux/errno/sparc.zig").E,1701 .sparc, .sparcel, .sparcv9 => @import("linux/errno/sparc.zig").E,
...@@ -5409,3 +5414,55 @@ pub const PERF = struct {...@@ -5409,3 +5414,55 @@ pub const PERF = struct {
54095414
5410 pub const IOC_FLAG_GROUP = 1;5415 pub const IOC_FLAG_GROUP = 1;
5411};5416};
5417
5418// TODO: Add the rest of the AUDIT defines?
5419pub const AUDIT = struct {
5420 pub const ARCH = enum(u32) {
5421 const _64BIT = 0x80000000;
5422 const _LE = 0x40000000;
5423
5424 pub const current = switch (native_arch) {
5425 .i386 => .I386,
5426 .x86_64 => .X86_64,
5427 .aarch64 => .AARCH64,
5428 .arm, .thumb => .ARM,
5429 .riscv64 => .RISCV64,
5430 .sparcv9 => .SPARC64,
5431 .mips => .MIPS,
5432 .mipsel => .MIPSEL,
5433 .powerpc => .PPC,
5434 .powerpc64 => .PPC64,
5435 .powerpc64le => .PPC64LE,
5436 else => undefined,
5437 };
5438
5439 AARCH64 = toAudit(.aarch64),
5440 ARM = toAudit(.arm),
5441 ARMEB = toAudit(.armeb),
5442 CSKY = toAudit(.csky),
5443 HEXAGON = @enumToInt(std.elf.EM._HEXAGON),
5444 I386 = toAudit(.i386),
5445 M68K = toAudit(.m68k),
5446 MIPS = toAudit(.mips),
5447 MIPSEL = toAudit(.mips) | _LE,
5448 MIPS64 = toAudit(.mips64),
5449 MIPSEL64 = toAudit(.mips64) | _LE,
5450 PPC = toAudit(.powerpc),
5451 PPC64 = toAudit(.powerpc64),
5452 PPC64LE = toAudit(.powerpc64le),
5453 RISCV32 = toAudit(.riscv32),
5454 RISCV64 = toAudit(.riscv64),
5455 S390X = toAudit(.s390x),
5456 SPARC = toAudit(.sparc),
5457 SPARC64 = toAudit(.sparcv9),
5458 X86_64 = toAudit(.x86_64),
5459
5460 fn toAudit(arch: std.Target.Cpu.Arch) u32 {
5461 var res: u32 = @enumToInt(arch.toElfMachine());
5462 if (arch.endian() == .Little) res |= _LE;
5463 if (arch.ptrBitWidth() == 64) res |= _64BIT;
5464
5465 return res;
5466 }
5467 };
5468};
lib/std/os/linux/seccomp.zig created+212
...@@ -0,0 +1,212 @@
1//! API bits for the Secure Computing facility in the Linux kernel, which allows
2//! processes to restrict access to the system call API.
3//!
4//! Seccomp started life with a single "strict" mode, which only allowed calls
5//! to read(2), write(2), _exit(2) and sigreturn(2). It turns out that this
6//! isn't that useful for general-purpose applications, and so a mode that
7//! utilizes user-supplied filters mode was added.
8//!
9//! Seccomp filters are classic BPF programs, which means that all the
10//! information under `std.x.net.bpf` applies here as well. Conceptually, a
11//! seccomp program is attached to the kernel and is executed on each syscall.
12//! The "packet" being validated is the `data` structure, and the verdict is an
13//! action that the kernel performs on the calling process. The actions are
14//! variations on a "pass" or "fail" result, where a pass allows the syscall to
15//! continue and a fail blocks the syscall and returns some sort of error value.
16//! See the full list of actions under ::RET for more information. Finally, only
17//! word-sized, absolute loads (`ld [k]`) are supported to read from the `data`
18//! structure.
19//!
20//! There are some issues with the filter API that have traditionally made
21//! writing them a pain:
22//!
23//! 1. Each CPU architecture supported by Linux has its own unique ABI and
24//! syscall API. It is not guaranteed that the syscall numbers and arguments
25//! are the same across architectures, or that they're even implemted. Thus,
26//! filters cannot be assumed to be portable without consulting documentation
27//! like syscalls(2) and testing on target hardware. This also requires
28//! checking the value of `data.arch` to make sure that a filter was compiled
29//! for the correct architecture.
30//! 2. Many syscalls take an `unsigned long` or `size_t` argument, the size of
31//! which is dependant on the ABI. Since BPF programs execute in a 32-bit
32//! machine, validation of 64-bit arguments necessitates two load-and-compare
33//! instructions for the upper and lower words.
34//! 3. A further wrinkle to the above is endianess. Unlike network packets,
35//! syscall data shares the endianess of the target machine. A filter
36//! compiled on a little-endian machine will not work on a big-endian one,
37//! and vice-versa. For example: Checking the upper 32-bits of `data.arg1`
38//! requires a load at `@offsetOf(data, "arg1") + 4` on big-endian systems
39//! and `@offsetOf(data, "arg1")` on little-endian systems. Endian-portable
40//! filters require adjusting these offsets at compile time, similar to how
41//! e.g. OpenSSH does[1].
42//! 4. Syscalls with userspace implementations via the vDSO cannot be traced or
43//! filtered. The vDSO can be disabled or just ignored, which must be taken
44//! into account when writing filters.
45//! 5. Software libraries - especially dynamically loaded ones - tend to use
46//! more of the syscall API over time, thus filters must evolve with them.
47//! Static filters can result in reduced or even broken functionality when
48//! calling newer code from these libraries. This is known to happen with
49//! critical libraries like glibc[2].
50//!
51//! Some of these issues can be mitigated with help from Zig and the standard
52//! library. Since the target CPU is known at compile time, the proper syscall
53//! numbers are mixed into the `os` namespace under `std.os.SYS (see the code
54//! for `arch_bits` in `os/linux.zig`). Referencing an unimplemented syscall
55//! would be a compile error. Endian offsets can also be defined in a similar
56//! manner to the OpenSSH example:
57//!
58//! ```zig
59//! const offset = if (native_endian == .Little) struct {
60//! pub const low = 0;
61//! pub const high = @sizeOf(u32);
62//! } else struct {
63//! pub const low = @sizeOf(u32);
64//! pub const high = 0;
65//! };
66//! ```
67//!
68//! Unfortunately, there is no easy solution for issue 5. The most reliable
69//! strategy is to keep testing; test newer Zig versions, different libcs,
70//! different distros, and design your filter to accomidate all of them.
71//! Alternatively, you could inject a filter at runtime. Since filters are
72//! preserved across execve(2), a filter could be setup before executing your
73//! program, without your program having any knowledge of this happening. This
74//! is the method used by systemd[3] and Cloudflare's sandbox library[4].
75//!
76//! [1]: https://github.com/openssh/openssh-portable/blob/master/sandbox-seccomp-filter.c#L81
77//! [2]: https://sourceware.org/legacy-ml/libc-alpha/2017-11/msg00246.html
78//! [3]: https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=
79//! [4]: https://github.com/cloudflare/sandbox
80//!
81//! See Also
82//! - seccomp(2), seccomp_unotify(2)
83//! - https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html
84const IOCTL = @import("ioctl.zig");
85
86// Modes for the prctl(2) form `prctl(PR_SET_SECCOMP, mode)`
87pub const MODE = struct {
88 /// Seccomp not in use.
89 pub const DISABLED = 0;
90 /// Uses a hard-coded filter.
91 pub const STRICT = 1;
92 /// Uses a user-supplied filter.
93 pub const FILTER = 2;
94};
95
96// Operations for the seccomp(2) form `seccomp(operation, flags, args)`
97pub const SET_MODE_STRICT = 0;
98pub const SET_MODE_FILTER = 1;
99pub const GET_ACTION_AVAIL = 2;
100pub const GET_NOTIF_SIZES = 3;
101
102/// Bitflags for the SET_MODE_FILTER operation.
103pub const FILTER_FLAG = struct {
104 pub const TSYNC = 1 << 0;
105 pub const LOG = 1 << 1;
106 pub const SPEC_ALLOW = 1 << 2;
107 pub const NEW_LISTENER = 1 << 3;
108 pub const TSYNC_ESRCH = 1 << 4;
109};
110
111/// Action values for seccomp BPF programs.
112/// The lower 16-bits are for optional return data.
113/// The upper 16-bits are ordered from least permissive values to most.
114pub const RET = struct {
115 /// Kill the process.
116 pub const KILL_PROCESS = 0x80000000;
117 /// Kill the thread.
118 pub const KILL_THREAD = 0x00000000;
119 pub const KILL = KILL_THREAD;
120 /// Disallow and force a SIGSYS.
121 pub const TRAP = 0x00030000;
122 /// Return an errno.
123 pub const ERRNO = 0x00050000;
124 /// Forward the syscall to a userspace supervisor to make a decision.
125 pub const USER_NOTIF = 0x7fc00000;
126 /// Pass to a tracer or disallow.
127 pub const TRACE = 0x7ff00000;
128 /// Allow after logging.
129 pub const LOG = 0x7ffc0000;
130 /// Allow.
131 pub const ALLOW = 0x7fff0000;
132
133 // Masks for the return value sections.
134 pub const ACTION_FULL = 0xffff0000;
135 pub const ACTION = 0x7fff0000;
136 pub const DATA = 0x0000ffff;
137};
138
139pub const IOCTL_NOTIF = struct {
140 pub const RECV = IOCTL.IOWR('!', 0, notif);
141 pub const SEND = IOCTL.IOWR('!', 1, notif_resp);
142 pub const ID_VALID = IOCTL.IOW('!', 2, u64);
143 pub const ADDFD = IOCTL.IOW('!', 3, notif_addfd);
144};
145
146/// Tells the kernel that the supervisor allows the syscall to continue.
147pub const USER_NOTIF_FLAG_CONTINUE = 1 << 0;
148
149/// See seccomp_unotify(2).
150pub const ADDFD_FLAG = struct {
151 pub const SETFD = 1 << 0;
152 pub const SEND = 1 << 1;
153};
154
155pub const data = extern struct {
156 /// The system call number.
157 nr: c_int,
158 /// The CPU architecture/system call convention.
159 /// One of the values defined in `std.os.linux.AUDIT`.
160 arch: u32,
161 instruction_pointer: u64,
162 arg0: u64,
163 arg1: u64,
164 arg2: u64,
165 arg3: u64,
166 arg4: u64,
167 arg5: u64,
168};
169
170/// Used with the ::GET_NOTIF_SIZES command to check if the kernel structures
171/// have changed.
172pub const notif_sizes = extern struct {
173 /// Size of ::notif.
174 notif: u16,
175 /// Size of ::resp.
176 notif_resp: u16,
177 /// Size of ::data.
178 data: u16,
179};
180
181pub const notif = extern struct {
182 /// Unique notification cookie for each filter.
183 id: u64,
184 /// ID of the thread that triggered the notification.
185 pid: u32,
186 /// Bitmask for event information. Currently set to zero.
187 flags: u32,
188 /// The current system call data.
189 data: data,
190};
191
192/// The decision payload the supervisor process sends to the kernel.
193pub const notif_resp = extern struct {
194 /// The filter cookie.
195 id: u64,
196 /// The return value for a spoofed syscall.
197 val: i64,
198 /// Set to zero for a spoofed success or a negative error number for a
199 /// failure.
200 @"error": i32,
201 /// Bitmask containing the decision. Either USER_NOTIF_FLAG_CONTINUE to
202 /// allow the syscall or zero to spoof the return values.
203 flags: u32,
204};
205
206pub const notif_addfd = extern struct {
207 id: u64,
208 flags: u32,
209 srcfd: u32,
210 newfd: u32,
211 newfd_flags: u32,
212};
lib/std/target.zig+1-1
...@@ -963,7 +963,7 @@ pub const Target = struct {...@@ -963,7 +963,7 @@ pub const Target = struct {
963 .amdgcn => ._NONE,963 .amdgcn => ._NONE,
964 .bpfel => ._BPF,964 .bpfel => ._BPF,
965 .bpfeb => ._BPF,965 .bpfeb => ._BPF,
966 .csky => ._NONE,966 .csky => ._CSKY,
967 .sparcv9 => ._SPARCV9,967 .sparcv9 => ._SPARCV9,
968 .s390x => ._S390,968 .s390x => ._S390,
969 .ve => ._NONE,969 .ve => ._NONE,
lib/std/x.zig+1
...@@ -9,6 +9,7 @@ pub const os = struct {...@@ -9,6 +9,7 @@ pub const os = struct {
9pub const net = struct {9pub const net = struct {
10 pub const ip = @import("x/net/ip.zig");10 pub const ip = @import("x/net/ip.zig");
11 pub const tcp = @import("x/net/tcp.zig");11 pub const tcp = @import("x/net/tcp.zig");
12 pub const bpf = @import("x/net/bpf.zig");
12};13};
1314
14test {15test {
lib/std/x/net/bpf.zig created+1008
...@@ -0,0 +1,1008 @@
1//! This package provides instrumentation for creating Berkeley Packet Filter[1]
2//! (BPF) programs, along with a simulator for running them.
3//!
4//! BPF is a mechanism for cheap, in-kernel packet filtering. Programs are
5//! attached to a network device and executed for every packet that flows
6//! through it. The program must then return a verdict: the amount of packet
7//! bytes that the kernel should copy into userspace. Execution speed is
8//! achieved by having programs run in a limited virtual machine, which has the
9//! added benefit of graceful failure in the face of buggy programs.
10//!
11//! The BPF virtual machine has a 32-bit word length and a small number of
12//! word-sized registers:
13//!
14//! - The accumulator, `a`: The source/destination of arithmetic and logic
15//! operations.
16//! - The index register, `x`: Used as an offset for indirect memory access and
17//! as a comparison value for conditional jumps.
18//! - The scratch memory store, `M[0]..M[15]`: Used for saving the value of a/x
19//! for later use.
20//!
21//! The packet being examined is an array of bytes, and is addressed using plain
22//! array subscript notation, e.g. [10] for the byte at offset 10. An implicit
23//! program counter, `pc`, is intialized to zero and incremented for each instruction.
24//!
25//! The machine has a fixed instruction set with the following form, where the
26//! numbers represent bit length:
27//!
28//! ```
29//! ┌───────────┬──────┬──────┐
30//! │ opcode:16 │ jt:8 │ jt:8 │
31//! ├───────────┴──────┴──────┤
32//! │ k:32 │
33//! └─────────────────────────┘
34//! ```
35//!
36//! The `opcode` indicates the instruction class and its addressing mode.
37//! Opcodes are generated by performing binary addition on the 8-bit class and
38//! mode constants. For example, the opcode for loading a byte from the packet
39//! at X + 2, (`ldb [x + 2]`), is:
40//!
41//! ```
42//! LD | IND | B = 0x00 | 0x40 | 0x20
43//! = 0x60
44//! ```
45//!
46//! `jt` is an offset used for conditional jumps, and increments the program
47//! counter by its amount if the comparison was true. Conversely, `jf`
48//! increments the counter if it was false. These fields are ignored in all
49//! other cases. `k` is a generic variable used for various purposes, most
50//! commonly as some sort of constant.
51//!
52//! This package contains opcode extensions used by different implementations,
53//! where "extension" is anything outside of the original that was imported into
54//! 4.4BSD[2]. These are marked with "EXTENSION", along with a list of
55//! implementations that use them.
56//!
57//! Most of the doc-comments use the BPF assembly syntax as described in the
58//! original paper[1]. For the sake of completeness, here is the complete
59//! instruction set, along with the extensions:
60//!
61//!```
62//! opcode addressing modes
63//! ld #k #len M[k] [k] [x + k]
64//! ldh [k] [x + k]
65//! ldb [k] [x + k]
66//! ldx #k #len M[k] 4 * ([k] & 0xf) arc4random()
67//! st M[k]
68//! stx M[k]
69//! jmp L
70//! jeq #k, Lt, Lf
71//! jgt #k, Lt, Lf
72//! jge #k, Lt, Lf
73//! jset #k, Lt, Lf
74//! add #k x
75//! sub #k x
76//! mul #k x
77//! div #k x
78//! or #k x
79//! and #k x
80//! lsh #k x
81//! rsh #k x
82//! neg #k x
83//! mod #k x
84//! xor #k x
85//! ret #k a
86//! tax
87//! txa
88//! ```
89//!
90//! Finally, a note on program design. The lack of backwards jumps leads to a
91//! "return early, return often" control flow. Take for example the program
92//! generated from the tcpdump filter `ip`:
93//!
94//! ```
95//! (000) ldh [12] ; Ethernet Packet Type
96//! (001) jeq #0x86dd, 2, 7 ; ETHERTYPE_IPV6
97//! (002) ldb [20] ; IPv6 Next Header
98//! (003) jeq #0x6, 10, 4 ; TCP
99//! (004) jeq #0x2c, 5, 11 ; IPv6 Fragment Header
100//! (005) ldb [54] ; TCP Source Port
101//! (006) jeq #0x6, 10, 11 ; IPPROTO_TCP
102//! (007) jeq #0x800, 8, 11 ; ETHERTYPE_IP
103//! (008) ldb [23] ; IPv4 Protocol
104//! (009) jeq #0x6, 10, 11 ; IPPROTO_TCP
105//! (010) ret #262144 ; copy 0x40000
106//! (011) ret #0 ; skip packet
107//! ```
108//!
109//! Here we can make a few observations:
110//!
111//! - The problem "filter only tcp packets" has essentially been transformed
112//! into a series of layer checks.
113//! - There are two distinct branches in the code, one for validating IPv4
114//! headers and one for IPv6 headers.
115//! - Most conditional jumps in these branches lead directly to the last two
116//! instructions, a pass or fail. Thus the goal of a program is to find the
117//! fastest route to a pass/fail comparison.
118//!
119//! [1]: S. McCanne and V. Jacobson, "The BSD Packet Filter: A New Architecture
120//! for User-level Packet Capture", Proceedings of the 1993 Winter USENIX.
121//! [2]: https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/src/sys/net/bpf.h
122const std = @import("std");
123const builtin = @import("builtin");
124const native_endian = builtin.target.cpu.arch.endian();
125const mem = std.mem;
126const math = std.math;
127const random = std.crypto.random;
128const assert = std.debug.assert;
129const expectEqual = std.testing.expectEqual;
130const expectError = std.testing.expectError;
131const expect = std.testing.expect;
132
133// instruction classes
134/// ld, ldh, ldb: Load data into a.
135pub const LD = 0x00;
136/// ldx: Load data into x.
137pub const LDX = 0x01;
138/// st: Store into scratch memory the value of a.
139pub const ST = 0x02;
140/// st: Store into scratch memory the value of x.
141pub const STX = 0x03;
142/// alu: Wrapping arithmetic/bitwise operations on a using the value of k/x.
143pub const ALU = 0x04;
144/// jmp, jeq, jgt, je, jset: Increment the program counter based on a comparison
145/// between k/x and the accumulator.
146pub const JMP = 0x05;
147/// ret: Return a verdict using the value of k/the accumulator.
148pub const RET = 0x06;
149/// tax, txa: Register value copying between X and a.
150pub const MISC = 0x07;
151
152// Size of data to be loaded from the packet.
153/// ld: 32-bit full word.
154pub const W = 0x00;
155/// ldh: 16-bit half word.
156pub const H = 0x08;
157/// ldb: Single byte.
158pub const B = 0x10;
159
160// Addressing modes used for loads to a/x.
161/// #k: The immediate value stored in k.
162pub const IMM = 0x00;
163/// [k]: The value at offset k in the packet.
164pub const ABS = 0x20;
165/// [x + k]: The value at offset x + k in the packet.
166pub const IND = 0x40;
167/// M[k]: The value of the k'th scratch memory register.
168pub const MEM = 0x60;
169/// #len: The size of the packet.
170pub const LEN = 0x80;
171/// 4 * ([k] & 0xf): Four times the low four bits of the byte at offset k in the
172/// packet. This is used for efficiently loading the header length of an IP
173/// packet.
174pub const MSH = 0xa0;
175/// arc4random: 32-bit integer generated from a CPRNG (see arc4random(3)) loaded into a.
176/// EXTENSION. Defined for:
177/// - OpenBSD.
178pub const RND = 0xc0;
179
180// Modifiers for different instruction classes.
181/// Use the value of k for alu operations (add #k).
182/// Compare against the value of k for jumps (jeq #k, Lt, Lf).
183/// Return the value of k for returns (ret #k).
184pub const K = 0x00;
185/// Use the value of x for alu operations (add x).
186/// Compare against the value of X for jumps (jeq x, Lt, Lf).
187pub const X = 0x08;
188/// Return the value of a for returns (ret a).
189pub const A = 0x10;
190
191// ALU Operations on a using the value of k/x.
192// All arithmetic operations are defined to overflow the value of a.
193/// add: a = a + k
194/// a = a + x.
195pub const ADD = 0x00;
196/// sub: a = a - k
197/// a = a - x.
198pub const SUB = 0x10;
199/// mul: a = a * k
200/// a = a * x.
201pub const MUL = 0x20;
202/// div: a = a / k
203/// a = a / x.
204/// Truncated division.
205pub const DIV = 0x30;
206/// or: a = a | k
207/// a = a | x.
208pub const OR = 0x40;
209/// and: a = a & k
210/// a = a & x.
211pub const AND = 0x50;
212/// lsh: a = a << k
213/// a = a << x.
214/// a = a << k, a = a << x.
215pub const LSH = 0x60;
216/// rsh: a = a >> k
217/// a = a >> x.
218pub const RSH = 0x70;
219/// neg: a = -a.
220/// Note that this isn't a binary negation, rather the value of `~a + 1`.
221pub const NEG = 0x80;
222/// mod: a = a % k
223/// a = a % x.
224/// EXTENSION. Defined for:
225/// - Linux.
226/// - NetBSD + Minix 3.
227/// - FreeBSD and derivitives.
228pub const MOD = 0x90;
229/// xor: a = a ^ k
230/// a = a ^ x.
231/// EXTENSION. Defined for:
232/// - Linux.
233/// - NetBSD + Minix 3.
234/// - FreeBSD and derivitives.
235pub const XOR = 0xa0;
236
237// Jump operations using a comparison between a and x/k.
238/// jmp L: pc += k.
239/// No comparison done here.
240pub const JA = 0x00;
241/// jeq #k, Lt, Lf: pc += (a == k) ? jt : jf.
242/// jeq x, Lt, Lf: pc += (a == x) ? jt : jf.
243pub const JEQ = 0x10;
244/// jgt #k, Lt, Lf: pc += (a > k) ? jt : jf.
245/// jgt x, Lt, Lf: pc += (a > x) ? jt : jf.
246pub const JGT = 0x20;
247/// jge #k, Lt, Lf: pc += (a >= k) ? jt : jf.
248/// jge x, Lt, Lf: pc += (a >= x) ? jt : jf.
249pub const JGE = 0x30;
250/// jset #k, Lt, Lf: pc += (a & k > 0) ? jt : jf.
251/// jset x, Lt, Lf: pc += (a & x > 0) ? jt : jf.
252pub const JSET = 0x40;
253
254// Miscellaneous operations/register copy.
255/// tax: x = a.
256pub const TAX = 0x00;
257/// txa: a = x.
258pub const TXA = 0x80;
259
260/// The 16 registers in the scratch memory store as named enums.
261pub const Scratch = enum(u4) { m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 };
262pub const MEMWORDS = 16;
263pub const MAXINSNS = switch (builtin.os.tag) {
264 .linux => 4096,
265 else => 512,
266};
267pub const MINBUFSIZE = 32;
268pub const MAXBUFSIZE = 1 << 21;
269
270pub const Insn = extern struct {
271 opcode: u16,
272 jt: u8,
273 jf: u8,
274 k: u32,
275
276 /// Implements the `std.fmt.format` API.
277 /// The formatting is similar to the output of tcpdump -dd.
278 pub fn format(
279 self: Insn,
280 comptime layout: []const u8,
281 opts: std.fmt.FormatOptions,
282 writer: anytype,
283 ) !void {
284 _ = opts;
285 if (comptime layout.len != 0 and layout[0] != 's')
286 @compileError("Unsupported format specifier for BPF Insn type '" ++ layout ++ "'.");
287
288 try std.fmt.format(
289 writer,
290 "Insn{{ 0x{X:0<2}, {d}, {d}, 0x{X:0<8} }}",
291 .{ self.opcode, self.jt, self.jf, self.k },
292 );
293 }
294
295 const Size = enum(u8) {
296 word = W,
297 half_word = H,
298 byte = B,
299 };
300
301 fn stmt(opcode: u16, k: u32) Insn {
302 return .{
303 .opcode = opcode,
304 .jt = 0,
305 .jf = 0,
306 .k = k,
307 };
308 }
309
310 pub fn ld_imm(value: u32) Insn {
311 return stmt(LD | IMM, value);
312 }
313
314 pub fn ld_abs(size: Size, offset: u32) Insn {
315 return stmt(LD | ABS | @enumToInt(size), offset);
316 }
317
318 pub fn ld_ind(size: Size, offset: u32) Insn {
319 return stmt(LD | IND | @enumToInt(size), offset);
320 }
321
322 pub fn ld_mem(reg: Scratch) Insn {
323 return stmt(LD | MEM, @enumToInt(reg));
324 }
325
326 pub fn ld_len() Insn {
327 return stmt(LD | LEN | W, 0);
328 }
329
330 pub fn ld_rnd() Insn {
331 return stmt(LD | RND | W, 0);
332 }
333
334 pub fn ldx_imm(value: u32) Insn {
335 return stmt(LDX | IMM, value);
336 }
337
338 pub fn ldx_mem(reg: Scratch) Insn {
339 return stmt(LDX | MEM, @enumToInt(reg));
340 }
341
342 pub fn ldx_len() Insn {
343 return stmt(LDX | LEN | W, 0);
344 }
345
346 pub fn ldx_msh(offset: u32) Insn {
347 return stmt(LDX | MSH | B, offset);
348 }
349
350 pub fn st(reg: Scratch) Insn {
351 return stmt(ST, @enumToInt(reg));
352 }
353 pub fn stx(reg: Scratch) Insn {
354 return stmt(STX, @enumToInt(reg));
355 }
356
357 const AluOp = enum(u16) {
358 add = ADD,
359 sub = SUB,
360 mul = MUL,
361 div = DIV,
362 @"or" = OR,
363 @"and" = AND,
364 lsh = LSH,
365 rsh = RSH,
366 mod = MOD,
367 xor = XOR,
368 };
369
370 const Source = enum(u16) {
371 k = K,
372 x = X,
373 };
374 const KOrX = union(Source) {
375 k: u32,
376 x: void,
377 };
378
379 pub fn alu_neg() Insn {
380 return stmt(ALU | NEG, 0);
381 }
382
383 pub fn alu(op: AluOp, source: KOrX) Insn {
384 return stmt(
385 ALU | @enumToInt(op) | @enumToInt(source),
386 if (source == .k) source.k else 0,
387 );
388 }
389
390 const JmpOp = enum(u16) {
391 jeq = JEQ,
392 jgt = JGT,
393 jge = JGE,
394 jset = JSET,
395 };
396
397 pub fn jmp_ja(location: u32) Insn {
398 return stmt(JMP | JA, location);
399 }
400
401 pub fn jmp(op: JmpOp, source: KOrX, jt: u8, jf: u8) Insn {
402 return Insn{
403 .opcode = JMP | @enumToInt(op) | @enumToInt(source),
404 .jt = jt,
405 .jf = jf,
406 .k = if (source == .k) source.k else 0,
407 };
408 }
409
410 const Verdict = enum(u16) {
411 k = K,
412 a = A,
413 };
414 const KOrA = union(Verdict) {
415 k: u32,
416 a: void,
417 };
418
419 pub fn ret(verdict: KOrA) Insn {
420 return stmt(
421 RET | @enumToInt(verdict),
422 if (verdict == .k) verdict.k else 0,
423 );
424 }
425
426 pub fn tax() Insn {
427 return stmt(MISC | TAX, 0);
428 }
429
430 pub fn txa() Insn {
431 return stmt(MISC | TXA, 0);
432 }
433};
434
435fn opcodeEqual(opcode: u16, insn: Insn) !void {
436 try expectEqual(opcode, insn.opcode);
437}
438
439test "opcodes" {
440 try opcodeEqual(0x00, Insn.ld_imm(0));
441 try opcodeEqual(0x20, Insn.ld_abs(.word, 0));
442 try opcodeEqual(0x28, Insn.ld_abs(.half_word, 0));
443 try opcodeEqual(0x30, Insn.ld_abs(.byte, 0));
444 try opcodeEqual(0x40, Insn.ld_ind(.word, 0));
445 try opcodeEqual(0x48, Insn.ld_ind(.half_word, 0));
446 try opcodeEqual(0x50, Insn.ld_ind(.byte, 0));
447 try opcodeEqual(0x60, Insn.ld_mem(.m0));
448 try opcodeEqual(0x80, Insn.ld_len());
449 try opcodeEqual(0xc0, Insn.ld_rnd());
450
451 try opcodeEqual(0x01, Insn.ldx_imm(0));
452 try opcodeEqual(0x61, Insn.ldx_mem(.m0));
453 try opcodeEqual(0x81, Insn.ldx_len());
454 try opcodeEqual(0xb1, Insn.ldx_msh(0));
455
456 try opcodeEqual(0x02, Insn.st(.m0));
457 try opcodeEqual(0x03, Insn.stx(.m0));
458
459 try opcodeEqual(0x04, Insn.alu(.add, .{ .k = 0 }));
460 try opcodeEqual(0x14, Insn.alu(.sub, .{ .k = 0 }));
461 try opcodeEqual(0x24, Insn.alu(.mul, .{ .k = 0 }));
462 try opcodeEqual(0x34, Insn.alu(.div, .{ .k = 0 }));
463 try opcodeEqual(0x44, Insn.alu(.@"or", .{ .k = 0 }));
464 try opcodeEqual(0x54, Insn.alu(.@"and", .{ .k = 0 }));
465 try opcodeEqual(0x64, Insn.alu(.lsh, .{ .k = 0 }));
466 try opcodeEqual(0x74, Insn.alu(.rsh, .{ .k = 0 }));
467 try opcodeEqual(0x94, Insn.alu(.mod, .{ .k = 0 }));
468 try opcodeEqual(0xa4, Insn.alu(.xor, .{ .k = 0 }));
469 try opcodeEqual(0x84, Insn.alu_neg());
470 try opcodeEqual(0x0c, Insn.alu(.add, .x));
471 try opcodeEqual(0x1c, Insn.alu(.sub, .x));
472 try opcodeEqual(0x2c, Insn.alu(.mul, .x));
473 try opcodeEqual(0x3c, Insn.alu(.div, .x));
474 try opcodeEqual(0x4c, Insn.alu(.@"or", .x));
475 try opcodeEqual(0x5c, Insn.alu(.@"and", .x));
476 try opcodeEqual(0x6c, Insn.alu(.lsh, .x));
477 try opcodeEqual(0x7c, Insn.alu(.rsh, .x));
478 try opcodeEqual(0x9c, Insn.alu(.mod, .x));
479 try opcodeEqual(0xac, Insn.alu(.xor, .x));
480
481 try opcodeEqual(0x05, Insn.jmp_ja(0));
482 try opcodeEqual(0x15, Insn.jmp(.jeq, .{ .k = 0 }, 0, 0));
483 try opcodeEqual(0x25, Insn.jmp(.jgt, .{ .k = 0 }, 0, 0));
484 try opcodeEqual(0x35, Insn.jmp(.jge, .{ .k = 0 }, 0, 0));
485 try opcodeEqual(0x45, Insn.jmp(.jset, .{ .k = 0 }, 0, 0));
486 try opcodeEqual(0x1d, Insn.jmp(.jeq, .x, 0, 0));
487 try opcodeEqual(0x2d, Insn.jmp(.jgt, .x, 0, 0));
488 try opcodeEqual(0x3d, Insn.jmp(.jge, .x, 0, 0));
489 try opcodeEqual(0x4d, Insn.jmp(.jset, .x, 0, 0));
490
491 try opcodeEqual(0x06, Insn.ret(.{ .k = 0 }));
492 try opcodeEqual(0x16, Insn.ret(.a));
493
494 try opcodeEqual(0x07, Insn.tax());
495 try opcodeEqual(0x87, Insn.txa());
496}
497
498pub const Error = error{
499 InvalidOpcode,
500 InvalidOffset,
501 InvalidLocation,
502 DivisionByZero,
503 NoReturn,
504};
505
506/// A simple implementation of the BPF virtual-machine.
507/// Use this to run/debug programs.
508pub fn simulate(
509 packet: []const u8,
510 filter: []const Insn,
511 byte_order: std.builtin.Endian,
512) Error!u32 {
513 assert(filter.len > 0 and filter.len < MAXINSNS);
514 assert(packet.len < MAXBUFSIZE);
515 const len = @intCast(u32, packet.len);
516
517 var a: u32 = 0;
518 var x: u32 = 0;
519 var m = mem.zeroes([MEMWORDS]u32);
520 var pc: usize = 0;
521
522 while (pc < filter.len) : (pc += 1) {
523 const i = filter[pc];
524 // Cast to a wider type to protect against overflow.
525 const k = @as(u64, i.k);
526 const remaining = filter.len - (pc + 1);
527
528 // Do validation/error checking here to compress the second switch.
529 switch (i.opcode) {
530 LD | ABS | W => if (k + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset,
531 LD | ABS | H => if (k + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset,
532 LD | ABS | B => if (k >= packet.len) return error.InvalidOffset,
533 LD | IND | W => if (k + x + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset,
534 LD | IND | H => if (k + x + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset,
535 LD | IND | B => if (k + x >= packet.len) return error.InvalidOffset,
536
537 LDX | MSH | B => if (k >= packet.len) return error.InvalidOffset,
538 ST, STX, LD | MEM, LDX | MEM => if (i.k >= MEMWORDS) return error.InvalidOffset,
539
540 JMP | JA => if (remaining <= i.k) return error.InvalidOffset,
541 JMP | JEQ | K,
542 JMP | JGT | K,
543 JMP | JGE | K,
544 JMP | JSET | K,
545 JMP | JEQ | X,
546 JMP | JGT | X,
547 JMP | JGE | X,
548 JMP | JSET | X,
549 => if (remaining <= i.jt or remaining <= i.jf) return error.InvalidLocation,
550 else => {},
551 }
552 switch (i.opcode) {
553 LD | IMM => a = i.k,
554 LD | MEM => a = m[i.k],
555 LD | LEN | W => a = len,
556 LD | RND | W => a = random.int(u32),
557 LD | ABS | W => a = mem.readInt(u32, packet[i.k..][0..@sizeOf(u32)], byte_order),
558 LD | ABS | H => a = mem.readInt(u16, packet[i.k..][0..@sizeOf(u16)], byte_order),
559 LD | ABS | B => a = packet[i.k],
560 LD | IND | W => a = mem.readInt(u32, packet[i.k + x ..][0..@sizeOf(u32)], byte_order),
561 LD | IND | H => a = mem.readInt(u16, packet[i.k + x ..][0..@sizeOf(u16)], byte_order),
562 LD | IND | B => a = packet[i.k + x],
563
564 LDX | IMM => x = i.k,
565 LDX | MEM => x = m[i.k],
566 LDX | LEN | W => x = len,
567 LDX | MSH | B => x = @as(u32, @truncate(u4, packet[i.k])) << 2,
568
569 ST => m[i.k] = a,
570 STX => m[i.k] = x,
571
572 ALU | ADD | K => a +%= i.k,
573 ALU | SUB | K => a -%= i.k,
574 ALU | MUL | K => a *%= i.k,
575 ALU | DIV | K => a = try math.divTrunc(u32, a, i.k),
576 ALU | OR | K => a |= i.k,
577 ALU | AND | K => a &= i.k,
578 ALU | LSH | K => a = math.shl(u32, a, i.k),
579 ALU | RSH | K => a = math.shr(u32, a, i.k),
580 ALU | MOD | K => a = try math.mod(u32, a, i.k),
581 ALU | XOR | K => a ^= i.k,
582 ALU | ADD | X => a +%= x,
583 ALU | SUB | X => a -%= x,
584 ALU | MUL | X => a *%= x,
585 ALU | DIV | X => a = try math.divTrunc(u32, a, x),
586 ALU | OR | X => a |= x,
587 ALU | AND | X => a &= x,
588 ALU | LSH | X => a = math.shl(u32, a, x),
589 ALU | RSH | X => a = math.shr(u32, a, x),
590 ALU | MOD | X => a = try math.mod(u32, a, x),
591 ALU | XOR | X => a ^= x,
592 ALU | NEG => a = @bitCast(u32, -%@bitCast(i32, a)),
593
594 JMP | JA => pc += i.k,
595 JMP | JEQ | K => pc += if (a == i.k) i.jt else i.jf,
596 JMP | JGT | K => pc += if (a > i.k) i.jt else i.jf,
597 JMP | JGE | K => pc += if (a >= i.k) i.jt else i.jf,
598 JMP | JSET | K => pc += if (a & i.k > 0) i.jt else i.jf,
599 JMP | JEQ | X => pc += if (a == x) i.jt else i.jf,
600 JMP | JGT | X => pc += if (a > x) i.jt else i.jf,
601 JMP | JGE | X => pc += if (a >= x) i.jt else i.jf,
602 JMP | JSET | X => pc += if (a & x > 0) i.jt else i.jf,
603
604 RET | K => return i.k,
605 RET | A => return a,
606
607 MISC | TAX => x = a,
608 MISC | TXA => a = x,
609 else => return error.InvalidOpcode,
610 }
611 }
612
613 return error.NoReturn;
614}
615
616// This program is the BPF form of the tcpdump filter:
617//
618// tcpdump -dd 'ip host mirror.internode.on.net and tcp port ftp-data'
619//
620// As of January 2022, mirror.internode.on.net resolves to 150.101.135.3
621//
622// For reference, here's what it looks like in BPF assembler.
623// Note that the jumps are used for TCP/IP layer checks.
624//
625// ```
626// ldh [12] (#proto)
627// jeq #0x0800 (ETHERTYPE_IP), L1, fail
628// L1: ld [26]
629// jeq #150.101.135.3, L2, dest
630// dest: ld [30]
631// jeq #150.101.135.3, L2, fail
632// L2: ldb [23]
633// jeq #0x6 (IPPROTO_TCP), L3, fail
634// L3: ldh [20]
635// jset #0x1fff, fail, plen
636// plen: ldx 4 * ([14] & 0xf)
637// ldh [x + 14]
638// jeq #0x14 (FTP), pass, dstp
639// dstp: ldh [x + 16]
640// jeq #0x14 (FTP), pass, fail
641// pass: ret #0x40000
642// fail: ret #0
643// ```
644const tcpdump_filter = [_]Insn{
645 Insn.ld_abs(.half_word, 12),
646 Insn.jmp(.jeq, .{ .k = 0x800 }, 0, 14),
647 Insn.ld_abs(.word, 26),
648 Insn.jmp(.jeq, .{ .k = 0x96658703 }, 2, 0),
649 Insn.ld_abs(.word, 30),
650 Insn.jmp(.jeq, .{ .k = 0x96658703 }, 0, 10),
651 Insn.ld_abs(.byte, 23),
652 Insn.jmp(.jeq, .{ .k = 0x6 }, 0, 8),
653 Insn.ld_abs(.half_word, 20),
654 Insn.jmp(.jset, .{ .k = 0x1fff }, 6, 0),
655 Insn.ldx_msh(14),
656 Insn.ld_ind(.half_word, 14),
657 Insn.jmp(.jeq, .{ .k = 0x14 }, 2, 0),
658 Insn.ld_ind(.half_word, 16),
659 Insn.jmp(.jeq, .{ .k = 0x14 }, 0, 1),
660 Insn.ret(.{ .k = 0x40000 }),
661 Insn.ret(.{ .k = 0 }),
662};
663
664// This packet is the output of `ls` on mirror.internode.on.net:/, captured
665// using the filter above.
666//
667// zig fmt: off
668const ftp_data = [_]u8{
669 // ethernet - 14 bytes: IPv4(0x0800) from a4:71:74:ad:4b:f0 -> de:ad:be:ef:f0:0f
670 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0f, 0xa4, 0x71, 0x74, 0xad, 0x4b, 0xf0, 0x08, 0x00,
671 // IPv4 - 20 bytes: TCP data from 150.101.135.3 -> 192.168.1.3
672 0x45, 0x00, 0x01, 0xf2, 0x70, 0x3b, 0x40, 0x00, 0x37, 0x06, 0xf2, 0xb6,
673 0x96, 0x65, 0x87, 0x03, 0xc0, 0xa8, 0x01, 0x03,
674 // TCP - 32 bytes: Source port: 20 (FTP). Payload = 446 bytes
675 0x00, 0x14, 0x80, 0x6d, 0x35, 0x81, 0x2d, 0x40, 0x4f, 0x8a, 0x29, 0x9e, 0x80, 0x18, 0x00, 0x2e,
676 0x88, 0x8d, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x0b, 0x59, 0x5d, 0x09, 0x32, 0x8b, 0x51, 0xa0
677} ++
678 // Raw line-based FTP data - 446 bytes
679 "lrwxrwxrwx 1 root root 12 Feb 14 2012 debian -> .pub2/debian\r\n" ++
680 "lrwxrwxrwx 1 root root 15 Feb 14 2012 debian-cd -> .pub2/debian-cd\r\n" ++
681 "lrwxrwxrwx 1 root root 9 Mar 9 2018 linux -> pub/linux\r\n" ++
682 "drwxr-xr-X 3 mirror mirror 4096 Sep 20 08:10 pub\r\n" ++
683 "lrwxrwxrwx 1 root root 12 Feb 14 2012 ubuntu -> .pub2/ubuntu\r\n" ++
684 "-rw-r--r-- 1 root root 1044 Jan 20 2015 welcome.msg\r\n";
685// zig fmt: on
686
687test "tcpdump filter" {
688 try expectEqual(
689 @as(u32, 0x40000),
690 try simulate(ftp_data, &tcpdump_filter, .Big),
691 );
692}
693
694fn expectPass(data: anytype, filter: []Insn) !void {
695 try expectEqual(
696 @as(u32, 0),
697 try simulate(mem.asBytes(data), filter, .Big),
698 );
699}
700
701fn expectFail(expected_error: anyerror, data: anytype, filter: []Insn) !void {
702 try expectError(
703 expected_error,
704 simulate(mem.asBytes(data), filter, native_endian),
705 );
706}
707
708test "simulator coverage" {
709 const some_data: packed struct {
710 foo: u32,
711 bar: u8,
712 } = .{
713 .foo = mem.nativeToBig(u32, 0xaabbccdd),
714 .bar = 0x7f,
715 };
716
717 try expectPass(&some_data, &.{
718 // ld #10
719 // ldx #1
720 // st M[0]
721 // stx M[1]
722 // fail if A != 10
723 Insn.ld_imm(10),
724 Insn.ldx_imm(1),
725 Insn.st(.m0),
726 Insn.stx(.m1),
727 Insn.jmp(.jeq, .{ .k = 10 }, 1, 0),
728 Insn.ret(.{ .k = 1 }),
729 // ld [0]
730 // fail if A != 0xaabbccdd
731 Insn.ld_abs(.word, 0),
732 Insn.jmp(.jeq, .{ .k = 0xaabbccdd }, 1, 0),
733 Insn.ret(.{ .k = 2 }),
734 // ldh [0]
735 // fail if A != 0xaabb
736 Insn.ld_abs(.half_word, 0),
737 Insn.jmp(.jeq, .{ .k = 0xaabb }, 1, 0),
738 Insn.ret(.{ .k = 3 }),
739 // ldb [0]
740 // fail if A != 0xaa
741 Insn.ld_abs(.byte, 0),
742 Insn.jmp(.jeq, .{ .k = 0xaa }, 1, 0),
743 Insn.ret(.{ .k = 4 }),
744 // ld [x + 0]
745 // fail if A != 0xbbccdd7f
746 Insn.ld_ind(.word, 0),
747 Insn.jmp(.jeq, .{ .k = 0xbbccdd7f }, 1, 0),
748 Insn.ret(.{ .k = 5 }),
749 // ldh [x + 0]
750 // fail if A != 0xbbcc
751 Insn.ld_ind(.half_word, 0),
752 Insn.jmp(.jeq, .{ .k = 0xbbcc }, 1, 0),
753 Insn.ret(.{ .k = 6 }),
754 // ldb [x + 0]
755 // fail if A != 0xbb
756 Insn.ld_ind(.byte, 0),
757 Insn.jmp(.jeq, .{ .k = 0xbb }, 1, 0),
758 Insn.ret(.{ .k = 7 }),
759 // ld M[0]
760 // fail if A != 10
761 Insn.ld_mem(.m0),
762 Insn.jmp(.jeq, .{ .k = 10 }, 1, 0),
763 Insn.ret(.{ .k = 8 }),
764 // ld #len
765 // fail if A != 5
766 Insn.ld_len(),
767 Insn.jmp(.jeq, .{ .k = @sizeOf(@TypeOf(some_data)) }, 1, 0),
768 Insn.ret(.{ .k = 9 }),
769 // ld #0
770 // ld arc4random()
771 // fail if A == 0
772 Insn.ld_imm(0),
773 Insn.ld_rnd(),
774 Insn.jmp(.jgt, .{ .k = 0 }, 1, 0),
775 Insn.ret(.{ .k = 10 }),
776 // ld #3
777 // ldx #10
778 // st M[2]
779 // txa
780 // fail if a != x
781 Insn.ld_imm(3),
782 Insn.ldx_imm(10),
783 Insn.st(.m2),
784 Insn.txa(),
785 Insn.jmp(.jeq, .x, 1, 0),
786 Insn.ret(.{ .k = 11 }),
787 // ldx M[2]
788 // fail if A <= X
789 Insn.ldx_mem(.m2),
790 Insn.jmp(.jgt, .x, 1, 0),
791 Insn.ret(.{ .k = 12 }),
792 // ldx #len
793 // fail if a <= x
794 Insn.ldx_len(),
795 Insn.jmp(.jgt, .x, 1, 0),
796 Insn.ret(.{ .k = 13 }),
797 // a = 4 * (0x7f & 0xf)
798 // x = 4 * ([4] & 0xf)
799 // fail if a != x
800 Insn.ld_imm(4 * (0x7f & 0xf)),
801 Insn.ldx_msh(4),
802 Insn.jmp(.jeq, .x, 1, 0),
803 Insn.ret(.{ .k = 14 }),
804 // ld #(u32)-1
805 // ldx #2
806 // add #1
807 // fail if a != 0
808 Insn.ld_imm(0xffffffff),
809 Insn.ldx_imm(2),
810 Insn.alu(.add, .{ .k = 1 }),
811 Insn.jmp(.jeq, .{ .k = 0 }, 1, 0),
812 Insn.ret(.{ .k = 15 }),
813 // sub #1
814 // fail if a != (u32)-1
815 Insn.alu(.sub, .{ .k = 1 }),
816 Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0),
817 Insn.ret(.{ .k = 16 }),
818 // add x
819 // fail if a != 1
820 Insn.alu(.add, .x),
821 Insn.jmp(.jeq, .{ .k = 1 }, 1, 0),
822 Insn.ret(.{ .k = 17 }),
823 // sub x
824 // fail if a != (u32)-1
825 Insn.alu(.sub, .x),
826 Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0),
827 Insn.ret(.{ .k = 18 }),
828 // ld #16
829 // mul #2
830 // fail if a != 32
831 Insn.ld_imm(16),
832 Insn.alu(.mul, .{ .k = 2 }),
833 Insn.jmp(.jeq, .{ .k = 32 }, 1, 0),
834 Insn.ret(.{ .k = 19 }),
835 // mul x
836 // fail if a != 64
837 Insn.alu(.mul, .x),
838 Insn.jmp(.jeq, .{ .k = 64 }, 1, 0),
839 Insn.ret(.{ .k = 20 }),
840 // div #2
841 // fail if a != 32
842 Insn.alu(.div, .{ .k = 2 }),
843 Insn.jmp(.jeq, .{ .k = 32 }, 1, 0),
844 Insn.ret(.{ .k = 21 }),
845 // div x
846 // fail if a != 16
847 Insn.alu(.div, .x),
848 Insn.jmp(.jeq, .{ .k = 16 }, 1, 0),
849 Insn.ret(.{ .k = 22 }),
850 // or #4
851 // fail if a != 20
852 Insn.alu(.@"or", .{ .k = 4 }),
853 Insn.jmp(.jeq, .{ .k = 20 }, 1, 0),
854 Insn.ret(.{ .k = 23 }),
855 // or x
856 // fail if a != 22
857 Insn.alu(.@"or", .x),
858 Insn.jmp(.jeq, .{ .k = 22 }, 1, 0),
859 Insn.ret(.{ .k = 24 }),
860 // and #6
861 // fail if a != 6
862 Insn.alu(.@"and", .{ .k = 0b110 }),
863 Insn.jmp(.jeq, .{ .k = 6 }, 1, 0),
864 Insn.ret(.{ .k = 25 }),
865 // and x
866 // fail if a != 2
867 Insn.alu(.@"and", .x),
868 Insn.jmp(.jeq, .x, 1, 0),
869 Insn.ret(.{ .k = 26 }),
870 // xor #15
871 // fail if a != 13
872 Insn.alu(.xor, .{ .k = 0b1111 }),
873 Insn.jmp(.jeq, .{ .k = 0b1101 }, 1, 0),
874 Insn.ret(.{ .k = 27 }),
875 // xor x
876 // fail if a != 15
877 Insn.alu(.xor, .x),
878 Insn.jmp(.jeq, .{ .k = 0b1111 }, 1, 0),
879 Insn.ret(.{ .k = 28 }),
880 // rsh #1
881 // fail if a != 7
882 Insn.alu(.rsh, .{ .k = 1 }),
883 Insn.jmp(.jeq, .{ .k = 0b0111 }, 1, 0),
884 Insn.ret(.{ .k = 29 }),
885 // rsh x
886 // fail if a != 1
887 Insn.alu(.rsh, .x),
888 Insn.jmp(.jeq, .{ .k = 0b0001 }, 1, 0),
889 Insn.ret(.{ .k = 30 }),
890 // lsh #1
891 // fail if a != 2
892 Insn.alu(.lsh, .{ .k = 1 }),
893 Insn.jmp(.jeq, .{ .k = 0b0010 }, 1, 0),
894 Insn.ret(.{ .k = 31 }),
895 // lsh x
896 // fail if a != 8
897 Insn.alu(.lsh, .x),
898 Insn.jmp(.jeq, .{ .k = 0b1000 }, 1, 0),
899 Insn.ret(.{ .k = 32 }),
900 // mod 6
901 // fail if a != 2
902 Insn.alu(.mod, .{ .k = 6 }),
903 Insn.jmp(.jeq, .{ .k = 2 }, 1, 0),
904 Insn.ret(.{ .k = 33 }),
905 // mod x
906 // fail if a != 0
907 Insn.alu(.mod, .x),
908 Insn.jmp(.jeq, .{ .k = 0 }, 1, 0),
909 Insn.ret(.{ .k = 34 }),
910 // tax
911 // neg
912 // fail if a != (u32)-2
913 Insn.txa(),
914 Insn.alu_neg(),
915 Insn.jmp(.jeq, .{ .k = ~@as(u32, 2) + 1 }, 1, 0),
916 Insn.ret(.{ .k = 35 }),
917 // ja #1 (skip the next instruction)
918 Insn.jmp_ja(1),
919 Insn.ret(.{ .k = 36 }),
920 // ld #20
921 // tax
922 // fail if a != 20
923 // fail if a != x
924 Insn.ld_imm(20),
925 Insn.tax(),
926 Insn.jmp(.jeq, .{ .k = 20 }, 1, 0),
927 Insn.ret(.{ .k = 37 }),
928 Insn.jmp(.jeq, .x, 1, 0),
929 Insn.ret(.{ .k = 38 }),
930 // ld #19
931 // fail if a == 20
932 // fail if a == x
933 // fail if a >= 20
934 // fail if a >= X
935 Insn.ld_imm(19),
936 Insn.jmp(.jeq, .{ .k = 20 }, 0, 1),
937 Insn.ret(.{ .k = 39 }),
938 Insn.jmp(.jeq, .x, 0, 1),
939 Insn.ret(.{ .k = 40 }),
940 Insn.jmp(.jgt, .{ .k = 20 }, 0, 1),
941 Insn.ret(.{ .k = 41 }),
942 Insn.jmp(.jgt, .x, 0, 1),
943 Insn.ret(.{ .k = 42 }),
944 // ld #21
945 // fail if a < 20
946 // fail if a < x
947 Insn.ld_imm(21),
948 Insn.jmp(.jgt, .{ .k = 20 }, 1, 0),
949 Insn.ret(.{ .k = 43 }),
950 Insn.jmp(.jgt, .x, 1, 0),
951 Insn.ret(.{ .k = 44 }),
952 // ldx #22
953 // fail if a < 22
954 // fail if a < x
955 Insn.ldx_imm(22),
956 Insn.jmp(.jge, .{ .k = 22 }, 0, 1),
957 Insn.ret(.{ .k = 45 }),
958 Insn.jmp(.jge, .x, 0, 1),
959 Insn.ret(.{ .k = 46 }),
960 // ld #23
961 // fail if a >= 22
962 // fail if a >= x
963 Insn.ld_imm(23),
964 Insn.jmp(.jge, .{ .k = 22 }, 1, 0),
965 Insn.ret(.{ .k = 47 }),
966 Insn.jmp(.jge, .x, 1, 0),
967 Insn.ret(.{ .k = 48 }),
968 // ldx #0b10100
969 // fail if a & 0b10100 == 0
970 // fail if a & x == 0
971 Insn.ldx_imm(0b10100),
972 Insn.jmp(.jset, .{ .k = 0b10100 }, 1, 0),
973 Insn.ret(.{ .k = 47 }),
974 Insn.jmp(.jset, .x, 1, 0),
975 Insn.ret(.{ .k = 48 }),
976 // ldx #0
977 // fail if a & 0 > 0
978 // fail if a & x > 0
979 Insn.ldx_imm(0),
980 Insn.jmp(.jset, .{ .k = 0 }, 0, 1),
981 Insn.ret(.{ .k = 49 }),
982 Insn.jmp(.jset, .x, 0, 1),
983 Insn.ret(.{ .k = 50 }),
984 Insn.ret(.{ .k = 0 }),
985 });
986 try expectPass(&some_data, &.{
987 Insn.ld_imm(35),
988 Insn.ld_imm(0),
989 Insn.ret(.a),
990 });
991
992 // Errors
993 try expectFail(error.NoReturn, &some_data, &.{
994 Insn.ld_imm(10),
995 });
996 try expectFail(error.InvalidOpcode, &some_data, &.{
997 Insn.stmt(0x7f, 0xdeadbeef),
998 });
999 try expectFail(error.InvalidOffset, &some_data, &.{
1000 Insn.stmt(LD | ABS | W, 10),
1001 });
1002 try expectFail(error.InvalidLocation, &some_data, &.{
1003 Insn.jmp(.jeq, .{ .k = 0 }, 10, 0),
1004 });
1005 try expectFail(error.InvalidLocation, &some_data, &.{
1006 Insn.jmp(.jeq, .{ .k = 0 }, 0, 10),
1007 });
1008}