blob: ea495f11a7024c09374cb317f3d44d3f27aacb74 [file] [log] [blame]
Luis Hector Chavez1ac9eca2018-12-04 21:28:52 -08001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2018 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17"""Tools to interact with BPF programs."""
18
19import abc
20import collections
21import struct
22
23# This comes from syscall(2). Most architectures only support passing 6 args to
24# syscalls, but ARM supports passing 7.
25MAX_SYSCALL_ARGUMENTS = 7
26
27# The following fields were copied from <linux/bpf_common.h>:
28
29# Instruction classes
30BPF_LD = 0x00
31BPF_LDX = 0x01
32BPF_ST = 0x02
33BPF_STX = 0x03
34BPF_ALU = 0x04
35BPF_JMP = 0x05
36BPF_RET = 0x06
37BPF_MISC = 0x07
38
39# LD/LDX fields.
40# Size
41BPF_W = 0x00
42BPF_H = 0x08
43BPF_B = 0x10
44# Mode
45BPF_IMM = 0x00
46BPF_ABS = 0x20
47BPF_IND = 0x40
48BPF_MEM = 0x60
49BPF_LEN = 0x80
50BPF_MSH = 0xa0
51
52# JMP fields.
53BPF_JA = 0x00
54BPF_JEQ = 0x10
55BPF_JGT = 0x20
56BPF_JGE = 0x30
57BPF_JSET = 0x40
58
59# Source
60BPF_K = 0x00
61BPF_X = 0x08
62
63BPF_MAXINSNS = 4096
64
65# The following fields were copied from <linux/seccomp.h>:
66
67SECCOMP_RET_KILL_PROCESS = 0x80000000
68SECCOMP_RET_KILL_THREAD = 0x00000000
69SECCOMP_RET_TRAP = 0x00030000
70SECCOMP_RET_ERRNO = 0x00050000
71SECCOMP_RET_TRACE = 0x7ff00000
Luis Héctor Chávez59a64492021-01-03 05:46:47 -080072SECCOMP_RET_USER_NOTIF = 0x7fc00000
Luis Hector Chavez1ac9eca2018-12-04 21:28:52 -080073SECCOMP_RET_LOG = 0x7ffc0000
74SECCOMP_RET_ALLOW = 0x7fff0000
75
Luis Hector Chavez05392b82018-10-28 21:40:10 -070076SECCOMP_RET_ACTION_FULL = 0xffff0000
Luis Hector Chavez1ac9eca2018-12-04 21:28:52 -080077SECCOMP_RET_DATA = 0x0000ffff
78
79
Luis Hector Chavez05392b82018-10-28 21:40:10 -070080def arg_offset(arg_index, hi=False):
81 """Return the BPF_LD|BPF_W|BPF_ABS addressing-friendly register offset."""
82 offsetof_args = 4 + 4 + 8
83 arg_width = 8
84 return offsetof_args + arg_width * arg_index + (arg_width // 2) * hi
85
86
87def simulate(instructions, arch, syscall_number, *args):
88 """Simulate a BPF program with the given arguments."""
89 args = ((args + (0, ) *
90 (MAX_SYSCALL_ARGUMENTS - len(args)))[:MAX_SYSCALL_ARGUMENTS])
91 input_memory = struct.pack('IIQ' + 'Q' * MAX_SYSCALL_ARGUMENTS,
92 syscall_number, arch, 0, *args)
93
94 register = 0
95 program_counter = 0
96 cost = 0
97 while program_counter < len(instructions):
98 ins = instructions[program_counter]
99 program_counter += 1
100 cost += 1
101 if ins.code == BPF_LD | BPF_W | BPF_ABS:
102 register = struct.unpack('I', input_memory[ins.k:ins.k + 4])[0]
103 elif ins.code == BPF_JMP | BPF_JA | BPF_K:
104 program_counter += ins.k
105 elif ins.code == BPF_JMP | BPF_JEQ | BPF_K:
106 if register == ins.k:
107 program_counter += ins.jt
108 else:
109 program_counter += ins.jf
110 elif ins.code == BPF_JMP | BPF_JGT | BPF_K:
111 if register > ins.k:
112 program_counter += ins.jt
113 else:
114 program_counter += ins.jf
115 elif ins.code == BPF_JMP | BPF_JGE | BPF_K:
116 if register >= ins.k:
117 program_counter += ins.jt
118 else:
119 program_counter += ins.jf
120 elif ins.code == BPF_JMP | BPF_JSET | BPF_K:
121 if register & ins.k != 0:
122 program_counter += ins.jt
123 else:
124 program_counter += ins.jf
125 elif ins.code == BPF_RET:
126 if ins.k == SECCOMP_RET_KILL_PROCESS:
127 return (cost, 'KILL_PROCESS')
128 if ins.k == SECCOMP_RET_KILL_THREAD:
129 return (cost, 'KILL_THREAD')
130 if ins.k == SECCOMP_RET_TRAP:
131 return (cost, 'TRAP')
132 if (ins.k & SECCOMP_RET_ACTION_FULL) == SECCOMP_RET_ERRNO:
133 return (cost, 'ERRNO', ins.k & SECCOMP_RET_DATA)
134 if ins.k == SECCOMP_RET_TRACE:
135 return (cost, 'TRACE')
Luis Héctor Chávez59a64492021-01-03 05:46:47 -0800136 if ins.k == SECCOMP_RET_USER_NOTIF:
137 return (cost, 'USER_NOTIF')
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700138 if ins.k == SECCOMP_RET_LOG:
139 return (cost, 'LOG')
140 if ins.k == SECCOMP_RET_ALLOW:
141 return (cost, 'ALLOW')
142 raise Exception('unknown return %#x' % ins.k)
143 else:
144 raise Exception('unknown instruction %r' % (ins, ))
145 raise Exception('out-of-bounds')
146
147
Luis Hector Chavez1ac9eca2018-12-04 21:28:52 -0800148class SockFilter(
149 collections.namedtuple('SockFilter', ['code', 'jt', 'jf', 'k'])):
150 """A representation of struct sock_filter."""
151
152 __slots__ = ()
153
154 def encode(self):
155 """Return an encoded version of the SockFilter."""
156 return struct.pack('HBBI', self.code, self.jt, self.jf, self.k)
157
158
159class AbstractBlock(abc.ABC):
160 """A class that implements the visitor pattern."""
161
162 def __init__(self):
163 super().__init__()
164
165 @abc.abstractmethod
166 def accept(self, visitor):
167 pass
168
169
170class BasicBlock(AbstractBlock):
171 """A concrete implementation of AbstractBlock that has been compiled."""
172
173 def __init__(self, instructions):
174 super().__init__()
175 self._instructions = instructions
176
177 def accept(self, visitor):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800178 if visitor.visited(self):
179 return
Luis Hector Chavez1ac9eca2018-12-04 21:28:52 -0800180 visitor.visit(self)
181
182 @property
183 def instructions(self):
184 return self._instructions
185
186 @property
187 def opcodes(self):
188 return b''.join(i.encode() for i in self._instructions)
189
190 def __eq__(self, o):
191 if not isinstance(o, BasicBlock):
192 return False
193 return self._instructions == o._instructions
194
195
196class KillProcess(BasicBlock):
197 """A BasicBlock that unconditionally returns KILL_PROCESS."""
198
199 def __init__(self):
200 super().__init__(
201 [SockFilter(BPF_RET, 0x00, 0x00, SECCOMP_RET_KILL_PROCESS)])
202
203
204class KillThread(BasicBlock):
205 """A BasicBlock that unconditionally returns KILL_THREAD."""
206
207 def __init__(self):
208 super().__init__(
209 [SockFilter(BPF_RET, 0x00, 0x00, SECCOMP_RET_KILL_THREAD)])
210
211
212class Trap(BasicBlock):
213 """A BasicBlock that unconditionally returns TRAP."""
214
215 def __init__(self):
216 super().__init__([SockFilter(BPF_RET, 0x00, 0x00, SECCOMP_RET_TRAP)])
217
218
219class Trace(BasicBlock):
220 """A BasicBlock that unconditionally returns TRACE."""
221
222 def __init__(self):
223 super().__init__([SockFilter(BPF_RET, 0x00, 0x00, SECCOMP_RET_TRACE)])
224
225
Luis Héctor Chávez59a64492021-01-03 05:46:47 -0800226class UserNotify(BasicBlock):
227 """A BasicBlock that unconditionally returns USER_NOTIF."""
228
229 def __init__(self):
230 super().__init__([SockFilter(BPF_RET, 0x00, 0x00, SECCOMP_RET_USER_NOTIF)])
231
232
Luis Hector Chavez1ac9eca2018-12-04 21:28:52 -0800233class Log(BasicBlock):
234 """A BasicBlock that unconditionally returns LOG."""
235
236 def __init__(self):
237 super().__init__([SockFilter(BPF_RET, 0x00, 0x00, SECCOMP_RET_LOG)])
238
239
240class ReturnErrno(BasicBlock):
241 """A BasicBlock that unconditionally returns the specified errno."""
242
243 def __init__(self, errno):
244 super().__init__([
245 SockFilter(BPF_RET, 0x00, 0x00,
246 SECCOMP_RET_ERRNO | (errno & SECCOMP_RET_DATA))
247 ])
248 self.errno = errno
249
250
251class Allow(BasicBlock):
252 """A BasicBlock that unconditionally returns ALLOW."""
253
254 def __init__(self):
255 super().__init__([SockFilter(BPF_RET, 0x00, 0x00, SECCOMP_RET_ALLOW)])
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700256
257
258class ValidateArch(AbstractBlock):
259 """An AbstractBlock that validates the architecture."""
260
261 def __init__(self, next_block):
262 super().__init__()
263 self.next_block = next_block
264
265 def accept(self, visitor):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800266 if visitor.visited(self):
267 return
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700268 self.next_block.accept(visitor)
269 visitor.visit(self)
270
271
272class SyscallEntry(AbstractBlock):
273 """An abstract block that represents a syscall comparison in a DAG."""
274
275 def __init__(self, syscall_number, jt, jf, *, op=BPF_JEQ):
276 super().__init__()
277 self.op = op
278 self.syscall_number = syscall_number
279 self.jt = jt
280 self.jf = jf
281
282 def __lt__(self, o):
283 # Defined because we want to compare tuples that contain SyscallEntries.
284 return False
285
286 def __gt__(self, o):
287 # Defined because we want to compare tuples that contain SyscallEntries.
288 return False
289
290 def accept(self, visitor):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800291 if visitor.visited(self):
292 return
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700293 self.jt.accept(visitor)
294 self.jf.accept(visitor)
295 visitor.visit(self)
296
297 def __lt__(self, o):
298 # Defined because we want to compare tuples that contain SyscallEntries.
299 return False
300
301 def __gt__(self, o):
302 # Defined because we want to compare tuples that contain SyscallEntries.
303 return False
304
305
306class WideAtom(AbstractBlock):
307 """A BasicBlock that represents a 32-bit wide atom."""
308
309 def __init__(self, arg_offset, op, value, jt, jf):
310 super().__init__()
311 self.arg_offset = arg_offset
312 self.op = op
313 self.value = value
314 self.jt = jt
315 self.jf = jf
316
317 def accept(self, visitor):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800318 if visitor.visited(self):
319 return
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700320 self.jt.accept(visitor)
321 self.jf.accept(visitor)
322 visitor.visit(self)
323
324
325class Atom(AbstractBlock):
326 """A BasicBlock that represents an atom (a simple comparison operation)."""
327
328 def __init__(self, arg_index, op, value, jt, jf):
329 super().__init__()
330 if op == '==':
331 op = BPF_JEQ
332 elif op == '!=':
333 op = BPF_JEQ
334 jt, jf = jf, jt
335 elif op == '>':
336 op = BPF_JGT
337 elif op == '<=':
338 op = BPF_JGT
339 jt, jf = jf, jt
340 elif op == '>=':
341 op = BPF_JGE
342 elif op == '<':
343 op = BPF_JGE
344 jt, jf = jf, jt
345 elif op == '&':
346 op = BPF_JSET
347 elif op == 'in':
348 op = BPF_JSET
349 # The mask is negated, so the comparison will be true when the
350 # argument includes a flag that wasn't listed in the original
351 # (non-negated) mask. This would be the failure case, so we switch
352 # |jt| and |jf|.
353 value = (~value) & ((1 << 64) - 1)
354 jt, jf = jf, jt
355 else:
356 raise Exception('Unknown operator %s' % op)
357
358 self.arg_index = arg_index
359 self.op = op
360 self.jt = jt
361 self.jf = jf
362 self.value = value
363
364 def accept(self, visitor):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800365 if visitor.visited(self):
366 return
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700367 self.jt.accept(visitor)
368 self.jf.accept(visitor)
369 visitor.visit(self)
370
371
372class AbstractVisitor(abc.ABC):
373 """An abstract visitor."""
374
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800375 def __init__(self):
376 self._visited = set()
377
378 def visited(self, block):
379 if id(block) in self._visited:
380 return True
381 self._visited.add(id(block))
382 return False
383
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700384 def process(self, block):
385 block.accept(self)
386 return block
387
388 def visit(self, block):
389 if isinstance(block, KillProcess):
390 self.visitKillProcess(block)
391 elif isinstance(block, KillThread):
392 self.visitKillThread(block)
393 elif isinstance(block, Trap):
394 self.visitTrap(block)
395 elif isinstance(block, ReturnErrno):
396 self.visitReturnErrno(block)
397 elif isinstance(block, Trace):
398 self.visitTrace(block)
Luis Héctor Chávez59a64492021-01-03 05:46:47 -0800399 elif isinstance(block, UserNotify):
400 self.visitUserNotify(block)
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700401 elif isinstance(block, Log):
402 self.visitLog(block)
403 elif isinstance(block, Allow):
404 self.visitAllow(block)
405 elif isinstance(block, BasicBlock):
406 self.visitBasicBlock(block)
407 elif isinstance(block, ValidateArch):
408 self.visitValidateArch(block)
409 elif isinstance(block, SyscallEntry):
410 self.visitSyscallEntry(block)
411 elif isinstance(block, WideAtom):
412 self.visitWideAtom(block)
413 elif isinstance(block, Atom):
414 self.visitAtom(block)
415 else:
416 raise Exception('Unknown block type: %r' % block)
417
418 @abc.abstractmethod
419 def visitKillProcess(self, block):
420 pass
421
422 @abc.abstractmethod
423 def visitKillThread(self, block):
424 pass
425
426 @abc.abstractmethod
427 def visitTrap(self, block):
428 pass
429
430 @abc.abstractmethod
431 def visitReturnErrno(self, block):
432 pass
433
434 @abc.abstractmethod
435 def visitTrace(self, block):
436 pass
437
438 @abc.abstractmethod
Luis Héctor Chávez59a64492021-01-03 05:46:47 -0800439 def visitUserNotify(self, block):
440 pass
441
442 @abc.abstractmethod
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700443 def visitLog(self, block):
444 pass
445
446 @abc.abstractmethod
447 def visitAllow(self, block):
448 pass
449
450 @abc.abstractmethod
451 def visitBasicBlock(self, block):
452 pass
453
454 @abc.abstractmethod
455 def visitValidateArch(self, block):
456 pass
457
458 @abc.abstractmethod
459 def visitSyscallEntry(self, block):
460 pass
461
462 @abc.abstractmethod
463 def visitWideAtom(self, block):
464 pass
465
466 @abc.abstractmethod
467 def visitAtom(self, block):
468 pass
469
470
471class CopyingVisitor(AbstractVisitor):
472 """A visitor that copies Blocks."""
473
474 def __init__(self):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800475 super().__init__()
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700476 self._mapping = {}
477
478 def process(self, block):
479 self._mapping = {}
480 block.accept(self)
481 return self._mapping[id(block)]
482
483 def visitKillProcess(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800484 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700485 self._mapping[id(block)] = KillProcess()
486
487 def visitKillThread(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800488 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700489 self._mapping[id(block)] = KillThread()
490
491 def visitTrap(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800492 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700493 self._mapping[id(block)] = Trap()
494
495 def visitReturnErrno(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800496 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700497 self._mapping[id(block)] = ReturnErrno(block.errno)
498
499 def visitTrace(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800500 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700501 self._mapping[id(block)] = Trace()
502
Luis Héctor Chávez59a64492021-01-03 05:46:47 -0800503 def visitUserNotify(self, block):
504 assert id(block) not in self._mapping
505 self._mapping[id(block)] = UserNotify()
506
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700507 def visitLog(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800508 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700509 self._mapping[id(block)] = Log()
510
511 def visitAllow(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800512 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700513 self._mapping[id(block)] = Allow()
514
515 def visitBasicBlock(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800516 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700517 self._mapping[id(block)] = BasicBlock(block.instructions)
518
519 def visitValidateArch(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800520 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700521 self._mapping[id(block)] = ValidateArch(
522 block.arch, self._mapping[id(block.next_block)])
523
524 def visitSyscallEntry(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800525 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700526 self._mapping[id(block)] = SyscallEntry(
527 block.syscall_number,
528 self._mapping[id(block.jt)],
529 self._mapping[id(block.jf)],
530 op=block.op)
531
532 def visitWideAtom(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800533 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700534 self._mapping[id(block)] = WideAtom(
535 block.arg_offset, block.op, block.value, self._mapping[id(
536 block.jt)], self._mapping[id(block.jf)])
537
538 def visitAtom(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800539 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700540 self._mapping[id(block)] = Atom(block.arg_index, block.op, block.value,
541 self._mapping[id(block.jt)],
542 self._mapping[id(block.jf)])
543
544
545class LoweringVisitor(CopyingVisitor):
546 """A visitor that lowers Atoms into WideAtoms."""
547
548 def __init__(self, *, arch):
549 super().__init__()
550 self._bits = arch.bits
551
552 def visitAtom(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800553 assert id(block) not in self._mapping
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700554
555 lo = block.value & 0xFFFFFFFF
556 hi = (block.value >> 32) & 0xFFFFFFFF
557
558 lo_block = WideAtom(
559 arg_offset(block.arg_index, False), block.op, lo,
560 self._mapping[id(block.jt)], self._mapping[id(block.jf)])
561
562 if self._bits == 32:
563 self._mapping[id(block)] = lo_block
564 return
565
566 if block.op in (BPF_JGE, BPF_JGT):
567 # hi_1,lo_1 <op> hi_2,lo_2
568 #
569 # hi_1 > hi_2 || hi_1 == hi_2 && lo_1 <op> lo_2
570 if hi == 0:
571 # Special case: it's not needed to check whether |hi_1 == hi_2|,
572 # because it's true iff the JGT test fails.
573 self._mapping[id(block)] = WideAtom(
574 arg_offset(block.arg_index, True), BPF_JGT, hi,
575 self._mapping[id(block.jt)], lo_block)
576 return
577 hi_eq_block = WideAtom(
578 arg_offset(block.arg_index, True), BPF_JEQ, hi, lo_block,
579 self._mapping[id(block.jf)])
580 self._mapping[id(block)] = WideAtom(
581 arg_offset(block.arg_index, True), BPF_JGT, hi,
582 self._mapping[id(block.jt)], hi_eq_block)
583 return
584 if block.op == BPF_JSET:
585 # hi_1,lo_1 & hi_2,lo_2
586 #
587 # hi_1 & hi_2 || lo_1 & lo_2
588 if hi == 0:
589 # Special case: |hi_1 & hi_2| will never be True, so jump
590 # directly into the |lo_1 & lo_2| case.
591 self._mapping[id(block)] = lo_block
592 return
593 self._mapping[id(block)] = WideAtom(
594 arg_offset(block.arg_index, True), block.op, hi,
595 self._mapping[id(block.jt)], lo_block)
596 return
597
598 assert block.op == BPF_JEQ, block.op
599
600 # hi_1,lo_1 == hi_2,lo_2
601 #
602 # hi_1 == hi_2 && lo_1 == lo_2
603 self._mapping[id(block)] = WideAtom(
604 arg_offset(block.arg_index, True), block.op, hi, lo_block,
605 self._mapping[id(block.jf)])
606
607
608class FlatteningVisitor:
609 """A visitor that flattens a DAG of Block objects."""
610
611 def __init__(self, *, arch, kill_action):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800612 self._visited = set()
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700613 self._kill_action = kill_action
614 self._instructions = []
615 self._arch = arch
616 self._offsets = {}
617
618 @property
619 def result(self):
620 return BasicBlock(self._instructions)
621
622 def _distance(self, block):
623 distance = self._offsets[id(block)] + len(self._instructions)
624 assert distance >= 0
625 return distance
626
627 def _emit_load_arg(self, offset):
628 return [SockFilter(BPF_LD | BPF_W | BPF_ABS, 0, 0, offset)]
629
630 def _emit_jmp(self, op, value, jt_distance, jf_distance):
631 if jt_distance < 0x100 and jf_distance < 0x100:
632 return [
633 SockFilter(BPF_JMP | op | BPF_K, jt_distance, jf_distance,
634 value),
635 ]
636 if jt_distance + 1 < 0x100:
637 return [
638 SockFilter(BPF_JMP | op | BPF_K, jt_distance + 1, 0, value),
639 SockFilter(BPF_JMP | BPF_JA, 0, 0, jf_distance),
640 ]
641 if jf_distance + 1 < 0x100:
642 return [
643 SockFilter(BPF_JMP | op | BPF_K, 0, jf_distance + 1, value),
644 SockFilter(BPF_JMP | BPF_JA, 0, 0, jt_distance),
645 ]
646 return [
647 SockFilter(BPF_JMP | op | BPF_K, 0, 1, value),
648 SockFilter(BPF_JMP | BPF_JA, 0, 0, jt_distance + 1),
649 SockFilter(BPF_JMP | BPF_JA, 0, 0, jf_distance),
650 ]
651
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800652 def visited(self, block):
653 if id(block) in self._visited:
654 return True
655 self._visited.add(id(block))
656 return False
657
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700658 def visit(self, block):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800659 assert id(block) not in self._offsets
Luis Hector Chavez05392b82018-10-28 21:40:10 -0700660
661 if isinstance(block, BasicBlock):
662 instructions = block.instructions
663 elif isinstance(block, ValidateArch):
664 instructions = [
665 SockFilter(BPF_LD | BPF_W | BPF_ABS, 0, 0, 4),
666 SockFilter(BPF_JMP | BPF_JEQ | BPF_K,
667 self._distance(block.next_block) + 1, 0,
668 self._arch.arch_nr),
669 ] + self._kill_action.instructions + [
670 SockFilter(BPF_LD | BPF_W | BPF_ABS, 0, 0, 0),
671 ]
672 elif isinstance(block, SyscallEntry):
673 instructions = self._emit_jmp(block.op, block.syscall_number,
674 self._distance(block.jt),
675 self._distance(block.jf))
676 elif isinstance(block, WideAtom):
677 instructions = (
678 self._emit_load_arg(block.arg_offset) + self._emit_jmp(
679 block.op, block.value, self._distance(block.jt),
680 self._distance(block.jf)))
681 else:
682 raise Exception('Unknown block type: %r' % block)
683
684 self._instructions = instructions + self._instructions
685 self._offsets[id(block)] = -len(self._instructions)
686 return
Luis Hector Chaveza54812b2018-11-01 20:02:22 -0700687
688
689class ArgFilterForwardingVisitor:
690 """A visitor that forwards visitation to all arg filters."""
691
692 def __init__(self, visitor):
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800693 self._visited = set()
Luis Hector Chaveza54812b2018-11-01 20:02:22 -0700694 self.visitor = visitor
695
Luis Hector Chavez3b41ae32019-03-09 18:46:20 -0800696 def visited(self, block):
697 if id(block) in self._visited:
698 return True
699 self._visited.add(id(block))
700 return False
701
Luis Hector Chaveza54812b2018-11-01 20:02:22 -0700702 def visit(self, block):
703 # All arg filters are BasicBlocks.
704 if not isinstance(block, BasicBlock):
705 return
706 # But the ALLOW, KILL_PROCESS, TRAP, etc. actions are too and we don't
707 # want to visit them just yet.
708 if (isinstance(block, KillProcess) or isinstance(block, KillThread)
709 or isinstance(block, Trap) or isinstance(block, ReturnErrno)
Luis Héctor Chávez59a64492021-01-03 05:46:47 -0800710 or isinstance(block, Trace) or isinstance(block, UserNotify)
711 or isinstance(block, Log) or isinstance(block, Allow)):
Luis Hector Chaveza54812b2018-11-01 20:02:22 -0700712 return
713 block.accept(self.visitor)