Class: Seccomp::Filter

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin, Arg::DSL, Attributes, DSL
Defined in:
lib/seccomp/filter.rb

Overview

Mutable seccomp filter with automatic native-resource management.

Constant Summary

Constants included from Attributes

Attributes::ATTRIBUTE_API_LEVELS, Attributes::ATTRIBUTE_NAMES, Attributes::BOOLEAN_ATTRIBUTES

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DSL

#arch, #no_new_privs, #tsync

Methods included from Arg::DSL

#arg

Methods included from Attributes

#[], #[]=, #bad_arch_action, #bad_arch_action=, #default_action, #optimize, #optimize=

Constructor Details

#initialize(default_action = :kill_process) ⇒ void

Examples:

Filter.new(:allow)

Parameters:

  • default_action (Symbol, Integer) (defaults to: :kill_process)

    default filter action

Raises:

  • (Error)

    if native initialization fails



20
21
22
23
24
25
26
27
# File 'lib/seccomp/filter.rb', line 20

def initialize(default_action = :kill_process)
  super()
  @default_action = Action.resolve(default_action)
  @notification_rule = @default_action == Action::NOTIFY
  @listener_active = false
  @notification_receive_lock = Mutex.new
  @context = LowLevel.init(@default_action)
end

Class Method Details

.open(default_action = :kill_process) {|Filter| ... } ⇒ Object, Filter

Returns block result, or a filter without a block.

Examples:

Filter.open(:allow) { |filter| filter.allow(:read) }

Parameters:

  • default_action (Symbol, Integer) (defaults to: :kill_process)

    default action

Yields:

  • (Filter)

    filter closed after the block

Returns:

  • (Object, Filter)

    block result, or a filter without a block

Raises:

  • (Error)

    if initialization fails



34
35
36
37
38
39
40
41
42
43
# File 'lib/seccomp/filter.rb', line 34

def self.open(default_action = :kill_process)
  filter = new(default_action)
  return filter unless block_given?

  begin
    yield filter
  ensure
    filter.close
  end
end

Instance Method Details

#add_arch(arch) ⇒ Filter

Returns receiver.

Examples:

filter.add_arch(:x86)

Parameters:

  • arch (Symbol, String, Integer)

    architecture

Returns:

Raises:

  • (Error)

    if it cannot be added



106
107
108
# File 'lib/seccomp/filter.rb', line 106

def add_arch(arch)
  change_arch(:arch_add, arch)
end

#add_rule(action, syscall, *comparisons) ⇒ Filter

Returns receiver.

Examples:

filter.add_rule(:allow, :write, filter.arg(0).eq(1))

Parameters:

  • action (Symbol, Integer, Array)

    rule action

  • syscall (Symbol, String, Integer)

    syscall

  • comparisons (Array<ArgCmp>)

    argument comparisons

Returns:

Raises:

  • (Error)

    if the rule is invalid



145
146
147
# File 'lib/seccomp/filter.rb', line 145

def add_rule(action, syscall, *comparisons)
  add_rule_with(:rule_add_array, action, syscall, comparisons)
end

#add_rule_exact(action, syscall, *comparisons) ⇒ Filter

Returns receiver.

Examples:

filter.add_rule_exact(:allow, :write)

Parameters:

  • action (Symbol, Integer, Array)

    rule action

  • syscall (Symbol, String, Integer)

    syscall

  • comparisons (Array<ArgCmp>)

    argument comparisons

Returns:

Raises:

  • (Error)

    if an exact rule cannot be represented



155
156
157
# File 'lib/seccomp/filter.rb', line 155

def add_rule_exact(action, syscall, *comparisons)
  add_rule_with(:rule_add_exact_array, action, syscall, comparisons)
end

#allow(syscall, *arguments) ⇒ Filter

Returns receiver.

Examples:

filter.allow(:read, :write)

Parameters:

  • syscall (Symbol, String, Integer)

    first syscall

  • arguments (Array)

    additional syscalls followed by comparisons

Returns:

Raises:

  • (Error)

    if a rule is invalid



164
165
166
167
168
169
170
171
# File 'lib/seccomp/filter.rb', line 164

