seccompy 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seccompy/__init__.py +87 -0
- seccompy/_syscall.py +124 -0
- seccompy/bpf.py +248 -0
- seccompy/errors.py +10 -0
- seccompy/filter.py +275 -0
- seccompy/py.typed +0 -0
- seccompy/syscalls.py +825 -0
- seccompy/testing.py +63 -0
- seccompy-0.1.0.dist-info/METADATA +78 -0
- seccompy-0.1.0.dist-info/RECORD +12 -0
- seccompy-0.1.0.dist-info/WHEEL +4 -0
- seccompy-0.1.0.dist-info/licenses/LICENSE +12 -0
seccompy/__init__.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SPDX-License-Identifier: 0BSD
|
|
2
|
+
"""Python bindings for Linux seccomp-BPF syscall filtering.
|
|
3
|
+
|
|
4
|
+
seccompy lets unprivileged processes sandbox themselves by installing
|
|
5
|
+
classic BPF filters that the kernel evaluates on every syscall entry.
|
|
6
|
+
Filters are built in pure Python, checked against the native
|
|
7
|
+
architecture and loaded through seccomp(2) with no compiler or libbpf.
|
|
8
|
+
|
|
9
|
+
Kernel reference: https://docs.kernel.org/userspace-api/seccomp_filter.html
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import errno
|
|
13
|
+
|
|
14
|
+
from . import _syscall, testing
|
|
15
|
+
from .errors import SeccompError, UnsupportedError
|
|
16
|
+
from .filter import Action, Filter, FilterFlag
|
|
17
|
+
from .syscalls import (
|
|
18
|
+
AUDIT_ARCH_AARCH64,
|
|
19
|
+
AUDIT_ARCH_X86_64,
|
|
20
|
+
arch,
|
|
21
|
+
audit_arch,
|
|
22
|
+
syscall_name,
|
|
23
|
+
syscall_nr,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AUDIT_ARCH_AARCH64",
|
|
30
|
+
"AUDIT_ARCH_X86_64",
|
|
31
|
+
"Action",
|
|
32
|
+
"Filter",
|
|
33
|
+
"FilterFlag",
|
|
34
|
+
"SeccompError",
|
|
35
|
+
"UnsupportedError",
|
|
36
|
+
"__version__",
|
|
37
|
+
"action_supported",
|
|
38
|
+
"arch",
|
|
39
|
+
"audit_arch",
|
|
40
|
+
"flag_supported",
|
|
41
|
+
"supported",
|
|
42
|
+
"syscall_name",
|
|
43
|
+
"syscall_nr",
|
|
44
|
+
"testing",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def supported() -> bool:
|
|
49
|
+
"""Return whether the running kernel can install seccomp filters.
|
|
50
|
+
|
|
51
|
+
Probes SECCOMP_GET_ACTION_AVAIL first; kernels older than 4.8 lack
|
|
52
|
+
that operation but still support filters, so an EINVAL answer also
|
|
53
|
+
counts as supported when the seccomp syscall itself exists.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
_syscall.action_avail(int(Action.ALLOW))
|
|
57
|
+
except UnsupportedError:
|
|
58
|
+
try:
|
|
59
|
+
return _syscall.probe_flag(0)
|
|
60
|
+
except UnsupportedError:
|
|
61
|
+
return False
|
|
62
|
+
except SeccompError as exc:
|
|
63
|
+
if exc.errno == errno.EINVAL:
|
|
64
|
+
return True
|
|
65
|
+
raise
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def action_supported(action: Action | int) -> bool:
|
|
70
|
+
"""Return whether the kernel accepts a SECCOMP_RET_* action."""
|
|
71
|
+
try:
|
|
72
|
+
_syscall.action_avail(int(action))
|
|
73
|
+
except UnsupportedError:
|
|
74
|
+
return False
|
|
75
|
+
except SeccompError as exc:
|
|
76
|
+
if exc.errno == errno.EINVAL:
|
|
77
|
+
return False
|
|
78
|
+
raise
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def flag_supported(flag: FilterFlag) -> bool:
|
|
83
|
+
"""Return whether the kernel accepts a SECCOMP_FILTER_FLAG_* flag."""
|
|
84
|
+
try:
|
|
85
|
+
return _syscall.probe_flag(int(flag))
|
|
86
|
+
except UnsupportedError:
|
|
87
|
+
return False
|
seccompy/_syscall.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# SPDX-License-Identifier: 0BSD
|
|
2
|
+
"""Raw ctypes bindings for the seccomp syscall and prctl.
|
|
3
|
+
|
|
4
|
+
All calls go through libc's syscall(2) wrapper, so no libffi or compiler
|
|
5
|
+
is needed. The seccomp syscall number differs per architecture; the
|
|
6
|
+
number for the running architecture is chosen at call time.
|
|
7
|
+
|
|
8
|
+
Kernel reference: https://docs.kernel.org/userspace-api/seccomp_filter.html
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import ctypes
|
|
12
|
+
import ctypes.util
|
|
13
|
+
import errno
|
|
14
|
+
import os
|
|
15
|
+
import platform
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from .errors import SeccompError, UnsupportedError
|
|
19
|
+
|
|
20
|
+
PR_SET_NO_NEW_PRIVS = 38
|
|
21
|
+
|
|
22
|
+
SECCOMP_SET_MODE_FILTER = 1
|
|
23
|
+
SECCOMP_GET_ACTION_AVAIL = 2
|
|
24
|
+
SECCOMP_GET_NOTIF_SIZES = 3
|
|
25
|
+
|
|
26
|
+
_SYS_SECCOMP = {
|
|
27
|
+
"x86_64": 317,
|
|
28
|
+
"amd64": 317,
|
|
29
|
+
"aarch64": 277,
|
|
30
|
+
"arm64": 277,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SockFprog(ctypes.Structure):
|
|
35
|
+
"""struct sock_fprog: a u16 instruction count and a filter pointer."""
|
|
36
|
+
|
|
37
|
+
_fields_ = [
|
|
38
|
+
("len", ctypes.c_ushort),
|
|
39
|
+
("filter", ctypes.c_void_p),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_libc: ctypes.CDLL | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_libc() -> ctypes.CDLL:
|
|
47
|
+
global _libc
|
|
48
|
+
if _libc is None:
|
|
49
|
+
if sys.platform != "linux":
|
|
50
|
+
raise UnsupportedError("seccomp is only available on Linux")
|
|
51
|
+
name = ctypes.util.find_library("c")
|
|
52
|
+
_libc = ctypes.CDLL(name or None, use_errno=True)
|
|
53
|
+
_libc.syscall.restype = ctypes.c_long
|
|
54
|
+
_libc.prctl.restype = ctypes.c_int
|
|
55
|
+
return _libc
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _seccomp_nr() -> int:
|
|
59
|
+
machine = platform.machine().lower()
|
|
60
|
+
try:
|
|
61
|
+
return _SYS_SECCOMP[machine]
|
|
62
|
+
except KeyError:
|
|
63
|
+
raise UnsupportedError(f"no seccomp syscall number for {machine}") from None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _call(*args: object) -> int:
|
|
67
|
+
ret = int(_get_libc().syscall(*args))
|
|
68
|
+
if ret != -1:
|
|
69
|
+
return ret
|
|
70
|
+
err = ctypes.get_errno()
|
|
71
|
+
if err in (errno.ENOSYS, errno.EOPNOTSUPP):
|
|
72
|
+
raise UnsupportedError(err, os.strerror(err))
|
|
73
|
+
raise SeccompError(err, os.strerror(err))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _seccomp(op: int, flags: int, uargs: object) -> int:
|
|
77
|
+
return _call(_seccomp_nr(), op, flags, uargs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def action_avail(action: int) -> int:
|
|
81
|
+
"""Check whether the kernel supports a seccomp return action."""
|
|
82
|
+
val = ctypes.c_uint32(action)
|
|
83
|
+
return _seccomp(SECCOMP_GET_ACTION_AVAIL, 0, ctypes.byref(val))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def probe_flag(flag: int) -> bool:
|
|
87
|
+
"""Return whether a SECCOMP_FILTER_FLAG_* is supported.
|
|
88
|
+
|
|
89
|
+
A NULL filter program is passed, so the kernel validates the flag and
|
|
90
|
+
then fails on the bad pointer without installing anything. EINVAL
|
|
91
|
+
means the flag is unknown; EFAULT or EACCES mean it passed flag
|
|
92
|
+
validation.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
_seccomp(SECCOMP_SET_MODE_FILTER, flag, None)
|
|
96
|
+
except UnsupportedError:
|
|
97
|
+
raise
|
|
98
|
+
except SeccompError as exc:
|
|
99
|
+
if exc.errno == errno.EINVAL:
|
|
100
|
+
return False
|
|
101
|
+
if exc.errno in (errno.EFAULT, errno.EACCES):
|
|
102
|
+
return True
|
|
103
|
+
raise
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def set_no_new_privs() -> None:
|
|
108
|
+
"""Set the no_new_privs attribute on the calling thread via prctl."""
|
|
109
|
+
ret = _get_libc().prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
|
|
110
|
+
if ret == -1:
|
|
111
|
+
err = ctypes.get_errno()
|
|
112
|
+
raise SeccompError(err, os.strerror(err))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def set_mode_filter(program: bytes, flags: int = 0) -> int:
|
|
116
|
+
"""Install a classic BPF filter via SECCOMP_SET_MODE_FILTER."""
|
|
117
|
+
if len(program) % 8 != 0 or not program:
|
|
118
|
+
raise ValueError("program must be a non-empty multiple of 8 bytes")
|
|
119
|
+
insns = len(program) // 8
|
|
120
|
+
if insns > 4096:
|
|
121
|
+
raise ValueError("program exceeds the kernel limit of 4096 instructions")
|
|
122
|
+
buf = (ctypes.c_char * len(program)).from_buffer_copy(program)
|
|
123
|
+
fprog = SockFprog(len=insns, filter=ctypes.addressof(buf))
|
|
124
|
+
return _seccomp(SECCOMP_SET_MODE_FILTER, flags, ctypes.byref(fprog))
|
seccompy/bpf.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# SPDX-License-Identifier: 0BSD
|
|
2
|
+
"""Classic BPF instruction model and assembler for seccomp filters.
|
|
3
|
+
|
|
4
|
+
A seccomp filter is a classic BPF program run over struct seccomp_data.
|
|
5
|
+
This module lets callers describe programs as instruction objects with
|
|
6
|
+
symbolic jump labels; assemble() resolves the labels into the 8-bit
|
|
7
|
+
relative offsets struct sock_filter requires. Classic BPF only jumps
|
|
8
|
+
forward, and conditional jumps are limited to 255 instructions, so a
|
|
9
|
+
conditional jump whose target lies farther away is expanded into a short
|
|
10
|
+
conditional hop over an unconditional 32-bit JA.
|
|
11
|
+
|
|
12
|
+
The result is packed as little-endian struct sock_filter records of
|
|
13
|
+
8 bytes each: u16 code, u8 jt, u8 jf, u32 k.
|
|
14
|
+
|
|
15
|
+
Kernel reference: https://docs.kernel.org/userspace-api/seccomp_filter.html
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import struct
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Final
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"BPF_JA",
|
|
24
|
+
"BPF_JEQ",
|
|
25
|
+
"BPF_JGEQ",
|
|
26
|
+
"BPF_JGT",
|
|
27
|
+
"BPF_JSET",
|
|
28
|
+
"BPF_LD_ABS_W",
|
|
29
|
+
"BPF_RET_K",
|
|
30
|
+
"Insn",
|
|
31
|
+
"Ja",
|
|
32
|
+
"Jump",
|
|
33
|
+
"Load",
|
|
34
|
+
"Ret",
|
|
35
|
+
"assemble",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
BPF_LD_ABS_W: Final = 0x20
|
|
39
|
+
"""BPF_LD | BPF_W | BPF_ABS: load the 32-bit word at byte offset k."""
|
|
40
|
+
|
|
41
|
+
BPF_RET_K: Final = 0x06
|
|
42
|
+
"""BPF_RET | BPF_K: terminate the program with result k."""
|
|
43
|
+
|
|
44
|
+
BPF_JA: Final = 0x05
|
|
45
|
+
"""BPF_JMP | BPF_JA: unconditional jump forward by k instructions."""
|
|
46
|
+
|
|
47
|
+
BPF_JEQ: Final = 0x15
|
|
48
|
+
"""BPF_JMP | BPF_JEQ | BPF_K: jump if A equals k."""
|
|
49
|
+
|
|
50
|
+
BPF_JGT: Final = 0x25
|
|
51
|
+
"""BPF_JMP | BPF_JGT | BPF_K: jump if A is greater than k."""
|
|
52
|
+
|
|
53
|
+
BPF_JGEQ: Final = 0x35
|
|
54
|
+
"""BPF_JMP | BPF_JGEQ | BPF_K: jump if A is greater than or equal to k."""
|
|
55
|
+
|
|
56
|
+
BPF_JSET: Final = 0x45
|
|
57
|
+
"""BPF_JMP | BPF_JSET | BPF_K: jump if A has any bit of k set."""
|
|
58
|
+
|
|
59
|
+
_JUMP_OPS: Final = frozenset({BPF_JEQ, BPF_JGT, BPF_JGEQ, BPF_JSET})
|
|
60
|
+
|
|
61
|
+
_MAX_COND_JUMP: Final = 255
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Insn:
|
|
65
|
+
"""Base class for BPF instructions."""
|
|
66
|
+
|
|
67
|
+
__slots__ = ()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class Load(Insn):
|
|
72
|
+
"""Load the 32-bit word at byte offset k of the packet data."""
|
|
73
|
+
|
|
74
|
+
k: int
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class Jump(Insn):
|
|
79
|
+
"""Compare A against k and jump to the jt or jf label.
|
|
80
|
+
|
|
81
|
+
A label of None means fall through to the next instruction.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
op: int
|
|
85
|
+
k: int
|
|
86
|
+
jt: str | None = None
|
|
87
|
+
jf: str | None = None
|
|
88
|
+
|
|
89
|
+
def __post_init__(self) -> None:
|
|
90
|
+
if self.op not in _JUMP_OPS:
|
|
91
|
+
raise ValueError(f"not a conditional jump opcode: {self.op:#x}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class Ja(Insn):
|
|
96
|
+
"""Jump unconditionally to a label."""
|
|
97
|
+
|
|
98
|
+
target: str
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class Ret(Insn):
|
|
103
|
+
"""Terminate the program with result k."""
|
|
104
|
+
|
|
105
|
+
k: int
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _positions(sizes: list[int]) -> list[int]:
|
|
109
|
+
"""Instruction start positions plus the position past the end."""
|
|
110
|
+
pos = [0] * (len(sizes) + 1)
|
|
111
|
+
for i in range(1, len(sizes) + 1):
|
|
112
|
+
pos[i] = pos[i - 1] + sizes[i - 1]
|
|
113
|
+
return pos
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _expanded_size(jump: Jump) -> int:
|
|
117
|
+
if jump.jt is None:
|
|
118
|
+
return 2
|
|
119
|
+
return 3 if jump.jf is not None else 2
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def assemble(insns: list[Insn], labels: dict[str, int]) -> bytes:
|
|
123
|
+
"""Resolve labels and pack insns into struct sock_filter bytes.
|
|
124
|
+
|
|
125
|
+
labels maps each symbolic name to an instruction index, which may be
|
|
126
|
+
len(insns) to name the position just past the last instruction.
|
|
127
|
+
Raises ValueError for unknown labels, backward jumps or jumps that
|
|
128
|
+
still cannot be encoded.
|
|
129
|
+
"""
|
|
130
|
+
_validate_labels(insns, labels)
|
|
131
|
+
sizes = _resolve_sizes(insns, labels)
|
|
132
|
+
pos = _positions(sizes)
|
|
133
|
+
out = bytearray()
|
|
134
|
+
for i, insn in enumerate(insns):
|
|
135
|
+
out += _emit(insn, labels, pos, i, sizes[i])
|
|
136
|
+
return bytes(out)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _validate_labels(insns: list[Insn], labels: dict[str, int]) -> None:
|
|
140
|
+
for insn in insns:
|
|
141
|
+
for target in _targets(insn):
|
|
142
|
+
if target not in labels:
|
|
143
|
+
raise ValueError(f"undefined label: {target}")
|
|
144
|
+
if labels[target] > len(insns):
|
|
145
|
+
raise ValueError(f"label out of range: {target}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _resolve_sizes(insns: list[Insn], labels: dict[str, int]) -> list[int]:
|
|
149
|
+
"""Size each instruction, expanding conditional jumps that resolve
|
|
150
|
+
to offsets beyond 255 into a hop plus JA trampolines. Expansion
|
|
151
|
+
only grows programs, so iterating to a fixed point terminates."""
|
|
152
|
+
sizes = [1] * len(insns)
|
|
153
|
+
for _ in range(len(insns) + 1):
|
|
154
|
+
pos = _positions(sizes)
|
|
155
|
+
changed = False
|
|
156
|
+
for i, insn in enumerate(insns):
|
|
157
|
+
if not isinstance(insn, Jump) or sizes[i] > 1:
|
|
158
|
+
continue
|
|
159
|
+
if _needs_expansion(insn, labels, pos, i):
|
|
160
|
+
sizes[i] = _expanded_size(insn)
|
|
161
|
+
changed = True
|
|
162
|
+
if not changed:
|
|
163
|
+
return sizes
|
|
164
|
+
raise ValueError("jump resolution did not converge")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _targets(insn: Insn) -> list[str]:
|
|
168
|
+
if isinstance(insn, Jump):
|
|
169
|
+
return [t for t in (insn.jt, insn.jf) if t is not None]
|
|
170
|
+
if isinstance(insn, Ja):
|
|
171
|
+
return [insn.target]
|
|
172
|
+
return []
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _offset(
|
|
176
|
+
target: str | None, labels: dict[str, int], pos: list[int], i: int
|
|
177
|
+
) -> int | None:
|
|
178
|
+
if target is None:
|
|
179
|
+
return None
|
|
180
|
+
off = pos[labels[target]] - pos[i] - 1
|
|
181
|
+
if off < 0:
|
|
182
|
+
raise ValueError(f"backward jump to label: {target}")
|
|
183
|
+
return off
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _needs_expansion(
|
|
187
|
+
jump: Jump, labels: dict[str, int], pos: list[int], i: int
|
|
188
|
+
) -> bool:
|
|
189
|
+
for target in (jump.jt, jump.jf):
|
|
190
|
+
off = _offset(target, labels, pos, i)
|
|
191
|
+
if off is not None and off > _MAX_COND_JUMP:
|
|
192
|
+
return True
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _emit(
|
|
197
|
+
insn: Insn, labels: dict[str, int], pos: list[int], i: int, size: int
|
|
198
|
+
) -> bytes:
|
|
199
|
+
if isinstance(insn, Load):
|
|
200
|
+
return struct.pack("<HBBI", BPF_LD_ABS_W, 0, 0, insn.k)
|
|
201
|
+
if isinstance(insn, Ja):
|
|
202
|
+
off = _offset(insn.target, labels, pos, i)
|
|
203
|
+
if off is None or off > 0xFFFFFFFF:
|
|
204
|
+
raise ValueError(f"JA target out of range: {insn.target}")
|
|
205
|
+
return struct.pack("<HBBI", BPF_JA, 0, 0, off)
|
|
206
|
+
if isinstance(insn, Ret):
|
|
207
|
+
return struct.pack("<HBBI", BPF_RET_K, 0, 0, insn.k)
|
|
208
|
+
if isinstance(insn, Jump):
|
|
209
|
+
jt = _offset(insn.jt, labels, pos, i)
|
|
210
|
+
jf = _offset(insn.jf, labels, pos, i)
|
|
211
|
+
if size == 1:
|
|
212
|
+
if (jt is not None and jt > _MAX_COND_JUMP) or (
|
|
213
|
+
jf is not None and jf > _MAX_COND_JUMP
|
|
214
|
+
):
|
|
215
|
+
raise ValueError("conditional jump out of range")
|
|
216
|
+
return struct.pack("<HBBI", insn.op, jt or 0, jf or 0, insn.k)
|
|
217
|
+
return _emit_expanded(insn, labels, pos, i)
|
|
218
|
+
raise TypeError(f"unknown instruction: {insn!r}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _emit_expanded(jump: Jump, labels: dict[str, int], pos: list[int], i: int) -> bytes:
|
|
222
|
+
"""Encode a far conditional jump as a hop plus JA trampolines.
|
|
223
|
+
|
|
224
|
+
jt None: jXX jt=1 jf=0; JA jf (true falls past the JA)
|
|
225
|
+
jf None: jXX jt=0 jf=1; JA jt (false falls past the JA)
|
|
226
|
+
both: jXX jt=0 jf=1; JA jt; JA jf
|
|
227
|
+
"""
|
|
228
|
+
out = bytearray()
|
|
229
|
+
if jump.jt is None:
|
|
230
|
+
out += struct.pack("<HBBI", jump.op, 1, 0, jump.k)
|
|
231
|
+
out += _ja_bytes(jump.jf, labels, pos, pos[i] + 1)
|
|
232
|
+
else:
|
|
233
|
+
out += struct.pack("<HBBI", jump.op, 0, 1, jump.k)
|
|
234
|
+
out += _ja_bytes(jump.jt, labels, pos, pos[i] + 1)
|
|
235
|
+
if jump.jf is not None:
|
|
236
|
+
out += _ja_bytes(jump.jf, labels, pos, pos[i] + 2)
|
|
237
|
+
return bytes(out)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _ja_bytes(
|
|
241
|
+
target: str | None, labels: dict[str, int], pos: list[int], pc: int
|
|
242
|
+
) -> bytes:
|
|
243
|
+
if target is None:
|
|
244
|
+
raise ValueError("JA without a target")
|
|
245
|
+
off = pos[labels[target]] - pc - 1
|
|
246
|
+
if off < 0 or off > 0xFFFFFFFF:
|
|
247
|
+
raise ValueError(f"JA target out of range: {target}")
|
|
248
|
+
return struct.pack("<HBBI", BPF_JA, 0, 0, off)
|
seccompy/errors.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# SPDX-License-Identifier: 0BSD
|
|
2
|
+
"""Exception types raised by seccompy."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SeccompError(OSError):
|
|
6
|
+
"""A seccomp-related syscall failed."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UnsupportedError(SeccompError):
|
|
10
|
+
"""The running kernel does not support seccomp or the requested feature."""
|