seccomp-ruby 0.1.0
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +43 -0
- data/.yardopts +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +132 -0
- data/Rakefile +34 -0
- data/examples/arg_filter.rb +18 -0
- data/examples/basic_allowlist.rb +20 -0
- data/examples/errno_filter.rb +18 -0
- data/examples/notify_supervisor.rb +26 -0
- data/ext/seccomp_ext/const.c +133 -0
- data/ext/seccomp_ext/extconf.rb +45 -0
- data/ext/seccomp_ext/filter.c +685 -0
- data/ext/seccomp_ext/notify.c +184 -0
- data/ext/seccomp_ext/seccomp_ext.c +20 -0
- data/ext/seccomp_ext/seccomp_ext.h +35 -0
- data/ext/seccomp_ext/syscall.c +86 -0
- data/ext/seccomp_ext/util.c +110 -0
- data/lib/libseccomp.rb +77 -0
- data/lib/seccomp/action.rb +154 -0
- data/lib/seccomp/arch.rb +48 -0
- data/lib/seccomp/arg.rb +107 -0
- data/lib/seccomp/attributes.rb +127 -0
- data/lib/seccomp/dsl.rb +33 -0
- data/lib/seccomp/errors.rb +83 -0
- data/lib/seccomp/filter.rb +425 -0
- data/lib/seccomp/low_level.rb +210 -0
- data/lib/seccomp/notification.rb +95 -0
- data/lib/seccomp/notifier.rb +187 -0
- data/lib/seccomp/syscall.rb +44 -0
- data/lib/seccomp/version.rb +6 -0
- data/lib/seccomp.rb +3 -0
- data/sig/seccomp.rbs +300 -0
- metadata +75 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
module Seccomp
|
|
6
|
+
# Mutable seccomp filter with automatic native-resource management.
|
|
7
|
+
class Filter
|
|
8
|
+
include MonitorMixin
|
|
9
|
+
include Attributes
|
|
10
|
+
include Arg::DSL
|
|
11
|
+
include DSL
|
|
12
|
+
|
|
13
|
+
NOTIFICATION_MONITOR = Monitor.new
|
|
14
|
+
private_constant :NOTIFICATION_MONITOR
|
|
15
|
+
|
|
16
|
+
# @param default_action [Symbol, Integer] default filter action
|
|
17
|
+
# @return [void]
|
|
18
|
+
# @raise [Error] if native initialization fails
|
|
19
|
+
# @example `Filter.new(:allow)`
|
|
20
|
+
def initialize(default_action = :kill_process)
|
|
21
|
+
super()
|
|
22
|
+
@default_action = Action.resolve(default_action)
|
|
23
|
+
@notification_rule = @default_action == Action::NOTIFY
|
|
24
|
+
@listener_active = false
|
|
25
|
+
@notification_receive_lock = Mutex.new
|
|
26
|
+
@context = LowLevel.init(@default_action)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param default_action [Symbol, Integer] default action
|
|
30
|
+
# @yield [Filter] filter closed after the block
|
|
31
|
+
# @return [Object, Filter] block result, or a filter without a block
|
|
32
|
+
# @raise [Error] if initialization fails
|
|
33
|
+
# @example `Filter.open(:allow) { |filter| filter.allow(:read) }`
|
|
34
|
+
def self.open(default_action = :kill_process)
|
|
35
|
+
filter = new(default_action)
|
|
36
|
+
return filter unless block_given?
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
yield filter
|
|
40
|
+
ensure
|
|
41
|
+
filter.close
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [nil]
|
|
46
|
+
# @example `filter.close`
|
|
47
|
+
def close
|
|
48
|
+
NOTIFICATION_MONITOR.synchronize do
|
|
49
|
+
synchronize do
|
|
50
|
+
LowLevel.release(@context)
|
|
51
|
+
@listener_active = false
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Boolean] whether native resources were released
|
|
58
|
+
# @example `filter.closed?`
|
|
59
|
+
def closed?
|
|
60
|
+
synchronize { LowLevel.closed?(@context) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param default_action [Symbol, Integer, nil] replacement action
|
|
64
|
+
# @return [Filter] receiver
|
|
65
|
+
# @raise [Error] if reset fails
|
|
66
|
+
# @example `filter.reset(:allow)`
|
|
67
|
+
def reset(default_action = nil)
|
|
68
|
+
synchronize do
|
|
69
|
+
action = Action.resolve(default_action.nil? ? @default_action : default_action)
|
|
70
|
+
ensure_notification_tsync_supported!(notification_action: action == Action::NOTIFY)
|
|
71
|
+
Error.check!(LowLevel.reset(@context, action), call: "seccomp_reset")
|
|
72
|
+
@default_action = action
|
|
73
|
+
@notification_rule = action == Action::NOTIFY
|
|
74
|
+
end
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param other [Filter] source consumed on success
|
|
79
|
+
# @return [Filter] receiver
|
|
80
|
+
# @raise [Error] if filters are incompatible
|
|
81
|
+
# @raise [TypeError] unless other is a Filter
|
|
82
|
+
# @example `destination.merge!(source)`
|
|
83
|
+
def merge!(other)
|
|
84
|
+
raise TypeError, "other must be a Seccomp::Filter" unless other.is_a?(Filter)
|
|
85
|
+
raise ArgumentError, "cannot merge a filter into itself" if equal?(other)
|
|
86
|
+
|
|
87
|
+
first, second = [self, other].sort_by(&:object_id)
|
|
88
|
+
first.synchronize do
|
|
89
|
+
second.synchronize do
|
|
90
|
+
notification_rule = @notification_rule || other.instance_variable_get(:@notification_rule)
|
|
91
|
+
if notification_rule
|
|
92
|
+
ensure_notification_tsync_supported!(notification_action: true,
|
|
93
|
+
tsync: tsync? || other.tsync?)
|
|
94
|
+
end
|
|
95
|
+
Error.check!(LowLevel.merge(@context, other.__send__(:context)), call: "seccomp_merge")
|
|
96
|
+
merge_state_from(other)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @param arch [Symbol, String, Integer] architecture
|
|
103
|
+
# @return [Filter] receiver
|
|
104
|
+
# @raise [Error] if it cannot be added
|
|
105
|
+
# @example `filter.add_arch(:x86)`
|
|
106
|
+
def add_arch(arch)
|
|
107
|
+
change_arch(:arch_add, arch)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @param arch [Symbol, String, Integer] architecture
|
|
111
|
+
# @return [Filter] receiver
|
|
112
|
+
# @raise [Error] if it cannot be removed
|
|
113
|
+
# @example `filter.remove_arch(:x86)`
|
|
114
|
+
def remove_arch(arch)
|
|
115
|
+
change_arch(:arch_remove, arch)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @param arch [Symbol, String, Integer] architecture
|
|
119
|
+
# @return [Boolean] whether present
|
|
120
|
+
# @raise [Error] if the query fails
|
|
121
|
+
# @example `filter.arch?(:x86_64)`
|
|
122
|
+
def arch?(arch)
|
|
123
|
+
synchronize do
|
|
124
|
+
result = LowLevel.arch_exist(@context, Arch.resolve(arch))
|
|
125
|
+
return true if result.zero?
|
|
126
|
+
return false if result == -Errno::EEXIST::Errno
|
|
127
|
+
|
|
128
|
+
Error.check!(result, call: "seccomp_arch_exist", hint: "architecture #{arch}")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @return [Array<Symbol>] configured architectures
|
|
133
|
+
# @raise [Error] if a query fails
|
|
134
|
+
# @example `filter.archs`
|
|
135
|
+
def archs
|
|
136
|
+
synchronize { Arch.all.keys.select { |arch| arch != :native && arch?(arch) } }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @param action [Symbol, Integer, Array] rule action
|
|
140
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
141
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
142
|
+
# @return [Filter] receiver
|
|
143
|
+
# @raise [Error] if the rule is invalid
|
|
144
|
+
# @example `filter.add_rule(:allow, :write, filter.arg(0).eq(1))`
|
|
145
|
+
def add_rule(action, syscall, *comparisons)
|
|
146
|
+
add_rule_with(:rule_add_array, action, syscall, comparisons)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @param action [Symbol, Integer, Array] rule action
|
|
150
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
151
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
152
|
+
# @return [Filter] receiver
|
|
153
|
+
# @raise [Error] if an exact rule cannot be represented
|
|
154
|
+
# @example `filter.add_rule_exact(:allow, :write)`
|
|
155
|
+
def add_rule_exact(action, syscall, *comparisons)
|
|
156
|
+
add_rule_with(:rule_add_exact_array, action, syscall, comparisons)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @param syscall [Symbol, String, Integer] first syscall
|
|
160
|
+
# @param arguments [Array] additional syscalls followed by comparisons
|
|
161
|
+
# @return [Filter] receiver
|
|
162
|
+
# @raise [Error] if a rule is invalid
|
|
163
|
+
# @example `filter.allow(:read, :write)`
|
|
164
|
+
def allow(syscall, *arguments)
|
|
165
|
+
comparisons = arguments.drop_while { |argument| !argument.is_a?(ArgCmp) }
|
|
166
|
+
syscalls = [syscall, *arguments.take(arguments.length - comparisons.length)]
|
|
167
|
+
raise TypeError, "syscalls must precede comparisons" unless comparisons.all?(ArgCmp)
|
|
168
|
+
|
|
169
|
+
synchronize { syscalls.each { |name| add_rule(:allow, name, *comparisons) } }
|
|
170
|
+
self
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
174
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
175
|
+
# @param errno [Integer, SystemCallError, Class] denial errno
|
|
176
|
+
# @return [Filter] receiver
|
|
177
|
+
# @raise [Error] if the rule is invalid
|
|
178
|
+
# @example `filter.deny(:ptrace, errno: Errno::EPERM)`
|
|
179
|
+
def deny(syscall, *comparisons, errno: Errno::EPERM)
|
|
180
|
+
add_rule(Action.errno(errno), syscall, *comparisons)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
184
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
185
|
+
# @return [Filter] receiver
|
|
186
|
+
# @raise [Error] if the rule is invalid
|
|
187
|
+
# @example `filter.kill(:ptrace)`
|
|
188
|
+
def kill(syscall, *comparisons)
|
|
189
|
+
add_rule(:kill_process, syscall, *comparisons)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
193
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
194
|
+
# @return [Filter] receiver
|
|
195
|
+
# @raise [Error] if the rule is invalid
|
|
196
|
+
# @example `filter.log(:socket)`
|
|
197
|
+
def log(syscall, *comparisons)
|
|
198
|
+
add_rule(:log, syscall, *comparisons)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
202
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
203
|
+
# @return [Filter] receiver
|
|
204
|
+
# @raise [Error] if notifications are unsupported
|
|
205
|
+
# @example `filter.notify(:openat)`
|
|
206
|
+
def notify(syscall, *comparisons)
|
|
207
|
+
add_rule(:notify, syscall, *comparisons)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
211
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
212
|
+
# @return [Filter] receiver
|
|
213
|
+
# @raise [Error] if the rule is invalid
|
|
214
|
+
# @example `filter.trap(:ptrace)`
|
|
215
|
+
def trap(syscall, *comparisons)
|
|
216
|
+
add_rule(:trap, syscall, *comparisons)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
220
|
+
# @param value [Integer] trace payload
|
|
221
|
+
# @param comparisons [Array<ArgCmp>] argument comparisons
|
|
222
|
+
# @return [Filter] receiver
|
|
223
|
+
# @raise [Error] if the rule is invalid
|
|
224
|
+
# @example `filter.trace(:ptrace, 42)`
|
|
225
|
+
def trace(syscall, value, *comparisons)
|
|
226
|
+
add_rule(Action.trace(value), syscall, *comparisons)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @param syscall [Symbol, String, Integer] syscall
|
|
230
|
+
# @param value [Integer] priority from 0 through 255
|
|
231
|
+
# @return [Filter] receiver
|
|
232
|
+
# @raise [Error] if rejected
|
|
233
|
+
# @example `filter.priority(:write, 100)`
|
|
234
|
+
def priority(syscall, value)
|
|
235
|
+
synchronize do
|
|
236
|
+
Error.check!(LowLevel.syscall_priority(@context, Syscall.number(syscall), value),
|
|
237
|
+
call: "seccomp_syscall_priority", hint: "syscall #{syscall}")
|
|
238
|
+
end
|
|
239
|
+
self
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @param io [IO] writable IO
|
|
243
|
+
# @return [nil]
|
|
244
|
+
# @raise [Error] if export fails
|
|
245
|
+
# @example `filter.export_pfc($stdout)`
|
|
246
|
+
def export_pfc(io)
|
|
247
|
+
export_to(:export_pfc, io)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# @param io [IO] writable binary IO
|
|
251
|
+
# @return [nil]
|
|
252
|
+
# @raise [Error] if export fails
|
|
253
|
+
# @example `filter.export_bpf(file)`
|
|
254
|
+
def export_bpf(io)
|
|
255
|
+
export_to(:export_bpf, io)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# @return [String] pseudo filter code
|
|
259
|
+
# @raise [Error] if export fails
|
|
260
|
+
# @example `puts filter.to_pfc`
|
|
261
|
+
def to_pfc
|
|
262
|
+
export_string(:export_pfc)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# @return [String] binary BPF program
|
|
266
|
+
# @raise [Error] if export fails
|
|
267
|
+
# @example `filter.to_bpf.bytesize`
|
|
268
|
+
def to_bpf
|
|
269
|
+
if Seccomp.supports?(:bpf_mem)
|
|
270
|
+
synchronize do
|
|
271
|
+
rc, result = LowLevel.export_bpf_mem(@context)
|
|
272
|
+
Error.check!(rc, call: "seccomp_export_bpf_mem")
|
|
273
|
+
result
|
|
274
|
+
end
|
|
275
|
+
else
|
|
276
|
+
export_string(:export_bpf, binmode: true)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# @return [Filter] receiver
|
|
281
|
+
# @raise [NotSupportedError] when unavailable
|
|
282
|
+
# @example `filter.precompute`
|
|
283
|
+
def precompute
|
|
284
|
+
synchronize do
|
|
285
|
+
Error.check!(LowLevel.precompute(@context), call: "seccomp_precompute")
|
|
286
|
+
end
|
|
287
|
+
self
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# @return [nil]
|
|
291
|
+
# @raise [Error] if the irreversible load fails
|
|
292
|
+
# @example `fork { filter.load!; Process.exit!(0) }`
|
|
293
|
+
def load!
|
|
294
|
+
NOTIFICATION_MONITOR.synchronize do
|
|
295
|
+
synchronize do
|
|
296
|
+
ensure_notification_tsync_supported!
|
|
297
|
+
if notification_action? && LowLevel.notify_fd(@context) >= 0
|
|
298
|
+
raise NotificationError, "a notification listener is already active"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
Error.check!(LowLevel.load(@context), call: "seccomp_load")
|
|
302
|
+
@listener_active ||= @notification_rule
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
nil
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# @return [Boolean] whether load succeeded at least once
|
|
309
|
+
# @example `filter.loaded?`
|
|
310
|
+
def loaded?
|
|
311
|
+
synchronize { LowLevel.loaded?(@context) }
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# @yield transactional filter changes
|
|
315
|
+
# @return [Object] block result
|
|
316
|
+
# @raise [NotSupportedError] when unavailable
|
|
317
|
+
# @example `filter.transaction { filter.allow(:read) }`
|
|
318
|
+
def transaction
|
|
319
|
+
synchronize do
|
|
320
|
+
raise ArgumentError, "nested transactions are not allowed" if @in_transaction
|
|
321
|
+
|
|
322
|
+
previous_notification_rule = @notification_rule
|
|
323
|
+
begin
|
|
324
|
+
Error.check!(LowLevel.transaction_start(@context), call: "seccomp_transaction_start")
|
|
325
|
+
@in_transaction = true
|
|
326
|
+
result = yield
|
|
327
|
+
Error.check!(LowLevel.transaction_commit(@context), call: "seccomp_transaction_commit")
|
|
328
|
+
committed = true
|
|
329
|
+
result
|
|
330
|
+
ensure
|
|
331
|
+
LowLevel.transaction_reject(@context) if @in_transaction && !committed && !closed?
|
|
332
|
+
@notification_rule = previous_notification_rule unless committed
|
|
333
|
+
@in_transaction = false
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# @return [Notifier] notification listener
|
|
339
|
+
# @raise [NotificationError] before loading or without a listener
|
|
340
|
+
# @example `notifier = filter.notifier`
|
|
341
|
+
def notifier
|
|
342
|
+
NOTIFICATION_MONITOR.synchronize do
|
|
343
|
+
synchronize do
|
|
344
|
+
raise NotificationError, "filter has not been loaded" unless loaded?
|
|
345
|
+
unless @listener_active
|
|
346
|
+
raise NotificationError, "filter has no active notification listener"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
fd = LowLevel.notify_fd(@context)
|
|
350
|
+
if fd.negative?
|
|
351
|
+
raise NotificationError.new("seccomp_notify_fd failed", errno: -fd,
|
|
352
|
+
call: "seccomp_notify_fd")
|
|
353
|
+
end
|
|
354
|
+
Notifier.new(self, fd)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
protected
|
|
360
|
+
|
|
361
|
+
attr_reader :context, :notification_receive_lock
|
|
362
|
+
|
|
363
|
+
private
|
|
364
|
+
|
|
365
|
+
def add_rule_with(operation, action, syscall, comparisons)
|
|
366
|
+
action = Action.resolve(action)
|
|
367
|
+
encoded = comparisons.map do |comparison|
|
|
368
|
+
raise TypeError, "comparison must be a Seccomp::ArgCmp" unless comparison.is_a?(ArgCmp)
|
|
369
|
+
|
|
370
|
+
comparison.to_a
|
|
371
|
+
end
|
|
372
|
+
synchronize do
|
|
373
|
+
ensure_notification_tsync_supported!(notification_action: true) if action == Action::NOTIFY
|
|
374
|
+
Error.check!(LowLevel.public_send(operation, @context, action,
|
|
375
|
+
Syscall.number(syscall), encoded),
|
|
376
|
+
call: "seccomp_#{operation}", hint: "syscall #{syscall}")
|
|
377
|
+
@notification_rule = true if action == Action::NOTIFY
|
|
378
|
+
end
|
|
379
|
+
self
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def export_to(operation, io)
|
|
383
|
+
fd = io.fileno
|
|
384
|
+
synchronize do
|
|
385
|
+
Error.check!(LowLevel.public_send(operation, @context, fd),
|
|
386
|
+
call: "seccomp_#{operation}")
|
|
387
|
+
end
|
|
388
|
+
io.flush
|
|
389
|
+
nil
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def notification_action? = @notification_rule
|
|
393
|
+
|
|
394
|
+
def merge_state_from(other)
|
|
395
|
+
@notification_rule ||= other.instance_variable_get(:@notification_rule)
|
|
396
|
+
@listener_active ||= other.instance_variable_get(:@listener_active)
|
|
397
|
+
other.instance_variable_set(:@listener_active, false)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def ensure_notification_tsync_supported!(notification_action: notification_action?, tsync: nil)
|
|
401
|
+
return unless notification_action
|
|
402
|
+
return unless (tsync.nil? ? tsync? : tsync) && Seccomp.api_level < 6
|
|
403
|
+
|
|
404
|
+
raise NotSupportedError, "tsync with notifications requires libseccomp API level 6"
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def export_string(operation, binmode: false)
|
|
408
|
+
require "tempfile"
|
|
409
|
+
Tempfile.create("seccomp") do |file|
|
|
410
|
+
file.binmode if binmode
|
|
411
|
+
export_to(operation, file)
|
|
412
|
+
file.rewind
|
|
413
|
+
return file.read
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def change_arch(operation, arch)
|
|
418
|
+
synchronize do
|
|
419
|
+
Error.check!(LowLevel.public_send(operation, @context, Arch.resolve(arch)),
|
|
420
|
+
call: "seccomp_#{operation}", hint: "architecture #{arch}")
|
|
421
|
+
end
|
|
422
|
+
self
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Seccomp
|
|
4
|
+
# Thin C-compatible API. Methods return negative errno values unless documented otherwise.
|
|
5
|
+
module LowLevel
|
|
6
|
+
# Opaque native filter context.
|
|
7
|
+
# rubocop:disable-next Lint/EmptyClass -- the C extension supplies its implementation.
|
|
8
|
+
class Context; end
|
|
9
|
+
|
|
10
|
+
# @!method self.version
|
|
11
|
+
# @return [Array<Integer>] major, minor, and micro version
|
|
12
|
+
# @example `LowLevel.version #=> [2, 6, 1]`
|
|
13
|
+
# @!method self.api_get
|
|
14
|
+
# @return [Integer] runtime API level
|
|
15
|
+
# @example `LowLevel.api_get`
|
|
16
|
+
# @!method self.api_set(level)
|
|
17
|
+
# @param level [Integer]
|
|
18
|
+
# @return [Integer] raw return code
|
|
19
|
+
# @raise [RangeError] for an invalid integer
|
|
20
|
+
# @example `LowLevel.api_set(6)`
|
|
21
|
+
# @!method self.supports?(feature)
|
|
22
|
+
# @param feature [Symbol]
|
|
23
|
+
# @return [Boolean]
|
|
24
|
+
# @example `LowLevel.supports?(:notify)`
|
|
25
|
+
# @!method self.init(action)
|
|
26
|
+
# @param action [Integer]
|
|
27
|
+
# @return [Context]
|
|
28
|
+
# @raise [OutOfMemoryError] if allocation fails
|
|
29
|
+
# @example `LowLevel.init(Action::ALLOW)`
|
|
30
|
+
# @!method self.release(context)
|
|
31
|
+
# @param context [Context]
|
|
32
|
+
# @return [nil]
|
|
33
|
+
# @example `LowLevel.release(context)`
|
|
34
|
+
# @!method self.closed?(context)
|
|
35
|
+
# @param context [Context]
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
# @example `LowLevel.closed?(context)`
|
|
38
|
+
# @!method self.reset(context, action)
|
|
39
|
+
# @param context [Context]
|
|
40
|
+
# @param action [Integer]
|
|
41
|
+
# @return [Integer] raw return code
|
|
42
|
+
# @raise [ClosedFilterError] if released
|
|
43
|
+
# @example `LowLevel.reset(context, Action::ALLOW)`
|
|
44
|
+
# @!method self.merge(destination, source)
|
|
45
|
+
# @param destination [Context]
|
|
46
|
+
# @param source [Context]
|
|
47
|
+
# @return [Integer] raw return code
|
|
48
|
+
# @raise [ClosedFilterError] if either context is released
|
|
49
|
+
# @example `LowLevel.merge(destination, source)`
|
|
50
|
+
# @!method self.attr_get(context, attribute)
|
|
51
|
+
# @param context [Context]
|
|
52
|
+
# @param attribute [Integer]
|
|
53
|
+
# @return [Array(Integer, Integer)] raw return code and value
|
|
54
|
+
# @raise [ClosedFilterError] if released
|
|
55
|
+
# @example `LowLevel.attr_get(context, FilterAttr::CTL_NNP)`
|
|
56
|
+
# @!method self.attr_set(context, attribute, value)
|
|
57
|
+
# @param context [Context]
|
|
58
|
+
# @param attribute [Integer]
|
|
59
|
+
# @param value [Integer]
|
|
60
|
+
# @return [Integer] raw return code
|
|
61
|
+
# @raise [ClosedFilterError] if released
|
|
62
|
+
# @example `LowLevel.attr_set(context, FilterAttr::CTL_NNP, 1)`
|
|
63
|
+
# @!method self.arch_add(context, arch)
|
|
64
|
+
# @param context [Context]
|
|
65
|
+
# @param arch [Integer]
|
|
66
|
+
# @return [Integer] raw return code
|
|
67
|
+
# @raise [ClosedFilterError] if released
|
|
68
|
+
# @example `LowLevel.arch_add(context, Arch::X86)`
|
|
69
|
+
# @!method self.arch_remove(context, arch)
|
|
70
|
+
# @param context [Context]
|
|
71
|
+
# @param arch [Integer]
|
|
72
|
+
# @return [Integer] raw return code
|
|
73
|
+
# @raise [ClosedFilterError] if released
|
|
74
|
+
# @example `LowLevel.arch_remove(context, Arch::X86)`
|
|
75
|
+
# @!method self.arch_exist(context, arch)
|
|
76
|
+
# @param context [Context]
|
|
77
|
+
# @param arch [Integer]
|
|
78
|
+
# @return [Integer] raw return code
|
|
79
|
+
# @raise [ClosedFilterError] if released
|
|
80
|
+
# @example `LowLevel.arch_exist(context, Arch::X86)`
|
|
81
|
+
# @!method self.arch_resolve_name(name)
|
|
82
|
+
# @param name [String]
|
|
83
|
+
# @return [Integer] architecture token, or zero
|
|
84
|
+
# @example `LowLevel.arch_resolve_name("x86_64")`
|
|
85
|
+
# @!method self.arch_native
|
|
86
|
+
# @return [Integer] native architecture token
|
|
87
|
+
# @example `LowLevel.arch_native`
|
|
88
|
+
# @!method self.syscall_resolve_name(name)
|
|
89
|
+
# @param name [String]
|
|
90
|
+
# @return [Integer] syscall number
|
|
91
|
+
# @example `LowLevel.syscall_resolve_name("write")`
|
|
92
|
+
# @!method self.syscall_resolve_name_arch(arch, name)
|
|
93
|
+
# @param arch [Integer]
|
|
94
|
+
# @param name [String]
|
|
95
|
+
# @return [Integer] syscall number
|
|
96
|
+
# @example `LowLevel.syscall_resolve_name_arch(Arch::X86, "write")`
|
|
97
|
+
# @!method self.syscall_resolve_name_rewrite(arch, name)
|
|
98
|
+
# @param arch [Integer]
|
|
99
|
+
# @param name [String]
|
|
100
|
+
# @return [Integer] rewritten syscall number
|
|
101
|
+
# @example `LowLevel.syscall_resolve_name_rewrite(Arch::X86, "socket")`
|
|
102
|
+
# @!method self.syscall_resolve_num_arch(arch, number)
|
|
103
|
+
# @param arch [Integer]
|
|
104
|
+
# @param number [Integer]
|
|
105
|
+
# @return [String, nil] syscall name
|
|
106
|
+
# @example `LowLevel.syscall_resolve_num_arch(Arch::X86_64, 1)`
|
|
107
|
+
# @!method self.rule_add_array(context, action, syscall, comparisons)
|
|
108
|
+
# @param context [Context]
|
|
109
|
+
# @param action [Integer]
|
|
110
|
+
# @param syscall [Integer]
|
|
111
|
+
# @param comparisons [Array<Array<Integer>>]
|
|
112
|
+
# @return [Integer] raw return code
|
|
113
|
+
# @raise [ArgumentError] for malformed comparisons
|
|
114
|
+
# @example `LowLevel.rule_add_array(context, Action::ALLOW, 1, [])`
|
|
115
|
+
# @!method self.rule_add_exact_array(context, action, syscall, comparisons)
|
|
116
|
+
# @param context [Context]
|
|
117
|
+
# @param action [Integer]
|
|
118
|
+
# @param syscall [Integer]
|
|
119
|
+
# @param comparisons [Array<Array<Integer>>]
|
|
120
|
+
# @return [Integer] raw return code
|
|
121
|
+
# @raise [ArgumentError] for malformed comparisons
|
|
122
|
+
# @example `LowLevel.rule_add_exact_array(context, Action::ALLOW, 1, [])`
|
|
123
|
+
# @!method self.syscall_priority(context, syscall, priority)
|
|
124
|
+
# @param context [Context]
|
|
125
|
+
# @param syscall [Integer]
|
|
126
|
+
# @param priority [Integer]
|
|
127
|
+
# @return [Integer] raw return code
|
|
128
|
+
# @raise [RangeError] outside 0..255
|
|
129
|
+
# @example `LowLevel.syscall_priority(context, 1, 100)`
|
|
130
|
+
# @!method self.export_pfc(context, fd)
|
|
131
|
+
# @param context [Context]
|
|
132
|
+
# @param fd [Integer]
|
|
133
|
+
# @return [Integer] raw return code
|
|
134
|
+
# @raise [ClosedFilterError] if released
|
|
135
|
+
# @example `LowLevel.export_pfc(context, io.fileno)`
|
|
136
|
+
# @!method self.export_bpf(context, fd)
|
|
137
|
+
# @param context [Context]
|
|
138
|
+
# @param fd [Integer]
|
|
139
|
+
# @return [Integer] raw return code
|
|
140
|
+
# @raise [ClosedFilterError] if released
|
|
141
|
+
# @example `LowLevel.export_bpf(context, io.fileno)`
|
|
142
|
+
# @!method self.export_bpf_mem(context)
|
|
143
|
+
# @param context [Context]
|
|
144
|
+
# @return [Array(Integer, String)] raw return code and BPF
|
|
145
|
+
# @raise [NotSupportedError] when unavailable
|
|
146
|
+
# @example `LowLevel.export_bpf_mem(context)`
|
|
147
|
+
# @!method self.precompute(context)
|
|
148
|
+
# @param context [Context]
|
|
149
|
+
# @return [Integer] raw return code
|
|
150
|
+
# @raise [NotSupportedError] when unavailable
|
|
151
|
+
# @example `LowLevel.precompute(context)`
|
|
152
|
+
# @!method self.load(context)
|
|
153
|
+
# @param context [Context]
|
|
154
|
+
# @return [Integer] raw return code
|
|
155
|
+
# @raise [ClosedFilterError] if released
|
|
156
|
+
# @example `fork { LowLevel.load(context) }`
|
|
157
|
+
# @!method self.loaded?(context)
|
|
158
|
+
# @param context [Context]
|
|
159
|
+
# @return [Boolean]
|
|
160
|
+
# @raise [ClosedFilterError] if released
|
|
161
|
+
# @example `LowLevel.loaded?(context)`
|
|
162
|
+
# @!method self.transaction_start(context)
|
|
163
|
+
# @param context [Context]
|
|
164
|
+
# @return [Integer] raw return code
|
|
165
|
+
# @raise [NotSupportedError] when unavailable
|
|
166
|
+
# @example `LowLevel.transaction_start(context)`
|
|
167
|
+
# @!method self.transaction_commit(context)
|
|
168
|
+
# @param context [Context]
|
|
169
|
+
# @return [Integer] raw return code
|
|
170
|
+
# @raise [NotSupportedError] when unavailable
|
|
171
|
+
# @example `LowLevel.transaction_commit(context)`
|
|
172
|
+
# @!method self.transaction_reject(context)
|
|
173
|
+
# @param context [Context]
|
|
174
|
+
# @return [Integer] zero
|
|
175
|
+
# @raise [NotSupportedError] when unavailable
|
|
176
|
+
# @example `LowLevel.transaction_reject(context)`
|
|
177
|
+
# @!method self.notify_fd(context)
|
|
178
|
+
# @param context [Context]
|
|
179
|
+
# @return [Integer] listener fd or negative errno
|
|
180
|
+
# @raise [ClosedFilterError] if released
|
|
181
|
+
# @example `LowLevel.notify_fd(context)`
|
|
182
|
+
# @!method self.notify_receive(fd)
|
|
183
|
+
# @param fd [Integer]
|
|
184
|
+
# @return [Array(Integer, Hash)] raw return code and request
|
|
185
|
+
# @example `LowLevel.notify_receive(fd)`
|
|
186
|
+
# @!method self.notify_respond(fd, id, value, error, flags)
|
|
187
|
+
# @param fd [Integer]
|
|
188
|
+
# @param id [Integer]
|
|
189
|
+
# @param value [Integer]
|
|
190
|
+
# @param error [Integer]
|
|
191
|
+
# @param flags [Integer]
|
|
192
|
+
# @return [Integer] raw return code
|
|
193
|
+
# @example `LowLevel.notify_respond(fd, id, 0, 0, 0)`
|
|
194
|
+
# @!method self.notify_id_valid(fd, id)
|
|
195
|
+
# @param fd [Integer]
|
|
196
|
+
# @param id [Integer]
|
|
197
|
+
# @return [Integer] raw return code
|
|
198
|
+
# @example `LowLevel.notify_id_valid(fd, id)`
|
|
199
|
+
# @!method self.notify_addfd(fd, id, flags, source_fd, newfd, newfd_flags)
|
|
200
|
+
# @param fd [Integer]
|
|
201
|
+
# @param id [Integer]
|
|
202
|
+
# @param flags [Integer]
|
|
203
|
+
# @param source_fd [Integer]
|
|
204
|
+
# @param newfd [Integer]
|
|
205
|
+
# @param newfd_flags [Integer]
|
|
206
|
+
# @return [Integer] installed fd or negative errno
|
|
207
|
+
# @raise [NotSupportedError] when unavailable
|
|
208
|
+
# @example `LowLevel.notify_addfd(fd, id, 0, source.fileno, 0, 0)`
|
|
209
|
+
end
|
|
210
|
+
end
|