The Pedigree Project 0.1
seccomp-filter.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "seccomp-filter.h"
3
4namespace PosixSeccomp {
5namespace {
6using namespace Bpf;
7constexpr uint32_t Reachable = 1U << 16;
8
9bool validInstruction(const Instruction& instruction, size_t remaining) {
10 switch (instruction.code) {
11 case LD | W | ABS:
12 return instruction.k < sizeof(Data) && !(instruction.k & 3);
13 case LD | MEM:
14 case LDX | MEM:
15 case ST:
16 case STX:
17 return instruction.k < 16;
18 case ALU | DIV | K:
19 return instruction.k != 0;
20 case ALU | LSH | K:
21 case ALU | RSH | K:
22 return instruction.k < 32;
23 case JMP | JA:
24 return instruction.k < remaining;
25 case JMP | JEQ | K:
26 case JMP | JEQ | X:
27 case JMP | JGT | K:
28 case JMP | JGT | X:
29 case JMP | JGE | K:
30 case JMP | JGE | X:
31 case JMP | JSET | K:
32 case JMP | JSET | X:
33 return instruction.jt < remaining && instruction.jf < remaining;
34 case LD | IMM:
35 case LDX | IMM:
36 case LD | W | LEN:
37 case LDX | W | LEN:
38 case ALU | ADD | K:
39 case ALU | ADD | X:
40 case ALU | SUB | K:
41 case ALU | SUB | X:
42 case ALU | MUL | K:
43 case ALU | MUL | X:
44 case ALU | DIV | X:
45 case ALU | OR | K:
46 case ALU | OR | X:
47 case ALU | AND | K:
48 case ALU | AND | X:
49 case ALU | LSH | X:
50 case ALU | RSH | X:
51 case ALU | NEG:
52 case ALU | XOR | K:
53 case ALU | XOR | X:
54 case RET | K:
55 case RET | A:
56 case MISC | TAX:
57 case MISC | TXA:
58 return true;
59 default:
60 return false;
61 }
62}
63
64bool validatePaths(const Instruction* instructions, size_t count, uint32_t* incoming) {
65 incoming[0] = Reachable;
66 const auto merge = [&](size_t destination, uint32_t initialized) {
67 incoming[destination] =
68 incoming[destination] ? incoming[destination] & initialized : initialized;
69 };
70 for (size_t pc = 0; pc < count; ++pc) {
71 const Instruction& instruction = instructions[pc];
72 if (!validInstruction(instruction, count - pc - 1)) {
73 return false;
74 }
75 if (!incoming[pc]) {
76 continue;
77 }
78 uint32_t initialized = incoming[pc];
79 if (instruction.code == (LD | MEM) || instruction.code == (LDX | MEM)) {
80 if (!(initialized & (1U << instruction.k))) {
81 return false;
82 }
83 } else if (instruction.code == ST || instruction.code == STX) {
84 initialized |= 1U << instruction.k;
85 }
86
87 // Forward-only jumps make this a single pass: every predecessor has
88 // contributed its initialized cells by the time we visit a join.
89 if ((instruction.code & 7) == RET) {
90 continue;
91 }
92 if (instruction.code == (JMP | JA)) {
93 merge(pc + 1 + instruction.k, initialized);
94 } else if ((instruction.code & 7) == JMP) {
95 merge(pc + 1 + instruction.jt, initialized);
96 merge(pc + 1 + instruction.jf, initialized);
97 } else {
98 if (pc + 1 == count) {
99 return false;
100 }
101 merge(pc + 1, initialized);
102 }
103 }
104 return true;
105}
106} // namespace
107
108bool validate(const Instruction* instructions, size_t count) {
109 if (!instructions || !count || count > MaximumInstructions) {
110 return false;
111 }
112 const uint16_t last = instructions[count - 1].code;
113 if (last != (RET | K) && last != (RET | A)) {
114 return false;
115 }
116 // Keep the bounded control-flow workspace off the kernel stack.
117 uint32_t* incoming = new uint32_t[count]();
118 if (!incoming) {
119 return false;
120 }
121 const bool valid = validatePaths(instructions, count, incoming);
122 delete[] incoming;
123 return valid;
124}
125
126uint32_t evaluate(const Instruction* instructions, size_t count, const Data& data) {
127 if (!instructions || !count || count > MaximumInstructions) {
128 return KillProcess;
129 }
130 uint32_t accumulator = 0, index = 0, initialized = 0;
131 uint32_t memory[16] = {};
132 for (size_t pc = 0; pc < count; ++pc) {
133 const Instruction& instruction = instructions[pc];
134 if (!validInstruction(instruction, count - pc - 1)) {
135 return KillProcess;
136 }
137 const uint32_t operand = instruction.code & X ? index : instruction.k;
138 switch (instruction.code) {
139 case LD | W | ABS: {
140 // seccomp loads use native byte order, including halves of 64-bit
141 // arguments. Copy bytes to avoid aliasing the Data object as uint32_t.
142 const unsigned char* source = reinterpret_cast<const unsigned char*>(&data) + instruction.k;
143 unsigned char* destination = reinterpret_cast<unsigned char*>(&accumulator);
144 for (size_t byte = 0; byte < sizeof(accumulator); ++byte) {
145 destination[byte] = source[byte];
146 }
147 break;
148 }
149 case LD | IMM:
150 accumulator = instruction.k;
151 break;
152 case LDX | IMM:
153 index = instruction.k;
154 break;
155 case LD | W | LEN:
156 accumulator = sizeof(Data);
157 break;
158 case LDX | W | LEN:
159 index = sizeof(Data);
160 break;
161 case LD | MEM:
162 case LDX | MEM:
163 if (!(initialized & (1U << instruction.k))) {
164 return KillProcess;
165 }
166 if (instruction.code == (LD | MEM)) {
167 accumulator = memory[instruction.k];
168 } else {
169 index = memory[instruction.k];
170 }
171 break;
172 case ST:
173 case STX:
174 memory[instruction.k] = instruction.code == ST ? accumulator : index;
175 initialized |= 1U << instruction.k;
176 break;
177 case ALU | ADD | K:
178 case ALU | ADD | X:
179 accumulator += operand;
180 break;
181 case ALU | SUB | K:
182 case ALU | SUB | X:
183 accumulator -= operand;
184 break;
185 case ALU | MUL | K:
186 case ALU | MUL | X:
187 accumulator *= operand;
188 break;
189 case ALU | DIV | K:
190 case ALU | DIV | X:
191 if (!operand) {
192 return KillProcess;
193 }
194 accumulator /= operand;
195 break;
196 case ALU | OR | K:
197 case ALU | OR | X:
198 accumulator |= operand;
199 break;
200 case ALU | AND | K:
201 case ALU | AND | X:
202 accumulator &= operand;
203 break;
204 case ALU | XOR | K:
205 case ALU | XOR | X:
206 accumulator ^= operand;
207 break;
208 case ALU | LSH | K:
209 case ALU | LSH | X:
210 accumulator <<= operand & 31;
211 break;
212 case ALU | RSH | K:
213 case ALU | RSH | X:
214 accumulator >>= operand & 31;
215 break;
216 case ALU | NEG:
217 accumulator = 0U - accumulator;
218 break;
219 case JMP | JA:
220 pc += instruction.k;
221 break;
222 case JMP | JEQ | K:
223 case JMP | JEQ | X:
224 pc += accumulator == operand ? instruction.jt : instruction.jf;
225 break;
226 case JMP | JGT | K:
227 case JMP | JGT | X:
228 pc += accumulator > operand ? instruction.jt : instruction.jf;
229 break;
230 case JMP | JGE | K:
231 case JMP | JGE | X:
232 pc += accumulator >= operand ? instruction.jt : instruction.jf;
233 break;
234 case JMP | JSET | K:
235 case JMP | JSET | X:
236 pc += accumulator & operand ? instruction.jt : instruction.jf;
237 break;
238 case RET | K:
239 return instruction.k;
240 case RET | A:
241 return accumulator;
242 case MISC | TAX:
243 index = accumulator;
244 break;
245 case MISC | TXA:
246 accumulator = index;
247 break;
248 default:
249 return KillProcess;
250 }
251 }
252 return KillProcess;
253}
254} // namespace PosixSeccomp