Module: Landlock
- Defined in:
- lib/landlock.rb,
lib/landlock/version.rb,
ext/landlock/landlock.c
Defined Under Namespace
Classes: Error, SyscallError, UnsupportedError
Constant Summary collapse
- FS_RIGHTS =
{ execute: ACCESS_FS_EXECUTE, write_file: ACCESS_FS_WRITE_FILE, read_file: ACCESS_FS_READ_FILE, read_dir: ACCESS_FS_READ_DIR, remove_dir: ACCESS_FS_REMOVE_DIR, remove_file: ACCESS_FS_REMOVE_FILE, make_char: ACCESS_FS_MAKE_CHAR, make_dir: ACCESS_FS_MAKE_DIR, make_reg: ACCESS_FS_MAKE_REG, make_sock: ACCESS_FS_MAKE_SOCK, make_fifo: ACCESS_FS_MAKE_FIFO, make_block: ACCESS_FS_MAKE_BLOCK, make_sym: ACCESS_FS_MAKE_SYM, refer: ACCESS_FS_REFER, truncate: ACCESS_FS_TRUNCATE, ioctl_dev: ACCESS_FS_IOCTL_DEV }.freeze
- NET_RIGHTS =
{ bind_tcp: ACCESS_NET_BIND_TCP, connect_tcp: ACCESS_NET_CONNECT_TCP }.freeze
- SCOPE_FLAGS =
{ abstract_unix_socket: SCOPE_ABSTRACT_UNIX_SOCKET, signal: SCOPE_SIGNAL }.freeze
- READ_RIGHTS =
%i[read_file read_dir].freeze
- EXEC_RIGHTS =
%i[execute read_file read_dir].freeze
- WRITE_RIGHTS =
%i[ read_file read_dir write_file truncate remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym refer ].freeze
- FILE_PATH_RIGHTS =
%i[execute write_file read_file truncate ioctl_dev].freeze
- VERSION =
"0.1.1"- ACCESS_FS_EXECUTE =
ULL2NUM(LANDLOCK_ACCESS_FS_EXECUTE)
- ACCESS_FS_WRITE_FILE =
ULL2NUM(LANDLOCK_ACCESS_FS_WRITE_FILE)
- ACCESS_FS_READ_FILE =
ULL2NUM(LANDLOCK_ACCESS_FS_READ_FILE)
- ACCESS_FS_READ_DIR =
ULL2NUM(LANDLOCK_ACCESS_FS_READ_DIR)
- ACCESS_FS_REMOVE_DIR =
ULL2NUM(LANDLOCK_ACCESS_FS_REMOVE_DIR)
- ACCESS_FS_REMOVE_FILE =
ULL2NUM(LANDLOCK_ACCESS_FS_REMOVE_FILE)
- ACCESS_FS_MAKE_CHAR =
ULL2NUM(LANDLOCK_ACCESS_FS_MAKE_CHAR)
- ACCESS_FS_MAKE_DIR =
ULL2NUM(LANDLOCK_ACCESS_FS_MAKE_DIR)
- ACCESS_FS_MAKE_REG =
ULL2NUM(LANDLOCK_ACCESS_FS_MAKE_REG)
- ACCESS_FS_MAKE_SOCK =
ULL2NUM(LANDLOCK_ACCESS_FS_MAKE_SOCK)
- ACCESS_FS_MAKE_FIFO =
ULL2NUM(LANDLOCK_ACCESS_FS_MAKE_FIFO)
- ACCESS_FS_MAKE_BLOCK =
ULL2NUM(LANDLOCK_ACCESS_FS_MAKE_BLOCK)
- ACCESS_FS_MAKE_SYM =
ULL2NUM(LANDLOCK_ACCESS_FS_MAKE_SYM)
- ACCESS_FS_REFER =
ULL2NUM(LANDLOCK_ACCESS_FS_REFER)
- ACCESS_FS_TRUNCATE =
ULL2NUM(LANDLOCK_ACCESS_FS_TRUNCATE)
- ACCESS_FS_IOCTL_DEV =
ULL2NUM(LANDLOCK_ACCESS_FS_IOCTL_DEV)
- ACCESS_NET_BIND_TCP =
ULL2NUM(LANDLOCK_ACCESS_NET_BIND_TCP)
- ACCESS_NET_CONNECT_TCP =
ULL2NUM(LANDLOCK_ACCESS_NET_CONNECT_TCP)
- SCOPE_ABSTRACT_UNIX_SOCKET =
ULL2NUM(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET)
- SCOPE_SIGNAL =
ULL2NUM(LANDLOCK_SCOPE_SIGNAL)
Class Method Summary collapse
- ._add_net_rule(ruleset_fd, port, access_bits) ⇒ Object
- ._add_path_rule(ruleset_fd, path, access_bits) ⇒ Object
- ._close_fd(fd_value) ⇒ Object
- ._create_ruleset(*args) ⇒ Object
- ._fs_rights_for_abi(abi) ⇒ Object
- ._restrict_self(ruleset_fd) ⇒ Object
- .abi_version ⇒ Object
- .exec(argv, read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], chdir: nil, env: nil, unsetenv_others: false, close_others: true, allow_all_known: false) ⇒ Object
- .restrict!(read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], allow_all_known: false) ⇒ Object
- .spawn(argv, read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], chdir: nil, env: nil, unsetenv_others: false, close_others: true, allow_all_known: false) ⇒ Object
- .supported? ⇒ Boolean
Class Method Details
._add_net_rule(ruleset_fd, port, access_bits) ⇒ Object
238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'ext/landlock/landlock.c', line 238
static VALUE rb_ll_add_net_rule(VALUE self, VALUE ruleset_fd, VALUE port, VALUE access_bits) {
unsigned long long p = NUM2ULL(port);
if (p > 65535ULL) rb_raise(rb_eArgError, "TCP port must be between 0 and 65535");
struct rb_landlock_net_port_attr rule;
memset(&rule, 0, sizeof(rule));
rule.allowed_access = NUM2ULL(access_bits);
rule.port = p;
long ret = ll_add_rule(NUM2INT(ruleset_fd), LANDLOCK_RULE_NET_PORT, &rule, 0);
if (ret < 0) raise_syscall_error("landlock_add_rule(net_port)");
return Qtrue;
}
|
._add_path_rule(ruleset_fd, path, access_bits) ⇒ Object
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'ext/landlock/landlock.c', line 215
static VALUE rb_ll_add_path_rule(VALUE self, VALUE ruleset_fd, VALUE path, VALUE access_bits) {
int ruleset = NUM2INT(ruleset_fd);
uint64_t allowed_access = NUM2ULL(access_bits);
Check_Type(path, T_STRING);
const char *cpath = StringValueCStr(path);
int parent_fd = open(cpath, O_PATH | O_CLOEXEC);
if (parent_fd < 0) raise_syscall_error("open");
struct rb_landlock_path_beneath_attr rule;
memset(&rule, 0, sizeof(rule));
rule.allowed_access = allowed_access;
rule.parent_fd = parent_fd;
long ret = ll_add_rule(ruleset, LANDLOCK_RULE_PATH_BENEATH, &rule, 0);
int saved_errno = errno;
close(parent_fd);
if (ret < 0) {
errno = saved_errno;
raise_syscall_error("landlock_add_rule(path_beneath)");
}
return Qtrue;
}
|
._close_fd(fd_value) ⇒ Object
262 263 264 265 266 |
# File 'ext/landlock/landlock.c', line 262
static VALUE rb_ll_close_fd(VALUE self, VALUE fd_value) {
int fd = NUM2INT(fd_value);
if (fd >= 0) close(fd);
return Qnil;
}
|
._create_ruleset(*args) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'ext/landlock/landlock.c', line 191
static VALUE rb_ll_create_ruleset(int argc, VALUE *argv, VALUE self) {
VALUE fs_bits, net_bits, scoped_bits;
rb_scan_args(argc, argv, "21", &fs_bits, &net_bits, &scoped_bits);
struct rb_landlock_ruleset_attr attr;
uint64_t handled_access_net = NUM2ULL(net_bits);
uint64_t scoped = NIL_P(scoped_bits) ? 0 : NUM2ULL(scoped_bits);
size_t attr_size = offsetof(struct rb_landlock_ruleset_attr, handled_access_net);
if (scoped != 0) {
attr_size = sizeof(struct rb_landlock_ruleset_attr);
} else if (handled_access_net != 0) {
attr_size = offsetof(struct rb_landlock_ruleset_attr, scoped);
}
memset(&attr, 0, sizeof(attr));
attr.handled_access_fs = NUM2ULL(fs_bits);
attr.handled_access_net = handled_access_net;
attr.scoped = scoped;
long fd = ll_create_ruleset(&attr, attr_size, 0);
if (fd < 0) raise_syscall_error("landlock_create_ruleset");
return INT2NUM(fd);
}
|
._fs_rights_for_abi(abi) ⇒ Object
227 228 229 230 231 232 233 |
# File 'lib/landlock.rb', line 227 def _fs_rights_for_abi(abi) rights = FS_RIGHTS.values.reduce(0, :|) rights &= ~ACCESS_FS_REFER if abi < 2 rights &= ~ACCESS_FS_TRUNCATE if abi < 3 rights &= ~ACCESS_FS_IOCTL_DEV if abi < 5 rights end |
._restrict_self(ruleset_fd) ⇒ Object
252 253 254 255 256 257 258 259 260 |
# File 'ext/landlock/landlock.c', line 252
static VALUE rb_ll_restrict_self(VALUE self, VALUE ruleset_fd) {
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
raise_syscall_error("prctl(PR_SET_NO_NEW_PRIVS)");
}
long ret = ll_restrict_self(NUM2INT(ruleset_fd), 0);
if (ret < 0) raise_syscall_error("landlock_restrict_self");
return Qtrue;
}
|
.abi_version ⇒ Object
182 183 184 185 186 187 188 189 |
# File 'ext/landlock/landlock.c', line 182
static VALUE rb_ll_abi_version(VALUE self) {
long abi = ll_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
if (abi < 0) {
if (errno == ENOSYS || errno == EOPNOTSUPP) return INT2FIX(0);
raise_syscall_error("landlock_create_ruleset");
}
return LONG2NUM(abi);
}
|
.exec(argv, read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], chdir: nil, env: nil, unsetenv_others: false, close_others: true, allow_all_known: false) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/landlock.rb', line 102 def exec(argv, read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], chdir: nil, env: nil, unsetenv_others: false, close_others: true, allow_all_known: false) argv = normalize_argv(argv) ensure_landlock_supported! pid = fork do begin # Safe after fork: this runs only in the child process before exec. Dir.chdir(chdir) if chdir # rubocop:disable Discourse/NoChdir restrict!(read:, write:, execute:, connect_tcp:, bind_tcp:, paths:, scope:, allow_all_known:) Kernel.exec(*kernel_exec_args(argv, env, unsetenv_others:, close_others:)) rescue Exception => error exit_child!(error) end end _, status = Process.wait2(pid) status end |
.restrict!(read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], allow_all_known: false) ⇒ Object
65 66 67 68 69 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 95 96 97 98 99 100 |
# File 'lib/landlock.rb', line 65 def restrict!(read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], allow_all_known: false) abi = abi_version raise UnsupportedError, "Linux Landlock is unavailable" unless abi.positive? fs_handled = allow_all_known ? _fs_rights_for_abi(abi) : _handled_fs_for(read:, write:, execute:, paths:, abi:) net_handled = _handled_net_for(connect_tcp:, bind_tcp:, abi:) scoped = _scope_for(scope:, abi:) if fs_handled.zero? && net_handled.zero? && scoped.zero? raise ArgumentError, "empty Landlock policy: provide filesystem paths, TCP ports, or scopes" end fd = _create_ruleset(fs_handled, net_handled, scoped) begin add_path_rules(fd, read, READ_RIGHTS, abi) add_path_rules(fd, execute, EXEC_RIGHTS, abi) add_path_rules(fd, write, WRITE_RIGHTS, abi) paths.each do |rule| path, rights = normalize_path_rule(rule) access_mask = mask(rights, FS_RIGHTS, abi) next if access_mask.zero? _add_path_rule(fd, File.(path), access_mask) end add_net_rules(fd, connect_tcp, [:connect_tcp], abi) add_net_rules(fd, bind_tcp, [:bind_tcp], abi) _restrict_self(fd) ensure _close_fd(fd) if fd && fd >= 0 end true end |
.spawn(argv, read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], chdir: nil, env: nil, unsetenv_others: false, close_others: true, allow_all_known: false) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/landlock.rb', line 122 def spawn(argv, read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], chdir: nil, env: nil, unsetenv_others: false, close_others: true, allow_all_known: false) argv = normalize_argv(argv) ensure_landlock_supported! fork do begin # Safe after fork: this runs only in the child process before exec. Dir.chdir(chdir) if chdir # rubocop:disable Discourse/NoChdir restrict!(read:, write:, execute:, connect_tcp:, bind_tcp:, paths:, scope:, allow_all_known:) Kernel.exec(*kernel_exec_args(argv, env, unsetenv_others:, close_others:)) rescue Exception => error exit_child!(error) end end end |
.supported? ⇒ Boolean
59 60 61 62 63 |
# File 'lib/landlock.rb', line 59 def supported? abi_version.positive? rescue Error false end |