Class: Sandbox::Landlock
- Inherits:
-
LinuxBackend
- Object
- LinuxBackend
- Sandbox::Landlock
- Includes:
- Utils::Output::Mixin
- Defined in:
- extend/os/linux/sandbox/landlock.rb
Class Method Summary collapse
- .abi_version ⇒ Integer? private
- .available? ⇒ Boolean private
- .close_file_descriptor(file_descriptor) ⇒ Integer private
- .failure_reason ⇒ String? private
-
.full_write_isolation? ⇒ Boolean
private
Landlock cannot restrict chmod, chown, extended attributes or timestamp changes.
-
.kernel_abi_version ⇒ Integer?
private
The Landlock ABI version provided by the running kernel, regardless of whether Homebrew's Linux sandbox is enabled or usable.
- .landlock_add_rule(ruleset_fd, type, attributes, flags) ⇒ Integer private
- .landlock_create_ruleset(attributes, size, flags) ⇒ Integer private
- .landlock_restrict_self(ruleset_fd, flags) ⇒ Integer private
- .last_error ⇒ Integer private
- .open_path(path) ⇒ Integer private
- .reset_state! ⇒ void private
- .set_no_new_privileges ⇒ Integer private
- .state ⇒ Symbol private
Instance Method Summary collapse
- #apply! ⇒ void private
- #command(args, tmpdir) ⇒ Array<String, ::Pathname> private
- #initialize(profile) ⇒ void constructor private
- #readable_paths(denied_paths) ⇒ Array<String> private
- #ruleset_attributes(abi) ⇒ Array<(String, Integer, Integer)> private
Methods included from Utils::Output::Mixin
#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_outside_github_actions, #opoo_without_github_actions_annotation, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_uninstalled, #pretty_unmarked, #pretty_upgradable, #pretty_warning
Methods inherited from LinuxBackend
Constructor Details
#initialize(profile) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
276 277 278 279 280 281 282 283 |
# File 'extend/os/linux/sandbox/landlock.rb', line 276 def initialize(profile) super @writable_paths = T.let([], T::Array[String]) @readable_paths = T.let([], T::Array[String]) @error_pipe_path = T.let(nil, T.nilable(String)) @deny_all_network = T.let(false, T::Boolean) @deny_read = T.let(false, T::Boolean) end |
Class Method Details
.abi_version ⇒ Integer?
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
114 115 116 117 |
# File 'extend/os/linux/sandbox/landlock.rb', line 114 def abi_version state @abi_version end |
.available? ⇒ Boolean
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
104 105 106 |
# File 'extend/os/linux/sandbox/landlock.rb', line 104 def available? state == :available end |
.close_file_descriptor(file_descriptor) ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
228 229 230 231 232 233 234 235 236 237 238 |
# File 'extend/os/linux/sandbox/landlock.rb', line 228 def close_file_descriptor(file_descriptor) @close ||= T.let( Fiddle::Function.new( Fiddle.dlopen(nil)["close"], [Fiddle::TYPE_INT], Fiddle::TYPE_INT, ), T.nilable(Fiddle::Function), ) @close.call(file_descriptor) end |
.failure_reason ⇒ String?
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'extend/os/linux/sandbox/landlock.rb', line 132 def failure_reason case state when :available nil when :config_disabled "Landlock cannot be used because Linux sandboxing is disabled." when :missing_fiddle "Landlock requires Ruby's bundled Fiddle library." when :unsupported "Landlock is not supported by this Linux kernel." when :disabled "Landlock is disabled by this Linux kernel." when :unsupported_abi abi = @abi_version if abi "Landlock ABI #{MINIMUM_ABI} or later is required; found ABI #{abi}." else "Landlock ABI #{MINIMUM_ABI} or later is required." end else "Landlock is not available." end end |
.full_write_isolation? ⇒ Boolean
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
Landlock cannot restrict chmod, chown, extended attributes or timestamp changes. Callers requiring full write isolation must compensate for these limitations: https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#filesystem-flags
101 |
# File 'extend/os/linux/sandbox/landlock.rb', line 101 def full_write_isolation? = false |
.kernel_abi_version ⇒ Integer?
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
The Landlock ABI version provided by the running kernel, regardless of whether Homebrew's Linux sandbox is enabled or usable.
122 123 124 125 126 127 128 129 |
# File 'extend/os/linux/sandbox/landlock.rb', line 122 def kernel_abi_version require "fiddle" version = landlock_create_ruleset(nil, 0, CREATE_RULESET_VERSION) version if version.positive? rescue LoadError, Fiddle::DLError nil end |
.landlock_add_rule(ruleset_fd, type, attributes, flags) ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
176 177 178 179 180 181 182 183 184 185 186 |
# File 'extend/os/linux/sandbox/landlock.rb', line 176 def landlock_add_rule(ruleset_fd, type, attributes, flags) @landlock_add_rule ||= T.let( Fiddle::Function.new( Fiddle.dlopen(nil)["syscall"], [Fiddle::TYPE_LONG, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT], Fiddle::TYPE_LONG, ), T.nilable(Fiddle::Function), ) @landlock_add_rule.call(ADD_RULE_SYSCALL, ruleset_fd, type, attributes, flags) end |
.landlock_create_ruleset(attributes, size, flags) ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
163 164 165 166 167 168 169 170 171 172 173 |
# File 'extend/os/linux/sandbox/landlock.rb', line 163 def landlock_create_ruleset(attributes, size, flags) @landlock_create_ruleset ||= T.let( Fiddle::Function.new( Fiddle.dlopen(nil)["syscall"], [Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_UINT], Fiddle::TYPE_LONG, ), T.nilable(Fiddle::Function), ) @landlock_create_ruleset.call(CREATE_RULESET_SYSCALL, attributes, size, flags) end |
.landlock_restrict_self(ruleset_fd, flags) ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
189 190 191 192 193 194 195 196 197 198 199 |
# File 'extend/os/linux/sandbox/landlock.rb', line 189 def landlock_restrict_self(ruleset_fd, flags) @landlock_restrict_self ||= T.let( Fiddle::Function.new( Fiddle.dlopen(nil)["syscall"], [Fiddle::TYPE_LONG, Fiddle::TYPE_INT, Fiddle::TYPE_UINT], Fiddle::TYPE_LONG, ), T.nilable(Fiddle::Function), ) @landlock_restrict_self.call(RESTRICT_SELF_SYSCALL, ruleset_fd, flags) end |
.last_error ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
241 242 243 |
# File 'extend/os/linux/sandbox/landlock.rb', line 241 def last_error Fiddle.last_error end |
.open_path(path) ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
215 216 217 218 219 220 221 222 223 224 225 |
# File 'extend/os/linux/sandbox/landlock.rb', line 215 def open_path(path) @open ||= T.let( Fiddle::Function.new( Fiddle.dlopen(nil)["open"], [Fiddle::TYPE_CONST_STRING, Fiddle::TYPE_INT], Fiddle::TYPE_INT, ), T.nilable(Fiddle::Function), ) @open.call(path, O_PATH | O_CLOEXEC) end |
.reset_state! ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
157 158 159 160 |
# File 'extend/os/linux/sandbox/landlock.rb', line 157 def reset_state! @state = T.let(nil, T.nilable(Symbol)) @abi_version = T.let(nil, T.nilable(Integer)) end |
.set_no_new_privileges ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
202 203 204 205 206 207 208 209 210 211 212 |
# File 'extend/os/linux/sandbox/landlock.rb', line 202 def set_no_new_privileges @prctl ||= T.let( Fiddle::Function.new( Fiddle.dlopen(nil)["prctl"], [Fiddle::TYPE_INT, Fiddle::TYPE_ULONG, Fiddle::TYPE_ULONG, Fiddle::TYPE_ULONG, Fiddle::TYPE_ULONG], Fiddle::TYPE_INT, ), T.nilable(Fiddle::Function), ) @prctl.call(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) end |
.state ⇒ Symbol
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
109 110 111 |
# File 'extend/os/linux/sandbox/landlock.rb', line 109 def state @state ||= T.let(compute_state, T.nilable(Symbol)) end |
Instance Method Details
#apply! ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'extend/os/linux/sandbox/landlock.rb', line 299 def apply! abi = self.class.abi_version if !abi || abi < MINIMUM_ABI raise self.class.failure_reason || "Landlock ABI #{MINIMUM_ABI} or later is required." end if @deny_all_network && abi < MINIMUM_FULL_NETWORK_ABI network_restrictions = if abi >= MINIMUM_NETWORK_ABI "Applying the network restrictions supported by this kernel." else "This kernel cannot restrict network access." end opoo "Landlock ABI #{MINIMUM_FULL_NETWORK_ABI} or later is required to deny all network access; " \ "found ABI #{abi}. #{network_restrictions}" end attributes, handled_access_fs, allowed_write_access_fs = ruleset_attributes(abi) ruleset_fd = self.class.landlock_create_ruleset(attributes, attributes.bytesize, 0) raise_system_call_error("landlock_create_ruleset") if ruleset_fd.negative? begin # PTY allocation opens `/dev/ptmx` read-write, then configures its # dynamically allocated `/dev/pts/*` slave with device ioctls: # https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c # https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#ioctl-support pty_access = ACCESS_FS_WRITE_FILE pty_access |= ACCESS_FS_IOCTL_DEV if abi >= 5 pty_access |= ACCESS_FS_READ_FILE if @deny_read # `/dev/full` is Linux's standard ENOSPC test device. Opening it with # `fopen(..., "w")` also requires Landlock's truncate right: # https://github.com/torvalds/linux/blob/master/drivers/char/mem.c # POSIX shared memory and message queues use `/dev/shm` and # `/dev/mqueue`. These grants retain normal kernel permissions but do # not provide a private IPC namespace: # https://github.com/bminor/glibc/blob/master/sysdeps/posix/shm-directory.c # https://www.kernel.org/doc/html/latest/filesystems/mqueue.html device_path_rules = T.let({ "/dev/full" => FILE_WRITE_ACCESS_FS, "/dev/mqueue" => allowed_write_access_fs | (@deny_read ? ACCESS_FS_READ_FILE : 0), "/dev/ptmx" => pty_access, "/dev/pts" => pty_access, "/dev/shm" => allowed_write_access_fs, "/dev/tty" => pty_access, }, T::Hash[String, Integer]) device_path_rules.each do |path, allowed_access| next unless File.exist?(path) add_path_rule(ruleset_fd, path, allowed_access & handled_access_fs) end error_pipe_path = @error_pipe_path if @deny_all_network && abi >= 9 && error_pipe_path add_path_rule(ruleset_fd, error_pipe_path, ACCESS_FS_RESOLVE_UNIX) end @readable_paths.each do |path| allowed_access = File.directory?(path) ? READ_ACCESS_FS : FILE_READ_ACCESS_FS add_path_rule(ruleset_fd, path, allowed_access) end @writable_paths.each do |path| allowed_access = if File.directory?(path) allowed_write_access_fs else allowed_write_access_fs & FILE_WRITE_ACCESS_FS end add_path_rule(ruleset_fd, path, allowed_access & handled_access_fs) end raise_system_call_error("prctl") if self.class.set_no_new_privileges.negative? if self.class.landlock_restrict_self(ruleset_fd, 0).negative? raise_system_call_error("landlock_restrict_self") end ensure close_file_descriptor(ruleset_fd) end end |
#command(args, tmpdir) ⇒ Array<String, ::Pathname>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
286 287 288 289 290 291 292 293 294 295 296 |
# File 'extend/os/linux/sandbox/landlock.rb', line 286 def command(args, tmpdir) paths = writable_paths @writable_paths = paths.keys | [File::NULL, tmpdir] @writable_paths.each { |path| prepare_writable_path(path, paths.fetch(path, :subpath)) } denied_read_paths = self.denied_read_paths @readable_paths = readable_paths(denied_read_paths) @deny_read = denied_read_paths.any? @deny_all_network = deny_all_network? @error_pipe_path = File.join(tmpdir, "socket") args end |
#readable_paths(denied_paths) ⇒ Array<String>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
403 404 405 406 407 408 409 |
# File 'extend/os/linux/sandbox/landlock.rb', line 403 def readable_paths(denied_paths) return [] if denied_paths.empty? || denied_paths.include?(root_path) root_path.children.sort.each_with_object([]) do |path, paths| add_readable_path(path, denied_paths, paths) end end |
#ruleset_attributes(abi) ⇒ Array<(String, Integer, Integer)>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'extend/os/linux/sandbox/landlock.rb', line 377 def ruleset_attributes(abi) allowed_access_fs = WRITE_ACCESS_FS allowed_access_fs |= ACCESS_FS_REFER if abi >= MINIMUM_ABI allowed_access_fs |= ACCESS_FS_TRUNCATE if abi >= MINIMUM_TRUNCATE_ABI handled_access_fs = allowed_access_fs # IOCTL_DEV is available from ABI 5 and deliberately remains absent from # allowed path rules, denying device ioctls opened inside the sandbox: # https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#ioctl-support handled_access_fs |= ACCESS_FS_IOCTL_DEV if abi >= 5 handled_access_fs |= READ_ACCESS_FS if @deny_read handled_access_fs |= ACCESS_FS_RESOLVE_UNIX if @deny_all_network && abi >= 9 # Optional ruleset fields are appended as `__u64` members, so only pass # the prefix needed for features supported by the running kernel. attributes = [handled_access_fs] if @deny_all_network && abi >= MINIMUM_NETWORK_ABI handled_access_net = ACCESS_NET_BIND_TCP | ACCESS_NET_CONNECT_TCP handled_access_net |= ACCESS_NET_BIND_UDP | ACCESS_NET_CONNECT_SEND_UDP if abi >= MINIMUM_FULL_NETWORK_ABI attributes << handled_access_net attributes << SCOPE_ABSTRACT_UNIX_SOCKET if abi >= 6 end [attributes.pack("Q*"), handled_access_fs, allowed_access_fs] end |