Module: Seccomp::Notify::Features

Defined in:
lib/seccomp/notify/features.rb

Class Method Summary collapse

Class Method Details

.detectObject



10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/seccomp/notify/features.rb', line 10

def detect
  disabled = ENV.fetch("SECCOMP_NOTIFY_DISABLE_FEATURES", "").split(",").map(&:strip)
  result = {user_notif: supported?, continue: false, addfd: false, addfd_send: false, wait_killable_recv: false}
  return result.freeze unless result[:user_notif]

  result[:continue] = probe_response(Constants::SECCOMP_USER_NOTIF_FLAG_CONTINUE)
  result[:addfd] = probe_addfd(0)
  result[:addfd_send] = probe_addfd(Constants::SECCOMP_ADDFD_FLAG_SEND)
  result[:wait_killable_recv] = probe_install(Constants::SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV)
  disabled.each { |name| result[name.to_sym] = false if result.key?(name.to_sym) }
  result[:addfd_send] = false unless result[:addfd]
  result.freeze
end

.probeObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/seccomp/notify/features.rb', line 70

def probe
  parent, child = UNIXSocket.pair
  pid = fork do
    parent.close
    listener = Filter.install!(BPF::Builder.new(Policy.new { notify :getpid }).build)
    FdPassing.send_fd(child, listener)
    Libc::SYSCALL.call(Syscalls.number(:getpid), 0, 0, 0)
    exit! 0
  rescue StandardError
    exit! 1
  end
  child.close
  listener = FdPassing.recv_fd(parent, timeout: 2)
  request = "\0" * Structs::NOTIF_SIZE
  Ioctl.call(listener, Ioctl::NOTIF_RECV, request)
  yield listener, request
  status = wait_for_probe(pid)
  status.success?
rescue SystemCallError, EOFError, Timeout::Error
  terminate_probe(pid)
  false
ensure
  listener&.close unless listener&.closed?
  parent&.close unless parent&.closed?
end

.probe_addfd(flags) ⇒ Object



38
39
40
41
42
43
44
45
46
# File 'lib/seccomp/notify/features.rb', line 38

def probe_addfd(flags)
  File.open("/dev/null") do |source|
    probe do |listener, request|
      id = request.unpack1("Q<")
      injected = Ioctl.call(listener, Ioctl::NOTIF_ADDFD, [id, flags, source.fileno, 0, 0].pack(Structs::ADDFD_FORMAT))
      Ioctl.call(listener, Ioctl::NOTIF_SEND, [id, injected, 0, 0].pack(Structs::RESPONSE_FORMAT)) if flags.zero?
    end
  end
end

.probe_install(flags) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/seccomp/notify/features.rb', line 48

def probe_install(flags)
  parent, child = UNIXSocket.pair
  pid = fork do
    parent.close
    listener = Filter.install!(BPF::Builder.new(Policy.new { notify :getpid }).build, flags:)
    FdPassing.send_fd(child, listener)
    exit! 0
  rescue SystemCallError
    exit! 1
  end
  child.close
  listener = FdPassing.recv_fd(parent, timeout: 2)
  listener.close
  status = wait_for_probe(pid)
  status.success?
rescue EOFError, Timeout::Error
  terminate_probe(pid)
  false
ensure
  parent&.close unless parent&.closed?
end

.probe_response(flags) ⇒ Object



31
32
33
34
35
36
# File 'lib/seccomp/notify/features.rb', line 31

def probe_response(flags)
  probe do |listener, request|
    response = [request.unpack1("Q<"), 0, 0, flags].pack(Structs::RESPONSE_FORMAT)
    Ioctl.call(listener, Ioctl::NOTIF_SEND, response)
  end
end

.supported?Boolean

Returns:

  • (Boolean)


24
25
26
27
28
29
# File 'lib/seccomp/notify/features.rb', line 24

def supported?
  Libc.notif_sizes
  true
rescue SystemCallError, NotSupportedError
  false
end

.terminate_probe(pid) ⇒ Object



100
101
102
103
# File 'lib/seccomp/notify/features.rb', line 100

def terminate_probe(pid)
  Process.kill("KILL", pid) rescue nil
  Process.waitpid(pid) rescue nil
end

.wait_for_probe(pid) ⇒ Object



96
97
98
# File 'lib/seccomp/notify/features.rb', line 96

def wait_for_probe(pid)
  Timeout.timeout(2) { Process.waitpid2(pid).last }
end