Module: Landlock::Policy
- Defined in:
- lib/landlock/policy.rb
Class Method Summary collapse
- .add_net_rules(fd, ports, rights, abi) ⇒ Object
- .add_path_rules(fd, paths, rights, abi) ⇒ Object
- .fs_rights_for_abi(abi) ⇒ Object
- .handled_fs_for(read:, write:, execute:, paths:, abi:) ⇒ Object
- .handled_net_for(connect_tcp:, bind_tcp:, abi:) ⇒ Object
- .mask(names, table, abi) ⇒ Object
- .normalize_path_rule(rule) ⇒ Object
- .path_rights(path, rights) ⇒ Object
- .path_rule_access_mask(path, rights, abi) ⇒ Object
- .requested?(read:, write:, execute:, connect_tcp:, bind_tcp:, paths:, scope:, allow_all_known:) ⇒ Boolean
- .restrict!(read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], allow_all_known: false) ⇒ Object
- .scope_for(scope:, abi:) ⇒ Object
Class Method Details
.add_net_rules(fd, ports, rights, abi) ⇒ Object
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/landlock/policy.rb', line 88 def add_net_rules(fd, ports, rights, abi) ports = Array(ports) return if ports.empty? raise UnsupportedError, "Landlock network rules require ABI v4+; running ABI v#{abi}" if abi < 4 access_mask = mask(rights, NET_RIGHTS, abi) return if access_mask.zero? ports.each { |port| Native.add_net_rule(fd, Integer(port), access_mask) } end |
.add_path_rules(fd, paths, rights, abi) ⇒ Object
78 79 80 81 82 83 84 85 86 |
# File 'lib/landlock/policy.rb', line 78 def add_path_rules(fd, paths, rights, abi) Array(paths).each do |path| = File.(path) access_mask = mask(path_rights(, rights), FS_RIGHTS, abi) next if access_mask.zero? Native.add_path_rule(fd, , access_mask) end end |
.fs_rights_for_abi(abi) ⇒ Object
121 122 123 124 125 126 127 |
# File 'lib/landlock/policy.rb', line 121 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 |
.handled_fs_for(read:, write:, execute:, paths:, abi:) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/landlock/policy.rb', line 129 def handled_fs_for(read:, write:, execute:, paths:, abi:) bits = 0 bits |= mask(READ_RIGHTS, FS_RIGHTS, abi) unless Array(read).empty? bits |= mask(EXEC_RIGHTS, FS_RIGHTS, abi) unless Array(execute).empty? bits |= mask(WRITE_RIGHTS, FS_RIGHTS, abi) unless Array(write).empty? Array(paths).each do |rule| path, rights = normalize_path_rule(rule) bits |= path_rule_access_mask(File.(path), rights, abi) end bits end |
.handled_net_for(connect_tcp:, bind_tcp:, abi:) ⇒ Object
141 142 143 144 145 146 147 148 149 150 |
# File 'lib/landlock/policy.rb', line 141 def handled_net_for(connect_tcp:, bind_tcp:, abi:) bits = 0 bits |= ACCESS_NET_CONNECT_TCP unless Array(connect_tcp).empty? bits |= ACCESS_NET_BIND_TCP unless Array(bind_tcp).empty? return 0 if bits.zero? raise UnsupportedError, "Landlock network rules require ABI v4+; running ABI v#{abi}" if abi < 4 bits end |
.mask(names, table, abi) ⇒ Object
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/landlock/policy.rb', line 110 def mask(names, table, abi) Array(names).reduce(0) do |bits, name| bit = table.fetch(name.to_sym) { raise ArgumentError, "unknown Landlock right: #{name.inspect}" } next bits if bit == ACCESS_FS_REFER && abi < 2 next bits if bit == ACCESS_FS_TRUNCATE && abi < 3 next bits if bit == ACCESS_FS_IOCTL_DEV && abi < 5 bits | bit end end |
.normalize_path_rule(rule) ⇒ Object
99 100 101 102 103 104 105 106 107 108 |
# File 'lib/landlock/policy.rb', line 99 def normalize_path_rule(rule) case rule when Hash [rule.fetch(:path), Array(rule.fetch(:rights))] when Array [rule.fetch(0), Array(rule.fetch(1))] else raise ArgumentError, "path rule must be {path:, rights:} or [path, rights]" end end |
.path_rights(path, rights) ⇒ Object
68 69 70 |
# File 'lib/landlock/policy.rb', line 68 def path_rights(path, rights) File.directory?(path) ? rights : Array(rights) & FILE_PATH_RIGHTS end |
.path_rule_access_mask(path, rights, abi) ⇒ Object
72 73 74 75 76 |
# File 'lib/landlock/policy.rb', line 72 def path_rule_access_mask(path, rights, abi) mask(path_rights(path, rights), FS_RIGHTS, abi).tap do |access_mask| raise ArgumentError, "path rule has no effective rights: #{path}" if access_mask.zero? end end |
.requested?(read:, write:, execute:, connect_tcp:, bind_tcp:, paths:, scope:, allow_all_known:) ⇒ Boolean
63 64 65 66 |
# File 'lib/landlock/policy.rb', line 63 def requested?(read:, write:, execute:, connect_tcp:, bind_tcp:, paths:, scope:, allow_all_known:) allow_all_known || Array(read).any? || Array(write).any? || Array(execute).any? || Array(connect_tcp).any? || Array(bind_tcp).any? || Array(paths).any? || Array(scope).any? end |
.restrict!(read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], allow_all_known: false) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/landlock/policy.rb', line 10 def restrict!( read: [], write: [], execute: [], connect_tcp: [], bind_tcp: [], paths: [], scope: [], allow_all_known: false ) abi = Native.abi_version raise UnsupportedError, "Linux Landlock is unavailable" unless abi.positive? fs_handled = ( if allow_all_known fs_rights_for_abi(abi) else handled_fs_for(read:, write:, execute:, paths:, abi:) end ) 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 = Native.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) Array(paths).each do |rule| path, rights = normalize_path_rule(rule) = File.(path) access_mask = path_rule_access_mask(, rights, abi) Native.add_path_rule(fd, , access_mask) end add_net_rules(fd, connect_tcp, [:connect_tcp], abi) add_net_rules(fd, bind_tcp, [:bind_tcp], abi) Native.restrict_self(fd) ensure Native.close_fd(fd) if fd && fd >= 0 end true end |
.scope_for(scope:, abi:) ⇒ Object
152 153 154 155 156 157 158 159 |
# File 'lib/landlock/policy.rb', line 152 def scope_for(scope:, abi:) bits = mask(scope, SCOPE_FLAGS, abi) return 0 if bits.zero? raise UnsupportedError, "Landlock scopes require ABI v6+; running ABI v#{abi}" if abi < 6 bits end |