seccomp-notify 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/CHANGELOG.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +82 -0
- data/Rakefile +8 -0
- data/examples/deny_network.rb +11 -0
- data/examples/emulate_open.rb +19 -0
- data/examples/strace_lite.rb +14 -0
- data/exe/seccomp-notify-supervisor +15 -0
- data/lib/seccomp/notify/bpf/builder.rb +61 -0
- data/lib/seccomp/notify/bpf/instruction.rb +13 -0
- data/lib/seccomp/notify/bpf/program.rb +30 -0
- data/lib/seccomp/notify/constants.rb +62 -0
- data/lib/seccomp/notify/errors.rb +13 -0
- data/lib/seccomp/notify/fd_passing.rb +28 -0
- data/lib/seccomp/notify/features.rb +106 -0
- data/lib/seccomp/notify/filter.rb +21 -0
- data/lib/seccomp/notify/ioctl.rb +34 -0
- data/lib/seccomp/notify/libc.rb +76 -0
- data/lib/seccomp/notify/policy.rb +84 -0
- data/lib/seccomp/notify/request.rb +165 -0
- data/lib/seccomp/notify/structs.rb +21 -0
- data/lib/seccomp/notify/supervisor.rb +198 -0
- data/lib/seccomp/notify/syscalls.rb +747 -0
- data/lib/seccomp/notify/target_memory.rb +78 -0
- data/lib/seccomp/notify/version.rb +7 -0
- data/lib/seccomp/notify.rb +221 -0
- data/spike/notify_min.c +65 -0
- data/tools/gen_syscall_table.rb +59 -0
- metadata +86 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ipaddr"
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
module Seccomp
|
|
7
|
+
module Notify
|
|
8
|
+
class TargetMemory
|
|
9
|
+
PAGE_SIZE = 4096
|
|
10
|
+
|
|
11
|
+
def initialize(listener, request, cache, cache_mutex = Mutex.new)
|
|
12
|
+
@listener = listener
|
|
13
|
+
@request = request
|
|
14
|
+
@cache = cache
|
|
15
|
+
@cache_mutex = cache_mutex
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def read(address, length)
|
|
19
|
+
raise ArgumentError, "length must be non-negative" if length.negative?
|
|
20
|
+
|
|
21
|
+
data = memory.pread(length, address)
|
|
22
|
+
raise MemoryReadError, "short read from target memory" unless data.bytesize == length
|
|
23
|
+
raise StaleNotificationError, "notification is no longer valid" unless @request.valid?
|
|
24
|
+
|
|
25
|
+
@request.mark_pointer_read!
|
|
26
|
+
data
|
|
27
|
+
rescue Errno::EIO, Errno::EFAULT, Errno::ENOENT, Errno::EACCES => error
|
|
28
|
+
raise MemoryReadError, error.message
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def read_string(address, max: PAGE_SIZE)
|
|
32
|
+
raise ArgumentError, "max must be positive" unless max.positive?
|
|
33
|
+
|
|
34
|
+
result = +""
|
|
35
|
+
while result.bytesize < max
|
|
36
|
+
length = [PAGE_SIZE - ((address + result.bytesize) % PAGE_SIZE), max - result.bytesize].min
|
|
37
|
+
chunk = read(address + result.bytesize, length)
|
|
38
|
+
nul = chunk.index("\0")
|
|
39
|
+
return result << chunk.byteslice(0, nul) if nul
|
|
40
|
+
|
|
41
|
+
result << chunk
|
|
42
|
+
end
|
|
43
|
+
raise MemoryReadError, "NUL terminator not found within #{max} bytes"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def read_sockaddr(address, length)
|
|
47
|
+
raise MemoryReadError, "sockaddr is shorter than sa_family" if length < 2
|
|
48
|
+
|
|
49
|
+
bytes = read(address, length)
|
|
50
|
+
case bytes.unpack1("S<")
|
|
51
|
+
when Socket::AF_INET
|
|
52
|
+
raise MemoryReadError, "short AF_INET sockaddr" if length < 16
|
|
53
|
+
Addrinfo.tcp(IPAddr.ntop(bytes.byteslice(4, 4)), bytes.byteslice(2, 2).unpack1("n"))
|
|
54
|
+
when Socket::AF_INET6
|
|
55
|
+
raise MemoryReadError, "short AF_INET6 sockaddr" if length < 28
|
|
56
|
+
address = IPAddr.ntop(bytes.byteslice(8, 16))
|
|
57
|
+
scope = bytes.byteslice(24, 4).unpack1("L<")
|
|
58
|
+
address = "#{address}%#{scope}" unless scope.zero?
|
|
59
|
+
Addrinfo.tcp(address, bytes.byteslice(2, 2).unpack1("n"))
|
|
60
|
+
when Socket::AF_UNIX
|
|
61
|
+
raw_path = bytes.byteslice(2..).to_s
|
|
62
|
+
path = raw_path.start_with?("\0") ? raw_path : raw_path.split("\0", 2).first
|
|
63
|
+
Addrinfo.unix(path)
|
|
64
|
+
else
|
|
65
|
+
raise MemoryReadError, "unsupported sockaddr family #{bytes.unpack1("S<")}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def memory
|
|
72
|
+
@cache[@request.tid] || @cache_mutex.synchronize do
|
|
73
|
+
@cache[@request.tid] ||= File.open("/proc/#{@request.tid}/mem", File::RDONLY)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "notify/version"
|
|
4
|
+
require_relative "notify/errors"
|
|
5
|
+
|
|
6
|
+
unless RUBY_PLATFORM.include?("linux")
|
|
7
|
+
raise Seccomp::Notify::NotSupportedError, "seccomp-notify supports Linux only"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require "rbconfig"
|
|
11
|
+
require "socket"
|
|
12
|
+
require_relative "notify/constants"
|
|
13
|
+
require_relative "notify/structs"
|
|
14
|
+
require_relative "notify/libc"
|
|
15
|
+
require_relative "notify/ioctl"
|
|
16
|
+
require_relative "notify/syscalls"
|
|
17
|
+
require_relative "notify/bpf/instruction"
|
|
18
|
+
require_relative "notify/bpf/program"
|
|
19
|
+
require_relative "notify/bpf/builder"
|
|
20
|
+
require_relative "notify/policy"
|
|
21
|
+
require_relative "notify/filter"
|
|
22
|
+
require_relative "notify/fd_passing"
|
|
23
|
+
require_relative "notify/target_memory"
|
|
24
|
+
require_relative "notify/request"
|
|
25
|
+
require_relative "notify/features"
|
|
26
|
+
require_relative "notify/supervisor"
|
|
27
|
+
|
|
28
|
+
module Seccomp
|
|
29
|
+
module Notify
|
|
30
|
+
class << self
|
|
31
|
+
def supported?
|
|
32
|
+
Features.supported?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Detects optional kernel capabilities with isolated child probes.
|
|
36
|
+
# @return [Hash<Symbol, Boolean>]
|
|
37
|
+
def features
|
|
38
|
+
@features ||= Features.detect
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns kernel-defined notification structure sizes.
|
|
42
|
+
# @return [Hash<Symbol, Integer>]
|
|
43
|
+
def notif_sizes
|
|
44
|
+
@notif_sizes ||= Libc.notif_sizes
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns whether the supervisor-side health pipe is still open.
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean, nil] nil when the process was not started by this gem
|
|
50
|
+
def supervisor_alive?
|
|
51
|
+
reader = supervisor_health_reader
|
|
52
|
+
reader ? IO.select([reader], nil, nil, 0).nil? : nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Starts a filtered child and returns its unfiltered parent supervisor.
|
|
56
|
+
# @param policy [Policy]
|
|
57
|
+
# @yield runs in the filtered child
|
|
58
|
+
# @return [Supervisor]
|
|
59
|
+
def spawn(policy, recv_timeout: 10, **supervisor_options, &target)
|
|
60
|
+
raise ArgumentError, "target block is required" unless target
|
|
61
|
+
unless recv_timeout.is_a?(Numeric) && recv_timeout.real? && recv_timeout.finite? && recv_timeout.positive?
|
|
62
|
+
raise ArgumentError, "recv_timeout must be positive"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
program, install_flags = prepare_supervision(policy, supervisor_options)
|
|
66
|
+
parent_socket, child_socket = UNIXSocket.pair
|
|
67
|
+
health_reader, health_writer = IO.pipe
|
|
68
|
+
pid = fork do
|
|
69
|
+
parent_socket.close
|
|
70
|
+
health_writer.close
|
|
71
|
+
keep_health_reader(health_reader)
|
|
72
|
+
listener = Filter.install!(program, flags: install_flags)
|
|
73
|
+
FdPassing.send_fd(child_socket, listener)
|
|
74
|
+
child_socket.close
|
|
75
|
+
listener.close
|
|
76
|
+
target.call
|
|
77
|
+
exit! 0
|
|
78
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
79
|
+
warn("seccomp-notify target setup failed: #{error.message}")
|
|
80
|
+
exit! 127
|
|
81
|
+
end
|
|
82
|
+
child_socket.close
|
|
83
|
+
health_reader.close
|
|
84
|
+
listener = FdPassing.recv_fd(parent_socket, timeout: recv_timeout)
|
|
85
|
+
parent_socket.close
|
|
86
|
+
Supervisor.new(listener, target_pid: pid, target_child: true, health_writer:, **supervisor_options)
|
|
87
|
+
rescue StandardError
|
|
88
|
+
listener&.close unless listener&.closed?
|
|
89
|
+
parent_socket&.close unless parent_socket&.closed?
|
|
90
|
+
child_socket&.close unless child_socket&.closed?
|
|
91
|
+
health_reader&.close unless health_reader&.closed?
|
|
92
|
+
health_writer&.close unless health_writer&.closed?
|
|
93
|
+
terminate_child(pid)
|
|
94
|
+
raise
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Installs a filter in the caller and starts a detached supervisor child.
|
|
98
|
+
# @param policy [Policy]
|
|
99
|
+
# @param supervisor [Symbol] :spawn or :fork
|
|
100
|
+
# @return [Integer] supervisor process id
|
|
101
|
+
def supervise_self(policy, supervisor: :spawn, **options, &configure)
|
|
102
|
+
raise ArgumentError, "supervisor must be :spawn or :fork" unless %i[spawn fork].include?(supervisor)
|
|
103
|
+
raise ArgumentError, "supervisor: :spawn does not support Ruby handler blocks" if supervisor == :spawn && configure
|
|
104
|
+
|
|
105
|
+
program, install_flags = prepare_supervision(policy, options)
|
|
106
|
+
selected_features = options.fetch(:features, features)
|
|
107
|
+
raise NotSupportedError, "supervise_self requires CONTINUE when no handler block is given" if !configure && !selected_features[:continue]
|
|
108
|
+
|
|
109
|
+
parent_socket, supervisor_socket = UNIXSocket.pair
|
|
110
|
+
health_reader, health_writer = IO.pipe
|
|
111
|
+
target_pid = Process.pid
|
|
112
|
+
supervisor_pid = case supervisor
|
|
113
|
+
when :fork
|
|
114
|
+
fork_supervisor(supervisor_socket, parent_socket, health_reader, health_writer, target_pid, options, configure)
|
|
115
|
+
when :spawn
|
|
116
|
+
spawn_supervisor(supervisor_socket, parent_socket, health_writer, target_pid, options)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
supervisor_socket.close
|
|
120
|
+
health_writer.close
|
|
121
|
+
keep_health_reader(health_reader)
|
|
122
|
+
listener = Filter.install!(program, flags: install_flags)
|
|
123
|
+
FdPassing.send_fd(parent_socket, listener)
|
|
124
|
+
listener.close
|
|
125
|
+
parent_socket.close
|
|
126
|
+
Process.detach(supervisor_pid)
|
|
127
|
+
supervisor_pid
|
|
128
|
+
rescue StandardError
|
|
129
|
+
listener&.close unless listener&.closed?
|
|
130
|
+
parent_socket&.close unless parent_socket&.closed?
|
|
131
|
+
supervisor_socket&.close unless supervisor_socket&.closed?
|
|
132
|
+
health_reader&.close unless health_reader&.closed?
|
|
133
|
+
health_writer&.close unless health_writer&.closed?
|
|
134
|
+
clear_health_reader(health_reader)
|
|
135
|
+
terminate_child(supervisor_pid)
|
|
136
|
+
raise
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def fork_supervisor(socket, other_socket, health_reader, health_writer, target_pid, options, configure)
|
|
142
|
+
fork do
|
|
143
|
+
other_socket.close
|
|
144
|
+
health_reader.close
|
|
145
|
+
Process.setsid
|
|
146
|
+
listener = FdPassing.recv_fd(socket)
|
|
147
|
+
socket.close
|
|
148
|
+
instance = Supervisor.new(listener, target_pid:, target_child: false, health_writer:, **options)
|
|
149
|
+
configure ? configure.call(instance) : instance.on_unknown { |request| request.continue! }
|
|
150
|
+
instance.run
|
|
151
|
+
exit! 0
|
|
152
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
153
|
+
warn("seccomp-notify supervisor failed: #{error.message}")
|
|
154
|
+
exit! 1
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def spawn_supervisor(socket, other_socket, health_writer, target_pid, options)
|
|
159
|
+
other_socket.close_on_exec = true
|
|
160
|
+
socket.close_on_exec = false
|
|
161
|
+
health_writer.close_on_exec = false
|
|
162
|
+
encoded_options = Marshal.dump(options).unpack1("H*")
|
|
163
|
+
Process.spawn(
|
|
164
|
+
RbConfig.ruby,
|
|
165
|
+
"-I",
|
|
166
|
+
File.expand_path("..", __dir__),
|
|
167
|
+
File.expand_path("../../exe/seccomp-notify-supervisor", __dir__),
|
|
168
|
+
socket.fileno.to_s,
|
|
169
|
+
target_pid.to_s,
|
|
170
|
+
encoded_options,
|
|
171
|
+
health_writer.fileno.to_s,
|
|
172
|
+
socket.fileno => socket.fileno,
|
|
173
|
+
health_writer.fileno => health_writer.fileno,
|
|
174
|
+
pgroup: true
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def prepare_supervision(policy, options)
|
|
179
|
+
Supervisor.validate_options!(options)
|
|
180
|
+
sizes = notif_sizes
|
|
181
|
+
raise NotSupportedError, "unsupported kernel notification sizes: #{sizes.inspect}" unless sizes == Supervisor::EXPECTED_SIZES
|
|
182
|
+
|
|
183
|
+
selected_features = options.fetch(:features, features)
|
|
184
|
+
optional_flags = selected_features[:wait_killable_recv] ? Constants::SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV : 0
|
|
185
|
+
[BPF::Builder.new(policy).build, policy.flags | optional_flags]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def keep_health_reader(reader)
|
|
189
|
+
reader.close_on_exec = false
|
|
190
|
+
ENV["SECCOMP_NOTIFY_HEALTH_FD"] = reader.fileno.to_s
|
|
191
|
+
@supervisor_health_reader = reader
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def supervisor_health_reader
|
|
195
|
+
return @supervisor_health_reader if defined?(@supervisor_health_reader)
|
|
196
|
+
|
|
197
|
+
fd = ENV["SECCOMP_NOTIFY_HEALTH_FD"]
|
|
198
|
+
@supervisor_health_reader = IO.for_fd(Integer(fd), autoclose: false) if fd
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def clear_health_reader(reader)
|
|
202
|
+
return unless defined?(@supervisor_health_reader) && @supervisor_health_reader.equal?(reader)
|
|
203
|
+
|
|
204
|
+
@supervisor_health_reader = nil
|
|
205
|
+
ENV.delete("SECCOMP_NOTIFY_HEALTH_FD")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def terminate_child(pid)
|
|
209
|
+
return unless pid
|
|
210
|
+
|
|
211
|
+
waited = Process.waitpid(pid, Process::WNOHANG)
|
|
212
|
+
return if waited
|
|
213
|
+
|
|
214
|
+
Process.kill("KILL", pid)
|
|
215
|
+
Process.waitpid(pid)
|
|
216
|
+
rescue Errno::ECHILD, Errno::ESRCH
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
data/spike/notify_min.c
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#define _GNU_SOURCE
|
|
2
|
+
#include <errno.h>
|
|
3
|
+
#include <fcntl.h>
|
|
4
|
+
#include <linux/audit.h>
|
|
5
|
+
#include <linux/filter.h>
|
|
6
|
+
#include <linux/seccomp.h>
|
|
7
|
+
#include <stddef.h>
|
|
8
|
+
#include <stdio.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <sys/ioctl.h>
|
|
11
|
+
#include <sys/prctl.h>
|
|
12
|
+
#include <sys/socket.h>
|
|
13
|
+
#include <sys/syscall.h>
|
|
14
|
+
#include <sys/wait.h>
|
|
15
|
+
#include <unistd.h>
|
|
16
|
+
|
|
17
|
+
#if defined(__aarch64__)
|
|
18
|
+
#define EXPECTED_ARCH AUDIT_ARCH_AARCH64
|
|
19
|
+
#else
|
|
20
|
+
#define EXPECTED_ARCH AUDIT_ARCH_X86_64
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
static void send_fd(int socket, int fd) {
|
|
24
|
+
char byte = 0, control[CMSG_SPACE(sizeof(fd))];
|
|
25
|
+
struct iovec iov = {.iov_base = &byte, .iov_len = 1};
|
|
26
|
+
struct msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1, .msg_control = control, .msg_controllen = sizeof(control)};
|
|
27
|
+
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
|
28
|
+
cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
|
|
29
|
+
*(int *)CMSG_DATA(cmsg) = fd;
|
|
30
|
+
if (sendmsg(socket, &msg, 0) < 0) perror("sendmsg"), exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static int recv_fd(int socket) {
|
|
34
|
+
char byte, control[CMSG_SPACE(sizeof(int))];
|
|
35
|
+
struct iovec iov = {.iov_base = &byte, .iov_len = 1};
|
|
36
|
+
struct msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1, .msg_control = control, .msg_controllen = sizeof(control)};
|
|
37
|
+
if (recvmsg(socket, &msg, 0) < 0) perror("recvmsg"), exit(1);
|
|
38
|
+
return *(int *)CMSG_DATA(CMSG_FIRSTHDR(&msg));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
int main(int argc, char **argv) {
|
|
42
|
+
int sockets[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
|
|
43
|
+
pid_t pid = fork();
|
|
44
|
+
if (pid == 0) {
|
|
45
|
+
struct sock_filter ins[] = {
|
|
46
|
+
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)),
|
|
47
|
+
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, EXPECTED_ARCH, 1, 0), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
|
|
48
|
+
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
|
|
49
|
+
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_openat, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF),
|
|
50
|
+
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
|
51
|
+
};
|
|
52
|
+
struct sock_fprog program = {.len = sizeof(ins) / sizeof(ins[0]), .filter = ins};
|
|
53
|
+
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
|
|
54
|
+
int listener = syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &program);
|
|
55
|
+
send_fd(sockets[1], listener);
|
|
56
|
+
int fd = syscall(SYS_openat, AT_FDCWD, "/dev/null", 0);
|
|
57
|
+
return (argc > 1 && argv[1][0] == 'c') ? (fd < 0) : !(fd < 0 && errno == EPERM);
|
|
58
|
+
}
|
|
59
|
+
int listener = recv_fd(sockets[0]);
|
|
60
|
+
struct seccomp_notif req = {0}; struct seccomp_notif_resp resp = {0};
|
|
61
|
+
ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req); resp.id = req.id;
|
|
62
|
+
if (argc > 1 && argv[1][0] == 'c') resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE; else resp.error = -EPERM;
|
|
63
|
+
ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp);
|
|
64
|
+
int status; waitpid(pid, &status, 0); return WEXITSTATUS(status);
|
|
65
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Usage: tools/gen_syscall_table.rb X86_SYSCALL_64_TBL GENERIC_SYSCALL_TBL OUTPUT
|
|
5
|
+
|
|
6
|
+
def syscall_table(path, abis)
|
|
7
|
+
rows = File.readlines(path, chomp: true).filter_map do |line|
|
|
8
|
+
number, abi, name = line.split
|
|
9
|
+
[Integer(number), abi, name.to_sym] if number&.match?(/\A\d+\z/) && abis.include?(abi)
|
|
10
|
+
end
|
|
11
|
+
rows.group_by(&:first).to_h do |number, choices|
|
|
12
|
+
_number, _abi, name = choices.max_by { |_nr, abi, _name| abi == "64" ? 2 : abi == "common" ? 1 : 0 }
|
|
13
|
+
[name, number]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ruby_hash(name, table)
|
|
18
|
+
entries = table.sort_by { |_syscall, number| number }.map { |syscall, number| " #{syscall}: #{number}" }
|
|
19
|
+
" #{name} = {\n#{entries.join(",\n")}\n }.freeze"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
x86_path, generic_path, output = ARGV
|
|
23
|
+
abort "usage: #{$PROGRAM_NAME} X86_SYSCALL_64_TBL GENERIC_SYSCALL_TBL OUTPUT" unless output
|
|
24
|
+
|
|
25
|
+
source = <<~RUBY
|
|
26
|
+
# frozen_string_literal: true
|
|
27
|
+
|
|
28
|
+
# Generated by tools/gen_syscall_table.rb from Linux kernel syscall tables.
|
|
29
|
+
module Seccomp
|
|
30
|
+
module Notify
|
|
31
|
+
module Syscalls
|
|
32
|
+
#{ruby_hash("X86_64", syscall_table(x86_path, %w[common 64]))}
|
|
33
|
+
|
|
34
|
+
#{ruby_hash("AARCH64", syscall_table(generic_path, %w[common 64 time32 renameat stat64 rlimit memfd_secret]))}
|
|
35
|
+
|
|
36
|
+
TABLES = {x86_64: X86_64, aarch64: AARCH64}.freeze
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
def number(name, arch = Libc.architecture)
|
|
41
|
+
TABLES.fetch(arch).fetch(name.to_sym) do
|
|
42
|
+
raise InvalidPolicyError, "unknown syscall \#{name.inspect} for \#{arch}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def name(number, arch = Libc.architecture)
|
|
47
|
+
reverse(arch)[number]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reverse(arch)
|
|
51
|
+
@reverse ||= {}
|
|
52
|
+
@reverse[arch] ||= TABLES.fetch(arch).invert.freeze
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
RUBY
|
|
58
|
+
|
|
59
|
+
File.write(output, source)
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: seccomp-notify
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fiddle
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Builds seccomp cBPF filters and supervises Linux syscalls through SECCOMP_RET_USER_NOTIF
|
|
27
|
+
without native extensions.
|
|
28
|
+
email:
|
|
29
|
+
- t.yudai92@gmail.com
|
|
30
|
+
executables:
|
|
31
|
+
- seccomp-notify-supervisor
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- CHANGELOG.md
|
|
36
|
+
- LICENSE.txt
|
|
37
|
+
- README.md
|
|
38
|
+
- Rakefile
|
|
39
|
+
- examples/deny_network.rb
|
|
40
|
+
- examples/emulate_open.rb
|
|
41
|
+
- examples/strace_lite.rb
|
|
42
|
+
- exe/seccomp-notify-supervisor
|
|
43
|
+
- lib/seccomp/notify.rb
|
|
44
|
+
- lib/seccomp/notify/bpf/builder.rb
|
|
45
|
+
- lib/seccomp/notify/bpf/instruction.rb
|
|
46
|
+
- lib/seccomp/notify/bpf/program.rb
|
|
47
|
+
- lib/seccomp/notify/constants.rb
|
|
48
|
+
- lib/seccomp/notify/errors.rb
|
|
49
|
+
- lib/seccomp/notify/fd_passing.rb
|
|
50
|
+
- lib/seccomp/notify/features.rb
|
|
51
|
+
- lib/seccomp/notify/filter.rb
|
|
52
|
+
- lib/seccomp/notify/ioctl.rb
|
|
53
|
+
- lib/seccomp/notify/libc.rb
|
|
54
|
+
- lib/seccomp/notify/policy.rb
|
|
55
|
+
- lib/seccomp/notify/request.rb
|
|
56
|
+
- lib/seccomp/notify/structs.rb
|
|
57
|
+
- lib/seccomp/notify/supervisor.rb
|
|
58
|
+
- lib/seccomp/notify/syscalls.rb
|
|
59
|
+
- lib/seccomp/notify/target_memory.rb
|
|
60
|
+
- lib/seccomp/notify/version.rb
|
|
61
|
+
- spike/notify_min.c
|
|
62
|
+
- tools/gen_syscall_table.rb
|
|
63
|
+
homepage: https://rubygems.org/gems/seccomp-notify
|
|
64
|
+
licenses:
|
|
65
|
+
- MIT
|
|
66
|
+
metadata:
|
|
67
|
+
rubygems_mfa_required: 'true'
|
|
68
|
+
homepage_uri: https://rubygems.org/gems/seccomp-notify
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 3.1.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 4.0.6
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: Pure Ruby bindings for Linux seccomp user notifications
|
|
86
|
+
test_files: []
|