def allow(syscall, *arguments)
  comparisons = arguments.drop_while { |argument| !argument.is_a?(ArgCmp) }
  syscalls = [syscall, *arguments.take(arguments.length - comparisons.length)]
  raise TypeError, "syscalls must precede comparisons" unless comparisons.all?(ArgCmp)

  synchronize { syscalls.each { |name| add_rule(:allow, name, *comparisons) } }
  self
end

#arch?(arch) ⇒ Boolean

Returns whether present.

Examples:

filter.arch?(:x86_64)

Parameters:

  • arch (Symbol, String, Integer)

    architecture

Returns:

  • (Boolean)

    whether present

Raises:

  • (Error)

    if the query fails



122
123
124
125
126
127
128
129
130
# File 'lib/seccomp/filter.rb', line 122

def arch?(arch)
  synchronize do
    result = LowLevel.arch_exist(@context, Arch.resolve(arch))
    return true if result.zero?
    return false if result == -Errno::EEXIST::Errno

    Error.check!(result, call: "seccomp_arch_exist", hint: "architecture #{arch}")
  end
end

#archsArray<Symbol>

Returns configured architectures.

Examples:

filter.archs

Returns:

  • (Array<Symbol>)

    configured architectures

Raises:

  • (Error)

    if a query fails



135
136
137
# File 'lib/seccomp/filter.rb', line 135

def archs
  synchronize { Arch.all.keys.select { |arch| arch != :native && arch?(arch) } }
end

#closenil

Examples:

filter.close

Returns:

  • (nil)


47
48
49
50
51
52
53
54
55
# File 'lib/seccomp/filter.rb', line 47

def close
  NOTIFICATION_MONITOR.synchronize do
    synchronize do
      LowLevel.release(@context)
      @listener_active = false
      nil
    end
  end
end

#closed?Boolean

Returns whether native resources were released.

Examples:

filter.closed?

Returns:

  • (Boolean)

    whether native resources were released



59
60
61
# File 'lib/seccomp/filter.rb', line 59

def closed?
  synchronize { LowLevel.closed?(@context) }
end

#deny(syscall, *comparisons, errno: Errno::EPERM) ⇒ Filter

Returns receiver.

Examples:

filter.deny(:ptrace, errno: Errno::EPERM)

Parameters:

  • syscall (Symbol, String, Integer)

    syscall

  • comparisons (Array<ArgCmp>)

    argument comparisons

  • errno (Integer, SystemCallError, Class) (defaults to: Errno::EPERM)

    denial errno

Returns:

Raises:

  • (Error)

    if the rule is invalid



179
180
181
# File 'lib/seccomp/filter.rb', line 179

def deny(syscall, *comparisons, errno: Errno::EPERM)
  add_rule(Action.errno(errno), syscall, *comparisons)
end

#export_bpf(io) ⇒ nil

Examples:

filter.export_bpf(file)

Parameters:

  • io (IO)

    writable binary IO

Returns:

  • (nil)

Raises:

  • (Error)

    if export fails



254
255
256
# File 'lib/seccomp/filter.rb', line 254

def export_bpf(io)
  export_to(:export_bpf, io)
end

#export_pfc(io) ⇒ nil

Examples:

filter.export_pfc($stdout)

Parameters:

  • io (IO)

    writable IO

Returns:

  • (nil)

Raises:

  • (Error)

    if export fails



246
247
248
# File 'lib/seccomp/filter.rb', line 246

def export_pfc(io)
  export_to(:export_pfc, io)
end

#kill(syscall, *comparisons) ⇒ Filter

Returns receiver.

Examples:

filter.kill(:ptrace)

Parameters:

  • syscall (Symbol, String, Integer)

    syscall

  • comparisons (Array<ArgCmp>)

    argument comparisons

Returns:

Raises:

  • (Error)

    if the rule is invalid



188
189
190
# File 'lib/seccomp/filter.rb', line 188

def kill(syscall, *comparisons)
  add_rule(:kill_process, syscall, *comparisons)
end

#load!nil

Examples:

fork { filter.load!; Process.exit!(0) }

Returns:

  • (nil)

Raises:

  • (Error)

    if the irreversible load fails



293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/seccomp/filter.rb', line 293

def load!
  NOTIFICATION_MONITOR.synchronize do
    synchronize do
      ensure_notification_tsync_supported!
      if notification_action? && LowLevel.notify_fd(@context) >= 0
        raise NotificationError, "a notification listener is already active"
      end

      Error.check!(LowLevel.load(@context), call: "seccomp_load")
      @listener_active ||= @notification_rule
    end
  end
  nil
end

#loaded?Boolean

Returns whether load succeeded at least once.

Examples:

filter.loaded?

Returns:

  • (Boolean)

    whether load succeeded at least once



310
311
312
# File 'lib/seccomp/filter.rb', line 310

def loaded?
  synchronize { LowLevel.loaded?(@context) }
end

#log(syscall, *comparisons) ⇒ Filter

Returns receiver.

Examples:

filter.log(:socket)

Parameters:

  • syscall (Symbol, String, Integer)

    syscall

  • comparisons (Array<ArgCmp>)

    argument comparisons

Returns:

Raises:

  • (Error)

    if the rule is invalid



197
198
199
# File 'lib/seccomp/filter.rb', line 197

def log(syscall, *comparisons)
  add_rule(:log, syscall, *comparisons)
end

#merge!(other) ⇒ Filter

Returns receiver.

Examples:

destination.merge!(source)

Parameters:

  • other (Filter)

    source consumed on success

Returns:

Raises:

  • (Error)

    if filters are incompatible

  • (TypeError)

    unless other is a Filter



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/seccomp/filter.rb', line 83

def merge!(other)
  raise TypeError, "other must be a Seccomp::Filter" unless other.is_a?(Filter)
  raise ArgumentError, "cannot merge a filter into itself" if equal?(other)

  first, second = [self, other].sort_by(&:object_id)
  first.synchronize do
    second.synchronize do
      notification_rule = @notification_rule || other.instance_variable_get(:@notification_rule)
      if notification_rule
        ensure_notification_tsync_supported!(notification_action: true,
                                             tsync: tsync? || other.tsync?)
      end
      Error.check!(LowLevel.merge(@context, other.__send__(:context)), call: "seccomp_merge")
      merge_state_from(other)
    end
  end
  self
end

#notifierNotifier

Returns notification listener.

Examples:

notifier = filter.notifier

Returns:

Raises:



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/seccomp/filter.rb', line 341

def notifier
  NOTIFICATION_MONITOR.synchronize do
    synchronize do
      raise NotificationError, "filter has not been loaded" unless loaded?
      unless @listener_active
        raise NotificationError, "filter has no active notification listener"
      end

      fd = LowLevel.notify_fd(@context)
      if fd.negative?
        raise NotificationError.new("seccomp_notify_fd failed", errno: -fd,
                                                                call: "seccomp_notify_fd")
      end
      Notifier.new(self, fd)
    end
  end
end

#notify(syscall, *comparisons) ⇒ Filter

Returns receiver.

Examples:

filter.notify(:openat)

Parameters:

  • syscall (Symbol, String, Integer)

    syscall

  • comparisons (Array<ArgCmp>)

    argument comparisons

Returns:

Raises:

  • (Error)

    if notifications are unsupported



206
207
208
# File 'lib/seccomp/filter.rb', line 206

def notify(syscall, *comparisons)
  add_rule(:notify, syscall, *comparisons)
end

#precomputeFilter

Returns receiver.

Examples:

filter.precompute

Returns:

Raises:



283
284
285
286
287
288
# File 'lib/seccomp/filter.rb', line 283

def precompute
  synchronize do
    Error.check!(LowLevel.precompute(@context), call: "seccomp_precompute")
  end
  self
end

#priority(syscall, value) ⇒ Filter

Returns receiver.

Examples:

filter.priority(:write, 100)

Parameters:

  • syscall (Symbol, String, Integer)

    syscall

  • value (Integer)

    priority from 0 through 255

Returns:

Raises:

  • (Error)

    if rejected



234
235
236
237
238
239
240
# File 'lib/seccomp/filter.rb', line 234

def priority(syscall, value)
  synchronize do
    Error.check!(LowLevel.syscall_priority(@context, Syscall.number(syscall), value),
                 call: "seccomp_syscall_priority", hint: "syscall #{syscall}")
  end
  self
end

#remove_arch(arch) ⇒ Filter

Returns receiver.

Examples:

filter.remove_arch(:x86)

Parameters:

  • arch (Symbol, String, Integer)

    architecture

Returns:

Raises:

  • (Error)

    if it cannot be removed



114
115
116
# File 'lib/seccomp/filter.rb', line 114

def remove_arch(arch)
  change_arch(:arch_remove, arch)
end

#reset(default_action = nil) ⇒ Filter

Returns receiver.

Examples:

filter.reset(:allow)

Parameters:

  • default_action (Symbol, Integer, nil) (defaults to: nil)

    replacement action

Returns:

Raises:

  • (Error)

    if reset fails



67
68
69
70
71
72
73
74
75
76
# File 'lib/seccomp/filter.rb', line 67

def reset(default_action = nil)
  synchronize do
    action = Action.resolve(default_action.nil? ? @default_action : default_action)
    ensure_notification_tsync_supported!(notification_action: action == Action::NOTIFY)
    Error.check!(LowLevel.reset(@context, action), call: "seccomp_reset")
    @default_action = action
    @notification_rule = action == Action::NOTIFY
  end
  self
end

#to_bpfString

Returns binary BPF program.

Examples:

filter.to_bpf.bytesize

Returns:

  • (String)

    binary BPF program

Raises:

  • (Error)

    if export fails



268
269
270
271
272
273
274
275
276
277
278
# File 'lib/seccomp/filter.rb', line 268

def to_bpf
  if Seccomp.supports?(:bpf_mem)
    synchronize do
      rc, result = LowLevel.export_bpf_mem(@context)
      Error.check!(rc, call: "seccomp_export_bpf_mem")
      result
    end
  else
    export_string(:export_bpf, binmode: true)
  end
end

#to_pfcString

Returns pseudo filter code.

Examples:

puts filter.to_pfc

Returns:

  • (String)

    pseudo filter code

Raises:

  • (Error)

    if export fails



261
262
263
# File 'lib/seccomp/filter.rb', line 261

def to_pfc
  export_string(:export_pfc)
end

#trace(syscall, value, *comparisons) ⇒ Filter

Returns receiver.

Examples:

filter.trace(:ptrace, 42)

Parameters:

  • syscall (Symbol, String, Integer)

    syscall

  • value (Integer)

    trace payload

  • comparisons (Array<ArgCmp>)

    argument comparisons

Returns:

Raises:

  • (Error)

    if the rule is invalid



225
226
227
# File 'lib/seccomp/filter.rb', line 225

def trace(syscall, value, *comparisons)
  add_rule(Action.trace(value), syscall, *comparisons)
end

#transaction { ... } ⇒ Object

Returns block result.

Examples:

filter.transaction { filter.allow(:read) }

Yields:

  • transactional filter changes

Returns:

  • (Object)

    block result

Raises:



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/seccomp/filter.rb', line 318

def transaction
  synchronize do
    raise ArgumentError, "nested transactions are not allowed" if @in_transaction

    previous_notification_rule = @notification_rule
    begin
      Error.check!(LowLevel.transaction_start(@context), call: "seccomp_transaction_start")
      @in_transaction = true
      result = yield
      Error.check!(LowLevel.transaction_commit(@context), call: "seccomp_transaction_commit")
      committed = true
      result
    ensure
      LowLevel.transaction_reject(@context) if @in_transaction && !committed && !closed?
      @notification_rule = previous_notification_rule unless committed
      @in_transaction = false
    end
  end
end

#trap(syscall, *comparisons) ⇒ Filter

Returns receiver.

Examples:

filter.trap(:ptrace)

Parameters:

  • syscall (Symbol, String, Integer)

    syscall

  • comparisons (Array<ArgCmp>)

    argument comparisons

Returns:

Raises:

  • (Error)

    if the rule is invalid



215
216
217
# File 'lib/seccomp/filter.rb', line 215

def trap(syscall, *comparisons)
  add_rule(:trap, syscall, *comparisons)
end