Pull Landlock LSM from James Morris: "Add Landlock, a new LSM from Mickaël Salaün. Briefly, Landlock provides for unprivileged application sandboxing. From Mickaël's cover letter: "The goal of Landlock is to enable to restrict ambient rights (e.g. global filesystem access) for a set of processes. Because Landlock is a stackable LSM [1], it makes possible to create safe security sandboxes as new security layers in addition to the existing system-wide access-controls. This kind of sandbox is expected to help mitigate the security impact of bugs or unexpected/malicious behaviors in user-space applications. Landlock empowers any process, including unprivileged ones, to securely restrict themselves. Landlock is inspired by seccomp-bpf but instead of filtering syscalls and their raw arguments, a Landlock rule can restrict the use of kernel objects like file hierarchies, according to the kernel semantic. Landlock also takes inspiration from other OS sandbox mechanisms: XNU Sandbox, FreeBSD Capsicum or OpenBSD Pledge/Unveil. In this current form, Landlock misses some access-control features. This enables to minimize this patch series and ease review. This series still addresses multiple use cases, especially with the combined use of seccomp-bpf: applications with built-in sandboxing, init systems, security sandbox tools and security-oriented APIs [2]" The cover letter and v34 posting is here: https://lore.kernel.org/linux-security-module/20210422154123.13086-1-mic@digikod.net/ See also: https://landlock.io/ This code has had extensive design discussion and review over several years" Link: https://lore.kernel.org/lkml/50db058a-7dde-441b-a7f9-f6837fe8b69f@schaufler-ca.com/ [1] Link: https://lore.kernel.org/lkml/f646e1c7-33cf-333f-070c-0a40ad0468cd@digikod.net/ [2] * tag 'landlock_v34' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: landlock: Enable user space to infer supported features landlock: Add user and kernel documentation samples/landlock: Add a sandbox manager example selftests/landlock: Add user space tests landlock: Add syscall implementations arch: Wire up Landlock syscalls fs,security: Add sb_delete hook landlock: Support filesystem access-control LSM: Infrastructure management of the superblock landlock: Add ptrace restrictions landlock: Set up the security framework and manage credentials landlock: Add ruleset and domain management landlock: Add object management
Unified · Split
| @@ -16,3 +16,4 @@ Security Documentation · 15 unmodified lines | |||
| 16 | siphash | 16 | siphash |
| 17 | tpm/index | 17 | tpm/index |
| 18 | digsig | 18 | digsig |
| 19 | landlock | ||
| @@ -0,0 +1,85 @@ | |||
| 1 | .. SPDX-License-Identifier: GPL-2.0 | ||
| 2 | .. Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 3 | .. Copyright © 2019-2020 ANSSI | ||
| 4 | |||
| 5 | ================================== | ||
| 6 | Landlock LSM: kernel documentation | ||
| 7 | ================================== | ||
| 8 | |||
| 9 | :Author: Mickaël Salaün | ||
| 10 | :Date: March 2021 | ||
| 11 | |||
| 12 | Landlock's goal is to create scoped access-control (i.e. sandboxing). To | ||
| 13 | harden a whole system, this feature should be available to any process, | ||
| 14 | including unprivileged ones. Because such process may be compromised or | ||
| 15 | backdoored (i.e. untrusted), Landlock's features must be safe to use from the | ||
| 16 | kernel and other processes point of view. Landlock's interface must therefore | ||
| 17 | expose a minimal attack surface. | ||
| 18 | |||
| 19 | Landlock is designed to be usable by unprivileged processes while following the | ||
| 20 | system security policy enforced by other access control mechanisms (e.g. DAC, | ||
| 21 | LSM). Indeed, a Landlock rule shall not interfere with other access-controls | ||
| 22 | enforced on the system, only add more restrictions. | ||
| 23 | |||
| 24 | Any user can enforce Landlock rulesets on their processes. They are merged and | ||
| 25 | evaluated according to the inherited ones in a way that ensures that only more | ||
| 26 | constraints can be added. | ||
| 27 | |||
| 28 | User space documentation can be found here: :doc:`/userspace-api/landlock`. | ||
| 29 | |||
| 30 | Guiding principles for safe access controls | ||
| 31 | =========================================== | ||
| 32 | |||
| 33 | * A Landlock rule shall be focused on access control on kernel objects instead | ||
| 34 | of syscall filtering (i.e. syscall arguments), which is the purpose of | ||
| 35 | seccomp-bpf. | ||
| 36 | * To avoid multiple kinds of side-channel attacks (e.g. leak of security | ||
| 37 | policies, CPU-based attacks), Landlock rules shall not be able to | ||
| 38 | programmatically communicate with user space. | ||
| 39 | * Kernel access check shall not slow down access request from unsandboxed | ||
| 40 | processes. | ||
| 41 | * Computation related to Landlock operations (e.g. enforcing a ruleset) shall | ||
| 42 | only impact the processes requesting them. | ||
| 43 | |||
| 44 | Tests | ||
| 45 | ===== | ||
| 46 | |||
| 47 | Userspace tests for backward compatibility, ptrace restrictions and filesystem | ||
| 48 | support can be found here: `tools/testing/selftests/landlock/`_. | ||
| 49 | |||
| 50 | Kernel structures | ||
| 51 | ================= | ||
| 52 | |||
| 53 | Object | ||
| 54 | ------ | ||
| 55 | |||
| 56 | .. kernel-doc:: security/landlock/object.h | ||
| 57 | :identifiers: | ||
| 58 | |||
| 59 | Filesystem | ||
| 60 | ---------- | ||
| 61 | |||
| 62 | .. kernel-doc:: security/landlock/fs.h | ||
| 63 | :identifiers: | ||
| 64 | |||
| 65 | Ruleset and domain | ||
| 66 | ------------------ | ||
| 67 | |||
| 68 | A domain is a read-only ruleset tied to a set of subjects (i.e. tasks' | ||
| 69 | credentials). Each time a ruleset is enforced on a task, the current domain is | ||
| 70 | duplicated and the ruleset is imported as a new layer of rules in the new | ||
| 71 | domain. Indeed, once in a domain, each rule is tied to a layer level. To | ||
| 72 | grant access to an object, at least one rule of each layer must allow the | ||
| 73 | requested action on the object. A task can then only transit to a new domain | ||
| 74 | that is the intersection of the constraints from the current domain and those | ||
| 75 | of a ruleset provided by the task. | ||
| 76 | |||
| 77 | The definition of a subject is implicit for a task sandboxing itself, which | ||
| 78 | makes the reasoning much easier and helps avoid pitfalls. | ||
| 79 | |||
| 80 | .. kernel-doc:: security/landlock/ruleset.h | ||
| 81 | :identifiers: | ||
| 82 | |||
| 83 | .. Links | ||
| 84 | .. _tools/testing/selftests/landlock/: | ||
| 85 | https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/tools/testing/selftests/landlock/ | ||
| @@ -18,6 +18,7 @@ place where this information is gathered. · 17 unmodified lines | |||
| 18 | 18 | ||
| 19 | no_new_privs | 19 | no_new_privs |
| 20 | seccomp_filter | 20 | seccomp_filter |
| 21 | landlock | ||
| 21 | unshare | 22 | unshare |
| 22 | spec_ctrl | 23 | spec_ctrl |
| 23 | accelerators/ocxl | 24 | accelerators/ocxl |
| @@ -0,0 +1,311 @@ | |||
| 1 | .. SPDX-License-Identifier: GPL-2.0 | ||
| 2 | .. Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 3 | .. Copyright © 2019-2020 ANSSI | ||
| 4 | .. Copyright © 2021 Microsoft Corporation | ||
| 5 | |||
| 6 | ===================================== | ||
| 7 | Landlock: unprivileged access control | ||
| 8 | ===================================== | ||
| 9 | |||
| 10 | :Author: Mickaël Salaün | ||
| 11 | :Date: March 2021 | ||
| 12 | |||
| 13 | The goal of Landlock is to enable to restrict ambient rights (e.g. global | ||
| 14 | filesystem access) for a set of processes. Because Landlock is a stackable | ||
| 15 | LSM, it makes possible to create safe security sandboxes as new security layers | ||
| 16 | in addition to the existing system-wide access-controls. This kind of sandbox | ||
| 17 | is expected to help mitigate the security impact of bugs or | ||
| 18 | unexpected/malicious behaviors in user space applications. Landlock empowers | ||
| 19 | any process, including unprivileged ones, to securely restrict themselves. | ||
| 20 | |||
| 21 | Landlock rules | ||
| 22 | ============== | ||
| 23 | |||
| 24 | A Landlock rule describes an action on an object. An object is currently a | ||
| 25 | file hierarchy, and the related filesystem actions are defined with `access | ||
| 26 | rights`_. A set of rules is aggregated in a ruleset, which can then restrict | ||
| 27 | the thread enforcing it, and its future children. | ||
| 28 | |||
| 29 | Defining and enforcing a security policy | ||
| 30 | ---------------------------------------- | ||
| 31 | |||
| 32 | We first need to create the ruleset that will contain our rules. For this | ||
| 33 | example, the ruleset will contain rules that only allow read actions, but write | ||
| 34 | actions will be denied. The ruleset then needs to handle both of these kind of | ||
| 35 | actions. | ||
| 36 | |||
| 37 | .. code-block:: c | ||
| 38 | |||
| 39 | int ruleset_fd; | ||
| 40 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 41 | .handled_access_fs = | ||
| 42 | LANDLOCK_ACCESS_FS_EXECUTE | | ||
| 43 | LANDLOCK_ACCESS_FS_WRITE_FILE | | ||
| 44 | LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 45 | LANDLOCK_ACCESS_FS_READ_DIR | | ||
| 46 | LANDLOCK_ACCESS_FS_REMOVE_DIR | | ||
| 47 | LANDLOCK_ACCESS_FS_REMOVE_FILE | | ||
| 48 | LANDLOCK_ACCESS_FS_MAKE_CHAR | | ||
| 49 | LANDLOCK_ACCESS_FS_MAKE_DIR | | ||
| 50 | LANDLOCK_ACCESS_FS_MAKE_REG | | ||
| 51 | LANDLOCK_ACCESS_FS_MAKE_SOCK | | ||
| 52 | LANDLOCK_ACCESS_FS_MAKE_FIFO | | ||
| 53 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | | ||
| 54 | LANDLOCK_ACCESS_FS_MAKE_SYM, | ||
| 55 | }; | ||
| 56 | |||
| 57 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | ||
| 58 | if (ruleset_fd < 0) { | ||
| 59 | perror("Failed to create a ruleset"); | ||
| 60 | return 1; | ||
| 61 | } | ||
| 62 | |||
| 63 | We can now add a new rule to this ruleset thanks to the returned file | ||
| 64 | descriptor referring to this ruleset. The rule will only allow reading the | ||
| 65 | file hierarchy ``/usr``. Without another rule, write actions would then be | ||
| 66 | denied by the ruleset. To add ``/usr`` to the ruleset, we open it with the | ||
| 67 | ``O_PATH`` flag and fill the &struct landlock_path_beneath_attr with this file | ||
| 68 | descriptor. | ||
| 69 | |||
| 70 | .. code-block:: c | ||
| 71 | |||
| 72 | int err; | ||
| 73 | struct landlock_path_beneath_attr path_beneath = { | ||
| 74 | .allowed_access = | ||
| 75 | LANDLOCK_ACCESS_FS_EXECUTE | | ||
| 76 | LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 77 | LANDLOCK_ACCESS_FS_READ_DIR, | ||
| 78 | }; | ||
| 79 | |||
| 80 | path_beneath.parent_fd = open("/usr", O_PATH | O_CLOEXEC); | ||
| 81 | if (path_beneath.parent_fd < 0) { | ||
| 82 | perror("Failed to open file"); | ||
| 83 | close(ruleset_fd); | ||
| 84 | return 1; | ||
| 85 | } | ||
| 86 | err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 87 | &path_beneath, 0); | ||
| 88 | close(path_beneath.parent_fd); | ||
| 89 | if (err) { | ||
| 90 | perror("Failed to update ruleset"); | ||
| 91 | close(ruleset_fd); | ||
| 92 | return 1; | ||
| 93 | } | ||
| 94 | |||
| 95 | We now have a ruleset with one rule allowing read access to ``/usr`` while | ||
| 96 | denying all other handled accesses for the filesystem. The next step is to | ||
| 97 | restrict the current thread from gaining more privileges (e.g. thanks to a SUID | ||
| 98 | binary). | ||
| 99 | |||
| 100 | .. code-block:: c | ||
| 101 | |||
| 102 | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { | ||
| 103 | perror("Failed to restrict privileges"); | ||
| 104 | close(ruleset_fd); | ||
| 105 | return 1; | ||
| 106 | } | ||
| 107 | |||
| 108 | The current thread is now ready to sandbox itself with the ruleset. | ||
| 109 | |||
| 110 | .. code-block:: c | ||
| 111 | |||
| 112 | if (landlock_restrict_self(ruleset_fd, 0)) { | ||
| 113 | perror("Failed to enforce ruleset"); | ||
| 114 | close(ruleset_fd); | ||
| 115 | return 1; | ||
| 116 | } | ||
| 117 | close(ruleset_fd); | ||
| 118 | |||
| 119 | If the `landlock_restrict_self` system call succeeds, the current thread is now | ||
| 120 | restricted and this policy will be enforced on all its subsequently created | ||
| 121 | children as well. Once a thread is landlocked, there is no way to remove its | ||
| 122 | security policy; only adding more restrictions is allowed. These threads are | ||
| 123 | now in a new Landlock domain, merge of their parent one (if any) with the new | ||
| 124 | ruleset. | ||
| 125 | |||
| 126 | Full working code can be found in `samples/landlock/sandboxer.c`_. | ||
| 127 | |||
| 128 | Layers of file path access rights | ||
| 129 | --------------------------------- | ||
| 130 | |||
| 131 | Each time a thread enforces a ruleset on itself, it updates its Landlock domain | ||
| 132 | with a new layer of policy. Indeed, this complementary policy is stacked with | ||
| 133 | the potentially other rulesets already restricting this thread. A sandboxed | ||
| 134 | thread can then safely add more constraints to itself with a new enforced | ||
| 135 | ruleset. | ||
| 136 | |||
| 137 | One policy layer grants access to a file path if at least one of its rules | ||
| 138 | encountered on the path grants the access. A sandboxed thread can only access | ||
| 139 | a file path if all its enforced policy layers grant the access as well as all | ||
| 140 | the other system access controls (e.g. filesystem DAC, other LSM policies, | ||
| 141 | etc.). | ||
| 142 | |||
| 143 | Bind mounts and OverlayFS | ||
| 144 | ------------------------- | ||
| 145 | |||
| 146 | Landlock enables to restrict access to file hierarchies, which means that these | ||
| 147 | access rights can be propagated with bind mounts (cf. | ||
| 148 | :doc:`/filesystems/sharedsubtree`) but not with :doc:`/filesystems/overlayfs`. | ||
| 149 | |||
| 150 | A bind mount mirrors a source file hierarchy to a destination. The destination | ||
| 151 | hierarchy is then composed of the exact same files, on which Landlock rules can | ||
| 152 | be tied, either via the source or the destination path. These rules restrict | ||
| 153 | access when they are encountered on a path, which means that they can restrict | ||
| 154 | access to multiple file hierarchies at the same time, whether these hierarchies | ||
| 155 | are the result of bind mounts or not. | ||
| 156 | |||
| 157 | An OverlayFS mount point consists of upper and lower layers. These layers are | ||
| 158 | combined in a merge directory, result of the mount point. This merge hierarchy | ||
| 159 | may include files from the upper and lower layers, but modifications performed | ||
| 160 | on the merge hierarchy only reflects on the upper layer. From a Landlock | ||
| 161 | policy point of view, each OverlayFS layers and merge hierarchies are | ||
| 162 | standalone and contains their own set of files and directories, which is | ||
| 163 | different from bind mounts. A policy restricting an OverlayFS layer will not | ||
| 164 | restrict the resulted merged hierarchy, and vice versa. Landlock users should | ||
| 165 | then only think about file hierarchies they want to allow access to, regardless | ||
| 166 | of the underlying filesystem. | ||
| 167 | |||
| 168 | Inheritance | ||
| 169 | ----------- | ||
| 170 | |||
| 171 | Every new thread resulting from a :manpage:`clone(2)` inherits Landlock domain | ||
| 172 | restrictions from its parent. This is similar to the seccomp inheritance (cf. | ||
| 173 | :doc:`/userspace-api/seccomp_filter`) or any other LSM dealing with task's | ||
| 174 | :manpage:`credentials(7)`. For instance, one process's thread may apply | ||
| 175 | Landlock rules to itself, but they will not be automatically applied to other | ||
| 176 | sibling threads (unlike POSIX thread credential changes, cf. | ||
| 177 | :manpage:`nptl(7)`). | ||
| 178 | |||
| 179 | When a thread sandboxes itself, we have the guarantee that the related security | ||
| 180 | policy will stay enforced on all this thread's descendants. This allows | ||
| 181 | creating standalone and modular security policies per application, which will | ||
| 182 | automatically be composed between themselves according to their runtime parent | ||
| 183 | policies. | ||
| 184 | |||
| 185 | Ptrace restrictions | ||
| 186 | ------------------- | ||
| 187 | |||
| 188 | A sandboxed process has less privileges than a non-sandboxed process and must | ||
| 189 | then be subject to additional restrictions when manipulating another process. | ||
| 190 | To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target | ||
| 191 | process, a sandboxed process should have a subset of the target process rules, | ||
| 192 | which means the tracee must be in a sub-domain of the tracer. | ||
| 193 | |||
| 194 | Kernel interface | ||
| 195 | ================ | ||
| 196 | |||
| 197 | Access rights | ||
| 198 | ------------- | ||
| 199 | |||
| 200 | .. kernel-doc:: include/uapi/linux/landlock.h | ||
| 201 | :identifiers: fs_access | ||
| 202 | |||
| 203 | Creating a new ruleset | ||
| 204 | ---------------------- | ||
| 205 | |||
| 206 | .. kernel-doc:: security/landlock/syscalls.c | ||
| 207 | :identifiers: sys_landlock_create_ruleset | ||
| 208 | |||
| 209 | .. kernel-doc:: include/uapi/linux/landlock.h | ||
| 210 | :identifiers: landlock_ruleset_attr | ||
| 211 | |||
| 212 | Extending a ruleset | ||
| 213 | ------------------- | ||
| 214 | |||
| 215 | .. kernel-doc:: security/landlock/syscalls.c | ||
| 216 | :identifiers: sys_landlock_add_rule | ||
| 217 | |||
| 218 | .. kernel-doc:: include/uapi/linux/landlock.h | ||
| 219 | :identifiers: landlock_rule_type landlock_path_beneath_attr | ||
| 220 | |||
| 221 | Enforcing a ruleset | ||
| 222 | ------------------- | ||
| 223 | |||
| 224 | .. kernel-doc:: security/landlock/syscalls.c | ||
| 225 | :identifiers: sys_landlock_restrict_self | ||
| 226 | |||
| 227 | Current limitations | ||
| 228 | =================== | ||
| 229 | |||
| 230 | File renaming and linking | ||
| 231 | ------------------------- | ||
| 232 | |||
| 233 | Because Landlock targets unprivileged access controls, it is needed to properly | ||
| 234 | handle composition of rules. Such property also implies rules nesting. | ||
| 235 | Properly handling multiple layers of ruleset, each one of them able to restrict | ||
| 236 | access to files, also implies to inherit the ruleset restrictions from a parent | ||
| 237 | to its hierarchy. Because files are identified and restricted by their | ||
| 238 | hierarchy, moving or linking a file from one directory to another implies to | ||
| 239 | propagate the hierarchy constraints. To protect against privilege escalations | ||
| 240 | through renaming or linking, and for the sake of simplicity, Landlock currently | ||
| 241 | limits linking and renaming to the same directory. Future Landlock evolutions | ||
| 242 | will enable more flexibility for renaming and linking, with dedicated ruleset | ||
| 243 | flags. | ||
| 244 | |||
| 245 | Filesystem topology modification | ||
| 246 | -------------------------------- | ||
| 247 | |||
| 248 | As for file renaming and linking, a sandboxed thread cannot modify its | ||
| 249 | filesystem topology, whether via :manpage:`mount(2)` or | ||
| 250 | :manpage:`pivot_root(2)`. However, :manpage:`chroot(2)` calls are not denied. | ||
| 251 | |||
| 252 | Special filesystems | ||
| 253 | ------------------- | ||
| 254 | |||
| 255 | Access to regular files and directories can be restricted by Landlock, | ||
| 256 | according to the handled accesses of a ruleset. However, files that do not | ||
| 257 | come from a user-visible filesystem (e.g. pipe, socket), but can still be | ||
| 258 | accessed through ``/proc/<pid>/fd/*``, cannot currently be explicitly | ||
| 259 | restricted. Likewise, some special kernel filesystems such as nsfs, which can | ||
| 260 | be accessed through ``/proc/<pid>/ns/*``, cannot currently be explicitly | ||
| 261 | restricted. However, thanks to the `ptrace restrictions`_, access to such | ||
| 262 | sensitive ``/proc`` files are automatically restricted according to domain | ||
| 263 | hierarchies. Future Landlock evolutions could still enable to explicitly | ||
| 264 | restrict such paths with dedicated ruleset flags. | ||
| 265 | |||
| 266 | Ruleset layers | ||
| 267 | -------------- | ||
| 268 | |||
| 269 | There is a limit of 64 layers of stacked rulesets. This can be an issue for a | ||
| 270 | task willing to enforce a new ruleset in complement to its 64 inherited | ||
| 271 | rulesets. Once this limit is reached, sys_landlock_restrict_self() returns | ||
| 272 | E2BIG. It is then strongly suggested to carefully build rulesets once in the | ||
| 273 | life of a thread, especially for applications able to launch other applications | ||
| 274 | that may also want to sandbox themselves (e.g. shells, container managers, | ||
| 275 | etc.). | ||
| 276 | |||
| 277 | Memory usage | ||
| 278 | ------------ | ||
| 279 | |||
| 280 | Kernel memory allocated to create rulesets is accounted and can be restricted | ||
| 281 | by the :doc:`/admin-guide/cgroup-v1/memory`. | ||
| 282 | |||
| 283 | Questions and answers | ||
| 284 | ===================== | ||
| 285 | |||
| 286 | What about user space sandbox managers? | ||
| 287 | --------------------------------------- | ||
| 288 | |||
| 289 | Using user space process to enforce restrictions on kernel resources can lead | ||
| 290 | to race conditions or inconsistent evaluations (i.e. `Incorrect mirroring of | ||
| 291 | the OS code and state | ||
| 292 | <https://www.ndss-symposium.org/ndss2003/traps-and-pitfalls-practical-problems-system-call-interposition-based-security-tools/>`_). | ||
| 293 | |||
| 294 | What about namespaces and containers? | ||
| 295 | ------------------------------------- | ||
| 296 | |||
| 297 | Namespaces can help create sandboxes but they are not designed for | ||
| 298 | access-control and then miss useful features for such use case (e.g. no | ||
| 299 | fine-grained restrictions). Moreover, their complexity can lead to security | ||
| 300 | issues, especially when untrusted processes can manipulate them (cf. | ||
| 301 | `Controlling access to user namespaces <https://lwn.net/Articles/673597/>`_). | ||
| 302 | |||
| 303 | Additional documentation | ||
| 304 | ======================== | ||
| 305 | |||
| 306 | * :doc:`/security/landlock` | ||
| 307 | * https://landlock.io | ||
| 308 | |||
| 309 | .. Links | ||
| 310 | .. _samples/landlock/sandboxer.c: | ||
| 311 | https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/samples/landlock/sandboxer.c | ||
| @@ -10191,6 +10191,21 @@ F: net/core/sock_map.c · 10190 unmodified lines | |||
| 10191 | F: net/ipv4/tcp_bpf.c | 10191 | F: net/ipv4/tcp_bpf.c |
| 10192 | F: net/ipv4/udp_bpf.c | 10192 | F: net/ipv4/udp_bpf.c |
| 10193 | 10193 | ||
| 10194 | LANDLOCK SECURITY MODULE | ||
| 10195 | M: Mickaël Salaün <mic@digikod.net> | ||
| 10196 | L: linux-security-module@vger.kernel.org | ||
| 10197 | S: Supported | ||
| 10198 | W: https://landlock.io | ||
| 10199 | T: git https://github.com/landlock-lsm/linux.git | ||
| 10200 | F: Documentation/security/landlock.rst | ||
| 10201 | F: Documentation/userspace-api/landlock.rst | ||
| 10202 | F: include/uapi/linux/landlock.h | ||
| 10203 | F: samples/landlock/ | ||
| 10204 | F: security/landlock/ | ||
| 10205 | F: tools/testing/selftests/landlock/ | ||
| 10206 | K: landlock | ||
| 10207 | K: LANDLOCK | ||
| 10208 | |||
| 10194 | LANTIQ / INTEL Ethernet drivers | 10209 | LANTIQ / INTEL Ethernet drivers |
| 10195 | M: Hauke Mehrtens <hauke@hauke-m.de> | 10210 | M: Hauke Mehrtens <hauke@hauke-m.de> |
| 10196 | L: netdev@vger.kernel.org | 10211 | L: netdev@vger.kernel.org |
| @@ -1068,6 +1068,13 @@ config COMPAT_32BIT_TIME · 1067 unmodified lines | |||
| 1068 | config ARCH_NO_PREEMPT | 1068 | config ARCH_NO_PREEMPT |
| 1069 | bool | 1069 | bool |
| 1070 | 1070 | ||
| 1071 | config ARCH_EPHEMERAL_INODES | ||
| 1072 | def_bool n | ||
| 1073 | help | ||
| 1074 | An arch should select this symbol if it doesn't keep track of inode | ||
| 1075 | instances on its own, but instead relies on something else (e.g. the | ||
| 1076 | host kernel for an UML kernel). | ||
| 1077 | |||
| 1071 | config ARCH_SUPPORTS_RT | 1078 | config ARCH_SUPPORTS_RT |
| 1072 | bool | 1079 | bool |
| 1073 | 1080 | ||
| @@ -483,3 +483,6 @@ · 482 unmodified lines | |||
| 483 | 551 common epoll_pwait2 sys_epoll_pwait2 | 483 | 551 common epoll_pwait2 sys_epoll_pwait2 |
| 484 | 552 common mount_setattr sys_mount_setattr | 484 | 552 common mount_setattr sys_mount_setattr |
| 485 | 553 common quotactl_path sys_quotactl_path | 485 | 553 common quotactl_path sys_quotactl_path |
| 486 | 554 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 487 | 555 common landlock_add_rule sys_landlock_add_rule | ||
| 488 | 556 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -457,3 +457,6 @@ · 456 unmodified lines | |||
| 457 | 441 common epoll_pwait2 sys_epoll_pwait2 | 457 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 458 | 442 common mount_setattr sys_mount_setattr | 458 | 442 common mount_setattr sys_mount_setattr |
| 459 | 443 common quotactl_path sys_quotactl_path | 459 | 443 common quotactl_path sys_quotactl_path |
| 460 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 461 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 462 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -38,7 +38,7 @@ · 37 unmodified lines | |||
| 38 | #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5) | 38 | #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5) |
| 39 | #define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800) | 39 | #define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800) |
| 40 | 40 | ||
| 41 | #define __NR_compat_syscalls 444 | 41 | #define __NR_compat_syscalls 447 |
| 42 | #endif | 42 | #endif |
| 43 | 43 | ||
| 44 | #define __ARCH_WANT_SYS_CLONE | 44 | #define __ARCH_WANT_SYS_CLONE |
| @@ -895,6 +895,12 @@ __SYSCALL(__NR_epoll_pwait2, compat_sys_epoll_pwait2) · 894 unmodified lines | |||
| 895 | __SYSCALL(__NR_mount_setattr, sys_mount_setattr) | 895 | __SYSCALL(__NR_mount_setattr, sys_mount_setattr) |
| 896 | #define __NR_quotactl_path 443 | 896 | #define __NR_quotactl_path 443 |
| 897 | __SYSCALL(__NR_quotactl_path, sys_quotactl_path) | 897 | __SYSCALL(__NR_quotactl_path, sys_quotactl_path) |
| 898 | #define __NR_landlock_create_ruleset 444 | ||
| 899 | __SYSCALL(__NR_landlock_create_ruleset, sys_landlock_create_ruleset) | ||
| 900 | #define __NR_landlock_add_rule 445 | ||
| 901 | __SYSCALL(__NR_landlock_add_rule, sys_landlock_add_rule) | ||
| 902 | #define __NR_landlock_restrict_self 446 | ||
| 903 | __SYSCALL(__NR_landlock_restrict_self, sys_landlock_restrict_self) | ||
| 898 | 904 | ||
| 899 | /* | 905 | /* |
| 900 | * Please add new compat syscalls above this comment and update | 906 | * Please add new compat syscalls above this comment and update |
| @@ -364,3 +364,6 @@ · 363 unmodified lines | |||
| 364 | 441 common epoll_pwait2 sys_epoll_pwait2 | 364 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 365 | 442 common mount_setattr sys_mount_setattr | 365 | 442 common mount_setattr sys_mount_setattr |
| 366 | 443 common quotactl_path sys_quotactl_path | 366 | 443 common quotactl_path sys_quotactl_path |
| 367 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 368 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 369 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -443,3 +443,6 @@ · 442 unmodified lines | |||
| 443 | 441 common epoll_pwait2 sys_epoll_pwait2 | 443 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 444 | 442 common mount_setattr sys_mount_setattr | 444 | 442 common mount_setattr sys_mount_setattr |
| 445 | 443 common quotactl_path sys_quotactl_path | 445 | 443 common quotactl_path sys_quotactl_path |
| 446 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 447 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 448 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -449,3 +449,6 @@ · 448 unmodified lines | |||
| 449 | 441 common epoll_pwait2 sys_epoll_pwait2 | 449 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 450 | 442 common mount_setattr sys_mount_setattr | 450 | 442 common mount_setattr sys_mount_setattr |
| 451 | 443 common quotactl_path sys_quotactl_path | 451 | 443 common quotactl_path sys_quotactl_path |
| 452 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 453 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 454 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -382,3 +382,6 @@ · 381 unmodified lines | |||
| 382 | 441 n32 epoll_pwait2 compat_sys_epoll_pwait2 | 382 | 441 n32 epoll_pwait2 compat_sys_epoll_pwait2 |
| 383 | 442 n32 mount_setattr sys_mount_setattr | 383 | 442 n32 mount_setattr sys_mount_setattr |
| 384 | 443 n32 quotactl_path sys_quotactl_path | 384 | 443 n32 quotactl_path sys_quotactl_path |
| 385 | 444 n32 landlock_create_ruleset sys_landlock_create_ruleset | ||
| 386 | 445 n32 landlock_add_rule sys_landlock_add_rule | ||
| 387 | 446 n32 landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -358,3 +358,6 @@ · 357 unmodified lines | |||
| 358 | 441 n64 epoll_pwait2 sys_epoll_pwait2 | 358 | 441 n64 epoll_pwait2 sys_epoll_pwait2 |
| 359 | 442 n64 mount_setattr sys_mount_setattr | 359 | 442 n64 mount_setattr sys_mount_setattr |
| 360 | 443 n64 quotactl_path sys_quotactl_path | 360 | 443 n64 quotactl_path sys_quotactl_path |
| 361 | 444 n64 landlock_create_ruleset sys_landlock_create_ruleset | ||
| 362 | 445 n64 landlock_add_rule sys_landlock_add_rule | ||
| 363 | 446 n64 landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -431,3 +431,6 @@ · 430 unmodified lines | |||
| 431 | 441 o32 epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 | 431 | 441 o32 epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 432 | 442 o32 mount_setattr sys_mount_setattr | 432 | 442 o32 mount_setattr sys_mount_setattr |
| 433 | 443 o32 quotactl_path sys_quotactl_path | 433 | 443 o32 quotactl_path sys_quotactl_path |
| 434 | 444 o32 landlock_create_ruleset sys_landlock_create_ruleset | ||
| 435 | 445 o32 landlock_add_rule sys_landlock_add_rule | ||
| 436 | 446 o32 landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -441,3 +441,6 @@ · 440 unmodified lines | |||
| 441 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 | 441 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 442 | 442 common mount_setattr sys_mount_setattr | 442 | 442 common mount_setattr sys_mount_setattr |
| 443 | 443 common quotactl_path sys_quotactl_path | 443 | 443 common quotactl_path sys_quotactl_path |
| 444 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 445 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 446 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -523,3 +523,6 @@ · 522 unmodified lines | |||
| 523 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 | 523 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 524 | 442 common mount_setattr sys_mount_setattr | 524 | 442 common mount_setattr sys_mount_setattr |
| 525 | 443 common quotactl_path sys_quotactl_path | 525 | 443 common quotactl_path sys_quotactl_path |
| 526 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 527 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 528 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -446,3 +446,6 @@ · 445 unmodified lines | |||
| 446 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 | 446 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 447 | 442 common mount_setattr sys_mount_setattr sys_mount_setattr | 447 | 442 common mount_setattr sys_mount_setattr sys_mount_setattr |
| 448 | 443 common quotactl_path sys_quotactl_path sys_quotactl_path | 448 | 443 common quotactl_path sys_quotactl_path sys_quotactl_path |
| 449 | 444 common landlock_create_ruleset sys_landlock_create_ruleset sys_landlock_create_ruleset | ||
| 450 | 445 common landlock_add_rule sys_landlock_add_rule sys_landlock_add_rule | ||
| 451 | 446 common landlock_restrict_self sys_landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -446,3 +446,6 @@ · 445 unmodified lines | |||
| 446 | 441 common epoll_pwait2 sys_epoll_pwait2 | 446 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 447 | 442 common mount_setattr sys_mount_setattr | 447 | 442 common mount_setattr sys_mount_setattr |
| 448 | 443 common quotactl_path sys_quotactl_path | 448 | 443 common quotactl_path sys_quotactl_path |
| 449 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 450 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 451 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -489,3 +489,6 @@ · 488 unmodified lines | |||
| 489 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 | 489 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 490 | 442 common mount_setattr sys_mount_setattr | 490 | 442 common mount_setattr sys_mount_setattr |
| 491 | 443 common quotactl_path sys_quotactl_path | 491 | 443 common quotactl_path sys_quotactl_path |
| 492 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 493 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 494 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -5,6 +5,7 @@ menu "UML-specific options" · 4 unmodified lines | |||
| 5 | config UML | 5 | config UML |
| 6 | bool | 6 | bool |
| 7 | default y | 7 | default y |
| 8 | select ARCH_EPHEMERAL_INODES | ||
| 8 | select ARCH_HAS_KCOV | 9 | select ARCH_HAS_KCOV |
| 9 | select ARCH_NO_PREEMPT | 10 | select ARCH_NO_PREEMPT |
| 10 | select HAVE_ARCH_AUDITSYSCALL | 11 | select HAVE_ARCH_AUDITSYSCALL |
| @@ -448,3 +448,6 @@ · 447 unmodified lines | |||
| 448 | 441 i386 epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 | 448 | 441 i386 epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 449 | 442 i386 mount_setattr sys_mount_setattr | 449 | 442 i386 mount_setattr sys_mount_setattr |
| 450 | 443 i386 quotactl_path sys_quotactl_path | 450 | 443 i386 quotactl_path sys_quotactl_path |
| 451 | 444 i386 landlock_create_ruleset sys_landlock_create_ruleset | ||
| 452 | 445 i386 landlock_add_rule sys_landlock_add_rule | ||
| 453 | 446 i386 landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -365,6 +365,9 @@ · 364 unmodified lines | |||
| 365 | 441 common epoll_pwait2 sys_epoll_pwait2 | 365 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 366 | 442 common mount_setattr sys_mount_setattr | 366 | 442 common mount_setattr sys_mount_setattr |
| 367 | 443 common quotactl_path sys_quotactl_path | 367 | 443 common quotactl_path sys_quotactl_path |
| 368 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 369 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 370 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| 368 | 371 | ||
| 369 | # | 372 | # |
| 370 | # Due to a historical design error, certain syscalls are numbered differently | 373 | # Due to a historical design error, certain syscalls are numbered differently |
| @@ -414,3 +414,6 @@ · 413 unmodified lines | |||
| 414 | 441 common epoll_pwait2 sys_epoll_pwait2 | 414 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 415 | 442 common mount_setattr sys_mount_setattr | 415 | 442 common mount_setattr sys_mount_setattr |
| 416 | 443 common quotactl_path sys_quotactl_path | 416 | 443 common quotactl_path sys_quotactl_path |
| 417 | 444 common landlock_create_ruleset sys_landlock_create_ruleset | ||
| 418 | 445 common landlock_add_rule sys_landlock_add_rule | ||
| 419 | 446 common landlock_restrict_self sys_landlock_restrict_self | ||
| @@ -454,6 +454,7 @@ void generic_shutdown_super(struct super_block *sb) · 453 unmodified lines | |||
| 454 | evict_inodes(sb); | 454 | evict_inodes(sb); |
| 455 | /* only nonzero refcount inodes can have marks */ | 455 | /* only nonzero refcount inodes can have marks */ |
| 456 | fsnotify_sb_delete(sb); | 456 | fsnotify_sb_delete(sb); |
| 457 | security_sb_delete(sb); | ||
| 457 | 458 | ||
| 458 | if (sb->s_dio_done_wq) { | 459 | if (sb->s_dio_done_wq) { |
| 459 | destroy_workqueue(sb->s_dio_done_wq); | 460 | destroy_workqueue(sb->s_dio_done_wq); |
| @@ -59,6 +59,7 @@ LSM_HOOK(int, 0, fs_context_dup, struct fs_context *fc, · 58 unmodified lines | |||
| 59 | LSM_HOOK(int, -ENOPARAM, fs_context_parse_param, struct fs_context *fc, | 59 | LSM_HOOK(int, -ENOPARAM, fs_context_parse_param, struct fs_context *fc, |
| 60 | struct fs_parameter *param) | 60 | struct fs_parameter *param) |
| 61 | LSM_HOOK(int, 0, sb_alloc_security, struct super_block *sb) | 61 | LSM_HOOK(int, 0, sb_alloc_security, struct super_block *sb) |
| 62 | LSM_HOOK(void, LSM_RET_VOID, sb_delete, struct super_block *sb) | ||
| 62 | LSM_HOOK(void, LSM_RET_VOID, sb_free_security, struct super_block *sb) | 63 | LSM_HOOK(void, LSM_RET_VOID, sb_free_security, struct super_block *sb) |
| 63 | LSM_HOOK(void, LSM_RET_VOID, sb_free_mnt_opts, void *mnt_opts) | 64 | LSM_HOOK(void, LSM_RET_VOID, sb_free_mnt_opts, void *mnt_opts) |
| 64 | LSM_HOOK(int, 0, sb_eat_lsm_opts, char *orig, void **mnt_opts) | 65 | LSM_HOOK(int, 0, sb_eat_lsm_opts, char *orig, void **mnt_opts) |
| @@ -108,6 +108,9 @@ · 107 unmodified lines | |||
| 108 | * allocated. | 108 | * allocated. |
| 109 | * @sb contains the super_block structure to be modified. | 109 | * @sb contains the super_block structure to be modified. |
| 110 | * Return 0 if operation was successful. | 110 | * Return 0 if operation was successful. |
| 111 | * @sb_delete: | ||
| 112 | * Release objects tied to a superblock (e.g. inodes). | ||
| 113 | * @sb contains the super_block structure being released. | ||
| 111 | * @sb_free_security: | 114 | * @sb_free_security: |
| 112 | * Deallocate and clear the sb->s_security field. | 115 | * Deallocate and clear the sb->s_security field. |
| 113 | * @sb contains the super_block structure to be modified. | 116 | * @sb contains the super_block structure to be modified. |
| @@ -1585,6 +1588,7 @@ struct lsm_blob_sizes { · 1471 unmodified lines | |||
| 1585 | int lbs_cred; | 1588 | int lbs_cred; |
| 1586 | int lbs_file; | 1589 | int lbs_file; |
| 1587 | int lbs_inode; | 1590 | int lbs_inode; |
| 1591 | int lbs_superblock; | ||
| 1588 | int lbs_ipc; | 1592 | int lbs_ipc; |
| 1589 | int lbs_msg_msg; | 1593 | int lbs_msg_msg; |
| 1590 | int lbs_task; | 1594 | int lbs_task; |
| @@ -291,6 +291,7 @@ void security_bprm_committed_creds(struct linux_binprm *bprm); · 290 unmodified lines | |||
| 291 | int security_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc); | 291 | int security_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc); |
| 292 | int security_fs_context_parse_param(struct fs_context *fc, struct fs_parameter *param); | 292 | int security_fs_context_parse_param(struct fs_context *fc, struct fs_parameter *param); |
| 293 | int security_sb_alloc(struct super_block *sb); | 293 | int security_sb_alloc(struct super_block *sb); |
| 294 | void security_sb_delete(struct super_block *sb); | ||
| 294 | void security_sb_free(struct super_block *sb); | 295 | void security_sb_free(struct super_block *sb); |
| 295 | void security_free_mnt_opts(void **mnt_opts); | 296 | void security_free_mnt_opts(void **mnt_opts); |
| 296 | int security_sb_eat_lsm_opts(char *options, void **mnt_opts); | 297 | int security_sb_eat_lsm_opts(char *options, void **mnt_opts); |
| @@ -633,6 +634,9 @@ static inline int security_sb_alloc(struct super_block *sb) · 336 unmodified lines | |||
| 633 | return 0; | 634 | return 0; |
| 634 | } | 635 | } |
| 635 | 636 | ||
| 637 | static inline void security_sb_delete(struct super_block *sb) | ||
| 638 | { } | ||
| 639 | |||
| 636 | static inline void security_sb_free(struct super_block *sb) | 640 | static inline void security_sb_free(struct super_block *sb) |
| 637 | { } | 641 | { } |
| 638 | 642 | ||
| @@ -69,6 +69,8 @@ struct io_uring_params; · 68 unmodified lines | |||
| 69 | struct clone_args; | 69 | struct clone_args; |
| 70 | struct open_how; | 70 | struct open_how; |
| 71 | struct mount_attr; | 71 | struct mount_attr; |
| 72 | struct landlock_ruleset_attr; | ||
| 73 | enum landlock_rule_type; | ||
| 72 | 74 | ||
| 73 | #include <linux/types.h> | 75 | #include <linux/types.h> |
| 74 | #include <linux/aio_abi.h> | 76 | #include <linux/aio_abi.h> |
| @@ -1043,6 +1045,11 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig, · 968 unmodified lines | |||
| 1043 | siginfo_t __user *info, | 1045 | siginfo_t __user *info, |
| 1044 | unsigned int flags); | 1046 | unsigned int flags); |
| 1045 | asmlinkage long sys_pidfd_getfd(int pidfd, int fd, unsigned int flags); | 1047 | asmlinkage long sys_pidfd_getfd(int pidfd, int fd, unsigned int flags); |
| 1048 | asmlinkage long sys_landlock_create_ruleset(const struct landlock_ruleset_attr __user *attr, | ||
| 1049 | size_t size, __u32 flags); | ||
| 1050 | asmlinkage long sys_landlock_add_rule(int ruleset_fd, enum landlock_rule_type rule_type, | ||
| 1051 | const void __user *rule_attr, __u32 flags); | ||
| 1052 | asmlinkage long sys_landlock_restrict_self(int ruleset_fd, __u32 flags); | ||
| 1046 | 1053 | ||
| 1047 | /* | 1054 | /* |
| 1048 | * Architecture-specific system calls | 1055 | * Architecture-specific system calls |
| @@ -866,8 +866,15 @@ __SYSCALL(__NR_mount_setattr, sys_mount_setattr) · 865 unmodified lines | |||
| 866 | #define __NR_quotactl_path 443 | 866 | #define __NR_quotactl_path 443 |
| 867 | __SYSCALL(__NR_quotactl_path, sys_quotactl_path) | 867 | __SYSCALL(__NR_quotactl_path, sys_quotactl_path) |
| 868 | 868 | ||
| 869 | #define __NR_landlock_create_ruleset 444 | ||
| 870 | __SYSCALL(__NR_landlock_create_ruleset, sys_landlock_create_ruleset) | ||
| 871 | #define __NR_landlock_add_rule 445 | ||
| 872 | __SYSCALL(__NR_landlock_add_rule, sys_landlock_add_rule) | ||
| 873 | #define __NR_landlock_restrict_self 446 | ||
| 874 | __SYSCALL(__NR_landlock_restrict_self, sys_landlock_restrict_self) | ||
| 875 | |||
| 869 | #undef __NR_syscalls | 876 | #undef __NR_syscalls |
| 870 | #define __NR_syscalls 444 | 877 | #define __NR_syscalls 447 |
| 871 | 878 | ||
| 872 | /* | 879 | /* |
| 873 | * 32 bit systems traditionally used different | 880 | * 32 bit systems traditionally used different |
| @@ -0,0 +1,137 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ | ||
| 2 | /* | ||
| 3 | * Landlock - User space API | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _UAPI_LINUX_LANDLOCK_H | ||
| 10 | #define _UAPI_LINUX_LANDLOCK_H | ||
| 11 | |||
| 12 | #include <linux/types.h> | ||
| 13 | |||
| 14 | /** | ||
| 15 | * struct landlock_ruleset_attr - Ruleset definition | ||
| 16 | * | ||
| 17 | * Argument of sys_landlock_create_ruleset(). This structure can grow in | ||
| 18 | * future versions. | ||
| 19 | */ | ||
| 20 | struct landlock_ruleset_attr { | ||
| 21 | /** | ||
| 22 | * @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_) | ||
| 23 | * that is handled by this ruleset and should then be forbidden if no | ||
| 24 | * rule explicitly allow them. This is needed for backward | ||
| 25 | * compatibility reasons. | ||
| 26 | */ | ||
| 27 | __u64 handled_access_fs; | ||
| 28 | }; | ||
| 29 | |||
| 30 | /* | ||
| 31 | * sys_landlock_create_ruleset() flags: | ||
| 32 | * | ||
| 33 | * - %LANDLOCK_CREATE_RULESET_VERSION: Get the highest supported Landlock ABI | ||
| 34 | * version. | ||
| 35 | */ | ||
| 36 | #define LANDLOCK_CREATE_RULESET_VERSION (1U << 0) | ||
| 37 | |||
| 38 | /** | ||
| 39 | * enum landlock_rule_type - Landlock rule type | ||
| 40 | * | ||
| 41 | * Argument of sys_landlock_add_rule(). | ||
| 42 | */ | ||
| 43 | enum landlock_rule_type { | ||
| 44 | /** | ||
| 45 | * @LANDLOCK_RULE_PATH_BENEATH: Type of a &struct | ||
| 46 | * landlock_path_beneath_attr . | ||
| 47 | */ | ||
| 48 | LANDLOCK_RULE_PATH_BENEATH = 1, | ||
| 49 | }; | ||
| 50 | |||
| 51 | /** | ||
| 52 | * struct landlock_path_beneath_attr - Path hierarchy definition | ||
| 53 | * | ||
| 54 | * Argument of sys_landlock_add_rule(). | ||
| 55 | */ | ||
| 56 | struct landlock_path_beneath_attr { | ||
| 57 | /** | ||
| 58 | * @allowed_access: Bitmask of allowed actions for this file hierarchy | ||
| 59 | * (cf. `Filesystem flags`_). | ||
| 60 | */ | ||
| 61 | __u64 allowed_access; | ||
| 62 | /** | ||
| 63 | * @parent_fd: File descriptor, open with ``O_PATH``, which identifies | ||
| 64 | * the parent directory of a file hierarchy, or just a file. | ||
| 65 | */ | ||
| 66 | __s32 parent_fd; | ||
| 67 | /* | ||
| 68 | * This struct is packed to avoid trailing reserved members. | ||
| 69 | * Cf. security/landlock/syscalls.c:build_check_abi() | ||
| 70 | */ | ||
| 71 | } __attribute__((packed)); | ||
| 72 | |||
| 73 | /** | ||
| 74 | * DOC: fs_access | ||
| 75 | * | ||
| 76 | * A set of actions on kernel objects may be defined by an attribute (e.g. | ||
| 77 | * &struct landlock_path_beneath_attr) including a bitmask of access. | ||
| 78 | * | ||
| 79 | * Filesystem flags | ||
| 80 | * ~~~~~~~~~~~~~~~~ | ||
| 81 | * | ||
| 82 | * These flags enable to restrict a sandboxed process to a set of actions on | ||
| 83 | * files and directories. Files or directories opened before the sandboxing | ||
| 84 | * are not subject to these restrictions. | ||
| 85 | * | ||
| 86 | * A file can only receive these access rights: | ||
| 87 | * | ||
| 88 | * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file. | ||
| 89 | * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. | ||
| 90 | * - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access. | ||
| 91 | * | ||
| 92 | * A directory can receive access rights related to files or directories. The | ||
| 93 | * following access right is applied to the directory itself, and the | ||
| 94 | * directories beneath it: | ||
| 95 | * | ||
| 96 | * - %LANDLOCK_ACCESS_FS_READ_DIR: Open a directory or list its content. | ||
| 97 | * | ||
| 98 | * However, the following access rights only apply to the content of a | ||
| 99 | * directory, not the directory itself: | ||
| 100 | * | ||
| 101 | * - %LANDLOCK_ACCESS_FS_REMOVE_DIR: Remove an empty directory or rename one. | ||
| 102 | * - %LANDLOCK_ACCESS_FS_REMOVE_FILE: Unlink (or rename) a file. | ||
| 103 | * - %LANDLOCK_ACCESS_FS_MAKE_CHAR: Create (or rename or link) a character | ||
| 104 | * device. | ||
| 105 | * - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create (or rename) a directory. | ||
| 106 | * - %LANDLOCK_ACCESS_FS_MAKE_REG: Create (or rename or link) a regular file. | ||
| 107 | * - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create (or rename or link) a UNIX domain | ||
| 108 | * socket. | ||
| 109 | * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create (or rename or link) a named pipe. | ||
| 110 | * - %LANDLOCK_ACCESS_FS_MAKE_BLOCK: Create (or rename or link) a block device. | ||
| 111 | * - %LANDLOCK_ACCESS_FS_MAKE_SYM: Create (or rename or link) a symbolic link. | ||
| 112 | * | ||
| 113 | * .. warning:: | ||
| 114 | * | ||
| 115 | * It is currently not possible to restrict some file-related actions | ||
| 116 | * accessible through these syscall families: :manpage:`chdir(2)`, | ||
| 117 | * :manpage:`truncate(2)`, :manpage:`stat(2)`, :manpage:`flock(2)`, | ||
| 118 | * :manpage:`chmod(2)`, :manpage:`chown(2)`, :manpage:`setxattr(2)`, | ||
| 119 | * :manpage:`utime(2)`, :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, | ||
| 120 | * :manpage:`access(2)`. | ||
| 121 | * Future Landlock evolutions will enable to restrict them. | ||
| 122 | */ | ||
| 123 | #define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0) | ||
| 124 | #define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1) | ||
| 125 | #define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2) | ||
| 126 | #define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3) | ||
| 127 | #define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4) | ||
| 128 | #define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5) | ||
| 129 | #define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6) | ||
| 130 | #define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7) | ||
| 131 | #define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8) | ||
| 132 | #define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9) | ||
| 133 | #define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10) | ||
| 134 | #define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) | ||
| 135 | #define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) | ||
| 136 | |||
| 137 | #endif /* _UAPI_LINUX_LANDLOCK_H */ | ||
| @@ -267,6 +267,11 @@ COND_SYSCALL(request_key); · 266 unmodified lines | |||
| 267 | COND_SYSCALL(keyctl); | 267 | COND_SYSCALL(keyctl); |
| 268 | COND_SYSCALL_COMPAT(keyctl); | 268 | COND_SYSCALL_COMPAT(keyctl); |
| 269 | 269 | ||
| 270 | /* security/landlock/syscalls.c */ | ||
| 271 | COND_SYSCALL(landlock_create_ruleset); | ||
| 272 | COND_SYSCALL(landlock_add_rule); | ||
| 273 | COND_SYSCALL(landlock_restrict_self); | ||
| 274 | |||
| 270 | /* arch/example/kernel/sys_example.c */ | 275 | /* arch/example/kernel/sys_example.c */ |
| 271 | 276 | ||
| 272 | /* mm/fadvise.c */ | 277 | /* mm/fadvise.c */ |
| @@ -124,6 +124,13 @@ config SAMPLE_HIDRAW · 123 unmodified lines | |||
| 124 | bool "hidraw sample" | 124 | bool "hidraw sample" |
| 125 | depends on CC_CAN_LINK && HEADERS_INSTALL | 125 | depends on CC_CAN_LINK && HEADERS_INSTALL |
| 126 | 126 | ||
| 127 | config SAMPLE_LANDLOCK | ||
| 128 | bool "Landlock example" | ||
| 129 | depends on CC_CAN_LINK && HEADERS_INSTALL | ||
| 130 | help | ||
| 131 | Build a simple Landlock sandbox manager able to start a process | ||
| 132 | restricted by a user-defined filesystem access control policy. | ||
| 133 | |||
| 127 | config SAMPLE_PIDFD | 134 | config SAMPLE_PIDFD |
| 128 | bool "pidfd sample" | 135 | bool "pidfd sample" |
| 129 | depends on CC_CAN_LINK && HEADERS_INSTALL | 136 | depends on CC_CAN_LINK && HEADERS_INSTALL |
| @@ -11,6 +11,7 @@ obj-$(CONFIG_SAMPLE_KDB) += kdb/ · 10 unmodified lines | |||
| 11 | obj-$(CONFIG_SAMPLE_KFIFO) += kfifo/ | 11 | obj-$(CONFIG_SAMPLE_KFIFO) += kfifo/ |
| 12 | obj-$(CONFIG_SAMPLE_KOBJECT) += kobject/ | 12 | obj-$(CONFIG_SAMPLE_KOBJECT) += kobject/ |
| 13 | obj-$(CONFIG_SAMPLE_KPROBES) += kprobes/ | 13 | obj-$(CONFIG_SAMPLE_KPROBES) += kprobes/ |
| 14 | subdir-$(CONFIG_SAMPLE_LANDLOCK) += landlock | ||
| 14 | obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch/ | 15 | obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch/ |
| 15 | subdir-$(CONFIG_SAMPLE_PIDFD) += pidfd | 16 | subdir-$(CONFIG_SAMPLE_PIDFD) += pidfd |
| 16 | obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi/ | 17 | obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi/ |
| @@ -0,0 +1 @@ | |||
| 1 | /sandboxer | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | # SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | |||
| 3 | userprogs-always-y := sandboxer | ||
| 4 | |||
| 5 | userccflags += -I usr/include | ||
| 6 | |||
| 7 | .PHONY: all clean | ||
| 8 | |||
| 9 | all: | ||
| 10 | $(MAKE) -C ../.. samples/landlock/ | ||
| 11 | |||
| 12 | clean: | ||
| 13 | $(MAKE) -C ../.. M=samples/landlock/ clean | ||
| @@ -0,0 +1,238 @@ | |||
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /* | ||
| 3 | * Simple Landlock sandbox manager able to launch a process restricted by a | ||
| 4 | * user-defined filesystem access control policy. | ||
| 5 | * | ||
| 6 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 7 | * Copyright © 2020 ANSSI | ||
| 8 | */ | ||
| 9 | |||
| 10 | #define _GNU_SOURCE | ||
| 11 | #include <errno.h> | ||
| 12 | #include <fcntl.h> | ||
| 13 | #include <linux/landlock.h> | ||
| 14 | #include <linux/prctl.h> | ||
| 15 | #include <stddef.h> | ||
| 16 | #include <stdio.h> | ||
| 17 | #include <stdlib.h> | ||
| 18 | #include <string.h> | ||
| 19 | #include <sys/prctl.h> | ||
| 20 | #include <sys/stat.h> | ||
| 21 | #include <sys/syscall.h> | ||
| 22 | #include <unistd.h> | ||
| 23 | |||
| 24 | #ifndef landlock_create_ruleset | ||
| 25 | static inline int landlock_create_ruleset( | ||
| 26 | const struct landlock_ruleset_attr *const attr, | ||
| 27 | const size_t size, const __u32 flags) | ||
| 28 | { | ||
| 29 | return syscall(__NR_landlock_create_ruleset, attr, size, flags); | ||
| 30 | } | ||
| 31 | #endif | ||
| 32 | |||
| 33 | #ifndef landlock_add_rule | ||
| 34 | static inline int landlock_add_rule(const int ruleset_fd, | ||
| 35 | const enum landlock_rule_type rule_type, | ||
| 36 | const void *const rule_attr, const __u32 flags) | ||
| 37 | { | ||
| 38 | return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, | ||
| 39 | rule_attr, flags); | ||
| 40 | } | ||
| 41 | #endif | ||
| 42 | |||
| 43 | #ifndef landlock_restrict_self | ||
| 44 | static inline int landlock_restrict_self(const int ruleset_fd, | ||
| 45 | const __u32 flags) | ||
| 46 | { | ||
| 47 | return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); | ||
| 48 | } | ||
| 49 | #endif | ||
| 50 | |||
| 51 | #define ENV_FS_RO_NAME "LL_FS_RO" | ||
| 52 | #define ENV_FS_RW_NAME "LL_FS_RW" | ||
| 53 | #define ENV_PATH_TOKEN ":" | ||
| 54 | |||
| 55 | static int parse_path(char *env_path, const char ***const path_list) | ||
| 56 | { | ||
| 57 | int i, num_paths = 0; | ||
| 58 | |||
| 59 | if (env_path) { | ||
| 60 | num_paths++; | ||
| 61 | for (i = 0; env_path[i]; i++) { | ||
| 62 | if (env_path[i] == ENV_PATH_TOKEN[0]) | ||
| 63 | num_paths++; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | *path_list = malloc(num_paths * sizeof(**path_list)); | ||
| 67 | for (i = 0; i < num_paths; i++) | ||
| 68 | (*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN); | ||
| 69 | |||
| 70 | return num_paths; | ||
| 71 | } | ||
| 72 | |||
| 73 | #define ACCESS_FILE ( \ | ||
| 74 | LANDLOCK_ACCESS_FS_EXECUTE | \ | ||
| 75 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | ||
| 76 | LANDLOCK_ACCESS_FS_READ_FILE) | ||
| 77 | |||
| 78 | static int populate_ruleset( | ||
| 79 | const char *const env_var, const int ruleset_fd, | ||
| 80 | const __u64 allowed_access) | ||
| 81 | { | ||
| 82 | int num_paths, i, ret = 1; | ||
| 83 | char *env_path_name; | ||
| 84 | const char **path_list = NULL; | ||
| 85 | struct landlock_path_beneath_attr path_beneath = { | ||
| 86 | .parent_fd = -1, | ||
| 87 | }; | ||
| 88 | |||
| 89 | env_path_name = getenv(env_var); | ||
| 90 | if (!env_path_name) { | ||
| 91 | /* Prevents users to forget a setting. */ | ||
| 92 | fprintf(stderr, "Missing environment variable %s\n", env_var); | ||
| 93 | return 1; | ||
| 94 | } | ||
| 95 | env_path_name = strdup(env_path_name); | ||
| 96 | unsetenv(env_var); | ||
| 97 | num_paths = parse_path(env_path_name, &path_list); | ||
| 98 | if (num_paths == 1 && path_list[0][0] == '\0') { | ||
| 99 | /* | ||
| 100 | * Allows to not use all possible restrictions (e.g. use | ||
| 101 | * LL_FS_RO without LL_FS_RW). | ||
| 102 | */ | ||
| 103 | ret = 0; | ||
| 104 | goto out_free_name; | ||
| 105 | } | ||
| 106 | |||
| 107 | for (i = 0; i < num_paths; i++) { | ||
| 108 | struct stat statbuf; | ||
| 109 | |||
| 110 | path_beneath.parent_fd = open(path_list[i], O_PATH | | ||
| 111 | O_CLOEXEC); | ||
| 112 | if (path_beneath.parent_fd < 0) { | ||
| 113 | fprintf(stderr, "Failed to open \"%s\": %s\n", | ||
| 114 | path_list[i], | ||
| 115 | strerror(errno)); | ||
| 116 | goto out_free_name; | ||
| 117 | } | ||
| 118 | if (fstat(path_beneath.parent_fd, &statbuf)) { | ||
| 119 | close(path_beneath.parent_fd); | ||
| 120 | goto out_free_name; | ||
| 121 | } | ||
| 122 | path_beneath.allowed_access = allowed_access; | ||
| 123 | if (!S_ISDIR(statbuf.st_mode)) | ||
| 124 | path_beneath.allowed_access &= ACCESS_FILE; | ||
| 125 | if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 126 | &path_beneath, 0)) { | ||
| 127 | fprintf(stderr, "Failed to update the ruleset with \"%s\": %s\n", | ||
| 128 | path_list[i], strerror(errno)); | ||
| 129 | close(path_beneath.parent_fd); | ||
| 130 | goto out_free_name; | ||
| 131 | } | ||
| 132 | close(path_beneath.parent_fd); | ||
| 133 | } | ||
| 134 | ret = 0; | ||
| 135 | |||
| 136 | out_free_name: | ||
| 137 | free(env_path_name); | ||
| 138 | return ret; | ||
| 139 | } | ||
| 140 | |||
| 141 | #define ACCESS_FS_ROUGHLY_READ ( \ | ||
| 142 | LANDLOCK_ACCESS_FS_EXECUTE | \ | ||
| 143 | LANDLOCK_ACCESS_FS_READ_FILE | \ | ||
| 144 | LANDLOCK_ACCESS_FS_READ_DIR) | ||
| 145 | |||
| 146 | #define ACCESS_FS_ROUGHLY_WRITE ( \ | ||
| 147 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | ||
| 148 | LANDLOCK_ACCESS_FS_REMOVE_DIR | \ | ||
| 149 | LANDLOCK_ACCESS_FS_REMOVE_FILE | \ | ||
| 150 | LANDLOCK_ACCESS_FS_MAKE_CHAR | \ | ||
| 151 | LANDLOCK_ACCESS_FS_MAKE_DIR | \ | ||
| 152 | LANDLOCK_ACCESS_FS_MAKE_REG | \ | ||
| 153 | LANDLOCK_ACCESS_FS_MAKE_SOCK | \ | ||
| 154 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ | ||
| 155 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ | ||
| 156 | LANDLOCK_ACCESS_FS_MAKE_SYM) | ||
| 157 | |||
| 158 | int main(const int argc, char *const argv[], char *const *const envp) | ||
| 159 | { | ||
| 160 | const char *cmd_path; | ||
| 161 | char *const *cmd_argv; | ||
| 162 | int ruleset_fd; | ||
| 163 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 164 | .handled_access_fs = ACCESS_FS_ROUGHLY_READ | | ||
| 165 | ACCESS_FS_ROUGHLY_WRITE, | ||
| 166 | }; | ||
| 167 | |||
| 168 | if (argc < 2) { | ||
| 169 | fprintf(stderr, "usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n", | ||
| 170 | ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); | ||
| 171 | fprintf(stderr, "Launch a command in a restricted environment.\n\n"); | ||
| 172 | fprintf(stderr, "Environment variables containing paths, " | ||
| 173 | "each separated by a colon:\n"); | ||
| 174 | fprintf(stderr, "* %s: list of paths allowed to be used in a read-only way.\n", | ||
| 175 | ENV_FS_RO_NAME); | ||
| 176 | fprintf(stderr, "* %s: list of paths allowed to be used in a read-write way.\n", | ||
| 177 | ENV_FS_RW_NAME); | ||
| 178 | fprintf(stderr, "\nexample:\n" | ||
| 179 | "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " | ||
| 180 | "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " | ||
| 181 | "%s bash -i\n", | ||
| 182 | ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); | ||
| 183 | return 1; | ||
| 184 | } | ||
| 185 | |||
| 186 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | ||
| 187 | if (ruleset_fd < 0) { | ||
| 188 | const int err = errno; | ||
| 189 | |||
| 190 | perror("Failed to create a ruleset"); | ||
| 191 | switch (err) { | ||
| 192 | case ENOSYS: | ||
| 193 | fprintf(stderr, "Hint: Landlock is not supported by the current kernel. " | ||
| 194 | "To support it, build the kernel with " | ||
| 195 | "CONFIG_SECURITY_LANDLOCK=y and prepend " | ||
| 196 | "\"landlock,\" to the content of CONFIG_LSM.\n"); | ||
| 197 | break; | ||
| 198 | case EOPNOTSUPP: | ||
| 199 | fprintf(stderr, "Hint: Landlock is currently disabled. " | ||
| 200 | "It can be enabled in the kernel configuration by " | ||
| 201 | "prepending \"landlock,\" to the content of CONFIG_LSM, " | ||
| 202 | "or at boot time by setting the same content to the " | ||
| 203 | "\"lsm\" kernel parameter.\n"); | ||
| 204 | break; | ||
| 205 | } | ||
| 206 | return 1; | ||
| 207 | } | ||
| 208 | if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, | ||
| 209 | ACCESS_FS_ROUGHLY_READ)) { | ||
| 210 | goto err_close_ruleset; | ||
| 211 | } | ||
| 212 | if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, | ||
| 213 | ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE)) { | ||
| 214 | goto err_close_ruleset; | ||
| 215 | } | ||
| 216 | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { | ||
| 217 | perror("Failed to restrict privileges"); | ||
| 218 | goto err_close_ruleset; | ||
| 219 | } | ||
| 220 | if (landlock_restrict_self(ruleset_fd, 0)) { | ||
| 221 | perror("Failed to enforce ruleset"); | ||
| 222 | goto err_close_ruleset; | ||
| 223 | } | ||
| 224 | close(ruleset_fd); | ||
| 225 | |||
| 226 | cmd_path = argv[1]; | ||
| 227 | cmd_argv = argv + 1; | ||
| 228 | execvpe(cmd_path, cmd_argv, envp); | ||
| 229 | fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, | ||
| 230 | strerror(errno)); | ||
| 231 | fprintf(stderr, "Hint: access to the binary, the interpreter or " | ||
| 232 | "shared libraries may be denied.\n"); | ||
| 233 | return 1; | ||
| 234 | |||
| 235 | err_close_ruleset: | ||
| 236 | close(ruleset_fd); | ||
| 237 | return 1; | ||
| 238 | } | ||
| @@ -238,6 +238,7 @@ source "security/loadpin/Kconfig" · 237 unmodified lines | |||
| 238 | source "security/yama/Kconfig" | 238 | source "security/yama/Kconfig" |
| 239 | source "security/safesetid/Kconfig" | 239 | source "security/safesetid/Kconfig" |
| 240 | source "security/lockdown/Kconfig" | 240 | source "security/lockdown/Kconfig" |
| 241 | source "security/landlock/Kconfig" | ||
| 241 | 242 | ||
| 242 | source "security/integrity/Kconfig" | 243 | source "security/integrity/Kconfig" |
| 243 | 244 | ||
| @@ -277,11 +278,11 @@ endchoice · 33 unmodified lines | |||
| 277 | 278 | ||
| 278 | config LSM | 279 | config LSM |
| 279 | string "Ordered list of enabled LSMs" | 280 | string "Ordered list of enabled LSMs" |
| 280 | default "lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK | 281 | default "landlock,lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK |
| 281 | default "lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR | 282 | default "landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR |
| 282 | default "lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO | 283 | default "landlock,lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO |
| 283 | default "lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC | 284 | default "landlock,lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC |
| 284 | default "lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf" | 285 | default "landlock,lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf" |
| 285 | help | 286 | help |
| 286 | A comma-separated list of LSMs, in initialization order. | 287 | A comma-separated list of LSMs, in initialization order. |
| 287 | Any LSMs left off this list will be ignored. This can be | 288 | Any LSMs left off this list will be ignored. This can be |
| @@ -13,6 +13,7 @@ subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin · 12 unmodified lines | |||
| 13 | subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid | 13 | subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid |
| 14 | subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown | 14 | subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown |
| 15 | subdir-$(CONFIG_BPF_LSM) += bpf | 15 | subdir-$(CONFIG_BPF_LSM) += bpf |
| 16 | subdir-$(CONFIG_SECURITY_LANDLOCK) += landlock | ||
| 16 | 17 | ||
| 17 | # always enable default capabilities | 18 | # always enable default capabilities |
| 18 | obj-y += commoncap.o | 19 | obj-y += commoncap.o |
| @@ -32,6 +33,7 @@ obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/ · 13 unmodified lines | |||
| 32 | obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ | 33 | obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ |
| 33 | obj-$(CONFIG_CGROUPS) += device_cgroup.o | 34 | obj-$(CONFIG_CGROUPS) += device_cgroup.o |
| 34 | obj-$(CONFIG_BPF_LSM) += bpf/ | 35 | obj-$(CONFIG_BPF_LSM) += bpf/ |
| 36 | obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ | ||
| 35 | 37 | ||
| 36 | # Object integrity file lists | 38 | # Object integrity file lists |
| 37 | subdir-$(CONFIG_INTEGRITY) += integrity | 39 | subdir-$(CONFIG_INTEGRITY) += integrity |
| @@ -0,0 +1,21 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | |||
| 3 | config SECURITY_LANDLOCK | ||
| 4 | bool "Landlock support" | ||
| 5 | depends on SECURITY && !ARCH_EPHEMERAL_INODES | ||
| 6 | select SECURITY_PATH | ||
| 7 | help | ||
| 8 | Landlock is a sandboxing mechanism that enables processes to restrict | ||
| 9 | themselves (and their future children) by gradually enforcing | ||
| 10 | tailored access control policies. A Landlock security policy is a | ||
| 11 | set of access rights (e.g. open a file in read-only, make a | ||
| 12 | directory, etc.) tied to a file hierarchy. Such policy can be | ||
| 13 | configured and enforced by any processes for themselves using the | ||
| 14 | dedicated system calls: landlock_create_ruleset(), | ||
| 15 | landlock_add_rule(), and landlock_restrict_self(). | ||
| 16 | |||
| 17 | See Documentation/userspace-api/landlock.rst for further information. | ||
| 18 | |||
| 19 | If you are unsure how to answer this question, answer N. Otherwise, | ||
| 20 | you should also prepend "landlock," to the content of CONFIG_LSM to | ||
| 21 | enable Landlock at boot time. | ||
| @@ -0,0 +1,4 @@ | |||
| 1 | obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o | ||
| 2 | |||
| 3 | landlock-y := setup.o syscalls.o object.o ruleset.o \ | ||
| 4 | cred.o ptrace.o fs.o | ||
| @@ -0,0 +1,20 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Common constants and helpers | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_COMMON_H | ||
| 10 | #define _SECURITY_LANDLOCK_COMMON_H | ||
| 11 | |||
| 12 | #define LANDLOCK_NAME "landlock" | ||
| 13 | |||
| 14 | #ifdef pr_fmt | ||
| 15 | #undef pr_fmt | ||
| 16 | #endif | ||
| 17 | |||
| 18 | #define pr_fmt(fmt) LANDLOCK_NAME ": " fmt | ||
| 19 | |||
| 20 | #endif /* _SECURITY_LANDLOCK_COMMON_H */ | ||
| @@ -0,0 +1,46 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Credential hooks | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/cred.h> | ||
| 10 | #include <linux/lsm_hooks.h> | ||
| 11 | |||
| 12 | #include "common.h" | ||
| 13 | #include "cred.h" | ||
| 14 | #include "ruleset.h" | ||
| 15 | #include "setup.h" | ||
| 16 | |||
| 17 | static int hook_cred_prepare(struct cred *const new, | ||
| 18 | const struct cred *const old, const gfp_t gfp) | ||
| 19 | { | ||
| 20 | struct landlock_ruleset *const old_dom = landlock_cred(old)->domain; | ||
| 21 | |||
| 22 | if (old_dom) { | ||
| 23 | landlock_get_ruleset(old_dom); | ||
| 24 | landlock_cred(new)->domain = old_dom; | ||
| 25 | } | ||
| 26 | return 0; | ||
| 27 | } | ||
| 28 | |||
| 29 | static void hook_cred_free(struct cred *const cred) | ||
| 30 | { | ||
| 31 | struct landlock_ruleset *const dom = landlock_cred(cred)->domain; | ||
| 32 | |||
| 33 | if (dom) | ||
| 34 | landlock_put_ruleset_deferred(dom); | ||
| 35 | } | ||
| 36 | |||
| 37 | static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { | ||
| 38 | LSM_HOOK_INIT(cred_prepare, hook_cred_prepare), | ||
| 39 | LSM_HOOK_INIT(cred_free, hook_cred_free), | ||
| 40 | }; | ||
| 41 | |||
| 42 | __init void landlock_add_cred_hooks(void) | ||
| 43 | { | ||
| 44 | security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), | ||
| 45 | LANDLOCK_NAME); | ||
| 46 | } | ||
| @@ -0,0 +1,58 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Credential hooks | ||
| 4 | * | ||
| 5 | * Copyright © 2019-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2019-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_CRED_H | ||
| 10 | #define _SECURITY_LANDLOCK_CRED_H | ||
| 11 | |||
| 12 | #include <linux/cred.h> | ||
| 13 | #include <linux/init.h> | ||
| 14 | #include <linux/rcupdate.h> | ||
| 15 | |||
| 16 | #include "ruleset.h" | ||
| 17 | #include "setup.h" | ||
| 18 | |||
| 19 | struct landlock_cred_security { | ||
| 20 | struct landlock_ruleset *domain; | ||
| 21 | }; | ||
| 22 | |||
| 23 | static inline struct landlock_cred_security *landlock_cred( | ||
| 24 | const struct cred *cred) | ||
| 25 | { | ||
| 26 | return cred->security + landlock_blob_sizes.lbs_cred; | ||
| 27 | } | ||
| 28 | |||
| 29 | static inline const struct landlock_ruleset *landlock_get_current_domain(void) | ||
| 30 | { | ||
| 31 | return landlock_cred(current_cred())->domain; | ||
| 32 | } | ||
| 33 | |||
| 34 | /* | ||
| 35 | * The call needs to come from an RCU read-side critical section. | ||
| 36 | */ | ||
| 37 | static inline const struct landlock_ruleset *landlock_get_task_domain( | ||
| 38 | const struct task_struct *const task) | ||
| 39 | { | ||
| 40 | return landlock_cred(__task_cred(task))->domain; | ||
| 41 | } | ||
| 42 | |||
| 43 | static inline bool landlocked(const struct task_struct *const task) | ||
| 44 | { | ||
| 45 | bool has_dom; | ||
| 46 | |||
| 47 | if (task == current) | ||
| 48 | return !!landlock_get_current_domain(); | ||
| 49 | |||
| 50 | rcu_read_lock(); | ||
| 51 | has_dom = !!landlock_get_task_domain(task); | ||
| 52 | rcu_read_unlock(); | ||
| 53 | return has_dom; | ||
| 54 | } | ||
| 55 | |||
| 56 | __init void landlock_add_cred_hooks(void); | ||
| 57 | |||
| 58 | #endif /* _SECURITY_LANDLOCK_CRED_H */ | ||
| @@ -0,0 +1,692 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Filesystem management and hooks | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/atomic.h> | ||
| 10 | #include <linux/bitops.h> | ||
| 11 | #include <linux/bits.h> | ||
| 12 | #include <linux/compiler_types.h> | ||
| 13 | #include <linux/dcache.h> | ||
| 14 | #include <linux/err.h> | ||
| 15 | #include <linux/fs.h> | ||
| 16 | #include <linux/init.h> | ||
| 17 | #include <linux/kernel.h> | ||
| 18 | #include <linux/limits.h> | ||
| 19 | #include <linux/list.h> | ||
| 20 | #include <linux/lsm_hooks.h> | ||
| 21 | #include <linux/mount.h> | ||
| 22 | #include <linux/namei.h> | ||
| 23 | #include <linux/path.h> | ||
| 24 | #include <linux/rcupdate.h> | ||
| 25 | #include <linux/spinlock.h> | ||
| 26 | #include <linux/stat.h> | ||
| 27 | #include <linux/types.h> | ||
| 28 | #include <linux/wait_bit.h> | ||
| 29 | #include <linux/workqueue.h> | ||
| 30 | #include <uapi/linux/landlock.h> | ||
| 31 | |||
| 32 | #include "common.h" | ||
| 33 | #include "cred.h" | ||
| 34 | #include "fs.h" | ||
| 35 | #include "limits.h" | ||
| 36 | #include "object.h" | ||
| 37 | #include "ruleset.h" | ||
| 38 | #include "setup.h" | ||
| 39 | |||
| 40 | /* Underlying object management */ | ||
| 41 | |||
| 42 | static void release_inode(struct landlock_object *const object) | ||
| 43 | __releases(object->lock) | ||
| 44 | { | ||
| 45 | struct inode *const inode = object->underobj; | ||
| 46 | struct super_block *sb; | ||
| 47 | |||
| 48 | if (!inode) { | ||
| 49 | spin_unlock(&object->lock); | ||
| 50 | return; | ||
| 51 | } | ||
| 52 | |||
| 53 | /* | ||
| 54 | * Protects against concurrent use by hook_sb_delete() of the reference | ||
| 55 | * to the underlying inode. | ||
| 56 | */ | ||
| 57 | object->underobj = NULL; | ||
| 58 | /* | ||
| 59 | * Makes sure that if the filesystem is concurrently unmounted, | ||
| 60 | * hook_sb_delete() will wait for us to finish iput(). | ||
| 61 | */ | ||
| 62 | sb = inode->i_sb; | ||
| 63 | atomic_long_inc(&landlock_superblock(sb)->inode_refs); | ||
| 64 | spin_unlock(&object->lock); | ||
| 65 | /* | ||
| 66 | * Because object->underobj was not NULL, hook_sb_delete() and | ||
| 67 | * get_inode_object() guarantee that it is safe to reset | ||
| 68 | * landlock_inode(inode)->object while it is not NULL. It is therefore | ||
| 69 | * not necessary to lock inode->i_lock. | ||
| 70 | */ | ||
| 71 | rcu_assign_pointer(landlock_inode(inode)->object, NULL); | ||
| 72 | /* | ||
| 73 | * Now, new rules can safely be tied to @inode with get_inode_object(). | ||
| 74 | */ | ||
| 75 | |||
| 76 | iput(inode); | ||
| 77 | if (atomic_long_dec_and_test(&landlock_superblock(sb)->inode_refs)) | ||
| 78 | wake_up_var(&landlock_superblock(sb)->inode_refs); | ||
| 79 | } | ||
| 80 | |||
| 81 | static const struct landlock_object_underops landlock_fs_underops = { | ||
| 82 | .release = release_inode | ||
| 83 | }; | ||
| 84 | |||
| 85 | /* Ruleset management */ | ||
| 86 | |||
| 87 | static struct landlock_object *get_inode_object(struct inode *const inode) | ||
| 88 | { | ||
| 89 | struct landlock_object *object, *new_object; | ||
| 90 | struct landlock_inode_security *inode_sec = landlock_inode(inode); | ||
| 91 | |||
| 92 | rcu_read_lock(); | ||
| 93 | retry: | ||
| 94 | object = rcu_dereference(inode_sec->object); | ||
| 95 | if (object) { | ||
| 96 | if (likely(refcount_inc_not_zero(&object->usage))) { | ||
| 97 | rcu_read_unlock(); | ||
| 98 | return object; | ||
| 99 | } | ||
| 100 | /* | ||
| 101 | * We are racing with release_inode(), the object is going | ||
| 102 | * away. Wait for release_inode(), then retry. | ||
| 103 | */ | ||
| 104 | spin_lock(&object->lock); | ||
| 105 | spin_unlock(&object->lock); | ||
| 106 | goto retry; | ||
| 107 | } | ||
| 108 | rcu_read_unlock(); | ||
| 109 | |||
| 110 | /* | ||
| 111 | * If there is no object tied to @inode, then create a new one (without | ||
| 112 | * holding any locks). | ||
| 113 | */ | ||
| 114 | new_object = landlock_create_object(&landlock_fs_underops, inode); | ||
| 115 | if (IS_ERR(new_object)) | ||
| 116 | return new_object; | ||
| 117 | |||
| 118 | /* | ||
| 119 | * Protects against concurrent calls to get_inode_object() or | ||
| 120 | * hook_sb_delete(). | ||
| 121 | */ | ||
| 122 | spin_lock(&inode->i_lock); | ||
| 123 | if (unlikely(rcu_access_pointer(inode_sec->object))) { | ||
| 124 | /* Someone else just created the object, bail out and retry. */ | ||
| 125 | spin_unlock(&inode->i_lock); | ||
| 126 | kfree(new_object); | ||
| 127 | |||
| 128 | rcu_read_lock(); | ||
| 129 | goto retry; | ||
| 130 | } | ||
| 131 | |||
| 132 | /* | ||
| 133 | * @inode will be released by hook_sb_delete() on its superblock | ||
| 134 | * shutdown, or by release_inode() when no more ruleset references the | ||
| 135 | * related object. | ||
| 136 | */ | ||
| 137 | ihold(inode); | ||
| 138 | rcu_assign_pointer(inode_sec->object, new_object); | ||
| 139 | spin_unlock(&inode->i_lock); | ||
| 140 | return new_object; | ||
| 141 | } | ||
| 142 | |||
| 143 | /* All access rights that can be tied to files. */ | ||
| 144 | #define ACCESS_FILE ( \ | ||
| 145 | LANDLOCK_ACCESS_FS_EXECUTE | \ | ||
| 146 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | ||
| 147 | LANDLOCK_ACCESS_FS_READ_FILE) | ||
| 148 | |||
| 149 | /* | ||
| 150 | * @path: Should have been checked by get_path_from_fd(). | ||
| 151 | */ | ||
| 152 | int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, | ||
| 153 | const struct path *const path, u32 access_rights) | ||
| 154 | { | ||
| 155 | int err; | ||
| 156 | struct landlock_object *object; | ||
| 157 | |||
| 158 | /* Files only get access rights that make sense. */ | ||
| 159 | if (!d_is_dir(path->dentry) && (access_rights | ACCESS_FILE) != | ||
| 160 | ACCESS_FILE) | ||
| 161 | return -EINVAL; | ||
| 162 | if (WARN_ON_ONCE(ruleset->num_layers != 1)) | ||
| 163 | return -EINVAL; | ||
| 164 | |||
| 165 | /* Transforms relative access rights to absolute ones. */ | ||
| 166 | access_rights |= LANDLOCK_MASK_ACCESS_FS & ~ruleset->fs_access_masks[0]; | ||
| 167 | object = get_inode_object(d_backing_inode(path->dentry)); | ||
| 168 | if (IS_ERR(object)) | ||
| 169 | return PTR_ERR(object); | ||
| 170 | mutex_lock(&ruleset->lock); | ||
| 171 | err = landlock_insert_rule(ruleset, object, access_rights); | ||
| 172 | mutex_unlock(&ruleset->lock); | ||
| 173 | /* | ||
| 174 | * No need to check for an error because landlock_insert_rule() | ||
| 175 | * increments the refcount for the new object if needed. | ||
| 176 | */ | ||
| 177 | landlock_put_object(object); | ||
| 178 | return err; | ||
| 179 | } | ||
| 180 | |||
| 181 | /* Access-control management */ | ||
| 182 | |||
| 183 | static inline u64 unmask_layers( | ||
| 184 | const struct landlock_ruleset *const domain, | ||
| 185 | const struct path *const path, const u32 access_request, | ||
| 186 | u64 layer_mask) | ||
| 187 | { | ||
| 188 | const struct landlock_rule *rule; | ||
| 189 | const struct inode *inode; | ||
| 190 | size_t i; | ||
| 191 | |||
| 192 | if (d_is_negative(path->dentry)) | ||
| 193 | /* Ignore nonexistent leafs. */ | ||
| 194 | return layer_mask; | ||
| 195 | inode = d_backing_inode(path->dentry); | ||
| 196 | rcu_read_lock(); | ||
| 197 | rule = landlock_find_rule(domain, | ||
| 198 | rcu_dereference(landlock_inode(inode)->object)); | ||
| 199 | rcu_read_unlock(); | ||
| 200 | if (!rule) | ||
| 201 | return layer_mask; | ||
| 202 | |||
| 203 | /* | ||
| 204 | * An access is granted if, for each policy layer, at least one rule | ||
| 205 | * encountered on the pathwalk grants the requested accesses, | ||
| 206 | * regardless of their position in the layer stack. We must then check | ||
| 207 | * the remaining layers for each inode, from the first added layer to | ||
| 208 | * the last one. | ||
| 209 | */ | ||
| 210 | for (i = 0; i < rule->num_layers; i++) { | ||
| 211 | const struct landlock_layer *const layer = &rule->layers[i]; | ||
| 212 | const u64 layer_level = BIT_ULL(layer->level - 1); | ||
| 213 | |||
| 214 | /* Checks that the layer grants access to the full request. */ | ||
| 215 | if ((layer->access & access_request) == access_request) { | ||
| 216 | layer_mask &= ~layer_level; | ||
| 217 | |||
| 218 | if (layer_mask == 0) | ||
| 219 | return layer_mask; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | return layer_mask; | ||
| 223 | } | ||
| 224 | |||
| 225 | static int check_access_path(const struct landlock_ruleset *const domain, | ||
| 226 | const struct path *const path, u32 access_request) | ||
| 227 | { | ||
| 228 | bool allowed = false; | ||
| 229 | struct path walker_path; | ||
| 230 | u64 layer_mask; | ||
| 231 | size_t i; | ||
| 232 | |||
| 233 | /* Make sure all layers can be checked. */ | ||
| 234 | BUILD_BUG_ON(BITS_PER_TYPE(layer_mask) < LANDLOCK_MAX_NUM_LAYERS); | ||
| 235 | |||
| 236 | if (!access_request) | ||
| 237 | return 0; | ||
| 238 | if (WARN_ON_ONCE(!domain || !path)) | ||
| 239 | return 0; | ||
| 240 | /* | ||
| 241 | * Allows access to pseudo filesystems that will never be mountable | ||
| 242 | * (e.g. sockfs, pipefs), but can still be reachable through | ||
| 243 | * /proc/<pid>/fd/<file-descriptor> . | ||
| 244 | */ | ||
| 245 | if ((path->dentry->d_sb->s_flags & SB_NOUSER) || | ||
| 246 | (d_is_positive(path->dentry) && | ||
| 247 | unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))) | ||
| 248 | return 0; | ||
| 249 | if (WARN_ON_ONCE(domain->num_layers < 1)) | ||
| 250 | return -EACCES; | ||
| 251 | |||
| 252 | /* Saves all layers handling a subset of requested accesses. */ | ||
| 253 | layer_mask = 0; | ||
| 254 | for (i = 0; i < domain->num_layers; i++) { | ||
| 255 | if (domain->fs_access_masks[i] & access_request) | ||
| 256 | layer_mask |= BIT_ULL(i); | ||
| 257 | } | ||
| 258 | /* An access request not handled by the domain is allowed. */ | ||
| 259 | if (layer_mask == 0) | ||
| 260 | return 0; | ||
| 261 | |||
| 262 | walker_path = *path; | ||
| 263 | path_get(&walker_path); | ||
| 264 | /* | ||
| 265 | * We need to walk through all the hierarchy to not miss any relevant | ||
| 266 | * restriction. | ||
| 267 | */ | ||
| 268 | while (true) { | ||
| 269 | struct dentry *parent_dentry; | ||
| 270 | |||
| 271 | layer_mask = unmask_layers(domain, &walker_path, | ||
| 272 | access_request, layer_mask); | ||
| 273 | if (layer_mask == 0) { | ||
| 274 | /* Stops when a rule from each layer grants access. */ | ||
| 275 | allowed = true; | ||
| 276 | break; | ||
| 277 | } | ||
| 278 | |||
| 279 | jump_up: | ||
| 280 | if (walker_path.dentry == walker_path.mnt->mnt_root) { | ||
| 281 | if (follow_up(&walker_path)) { | ||
| 282 | /* Ignores hidden mount points. */ | ||
| 283 | goto jump_up; | ||
| 284 | } else { | ||
| 285 | /* | ||
| 286 | * Stops at the real root. Denies access | ||
| 287 | * because not all layers have granted access. | ||
| 288 | */ | ||
| 289 | allowed = false; | ||
| 290 | break; | ||
| 291 | } | ||
| 292 | } | ||
| 293 | if (unlikely(IS_ROOT(walker_path.dentry))) { | ||
| 294 | /* | ||
| 295 | * Stops at disconnected root directories. Only allows | ||
| 296 | * access to internal filesystems (e.g. nsfs, which is | ||
| 297 | * reachable through /proc/<pid>/ns/<namespace>). | ||
| 298 | */ | ||
| 299 | allowed = !!(walker_path.mnt->mnt_flags & MNT_INTERNAL); | ||
| 300 | break; | ||
| 301 | } | ||
| 302 | parent_dentry = dget_parent(walker_path.dentry); | ||
| 303 | dput(walker_path.dentry); | ||
| 304 | walker_path.dentry = parent_dentry; | ||
| 305 | } | ||
| 306 | path_put(&walker_path); | ||
| 307 | return allowed ? 0 : -EACCES; | ||
| 308 | } | ||
| 309 | |||
| 310 | static inline int current_check_access_path(const struct path *const path, | ||
| 311 | const u32 access_request) | ||
| 312 | { | ||
| 313 | const struct landlock_ruleset *const dom = | ||
| 314 | landlock_get_current_domain(); | ||
| 315 | |||
| 316 | if (!dom) | ||
| 317 | return 0; | ||
| 318 | return check_access_path(dom, path, access_request); | ||
| 319 | } | ||
| 320 | |||
| 321 | /* Inode hooks */ | ||
| 322 | |||
| 323 | static void hook_inode_free_security(struct inode *const inode) | ||
| 324 | { | ||
| 325 | /* | ||
| 326 | * All inodes must already have been untied from their object by | ||
| 327 | * release_inode() or hook_sb_delete(). | ||
| 328 | */ | ||
| 329 | WARN_ON_ONCE(landlock_inode(inode)->object); | ||
| 330 | } | ||
| 331 | |||
| 332 | /* Super-block hooks */ | ||
| 333 | |||
| 334 | /* | ||
| 335 | * Release the inodes used in a security policy. | ||
| 336 | * | ||
| 337 | * Cf. fsnotify_unmount_inodes() and invalidate_inodes() | ||
| 338 | */ | ||
| 339 | static void hook_sb_delete(struct super_block *const sb) | ||
| 340 | { | ||
| 341 | struct inode *inode, *prev_inode = NULL; | ||
| 342 | |||
| 343 | if (!landlock_initialized) | ||
| 344 | return; | ||
| 345 | |||
| 346 | spin_lock(&sb->s_inode_list_lock); | ||
| 347 | list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { | ||
| 348 | struct landlock_object *object; | ||
| 349 | |||
| 350 | /* Only handles referenced inodes. */ | ||
| 351 | if (!atomic_read(&inode->i_count)) | ||
| 352 | continue; | ||
| 353 | |||
| 354 | /* | ||
| 355 | * Protects against concurrent modification of inode (e.g. | ||
| 356 | * from get_inode_object()). | ||
| 357 | */ | ||
| 358 | spin_lock(&inode->i_lock); | ||
| 359 | /* | ||
| 360 | * Checks I_FREEING and I_WILL_FREE to protect against a race | ||
| 361 | * condition when release_inode() just called iput(), which | ||
| 362 | * could lead to a NULL dereference of inode->security or a | ||
| 363 | * second call to iput() for the same Landlock object. Also | ||
| 364 | * checks I_NEW because such inode cannot be tied to an object. | ||
| 365 | */ | ||
| 366 | if (inode->i_state & (I_FREEING | I_WILL_FREE | I_NEW)) { | ||
| 367 | spin_unlock(&inode->i_lock); | ||
| 368 | continue; | ||
| 369 | } | ||
| 370 | |||
| 371 | rcu_read_lock(); | ||
| 372 | object = rcu_dereference(landlock_inode(inode)->object); | ||
| 373 | if (!object) { | ||
| 374 | rcu_read_unlock(); | ||
| 375 | spin_unlock(&inode->i_lock); | ||
| 376 | continue; | ||
| 377 | } | ||
| 378 | /* Keeps a reference to this inode until the next loop walk. */ | ||
| 379 | __iget(inode); | ||
| 380 | spin_unlock(&inode->i_lock); | ||
| 381 | |||
| 382 | /* | ||
| 383 | * If there is no concurrent release_inode() ongoing, then we | ||
| 384 | * are in charge of calling iput() on this inode, otherwise we | ||
| 385 | * will just wait for it to finish. | ||
| 386 | */ | ||
| 387 | spin_lock(&object->lock); | ||
| 388 | if (object->underobj == inode) { | ||
| 389 | object->underobj = NULL; | ||
| 390 | spin_unlock(&object->lock); | ||
| 391 | rcu_read_unlock(); | ||
| 392 | |||
| 393 | /* | ||
| 394 | * Because object->underobj was not NULL, | ||
| 395 | * release_inode() and get_inode_object() guarantee | ||
| 396 | * that it is safe to reset | ||
| 397 | * landlock_inode(inode)->object while it is not NULL. | ||
| 398 | * It is therefore not necessary to lock inode->i_lock. | ||
| 399 | */ | ||
| 400 | rcu_assign_pointer(landlock_inode(inode)->object, NULL); | ||
| 401 | /* | ||
| 402 | * At this point, we own the ihold() reference that was | ||
| 403 | * originally set up by get_inode_object() and the | ||
| 404 | * __iget() reference that we just set in this loop | ||
| 405 | * walk. Therefore the following call to iput() will | ||
| 406 | * not sleep nor drop the inode because there is now at | ||
| 407 | * least two references to it. | ||
| 408 | */ | ||
| 409 | iput(inode); | ||
| 410 | } else { | ||
| 411 | spin_unlock(&object->lock); | ||
| 412 | rcu_read_unlock(); | ||
| 413 | } | ||
| 414 | |||
| 415 | if (prev_inode) { | ||
| 416 | /* | ||
| 417 | * At this point, we still own the __iget() reference | ||
| 418 | * that we just set in this loop walk. Therefore we | ||
| 419 | * can drop the list lock and know that the inode won't | ||
| 420 | * disappear from under us until the next loop walk. | ||
| 421 | */ | ||
| 422 | spin_unlock(&sb->s_inode_list_lock); | ||
| 423 | /* | ||
| 424 | * We can now actually put the inode reference from the | ||
| 425 | * previous loop walk, which is not needed anymore. | ||
| 426 | */ | ||
| 427 | iput(prev_inode); | ||
| 428 | cond_resched(); | ||
| 429 | spin_lock(&sb->s_inode_list_lock); | ||
| 430 | } | ||
| 431 | prev_inode = inode; | ||
| 432 | } | ||
| 433 | spin_unlock(&sb->s_inode_list_lock); | ||
| 434 | |||
| 435 | /* Puts the inode reference from the last loop walk, if any. */ | ||
| 436 | if (prev_inode) | ||
| 437 | iput(prev_inode); | ||
| 438 | /* Waits for pending iput() in release_inode(). */ | ||
| 439 | wait_var_event(&landlock_superblock(sb)->inode_refs, !atomic_long_read( | ||
| 440 | &landlock_superblock(sb)->inode_refs)); | ||
| 441 | } | ||
| 442 | |||
| 443 | /* | ||
| 444 | * Because a Landlock security policy is defined according to the filesystem | ||
| 445 | * topology (i.e. the mount namespace), changing it may grant access to files | ||
| 446 | * not previously allowed. | ||
| 447 | * | ||
| 448 | * To make it simple, deny any filesystem topology modification by landlocked | ||
| 449 | * processes. Non-landlocked processes may still change the namespace of a | ||
| 450 | * landlocked process, but this kind of threat must be handled by a system-wide | ||
| 451 | * access-control security policy. | ||
| 452 | * | ||
| 453 | * This could be lifted in the future if Landlock can safely handle mount | ||
| 454 | * namespace updates requested by a landlocked process. Indeed, we could | ||
| 455 | * update the current domain (which is currently read-only) by taking into | ||
| 456 | * account the accesses of the source and the destination of a new mount point. | ||
| 457 | * However, it would also require to make all the child domains dynamically | ||
| 458 | * inherit these new constraints. Anyway, for backward compatibility reasons, | ||
| 459 | * a dedicated user space option would be required (e.g. as a ruleset flag). | ||
| 460 | */ | ||
| 461 | static int hook_sb_mount(const char *const dev_name, | ||
| 462 | const struct path *const path, const char *const type, | ||
| 463 | const unsigned long flags, void *const data) | ||
| 464 | { | ||
| 465 | if (!landlock_get_current_domain()) | ||
| 466 | return 0; | ||
| 467 | return -EPERM; | ||
| 468 | } | ||
| 469 | |||
| 470 | static int hook_move_mount(const struct path *const from_path, | ||
| 471 | const struct path *const to_path) | ||
| 472 | { | ||
| 473 | if (!landlock_get_current_domain()) | ||
| 474 | return 0; | ||
| 475 | return -EPERM; | ||
| 476 | } | ||
| 477 | |||
| 478 | /* | ||
| 479 | * Removing a mount point may reveal a previously hidden file hierarchy, which | ||
| 480 | * may then grant access to files, which may have previously been forbidden. | ||
| 481 | */ | ||
| 482 | static int hook_sb_umount(struct vfsmount *const mnt, const int flags) | ||
| 483 | { | ||
| 484 | if (!landlock_get_current_domain()) | ||
| 485 | return 0; | ||
| 486 | return -EPERM; | ||
| 487 | } | ||
| 488 | |||
| 489 | static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts) | ||
| 490 | { | ||
| 491 | if (!landlock_get_current_domain()) | ||
| 492 | return 0; | ||
| 493 | return -EPERM; | ||
| 494 | } | ||
| 495 | |||
| 496 | /* | ||
| 497 | * pivot_root(2), like mount(2), changes the current mount namespace. It must | ||
| 498 | * then be forbidden for a landlocked process. | ||
| 499 | * | ||
| 500 | * However, chroot(2) may be allowed because it only changes the relative root | ||
| 501 | * directory of the current process. Moreover, it can be used to restrict the | ||
| 502 | * view of the filesystem. | ||
| 503 | */ | ||
| 504 | static int hook_sb_pivotroot(const struct path *const old_path, | ||
| 505 | const struct path *const new_path) | ||
| 506 | { | ||
| 507 | if (!landlock_get_current_domain()) | ||
| 508 | return 0; | ||
| 509 | return -EPERM; | ||
| 510 | } | ||
| 511 | |||
| 512 | /* Path hooks */ | ||
| 513 | |||
| 514 | static inline u32 get_mode_access(const umode_t mode) | ||
| 515 | { | ||
| 516 | switch (mode & S_IFMT) { | ||
| 517 | case S_IFLNK: | ||
| 518 | return LANDLOCK_ACCESS_FS_MAKE_SYM; | ||
| 519 | case 0: | ||
| 520 | /* A zero mode translates to S_IFREG. */ | ||
| 521 | case S_IFREG: | ||
| 522 | return LANDLOCK_ACCESS_FS_MAKE_REG; | ||
| 523 | case S_IFDIR: | ||
| 524 | return LANDLOCK_ACCESS_FS_MAKE_DIR; | ||
| 525 | case S_IFCHR: | ||
| 526 | return LANDLOCK_ACCESS_FS_MAKE_CHAR; | ||
| 527 | case S_IFBLK: | ||
| 528 | return LANDLOCK_ACCESS_FS_MAKE_BLOCK; | ||
| 529 | case S_IFIFO: | ||
| 530 | return LANDLOCK_ACCESS_FS_MAKE_FIFO; | ||
| 531 | case S_IFSOCK: | ||
| 532 | return LANDLOCK_ACCESS_FS_MAKE_SOCK; | ||
| 533 | default: | ||
| 534 | WARN_ON_ONCE(1); | ||
| 535 | return 0; | ||
| 536 | } | ||
| 537 | } | ||
| 538 | |||
| 539 | /* | ||
| 540 | * Creating multiple links or renaming may lead to privilege escalations if not | ||
| 541 | * handled properly. Indeed, we must be sure that the source doesn't gain more | ||
| 542 | * privileges by being accessible from the destination. This is getting more | ||
| 543 | * complex when dealing with multiple layers. The whole picture can be seen as | ||
| 544 | * a multilayer partial ordering problem. A future version of Landlock will | ||
| 545 | * deal with that. | ||
| 546 | */ | ||
| 547 | static int hook_path_link(struct dentry *const old_dentry, | ||
| 548 | const struct path *const new_dir, | ||
| 549 | struct dentry *const new_dentry) | ||
| 550 | { | ||
| 551 | const struct landlock_ruleset *const dom = | ||
| 552 | landlock_get_current_domain(); | ||
| 553 | |||
| 554 | if (!dom) | ||
| 555 | return 0; | ||
| 556 | /* The mount points are the same for old and new paths, cf. EXDEV. */ | ||
| 557 | if (old_dentry->d_parent != new_dir->dentry) | ||
| 558 | /* Gracefully forbids reparenting. */ | ||
| 559 | return -EXDEV; | ||
| 560 | if (unlikely(d_is_negative(old_dentry))) | ||
| 561 | return -ENOENT; | ||
| 562 | return check_access_path(dom, new_dir, | ||
| 563 | get_mode_access(d_backing_inode(old_dentry)->i_mode)); | ||
| 564 | } | ||
| 565 | |||
| 566 | static inline u32 maybe_remove(const struct dentry *const dentry) | ||
| 567 | { | ||
| 568 | if (d_is_negative(dentry)) | ||
| 569 | return 0; | ||
| 570 | return d_is_dir(dentry) ? LANDLOCK_ACCESS_FS_REMOVE_DIR : | ||
| 571 | LANDLOCK_ACCESS_FS_REMOVE_FILE; | ||
| 572 | } | ||
| 573 | |||
| 574 | static int hook_path_rename(const struct path *const old_dir, | ||
| 575 | struct dentry *const old_dentry, | ||
| 576 | const struct path *const new_dir, | ||
| 577 | struct dentry *const new_dentry) | ||
| 578 | { | ||
| 579 | const struct landlock_ruleset *const dom = | ||
| 580 | landlock_get_current_domain(); | ||
| 581 | |||
| 582 | if (!dom) | ||
| 583 | return 0; | ||
| 584 | /* The mount points are the same for old and new paths, cf. EXDEV. */ | ||
| 585 | if (old_dir->dentry != new_dir->dentry) | ||
| 586 | /* Gracefully forbids reparenting. */ | ||
| 587 | return -EXDEV; | ||
| 588 | if (unlikely(d_is_negative(old_dentry))) | ||
| 589 | return -ENOENT; | ||
| 590 | /* RENAME_EXCHANGE is handled because directories are the same. */ | ||
| 591 | return check_access_path(dom, old_dir, maybe_remove(old_dentry) | | ||
| 592 | maybe_remove(new_dentry) | | ||
| 593 | get_mode_access(d_backing_inode(old_dentry)->i_mode)); | ||
| 594 | } | ||
| 595 | |||
| 596 | static int hook_path_mkdir(const struct path *const dir, | ||
| 597 | struct dentry *const dentry, const umode_t mode) | ||
| 598 | { | ||
| 599 | return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR); | ||
| 600 | } | ||
| 601 | |||
| 602 | static int hook_path_mknod(const struct path *const dir, | ||
| 603 | struct dentry *const dentry, const umode_t mode, | ||
| 604 | const unsigned int dev) | ||
| 605 | { | ||
| 606 | const struct landlock_ruleset *const dom = | ||
| 607 | landlock_get_current_domain(); | ||
| 608 | |||
| 609 | if (!dom) | ||
| 610 | return 0; | ||
| 611 | return check_access_path(dom, dir, get_mode_access(mode)); | ||
| 612 | } | ||
| 613 | |||
| 614 | static int hook_path_symlink(const struct path *const dir, | ||
| 615 | struct dentry *const dentry, const char *const old_name) | ||
| 616 | { | ||
| 617 | return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM); | ||
| 618 | } | ||
| 619 | |||
| 620 | static int hook_path_unlink(const struct path *const dir, | ||
| 621 | struct dentry *const dentry) | ||
| 622 | { | ||
| 623 | return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE); | ||
| 624 | } | ||
| 625 | |||
| 626 | static int hook_path_rmdir(const struct path *const dir, | ||
| 627 | struct dentry *const dentry) | ||
| 628 | { | ||
| 629 | return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); | ||
| 630 | } | ||
| 631 | |||
| 632 | /* File hooks */ | ||
| 633 | |||
| 634 | static inline u32 get_file_access(const struct file *const file) | ||
| 635 | { | ||
| 636 | u32 access = 0; | ||
| 637 | |||
| 638 | if (file->f_mode & FMODE_READ) { | ||
| 639 | /* A directory can only be opened in read mode. */ | ||
| 640 | if (S_ISDIR(file_inode(file)->i_mode)) | ||
| 641 | return LANDLOCK_ACCESS_FS_READ_DIR; | ||
| 642 | access = LANDLOCK_ACCESS_FS_READ_FILE; | ||
| 643 | } | ||
| 644 | if (file->f_mode & FMODE_WRITE) | ||
| 645 | access |= LANDLOCK_ACCESS_FS_WRITE_FILE; | ||
| 646 | /* __FMODE_EXEC is indeed part of f_flags, not f_mode. */ | ||
| 647 | if (file->f_flags & __FMODE_EXEC) | ||
| 648 | access |= LANDLOCK_ACCESS_FS_EXECUTE; | ||
| 649 | return access; | ||
| 650 | } | ||
| 651 | |||
| 652 | static int hook_file_open(struct file *const file) | ||
| 653 | { | ||
| 654 | const struct landlock_ruleset *const dom = | ||
| 655 | landlock_get_current_domain(); | ||
| 656 | |||
| 657 | if (!dom) | ||
| 658 | return 0; | ||
| 659 | /* | ||
| 660 | * Because a file may be opened with O_PATH, get_file_access() may | ||
| 661 | * return 0. This case will be handled with a future Landlock | ||
| 662 | * evolution. | ||
| 663 | */ | ||
| 664 | return check_access_path(dom, &file->f_path, get_file_access(file)); | ||
| 665 | } | ||
| 666 | |||
| 667 | static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { | ||
| 668 | LSM_HOOK_INIT(inode_free_security, hook_inode_free_security), | ||
| 669 | |||
| 670 | LSM_HOOK_INIT(sb_delete, hook_sb_delete), | ||
| 671 | LSM_HOOK_INIT(sb_mount, hook_sb_mount), | ||
| 672 | LSM_HOOK_INIT(move_mount, hook_move_mount), | ||
| 673 | LSM_HOOK_INIT(sb_umount, hook_sb_umount), | ||
| 674 | LSM_HOOK_INIT(sb_remount, hook_sb_remount), | ||
| 675 | LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot), | ||
| 676 | |||
| 677 | LSM_HOOK_INIT(path_link, hook_path_link), | ||
| 678 | LSM_HOOK_INIT(path_rename, hook_path_rename), | ||
| 679 | LSM_HOOK_INIT(path_mkdir, hook_path_mkdir), | ||
| 680 | LSM_HOOK_INIT(path_mknod, hook_path_mknod), | ||
| 681 | LSM_HOOK_INIT(path_symlink, hook_path_symlink), | ||
| 682 | LSM_HOOK_INIT(path_unlink, hook_path_unlink), | ||
| 683 | LSM_HOOK_INIT(path_rmdir, hook_path_rmdir), | ||
| 684 | |||
| 685 | LSM_HOOK_INIT(file_open, hook_file_open), | ||
| 686 | }; | ||
| 687 | |||
| 688 | __init void landlock_add_fs_hooks(void) | ||
| 689 | { | ||
| 690 | security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), | ||
| 691 | LANDLOCK_NAME); | ||
| 692 | } | ||
| @@ -0,0 +1,70 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Filesystem management and hooks | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_FS_H | ||
| 10 | #define _SECURITY_LANDLOCK_FS_H | ||
| 11 | |||
| 12 | #include <linux/fs.h> | ||
| 13 | #include <linux/init.h> | ||
| 14 | #include <linux/rcupdate.h> | ||
| 15 | |||
| 16 | #include "ruleset.h" | ||
| 17 | #include "setup.h" | ||
| 18 | |||
| 19 | /** | ||
| 20 | * struct landlock_inode_security - Inode security blob | ||
| 21 | * | ||
| 22 | * Enable to reference a &struct landlock_object tied to an inode (i.e. | ||
| 23 | * underlying object). | ||
| 24 | */ | ||
| 25 | struct landlock_inode_security { | ||
| 26 | /** | ||
| 27 | * @object: Weak pointer to an allocated object. All assignments of a | ||
| 28 | * new object are protected by the underlying inode->i_lock. However, | ||
| 29 | * atomically disassociating @object from the inode is only protected | ||
| 30 | * by @object->lock, from the time @object's usage refcount drops to | ||
| 31 | * zero to the time this pointer is nulled out (cf. release_inode() and | ||
| 32 | * hook_sb_delete()). Indeed, such disassociation doesn't require | ||
| 33 | * inode->i_lock thanks to the careful rcu_access_pointer() check | ||
| 34 | * performed by get_inode_object(). | ||
| 35 | */ | ||
| 36 | struct landlock_object __rcu *object; | ||
| 37 | }; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * struct landlock_superblock_security - Superblock security blob | ||
| 41 | * | ||
| 42 | * Enable hook_sb_delete() to wait for concurrent calls to release_inode(). | ||
| 43 | */ | ||
| 44 | struct landlock_superblock_security { | ||
| 45 | /** | ||
| 46 | * @inode_refs: Number of pending inodes (from this superblock) that | ||
| 47 | * are being released by release_inode(). | ||
| 48 | * Cf. struct super_block->s_fsnotify_inode_refs . | ||
| 49 | */ | ||
| 50 | atomic_long_t inode_refs; | ||
| 51 | }; | ||
| 52 | |||
| 53 | static inline struct landlock_inode_security *landlock_inode( | ||
| 54 | const struct inode *const inode) | ||
| 55 | { | ||
| 56 | return inode->i_security + landlock_blob_sizes.lbs_inode; | ||
| 57 | } | ||
| 58 | |||
| 59 | static inline struct landlock_superblock_security *landlock_superblock( | ||
| 60 | const struct super_block *const superblock) | ||
| 61 | { | ||
| 62 | return superblock->s_security + landlock_blob_sizes.lbs_superblock; | ||
| 63 | } | ||
| 64 | |||
| 65 | __init void landlock_add_fs_hooks(void); | ||
| 66 | |||
| 67 | int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, | ||
| 68 | const struct path *const path, u32 access_hierarchy); | ||
| 69 | |||
| 70 | #endif /* _SECURITY_LANDLOCK_FS_H */ | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Limits for different components | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_LIMITS_H | ||
| 10 | #define _SECURITY_LANDLOCK_LIMITS_H | ||
| 11 | |||
| 12 | #include <linux/limits.h> | ||
| 13 | #include <uapi/linux/landlock.h> | ||
| 14 | |||
| 15 | #define LANDLOCK_MAX_NUM_LAYERS 64 | ||
| 16 | #define LANDLOCK_MAX_NUM_RULES U32_MAX | ||
| 17 | |||
| 18 | #define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_MAKE_SYM | ||
| 19 | #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) | ||
| 20 | |||
| 21 | #endif /* _SECURITY_LANDLOCK_LIMITS_H */ | ||
| @@ -0,0 +1,67 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Object management | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/bug.h> | ||
| 10 | #include <linux/compiler_types.h> | ||
| 11 | #include <linux/err.h> | ||
| 12 | #include <linux/kernel.h> | ||
| 13 | #include <linux/rcupdate.h> | ||
| 14 | #include <linux/refcount.h> | ||
| 15 | #include <linux/slab.h> | ||
| 16 | #include <linux/spinlock.h> | ||
| 17 | |||
| 18 | #include "object.h" | ||
| 19 | |||
| 20 | struct landlock_object *landlock_create_object( | ||
| 21 | const struct landlock_object_underops *const underops, | ||
| 22 | void *const underobj) | ||
| 23 | { | ||
| 24 | struct landlock_object *new_object; | ||
| 25 | |||
| 26 | if (WARN_ON_ONCE(!underops || !underobj)) | ||
| 27 | return ERR_PTR(-ENOENT); | ||
| 28 | new_object = kzalloc(sizeof(*new_object), GFP_KERNEL_ACCOUNT); | ||
| 29 | if (!new_object) | ||
| 30 | return ERR_PTR(-ENOMEM); | ||
| 31 | refcount_set(&new_object->usage, 1); | ||
| 32 | spin_lock_init(&new_object->lock); | ||
| 33 | new_object->underops = underops; | ||
| 34 | new_object->underobj = underobj; | ||
| 35 | return new_object; | ||
| 36 | } | ||
| 37 | |||
| 38 | /* | ||
| 39 | * The caller must own the object (i.e. thanks to object->usage) to safely put | ||
| 40 | * it. | ||
| 41 | */ | ||
| 42 | void landlock_put_object(struct landlock_object *const object) | ||
| 43 | { | ||
| 44 | /* | ||
| 45 | * The call to @object->underops->release(object) might sleep, e.g. | ||
| 46 | * because of iput(). | ||
| 47 | */ | ||
| 48 | might_sleep(); | ||
| 49 | if (!object) | ||
| 50 | return; | ||
| 51 | |||
| 52 | /* | ||
| 53 | * If the @object's refcount cannot drop to zero, we can just decrement | ||
| 54 | * the refcount without holding a lock. Otherwise, the decrement must | ||
| 55 | * happen under @object->lock for synchronization with things like | ||
| 56 | * get_inode_object(). | ||
| 57 | */ | ||
| 58 | if (refcount_dec_and_lock(&object->usage, &object->lock)) { | ||
| 59 | __acquire(&object->lock); | ||
| 60 | /* | ||
| 61 | * With @object->lock initially held, remove the reference from | ||
| 62 | * @object->underobj to @object (if it still exists). | ||
| 63 | */ | ||
| 64 | object->underops->release(object); | ||
| 65 | kfree_rcu(object, rcu_free); | ||
| 66 | } | ||
| 67 | } | ||
| @@ -0,0 +1,91 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Object management | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_OBJECT_H | ||
| 10 | #define _SECURITY_LANDLOCK_OBJECT_H | ||
| 11 | |||
| 12 | #include <linux/compiler_types.h> | ||
| 13 | #include <linux/refcount.h> | ||
| 14 | #include <linux/spinlock.h> | ||
| 15 | |||
| 16 | struct landlock_object; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * struct landlock_object_underops - Operations on an underlying object | ||
| 20 | */ | ||
| 21 | struct landlock_object_underops { | ||
| 22 | /** | ||
| 23 | * @release: Releases the underlying object (e.g. iput() for an inode). | ||
| 24 | */ | ||
| 25 | void (*release)(struct landlock_object *const object) | ||
| 26 | __releases(object->lock); | ||
| 27 | }; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * struct landlock_object - Security blob tied to a kernel object | ||
| 31 | * | ||
| 32 | * The goal of this structure is to enable to tie a set of ephemeral access | ||
| 33 | * rights (pertaining to different domains) to a kernel object (e.g an inode) | ||
| 34 | * in a safe way. This implies to handle concurrent use and modification. | ||
| 35 | * | ||
| 36 | * The lifetime of a &struct landlock_object depends on the rules referring to | ||
| 37 | * it. | ||
| 38 | */ | ||
| 39 | struct landlock_object { | ||
| 40 | /** | ||
| 41 | * @usage: This counter is used to tie an object to the rules matching | ||
| 42 | * it or to keep it alive while adding a new rule. If this counter | ||
| 43 | * reaches zero, this struct must not be modified, but this counter can | ||
| 44 | * still be read from within an RCU read-side critical section. When | ||
| 45 | * adding a new rule to an object with a usage counter of zero, we must | ||
| 46 | * wait until the pointer to this object is set to NULL (or recycled). | ||
| 47 | */ | ||
| 48 | refcount_t usage; | ||
| 49 | /** | ||
| 50 | * @lock: Protects against concurrent modifications. This lock must be | ||
| 51 | * held from the time @usage drops to zero until any weak references | ||
| 52 | * from @underobj to this object have been cleaned up. | ||
| 53 | * | ||
| 54 | * Lock ordering: inode->i_lock nests inside this. | ||
| 55 | */ | ||
| 56 | spinlock_t lock; | ||
| 57 | /** | ||
| 58 | * @underobj: Used when cleaning up an object and to mark an object as | ||
| 59 | * tied to its underlying kernel structure. This pointer is protected | ||
| 60 | * by @lock. Cf. landlock_release_inodes() and release_inode(). | ||
| 61 | */ | ||
| 62 | void *underobj; | ||
| 63 | union { | ||
| 64 | /** | ||
| 65 | * @rcu_free: Enables lockless use of @usage, @lock and | ||
| 66 | * @underobj from within an RCU read-side critical section. | ||
| 67 | * @rcu_free and @underops are only used by | ||
| 68 | * landlock_put_object(). | ||
| 69 | */ | ||
| 70 | struct rcu_head rcu_free; | ||
| 71 | /** | ||
| 72 | * @underops: Enables landlock_put_object() to release the | ||
| 73 | * underlying object (e.g. inode). | ||
| 74 | */ | ||
| 75 | const struct landlock_object_underops *underops; | ||
| 76 | }; | ||
| 77 | }; | ||
| 78 | |||
| 79 | struct landlock_object *landlock_create_object( | ||
| 80 | const struct landlock_object_underops *const underops, | ||
| 81 | void *const underobj); | ||
| 82 | |||
| 83 | void landlock_put_object(struct landlock_object *const object); | ||
| 84 | |||
| 85 | static inline void landlock_get_object(struct landlock_object *const object) | ||
| 86 | { | ||
| 87 | if (object) | ||
| 88 | refcount_inc(&object->usage); | ||
| 89 | } | ||
| 90 | |||
| 91 | #endif /* _SECURITY_LANDLOCK_OBJECT_H */ | ||
| @@ -0,0 +1,120 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Ptrace hooks | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2019-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <asm/current.h> | ||
| 10 | #include <linux/cred.h> | ||
| 11 | #include <linux/errno.h> | ||
| 12 | #include <linux/kernel.h> | ||
| 13 | #include <linux/lsm_hooks.h> | ||
| 14 | #include <linux/rcupdate.h> | ||
| 15 | #include <linux/sched.h> | ||
| 16 | |||
| 17 | #include "common.h" | ||
| 18 | #include "cred.h" | ||
| 19 | #include "ptrace.h" | ||
| 20 | #include "ruleset.h" | ||
| 21 | #include "setup.h" | ||
| 22 | |||
| 23 | /** | ||
| 24 | * domain_scope_le - Checks domain ordering for scoped ptrace | ||
| 25 | * | ||
| 26 | * @parent: Parent domain. | ||
| 27 | * @child: Potential child of @parent. | ||
| 28 | * | ||
| 29 | * Checks if the @parent domain is less or equal to (i.e. an ancestor, which | ||
| 30 | * means a subset of) the @child domain. | ||
| 31 | */ | ||
| 32 | static bool domain_scope_le(const struct landlock_ruleset *const parent, | ||
| 33 | const struct landlock_ruleset *const child) | ||
| 34 | { | ||
| 35 | const struct landlock_hierarchy *walker; | ||
| 36 | |||
| 37 | if (!parent) | ||
| 38 | return true; | ||
| 39 | if (!child) | ||
| 40 | return false; | ||
| 41 | for (walker = child->hierarchy; walker; walker = walker->parent) { | ||
| 42 | if (walker == parent->hierarchy) | ||
| 43 | /* @parent is in the scoped hierarchy of @child. */ | ||
| 44 | return true; | ||
| 45 | } | ||
| 46 | /* There is no relationship between @parent and @child. */ | ||
| 47 | return false; | ||
| 48 | } | ||
| 49 | |||
| 50 | static bool task_is_scoped(const struct task_struct *const parent, | ||
| 51 | const struct task_struct *const child) | ||
| 52 | { | ||
| 53 | bool is_scoped; | ||
| 54 | const struct landlock_ruleset *dom_parent, *dom_child; | ||
| 55 | |||
| 56 | rcu_read_lock(); | ||
| 57 | dom_parent = landlock_get_task_domain(parent); | ||
| 58 | dom_child = landlock_get_task_domain(child); | ||
| 59 | is_scoped = domain_scope_le(dom_parent, dom_child); | ||
| 60 | rcu_read_unlock(); | ||
| 61 | return is_scoped; | ||
| 62 | } | ||
| 63 | |||
| 64 | static int task_ptrace(const struct task_struct *const parent, | ||
| 65 | const struct task_struct *const child) | ||
| 66 | { | ||
| 67 | /* Quick return for non-landlocked tasks. */ | ||
| 68 | if (!landlocked(parent)) | ||
| 69 | return 0; | ||
| 70 | if (task_is_scoped(parent, child)) | ||
| 71 | return 0; | ||
| 72 | return -EPERM; | ||
| 73 | } | ||
| 74 | |||
| 75 | /** | ||
| 76 | * hook_ptrace_access_check - Determines whether the current process may access | ||
| 77 | * another | ||
| 78 | * | ||
| 79 | * @child: Process to be accessed. | ||
| 80 | * @mode: Mode of attachment. | ||
| 81 | * | ||
| 82 | * If the current task has Landlock rules, then the child must have at least | ||
| 83 | * the same rules. Else denied. | ||
| 84 | * | ||
| 85 | * Determines whether a process may access another, returning 0 if permission | ||
| 86 | * granted, -errno if denied. | ||
| 87 | */ | ||
| 88 | static int hook_ptrace_access_check(struct task_struct *const child, | ||
| 89 | const unsigned int mode) | ||
| 90 | { | ||
| 91 | return task_ptrace(current, child); | ||
| 92 | } | ||
| 93 | |||
| 94 | /** | ||
| 95 | * hook_ptrace_traceme - Determines whether another process may trace the | ||
| 96 | * current one | ||
| 97 | * | ||
| 98 | * @parent: Task proposed to be the tracer. | ||
| 99 | * | ||
| 100 | * If the parent has Landlock rules, then the current task must have the same | ||
| 101 | * or more rules. Else denied. | ||
| 102 | * | ||
| 103 | * Determines whether the nominated task is permitted to trace the current | ||
| 104 | * process, returning 0 if permission is granted, -errno if denied. | ||
| 105 | */ | ||
| 106 | static int hook_ptrace_traceme(struct task_struct *const parent) | ||
| 107 | { | ||
| 108 | return task_ptrace(parent, current); | ||
| 109 | } | ||
| 110 | |||
| 111 | static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { | ||
| 112 | LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), | ||
| 113 | LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), | ||
| 114 | }; | ||
| 115 | |||
| 116 | __init void landlock_add_ptrace_hooks(void) | ||
| 117 | { | ||
| 118 | security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), | ||
| 119 | LANDLOCK_NAME); | ||
| 120 | } | ||
| @@ -0,0 +1,14 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Ptrace hooks | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2019 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2019 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_PTRACE_H | ||
| 10 | #define _SECURITY_LANDLOCK_PTRACE_H | ||
| 11 | |||
| 12 | __init void landlock_add_ptrace_hooks(void); | ||
| 13 | |||
| 14 | #endif /* _SECURITY_LANDLOCK_PTRACE_H */ | ||
| @@ -0,0 +1,473 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Ruleset management | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/bits.h> | ||
| 10 | #include <linux/bug.h> | ||
| 11 | #include <linux/compiler_types.h> | ||
| 12 | #include <linux/err.h> | ||
| 13 | #include <linux/errno.h> | ||
| 14 | #include <linux/kernel.h> | ||
| 15 | #include <linux/lockdep.h> | ||
| 16 | #include <linux/overflow.h> | ||
| 17 | #include <linux/rbtree.h> | ||
| 18 | #include <linux/refcount.h> | ||
| 19 | #include <linux/slab.h> | ||
| 20 | #include <linux/spinlock.h> | ||
| 21 | #include <linux/workqueue.h> | ||
| 22 | |||
| 23 | #include "limits.h" | ||
| 24 | #include "object.h" | ||
| 25 | #include "ruleset.h" | ||
| 26 | |||
| 27 | static struct landlock_ruleset *create_ruleset(const u32 num_layers) | ||
| 28 | { | ||
| 29 | struct landlock_ruleset *new_ruleset; | ||
| 30 | |||
| 31 | new_ruleset = kzalloc(struct_size(new_ruleset, fs_access_masks, | ||
| 32 | num_layers), GFP_KERNEL_ACCOUNT); | ||
| 33 | if (!new_ruleset) | ||
| 34 | return ERR_PTR(-ENOMEM); | ||
| 35 | refcount_set(&new_ruleset->usage, 1); | ||
| 36 | mutex_init(&new_ruleset->lock); | ||
| 37 | new_ruleset->root = RB_ROOT; | ||
| 38 | new_ruleset->num_layers = num_layers; | ||
| 39 | /* | ||
| 40 | * hierarchy = NULL | ||
| 41 | * num_rules = 0 | ||
| 42 | * fs_access_masks[] = 0 | ||
| 43 | */ | ||
| 44 | return new_ruleset; | ||
| 45 | } | ||
| 46 | |||
| 47 | struct landlock_ruleset *landlock_create_ruleset(const u32 fs_access_mask) | ||
| 48 | { | ||
| 49 | struct landlock_ruleset *new_ruleset; | ||
| 50 | |||
| 51 | /* Informs about useless ruleset. */ | ||
| 52 | if (!fs_access_mask) | ||
| 53 | return ERR_PTR(-ENOMSG); | ||
| 54 | new_ruleset = create_ruleset(1); | ||
| 55 | if (!IS_ERR(new_ruleset)) | ||
| 56 | new_ruleset->fs_access_masks[0] = fs_access_mask; | ||
| 57 | return new_ruleset; | ||
| 58 | } | ||
| 59 | |||
| 60 | static void build_check_rule(void) | ||
| 61 | { | ||
| 62 | const struct landlock_rule rule = { | ||
| 63 | .num_layers = ~0, | ||
| 64 | }; | ||
| 65 | |||
| 66 | BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS); | ||
| 67 | } | ||
| 68 | |||
| 69 | static struct landlock_rule *create_rule( | ||
| 70 | struct landlock_object *const object, | ||
| 71 | const struct landlock_layer (*const layers)[], | ||
| 72 | const u32 num_layers, | ||
| 73 | const struct landlock_layer *const new_layer) | ||
| 74 | { | ||
| 75 | struct landlock_rule *new_rule; | ||
| 76 | u32 new_num_layers; | ||
| 77 | |||
| 78 | build_check_rule(); | ||
| 79 | if (new_layer) { | ||
| 80 | /* Should already be checked by landlock_merge_ruleset(). */ | ||
| 81 | if (WARN_ON_ONCE(num_layers >= LANDLOCK_MAX_NUM_LAYERS)) | ||
| 82 | return ERR_PTR(-E2BIG); | ||
| 83 | new_num_layers = num_layers + 1; | ||
| 84 | } else { | ||
| 85 | new_num_layers = num_layers; | ||
| 86 | } | ||
| 87 | new_rule = kzalloc(struct_size(new_rule, layers, new_num_layers), | ||
| 88 | GFP_KERNEL_ACCOUNT); | ||
| 89 | if (!new_rule) | ||
| 90 | return ERR_PTR(-ENOMEM); | ||
| 91 | RB_CLEAR_NODE(&new_rule->node); | ||
| 92 | landlock_get_object(object); | ||
| 93 | new_rule->object = object; | ||
| 94 | new_rule->num_layers = new_num_layers; | ||
| 95 | /* Copies the original layer stack. */ | ||
| 96 | memcpy(new_rule->layers, layers, | ||
| 97 | flex_array_size(new_rule, layers, num_layers)); | ||
| 98 | if (new_layer) | ||
| 99 | /* Adds a copy of @new_layer on the layer stack. */ | ||
| 100 | new_rule->layers[new_rule->num_layers - 1] = *new_layer; | ||
| 101 | return new_rule; | ||
| 102 | } | ||
| 103 | |||
| 104 | static void free_rule(struct landlock_rule *const rule) | ||
| 105 | { | ||
| 106 | might_sleep(); | ||
| 107 | if (!rule) | ||
| 108 | return; | ||
| 109 | landlock_put_object(rule->object); | ||
| 110 | kfree(rule); | ||
| 111 | } | ||
| 112 | |||
| 113 | static void build_check_ruleset(void) | ||
| 114 | { | ||
| 115 | const struct landlock_ruleset ruleset = { | ||
| 116 | .num_rules = ~0, | ||
| 117 | .num_layers = ~0, | ||
| 118 | }; | ||
| 119 | typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0; | ||
| 120 | |||
| 121 | BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES); | ||
| 122 | BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS); | ||
| 123 | BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS); | ||
| 124 | } | ||
| 125 | |||
| 126 | /** | ||
| 127 | * insert_rule - Create and insert a rule in a ruleset | ||
| 128 | * | ||
| 129 | * @ruleset: The ruleset to be updated. | ||
| 130 | * @object: The object to build the new rule with. The underlying kernel | ||
| 131 | * object must be held by the caller. | ||
| 132 | * @layers: One or multiple layers to be copied into the new rule. | ||
| 133 | * @num_layers: The number of @layers entries. | ||
| 134 | * | ||
| 135 | * When user space requests to add a new rule to a ruleset, @layers only | ||
| 136 | * contains one entry and this entry is not assigned to any level. In this | ||
| 137 | * case, the new rule will extend @ruleset, similarly to a boolean OR between | ||
| 138 | * access rights. | ||
| 139 | * | ||
| 140 | * When merging a ruleset in a domain, or copying a domain, @layers will be | ||
| 141 | * added to @ruleset as new constraints, similarly to a boolean AND between | ||
| 142 | * access rights. | ||
| 143 | */ | ||
| 144 | static int insert_rule(struct landlock_ruleset *const ruleset, | ||
| 145 | struct landlock_object *const object, | ||
| 146 | const struct landlock_layer (*const layers)[], | ||
| 147 | size_t num_layers) | ||
| 148 | { | ||
| 149 | struct rb_node **walker_node; | ||
| 150 | struct rb_node *parent_node = NULL; | ||
| 151 | struct landlock_rule *new_rule; | ||
| 152 | |||
| 153 | might_sleep(); | ||
| 154 | lockdep_assert_held(&ruleset->lock); | ||
| 155 | if (WARN_ON_ONCE(!object || !layers)) | ||
| 156 | return -ENOENT; | ||
| 157 | walker_node = &(ruleset->root.rb_node); | ||
| 158 | while (*walker_node) { | ||
| 159 | struct landlock_rule *const this = rb_entry(*walker_node, | ||
| 160 | struct landlock_rule, node); | ||
| 161 | |||
| 162 | if (this->object != object) { | ||
| 163 | parent_node = *walker_node; | ||
| 164 | if (this->object < object) | ||
| 165 | walker_node = &((*walker_node)->rb_right); | ||
| 166 | else | ||
| 167 | walker_node = &((*walker_node)->rb_left); | ||
| 168 | continue; | ||
| 169 | } | ||
| 170 | |||
| 171 | /* Only a single-level layer should match an existing rule. */ | ||
| 172 | if (WARN_ON_ONCE(num_layers != 1)) | ||
| 173 | return -EINVAL; | ||
| 174 | |||
| 175 | /* If there is a matching rule, updates it. */ | ||
| 176 | if ((*layers)[0].level == 0) { | ||
| 177 | /* | ||
| 178 | * Extends access rights when the request comes from | ||
| 179 | * landlock_add_rule(2), i.e. @ruleset is not a domain. | ||
| 180 | */ | ||
| 181 | if (WARN_ON_ONCE(this->num_layers != 1)) | ||
| 182 | return -EINVAL; | ||
| 183 | if (WARN_ON_ONCE(this->layers[0].level != 0)) | ||
| 184 | return -EINVAL; | ||
| 185 | this->layers[0].access |= (*layers)[0].access; | ||
| 186 | return 0; | ||
| 187 | } | ||
| 188 | |||
| 189 | if (WARN_ON_ONCE(this->layers[0].level == 0)) | ||
| 190 | return -EINVAL; | ||
| 191 | |||
| 192 | /* | ||
| 193 | * Intersects access rights when it is a merge between a | ||
| 194 | * ruleset and a domain. | ||
| 195 | */ | ||
| 196 | new_rule = create_rule(object, &this->layers, this->num_layers, | ||
| 197 | &(*layers)[0]); | ||
| 198 | if (IS_ERR(new_rule)) | ||
| 199 | return PTR_ERR(new_rule); | ||
| 200 | rb_replace_node(&this->node, &new_rule->node, &ruleset->root); | ||
| 201 | free_rule(this); | ||
| 202 | return 0; | ||
| 203 | } | ||
| 204 | |||
| 205 | /* There is no match for @object. */ | ||
| 206 | build_check_ruleset(); | ||
| 207 | if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES) | ||
| 208 | return -E2BIG; | ||
| 209 | new_rule = create_rule(object, layers, num_layers, NULL); | ||
| 210 | if (IS_ERR(new_rule)) | ||
| 211 | return PTR_ERR(new_rule); | ||
| 212 | rb_link_node(&new_rule->node, parent_node, walker_node); | ||
| 213 | rb_insert_color(&new_rule->node, &ruleset->root); | ||
| 214 | ruleset->num_rules++; | ||
| 215 | return 0; | ||
| 216 | } | ||
| 217 | |||
| 218 | static void build_check_layer(void) | ||
| 219 | { | ||
| 220 | const struct landlock_layer layer = { | ||
| 221 | .level = ~0, | ||
| 222 | .access = ~0, | ||
| 223 | }; | ||
| 224 | |||
| 225 | BUILD_BUG_ON(layer.level < LANDLOCK_MAX_NUM_LAYERS); | ||
| 226 | BUILD_BUG_ON(layer.access < LANDLOCK_MASK_ACCESS_FS); | ||
| 227 | } | ||
| 228 | |||
| 229 | /* @ruleset must be locked by the caller. */ | ||
| 230 | int landlock_insert_rule(struct landlock_ruleset *const ruleset, | ||
| 231 | struct landlock_object *const object, const u32 access) | ||
| 232 | { | ||
| 233 | struct landlock_layer layers[] = {{ | ||
| 234 | .access = access, | ||
| 235 | /* When @level is zero, insert_rule() extends @ruleset. */ | ||
| 236 | .level = 0, | ||
| 237 | }}; | ||
| 238 | |||
| 239 | build_check_layer(); | ||
| 240 | return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers)); | ||
| 241 | } | ||
| 242 | |||
| 243 | static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy) | ||
| 244 | { | ||
| 245 | if (hierarchy) | ||
| 246 | refcount_inc(&hierarchy->usage); | ||
| 247 | } | ||
| 248 | |||
| 249 | static void put_hierarchy(struct landlock_hierarchy *hierarchy) | ||
| 250 | { | ||
| 251 | while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) { | ||
| 252 | const struct landlock_hierarchy *const freeme = hierarchy; | ||
| 253 | |||
| 254 | hierarchy = hierarchy->parent; | ||
| 255 | kfree(freeme); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | static int merge_ruleset(struct landlock_ruleset *const dst, | ||
| 260 | struct landlock_ruleset *const src) | ||
| 261 | { | ||
| 262 | struct landlock_rule *walker_rule, *next_rule; | ||
| 263 | int err = 0; | ||
| 264 | |||
| 265 | might_sleep(); | ||
| 266 | /* Should already be checked by landlock_merge_ruleset() */ | ||
| 267 | if (WARN_ON_ONCE(!src)) | ||
| 268 | return 0; | ||
| 269 | /* Only merge into a domain. */ | ||
| 270 | if (WARN_ON_ONCE(!dst || !dst->hierarchy)) | ||
| 271 | return -EINVAL; | ||
| 272 | |||
| 273 | /* Locks @dst first because we are its only owner. */ | ||
| 274 | mutex_lock(&dst->lock); | ||
| 275 | mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING); | ||
| 276 | |||
| 277 | /* Stacks the new layer. */ | ||
| 278 | if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) { | ||
| 279 | err = -EINVAL; | ||
| 280 | goto out_unlock; | ||
| 281 | } | ||
| 282 | dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0]; | ||
| 283 | |||
| 284 | /* Merges the @src tree. */ | ||
| 285 | rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, | ||
| 286 | &src->root, node) { | ||
| 287 | struct landlock_layer layers[] = {{ | ||
| 288 | .level = dst->num_layers, | ||
| 289 | }}; | ||
| 290 | |||
| 291 | if (WARN_ON_ONCE(walker_rule->num_layers != 1)) { | ||
| 292 | err = -EINVAL; | ||
| 293 | goto out_unlock; | ||
| 294 | } | ||
| 295 | if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) { | ||
| 296 | err = -EINVAL; | ||
| 297 | goto out_unlock; | ||
| 298 | } | ||
| 299 | layers[0].access = walker_rule->layers[0].access; | ||
| 300 | err = insert_rule(dst, walker_rule->object, &layers, | ||
| 301 | ARRAY_SIZE(layers)); | ||
| 302 | if (err) | ||
| 303 | goto out_unlock; | ||
| 304 | } | ||
| 305 | |||
| 306 | out_unlock: | ||
| 307 | mutex_unlock(&src->lock); | ||
| 308 | mutex_unlock(&dst->lock); | ||
| 309 | return err; | ||
| 310 | } | ||
| 311 | |||
| 312 | static int inherit_ruleset(struct landlock_ruleset *const parent, | ||
| 313 | struct landlock_ruleset *const child) | ||
| 314 | { | ||
| 315 | struct landlock_rule *walker_rule, *next_rule; | ||
| 316 | int err = 0; | ||
| 317 | |||
| 318 | might_sleep(); | ||
| 319 | if (!parent) | ||
| 320 | return 0; | ||
| 321 | |||
| 322 | /* Locks @child first because we are its only owner. */ | ||
| 323 | mutex_lock(&child->lock); | ||
| 324 | mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING); | ||
| 325 | |||
| 326 | /* Copies the @parent tree. */ | ||
| 327 | rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, | ||
| 328 | &parent->root, node) { | ||
| 329 | err = insert_rule(child, walker_rule->object, | ||
| 330 | &walker_rule->layers, walker_rule->num_layers); | ||
| 331 | if (err) | ||
| 332 | goto out_unlock; | ||
| 333 | } | ||
| 334 | |||
| 335 | if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) { | ||
| 336 | err = -EINVAL; | ||
| 337 | goto out_unlock; | ||
| 338 | } | ||
| 339 | /* Copies the parent layer stack and leaves a space for the new layer. */ | ||
| 340 | memcpy(child->fs_access_masks, parent->fs_access_masks, | ||
| 341 | flex_array_size(parent, fs_access_masks, parent->num_layers)); | ||
| 342 | |||
| 343 | if (WARN_ON_ONCE(!parent->hierarchy)) { | ||
| 344 | err = -EINVAL; | ||
| 345 | goto out_unlock; | ||
| 346 | } | ||
| 347 | get_hierarchy(parent->hierarchy); | ||
| 348 | child->hierarchy->parent = parent->hierarchy; | ||
| 349 | |||
| 350 | out_unlock: | ||
| 351 | mutex_unlock(&parent->lock); | ||
| 352 | mutex_unlock(&child->lock); | ||
| 353 | return err; | ||
| 354 | } | ||
| 355 | |||
| 356 | static void free_ruleset(struct landlock_ruleset *const ruleset) | ||
| 357 | { | ||
| 358 | struct landlock_rule *freeme, *next; | ||
| 359 | |||
| 360 | might_sleep(); | ||
| 361 | rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, | ||
| 362 | node) | ||
| 363 | free_rule(freeme); | ||
| 364 | put_hierarchy(ruleset->hierarchy); | ||
| 365 | kfree(ruleset); | ||
| 366 | } | ||
| 367 | |||
| 368 | void landlock_put_ruleset(struct landlock_ruleset *const ruleset) | ||
| 369 | { | ||
| 370 | might_sleep(); | ||
| 371 | if (ruleset && refcount_dec_and_test(&ruleset->usage)) | ||
| 372 | free_ruleset(ruleset); | ||
| 373 | } | ||
| 374 | |||
| 375 | static void free_ruleset_work(struct work_struct *const work) | ||
| 376 | { | ||
| 377 | struct landlock_ruleset *ruleset; | ||
| 378 | |||
| 379 | ruleset = container_of(work, struct landlock_ruleset, work_free); | ||
| 380 | free_ruleset(ruleset); | ||
| 381 | } | ||
| 382 | |||
| 383 | void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset) | ||
| 384 | { | ||
| 385 | if (ruleset && refcount_dec_and_test(&ruleset->usage)) { | ||
| 386 | INIT_WORK(&ruleset->work_free, free_ruleset_work); | ||
| 387 | schedule_work(&ruleset->work_free); | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | /** | ||
| 392 | * landlock_merge_ruleset - Merge a ruleset with a domain | ||
| 393 | * | ||
| 394 | * @parent: Parent domain. | ||
| 395 | * @ruleset: New ruleset to be merged. | ||
| 396 | * | ||
| 397 | * Returns the intersection of @parent and @ruleset, or returns @parent if | ||
| 398 | * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty. | ||
| 399 | */ | ||
| 400 | struct landlock_ruleset *landlock_merge_ruleset( | ||
| 401 | struct landlock_ruleset *const parent, | ||
| 402 | struct landlock_ruleset *const ruleset) | ||
| 403 | { | ||
| 404 | struct landlock_ruleset *new_dom; | ||
| 405 | u32 num_layers; | ||
| 406 | int err; | ||
| 407 | |||
| 408 | might_sleep(); | ||
| 409 | if (WARN_ON_ONCE(!ruleset || parent == ruleset)) | ||
| 410 | return ERR_PTR(-EINVAL); | ||
| 411 | |||
| 412 | if (parent) { | ||
| 413 | if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS) | ||
| 414 | return ERR_PTR(-E2BIG); | ||
| 415 | num_layers = parent->num_layers + 1; | ||
| 416 | } else { | ||
| 417 | num_layers = 1; | ||
| 418 | } | ||
| 419 | |||
| 420 | /* Creates a new domain... */ | ||
| 421 | new_dom = create_ruleset(num_layers); | ||
| 422 | if (IS_ERR(new_dom)) | ||
| 423 | return new_dom; | ||
| 424 | new_dom->hierarchy = kzalloc(sizeof(*new_dom->hierarchy), | ||
| 425 | GFP_KERNEL_ACCOUNT); | ||
| 426 | if (!new_dom->hierarchy) { | ||
| 427 | err = -ENOMEM; | ||
| 428 | goto out_put_dom; | ||
| 429 | } | ||
| 430 | refcount_set(&new_dom->hierarchy->usage, 1); | ||
| 431 | |||
| 432 | /* ...as a child of @parent... */ | ||
| 433 | err = inherit_ruleset(parent, new_dom); | ||
| 434 | if (err) | ||
| 435 | goto out_put_dom; | ||
| 436 | |||
| 437 | /* ...and including @ruleset. */ | ||
| 438 | err = merge_ruleset(new_dom, ruleset); | ||
| 439 | if (err) | ||
| 440 | goto out_put_dom; | ||
| 441 | |||
| 442 | return new_dom; | ||
| 443 | |||
| 444 | out_put_dom: | ||
| 445 | landlock_put_ruleset(new_dom); | ||
| 446 | return ERR_PTR(err); | ||
| 447 | } | ||
| 448 | |||
| 449 | /* | ||
| 450 | * The returned access has the same lifetime as @ruleset. | ||
| 451 | */ | ||
| 452 | const struct landlock_rule *landlock_find_rule( | ||
| 453 | const struct landlock_ruleset *const ruleset, | ||
| 454 | const struct landlock_object *const object) | ||
| 455 | { | ||
| 456 | const struct rb_node *node; | ||
| 457 | |||
| 458 | if (!object) | ||
| 459 | return NULL; | ||
| 460 | node = ruleset->root.rb_node; | ||
| 461 | while (node) { | ||
| 462 | struct landlock_rule *this = rb_entry(node, | ||
| 463 | struct landlock_rule, node); | ||
| 464 | |||
| 465 | if (this->object == object) | ||
| 466 | return this; | ||
| 467 | if (this->object < object) | ||
| 468 | node = node->rb_right; | ||
| 469 | else | ||
| 470 | node = node->rb_left; | ||
| 471 | } | ||
| 472 | return NULL; | ||
| 473 | } | ||
| @@ -0,0 +1,165 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Ruleset management | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_RULESET_H | ||
| 10 | #define _SECURITY_LANDLOCK_RULESET_H | ||
| 11 | |||
| 12 | #include <linux/mutex.h> | ||
| 13 | #include <linux/rbtree.h> | ||
| 14 | #include <linux/refcount.h> | ||
| 15 | #include <linux/workqueue.h> | ||
| 16 | |||
| 17 | #include "object.h" | ||
| 18 | |||
| 19 | /** | ||
| 20 | * struct landlock_layer - Access rights for a given layer | ||
| 21 | */ | ||
| 22 | struct landlock_layer { | ||
| 23 | /** | ||
| 24 | * @level: Position of this layer in the layer stack. | ||
| 25 | */ | ||
| 26 | u16 level; | ||
| 27 | /** | ||
| 28 | * @access: Bitfield of allowed actions on the kernel object. They are | ||
| 29 | * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ). | ||
| 30 | */ | ||
| 31 | u16 access; | ||
| 32 | }; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * struct landlock_rule - Access rights tied to an object | ||
| 36 | */ | ||
| 37 | struct landlock_rule { | ||
| 38 | /** | ||
| 39 | * @node: Node in the ruleset's red-black tree. | ||
| 40 | */ | ||
| 41 | struct rb_node node; | ||
| 42 | /** | ||
| 43 | * @object: Pointer to identify a kernel object (e.g. an inode). This | ||
| 44 | * is used as a key for this ruleset element. This pointer is set once | ||
| 45 | * and never modified. It always points to an allocated object because | ||
| 46 | * each rule increments the refcount of its object. | ||
| 47 | */ | ||
| 48 | struct landlock_object *object; | ||
| 49 | /** | ||
| 50 | * @num_layers: Number of entries in @layers. | ||
| 51 | */ | ||
| 52 | u32 num_layers; | ||
| 53 | /** | ||
| 54 | * @layers: Stack of layers, from the latest to the newest, implemented | ||
| 55 | * as a flexible array member (FAM). | ||
| 56 | */ | ||
| 57 | struct landlock_layer layers[]; | ||
| 58 | }; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * struct landlock_hierarchy - Node in a ruleset hierarchy | ||
| 62 | */ | ||
| 63 | struct landlock_hierarchy { | ||
| 64 | /** | ||
| 65 | * @parent: Pointer to the parent node, or NULL if it is a root | ||
| 66 | * Landlock domain. | ||
| 67 | */ | ||
| 68 | struct landlock_hierarchy *parent; | ||
| 69 | /** | ||
| 70 | * @usage: Number of potential children domains plus their parent | ||
| 71 | * domain. | ||
| 72 | */ | ||
| 73 | refcount_t usage; | ||
| 74 | }; | ||
| 75 | |||
| 76 | /** | ||
| 77 | * struct landlock_ruleset - Landlock ruleset | ||
| 78 | * | ||
| 79 | * This data structure must contain unique entries, be updatable, and quick to | ||
| 80 | * match an object. | ||
| 81 | */ | ||
| 82 | struct landlock_ruleset { | ||
| 83 | /** | ||
| 84 | * @root: Root of a red-black tree containing &struct landlock_rule | ||
| 85 | * nodes. Once a ruleset is tied to a process (i.e. as a domain), this | ||
| 86 | * tree is immutable until @usage reaches zero. | ||
| 87 | */ | ||
| 88 | struct rb_root root; | ||
| 89 | /** | ||
| 90 | * @hierarchy: Enables hierarchy identification even when a parent | ||
| 91 | * domain vanishes. This is needed for the ptrace protection. | ||
| 92 | */ | ||
| 93 | struct landlock_hierarchy *hierarchy; | ||
| 94 | union { | ||
| 95 | /** | ||
| 96 | * @work_free: Enables to free a ruleset within a lockless | ||
| 97 | * section. This is only used by | ||
| 98 | * landlock_put_ruleset_deferred() when @usage reaches zero. | ||
| 99 | * The fields @lock, @usage, @num_rules, @num_layers and | ||
| 100 | * @fs_access_masks are then unused. | ||
| 101 | */ | ||
| 102 | struct work_struct work_free; | ||
| 103 | struct { | ||
| 104 | /** | ||
| 105 | * @lock: Protects against concurrent modifications of | ||
| 106 | * @root, if @usage is greater than zero. | ||
| 107 | */ | ||
| 108 | struct mutex lock; | ||
| 109 | /** | ||
| 110 | * @usage: Number of processes (i.e. domains) or file | ||
| 111 | * descriptors referencing this ruleset. | ||
| 112 | */ | ||
| 113 | refcount_t usage; | ||
| 114 | /** | ||
| 115 | * @num_rules: Number of non-overlapping (i.e. not for | ||
| 116 | * the same object) rules in this ruleset. | ||
| 117 | */ | ||
| 118 | u32 num_rules; | ||
| 119 | /** | ||
| 120 | * @num_layers: Number of layers that are used in this | ||
| 121 | * ruleset. This enables to check that all the layers | ||
| 122 | * allow an access request. A value of 0 identifies a | ||
| 123 | * non-merged ruleset (i.e. not a domain). | ||
| 124 | */ | ||
| 125 | u32 num_layers; | ||
| 126 | /** | ||
| 127 | * @fs_access_masks: Contains the subset of filesystem | ||
| 128 | * actions that are restricted by a ruleset. A domain | ||
| 129 | * saves all layers of merged rulesets in a stack | ||
| 130 | * (FAM), starting from the first layer to the last | ||
| 131 | * one. These layers are used when merging rulesets, | ||
| 132 | * for user space backward compatibility (i.e. | ||
| 133 | * future-proof), and to properly handle merged | ||
| 134 | * rulesets without overlapping access rights. These | ||
| 135 | * layers are set once and never changed for the | ||
| 136 | * lifetime of the ruleset. | ||
| 137 | */ | ||
| 138 | u16 fs_access_masks[]; | ||
| 139 | }; | ||
| 140 | }; | ||
| 141 | }; | ||
| 142 | |||
| 143 | struct landlock_ruleset *landlock_create_ruleset(const u32 fs_access_mask); | ||
| 144 | |||
| 145 | void landlock_put_ruleset(struct landlock_ruleset *const ruleset); | ||
| 146 | void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); | ||
| 147 | |||
| 148 | int landlock_insert_rule(struct landlock_ruleset *const ruleset, | ||
| 149 | struct landlock_object *const object, const u32 access); | ||
| 150 | |||
| 151 | struct landlock_ruleset *landlock_merge_ruleset( | ||
| 152 | struct landlock_ruleset *const parent, | ||
| 153 | struct landlock_ruleset *const ruleset); | ||
| 154 | |||
| 155 | const struct landlock_rule *landlock_find_rule( | ||
| 156 | const struct landlock_ruleset *const ruleset, | ||
| 157 | const struct landlock_object *const object); | ||
| 158 | |||
| 159 | static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) | ||
| 160 | { | ||
| 161 | if (ruleset) | ||
| 162 | refcount_inc(&ruleset->usage); | ||
| 163 | } | ||
| 164 | |||
| 165 | #endif /* _SECURITY_LANDLOCK_RULESET_H */ | ||
| @@ -0,0 +1,40 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Security framework setup | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/init.h> | ||
| 10 | #include <linux/lsm_hooks.h> | ||
| 11 | |||
| 12 | #include "common.h" | ||
| 13 | #include "cred.h" | ||
| 14 | #include "fs.h" | ||
| 15 | #include "ptrace.h" | ||
| 16 | #include "setup.h" | ||
| 17 | |||
| 18 | bool landlock_initialized __lsm_ro_after_init = false; | ||
| 19 | |||
| 20 | struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = { | ||
| 21 | .lbs_cred = sizeof(struct landlock_cred_security), | ||
| 22 | .lbs_inode = sizeof(struct landlock_inode_security), | ||
| 23 | .lbs_superblock = sizeof(struct landlock_superblock_security), | ||
| 24 | }; | ||
| 25 | |||
| 26 | static int __init landlock_init(void) | ||
| 27 | { | ||
| 28 | landlock_add_cred_hooks(); | ||
| 29 | landlock_add_ptrace_hooks(); | ||
| 30 | landlock_add_fs_hooks(); | ||
| 31 | landlock_initialized = true; | ||
| 32 | pr_info("Up and running.\n"); | ||
| 33 | return 0; | ||
| 34 | } | ||
| 35 | |||
| 36 | DEFINE_LSM(LANDLOCK_NAME) = { | ||
| 37 | .name = LANDLOCK_NAME, | ||
| 38 | .init = landlock_init, | ||
| 39 | .blobs = &landlock_blob_sizes, | ||
| 40 | }; | ||
| @@ -0,0 +1,18 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Landlock LSM - Security framework setup | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef _SECURITY_LANDLOCK_SETUP_H | ||
| 10 | #define _SECURITY_LANDLOCK_SETUP_H | ||
| 11 | |||
| 12 | #include <linux/lsm_hooks.h> | ||
| 13 | |||
| 14 | extern bool landlock_initialized; | ||
| 15 | |||
| 16 | extern struct lsm_blob_sizes landlock_blob_sizes; | ||
| 17 | |||
| 18 | #endif /* _SECURITY_LANDLOCK_SETUP_H */ | ||
| @@ -0,0 +1,451 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Landlock LSM - System call implementations and user space interfaces | ||
| 4 | * | ||
| 5 | * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2018-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <asm/current.h> | ||
| 10 | #include <linux/anon_inodes.h> | ||
| 11 | #include <linux/build_bug.h> | ||
| 12 | #include <linux/capability.h> | ||
| 13 | #include <linux/compiler_types.h> | ||
| 14 | #include <linux/dcache.h> | ||
| 15 | #include <linux/err.h> | ||
| 16 | #include <linux/errno.h> | ||
| 17 | #include <linux/fs.h> | ||
| 18 | #include <linux/limits.h> | ||
| 19 | #include <linux/mount.h> | ||
| 20 | #include <linux/path.h> | ||
| 21 | #include <linux/sched.h> | ||
| 22 | #include <linux/security.h> | ||
| 23 | #include <linux/stddef.h> | ||
| 24 | #include <linux/syscalls.h> | ||
| 25 | #include <linux/types.h> | ||
| 26 | #include <linux/uaccess.h> | ||
| 27 | #include <uapi/linux/landlock.h> | ||
| 28 | |||
| 29 | #include "cred.h" | ||
| 30 | #include "fs.h" | ||
| 31 | #include "limits.h" | ||
| 32 | #include "ruleset.h" | ||
| 33 | #include "setup.h" | ||
| 34 | |||
| 35 | /** | ||
| 36 | * copy_min_struct_from_user - Safe future-proof argument copying | ||
| 37 | * | ||
| 38 | * Extend copy_struct_from_user() to check for consistent user buffer. | ||
| 39 | * | ||
| 40 | * @dst: Kernel space pointer or NULL. | ||
| 41 | * @ksize: Actual size of the data pointed to by @dst. | ||
| 42 | * @ksize_min: Minimal required size to be copied. | ||
| 43 | * @src: User space pointer or NULL. | ||
| 44 | * @usize: (Alleged) size of the data pointed to by @src. | ||
| 45 | */ | ||
| 46 | static __always_inline int copy_min_struct_from_user(void *const dst, | ||
| 47 | const size_t ksize, const size_t ksize_min, | ||
| 48 | const void __user *const src, const size_t usize) | ||
| 49 | { | ||
| 50 | /* Checks buffer inconsistencies. */ | ||
| 51 | BUILD_BUG_ON(!dst); | ||
| 52 | if (!src) | ||
| 53 | return -EFAULT; | ||
| 54 | |||
| 55 | /* Checks size ranges. */ | ||
| 56 | BUILD_BUG_ON(ksize <= 0); | ||
| 57 | BUILD_BUG_ON(ksize < ksize_min); | ||
| 58 | if (usize < ksize_min) | ||
| 59 | return -EINVAL; | ||
| 60 | if (usize > PAGE_SIZE) | ||
| 61 | return -E2BIG; | ||
| 62 | |||
| 63 | /* Copies user buffer and fills with zeros. */ | ||
| 64 | return copy_struct_from_user(dst, ksize, src, usize); | ||
| 65 | } | ||
| 66 | |||
| 67 | /* | ||
| 68 | * This function only contains arithmetic operations with constants, leading to | ||
| 69 | * BUILD_BUG_ON(). The related code is evaluated and checked at build time, | ||
| 70 | * but it is then ignored thanks to compiler optimizations. | ||
| 71 | */ | ||
| 72 | static void build_check_abi(void) | ||
| 73 | { | ||
| 74 | struct landlock_ruleset_attr ruleset_attr; | ||
| 75 | struct landlock_path_beneath_attr path_beneath_attr; | ||
| 76 | size_t ruleset_size, path_beneath_size; | ||
| 77 | |||
| 78 | /* | ||
| 79 | * For each user space ABI structures, first checks that there is no | ||
| 80 | * hole in them, then checks that all architectures have the same | ||
| 81 | * struct size. | ||
| 82 | */ | ||
| 83 | ruleset_size = sizeof(ruleset_attr.handled_access_fs); | ||
| 84 | BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); | ||
| 85 | BUILD_BUG_ON(sizeof(ruleset_attr) != 8); | ||
| 86 | |||
| 87 | path_beneath_size = sizeof(path_beneath_attr.allowed_access); | ||
| 88 | path_beneath_size += sizeof(path_beneath_attr.parent_fd); | ||
| 89 | BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size); | ||
| 90 | BUILD_BUG_ON(sizeof(path_beneath_attr) != 12); | ||
| 91 | } | ||
| 92 | |||
| 93 | /* Ruleset handling */ | ||
| 94 | |||
| 95 | static int fop_ruleset_release(struct inode *const inode, | ||
| 96 | struct file *const filp) | ||
| 97 | { | ||
| 98 | struct landlock_ruleset *ruleset = filp->private_data; | ||
| 99 | |||
| 100 | landlock_put_ruleset(ruleset); | ||
| 101 | return 0; | ||
| 102 | } | ||
| 103 | |||
| 104 | static ssize_t fop_dummy_read(struct file *const filp, char __user *const buf, | ||
| 105 | const size_t size, loff_t *const ppos) | ||
| 106 | { | ||
| 107 | /* Dummy handler to enable FMODE_CAN_READ. */ | ||
| 108 | return -EINVAL; | ||
| 109 | } | ||
| 110 | |||
| 111 | static ssize_t fop_dummy_write(struct file *const filp, | ||
| 112 | const char __user *const buf, const size_t size, | ||
| 113 | loff_t *const ppos) | ||
| 114 | { | ||
| 115 | /* Dummy handler to enable FMODE_CAN_WRITE. */ | ||
| 116 | return -EINVAL; | ||
| 117 | } | ||
| 118 | |||
| 119 | /* | ||
| 120 | * A ruleset file descriptor enables to build a ruleset by adding (i.e. | ||
| 121 | * writing) rule after rule, without relying on the task's context. This | ||
| 122 | * reentrant design is also used in a read way to enforce the ruleset on the | ||
| 123 | * current task. | ||
| 124 | */ | ||
| 125 | static const struct file_operations ruleset_fops = { | ||
| 126 | .release = fop_ruleset_release, | ||
| 127 | .read = fop_dummy_read, | ||
| 128 | .write = fop_dummy_write, | ||
| 129 | }; | ||
| 130 | |||
| 131 | #define LANDLOCK_ABI_VERSION 1 | ||
| 132 | |||
| 133 | /** | ||
| 134 | * sys_landlock_create_ruleset - Create a new ruleset | ||
| 135 | * | ||
| 136 | * @attr: Pointer to a &struct landlock_ruleset_attr identifying the scope of | ||
| 137 | * the new ruleset. | ||
| 138 | * @size: Size of the pointed &struct landlock_ruleset_attr (needed for | ||
| 139 | * backward and forward compatibility). | ||
| 140 | * @flags: Supported value: %LANDLOCK_CREATE_RULESET_VERSION. | ||
| 141 | * | ||
| 142 | * This system call enables to create a new Landlock ruleset, and returns the | ||
| 143 | * related file descriptor on success. | ||
| 144 | * | ||
| 145 | * If @flags is %LANDLOCK_CREATE_RULESET_VERSION and @attr is NULL and @size is | ||
| 146 | * 0, then the returned value is the highest supported Landlock ABI version | ||
| 147 | * (starting at 1). | ||
| 148 | * | ||
| 149 | * Possible returned errors are: | ||
| 150 | * | ||
| 151 | * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; | ||
| 152 | * - EINVAL: unknown @flags, or unknown access, or too small @size; | ||
| 153 | * - E2BIG or EFAULT: @attr or @size inconsistencies; | ||
| 154 | * - ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. | ||
| 155 | */ | ||
| 156 | SYSCALL_DEFINE3(landlock_create_ruleset, | ||
| 157 | const struct landlock_ruleset_attr __user *const, attr, | ||
| 158 | const size_t, size, const __u32, flags) | ||
| 159 | { | ||
| 160 | struct landlock_ruleset_attr ruleset_attr; | ||
| 161 | struct landlock_ruleset *ruleset; | ||
| 162 | int err, ruleset_fd; | ||
| 163 | |||
| 164 | /* Build-time checks. */ | ||
| 165 | build_check_abi(); | ||
| 166 | |||
| 167 | if (!landlock_initialized) | ||
| 168 | return -EOPNOTSUPP; | ||
| 169 | |||
| 170 | if (flags) { | ||
| 171 | if ((flags == LANDLOCK_CREATE_RULESET_VERSION) | ||
| 172 | && !attr && !size) | ||
| 173 | return LANDLOCK_ABI_VERSION; | ||
| 174 | return -EINVAL; | ||
| 175 | } | ||
| 176 | |||
| 177 | /* Copies raw user space buffer. */ | ||
| 178 | err = copy_min_struct_from_user(&ruleset_attr, sizeof(ruleset_attr), | ||
| 179 | offsetofend(typeof(ruleset_attr), handled_access_fs), | ||
| 180 | attr, size); | ||
| 181 | if (err) | ||
| 182 | return err; | ||
| 183 | |||
| 184 | /* Checks content (and 32-bits cast). */ | ||
| 185 | if ((ruleset_attr.handled_access_fs | LANDLOCK_MASK_ACCESS_FS) != | ||
| 186 | LANDLOCK_MASK_ACCESS_FS) | ||
| 187 | return -EINVAL; | ||
| 188 | |||
| 189 | /* Checks arguments and transforms to kernel struct. */ | ||
| 190 | ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs); | ||
| 191 | if (IS_ERR(ruleset)) | ||
| 192 | return PTR_ERR(ruleset); | ||
| 193 | |||
| 194 | /* Creates anonymous FD referring to the ruleset. */ | ||
| 195 | ruleset_fd = anon_inode_getfd("landlock-ruleset", &ruleset_fops, | ||
| 196 | ruleset, O_RDWR | O_CLOEXEC); | ||
| 197 | if (ruleset_fd < 0) | ||
| 198 | landlock_put_ruleset(ruleset); | ||
| 199 | return ruleset_fd; | ||
| 200 | } | ||
| 201 | |||
| 202 | /* | ||
| 203 | * Returns an owned ruleset from a FD. It is thus needed to call | ||
| 204 | * landlock_put_ruleset() on the return value. | ||
| 205 | */ | ||
| 206 | static struct landlock_ruleset *get_ruleset_from_fd(const int fd, | ||
| 207 | const fmode_t mode) | ||
| 208 | { | ||
| 209 | struct fd ruleset_f; | ||
| 210 | struct landlock_ruleset *ruleset; | ||
| 211 | |||
| 212 | ruleset_f = fdget(fd); | ||
| 213 | if (!ruleset_f.file) | ||
| 214 | return ERR_PTR(-EBADF); | ||
| 215 | |||
| 216 | /* Checks FD type and access right. */ | ||
| 217 | if (ruleset_f.file->f_op != &ruleset_fops) { | ||
| 218 | ruleset = ERR_PTR(-EBADFD); | ||
| 219 | goto out_fdput; | ||
| 220 | } | ||
| 221 | if (!(ruleset_f.file->f_mode & mode)) { | ||
| 222 | ruleset = ERR_PTR(-EPERM); | ||
| 223 | goto out_fdput; | ||
| 224 | } | ||
| 225 | ruleset = ruleset_f.file->private_data; | ||
| 226 | if (WARN_ON_ONCE(ruleset->num_layers != 1)) { | ||
| 227 | ruleset = ERR_PTR(-EINVAL); | ||
| 228 | goto out_fdput; | ||
| 229 | } | ||
| 230 | landlock_get_ruleset(ruleset); | ||
| 231 | |||
| 232 | out_fdput: | ||
| 233 | fdput(ruleset_f); | ||
| 234 | return ruleset; | ||
| 235 | } | ||
| 236 | |||
| 237 | /* Path handling */ | ||
| 238 | |||
| 239 | /* | ||
| 240 | * @path: Must call put_path(@path) after the call if it succeeded. | ||
| 241 | */ | ||
| 242 | static int get_path_from_fd(const s32 fd, struct path *const path) | ||
| 243 | { | ||
| 244 | struct fd f; | ||
| 245 | int err = 0; | ||
| 246 | |||
| 247 | BUILD_BUG_ON(!__same_type(fd, | ||
| 248 | ((struct landlock_path_beneath_attr *)NULL)->parent_fd)); | ||
| 249 | |||
| 250 | /* Handles O_PATH. */ | ||
| 251 | f = fdget_raw(fd); | ||
| 252 | if (!f.file) | ||
| 253 | return -EBADF; | ||
| 254 | /* | ||
| 255 | * Forbids ruleset FDs, internal filesystems (e.g. nsfs), including | ||
| 256 | * pseudo filesystems that will never be mountable (e.g. sockfs, | ||
| 257 | * pipefs). | ||
| 258 | */ | ||
| 259 | if ((f.file->f_op == &ruleset_fops) || | ||
| 260 | (f.file->f_path.mnt->mnt_flags & MNT_INTERNAL) || | ||
| 261 | (f.file->f_path.dentry->d_sb->s_flags & SB_NOUSER) || | ||
| 262 | d_is_negative(f.file->f_path.dentry) || | ||
| 263 | IS_PRIVATE(d_backing_inode(f.file->f_path.dentry))) { | ||
| 264 | err = -EBADFD; | ||
| 265 | goto out_fdput; | ||
| 266 | } | ||
| 267 | *path = f.file->f_path; | ||
| 268 | path_get(path); | ||
| 269 | |||
| 270 | out_fdput: | ||
| 271 | fdput(f); | ||
| 272 | return err; | ||
| 273 | } | ||
| 274 | |||
| 275 | /** | ||
| 276 | * sys_landlock_add_rule - Add a new rule to a ruleset | ||
| 277 | * | ||
| 278 | * @ruleset_fd: File descriptor tied to the ruleset that should be extended | ||
| 279 | * with the new rule. | ||
| 280 | * @rule_type: Identify the structure type pointed to by @rule_attr (only | ||
| 281 | * LANDLOCK_RULE_PATH_BENEATH for now). | ||
| 282 | * @rule_attr: Pointer to a rule (only of type &struct | ||
| 283 | * landlock_path_beneath_attr for now). | ||
| 284 | * @flags: Must be 0. | ||
| 285 | * | ||
| 286 | * This system call enables to define a new rule and add it to an existing | ||
| 287 | * ruleset. | ||
| 288 | * | ||
| 289 | * Possible returned errors are: | ||
| 290 | * | ||
| 291 | * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; | ||
| 292 | * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. | ||
| 293 | * &landlock_path_beneath_attr.allowed_access is not a subset of the rule's | ||
| 294 | * accesses); | ||
| 295 | * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access); | ||
| 296 | * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a | ||
| 297 | * member of @rule_attr is not a file descriptor as expected; | ||
| 298 | * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of | ||
| 299 | * @rule_attr is not the expected file descriptor type (e.g. file open | ||
| 300 | * without O_PATH); | ||
| 301 | * - EPERM: @ruleset_fd has no write access to the underlying ruleset; | ||
| 302 | * - EFAULT: @rule_attr inconsistency. | ||
| 303 | */ | ||
| 304 | SYSCALL_DEFINE4(landlock_add_rule, | ||
| 305 | const int, ruleset_fd, const enum landlock_rule_type, rule_type, | ||
| 306 | const void __user *const, rule_attr, const __u32, flags) | ||
| 307 | { | ||
| 308 | struct landlock_path_beneath_attr path_beneath_attr; | ||
| 309 | struct path path; | ||
| 310 | struct landlock_ruleset *ruleset; | ||
| 311 | int res, err; | ||
| 312 | |||
| 313 | if (!landlock_initialized) | ||
| 314 | return -EOPNOTSUPP; | ||
| 315 | |||
| 316 | /* No flag for now. */ | ||
| 317 | if (flags) | ||
| 318 | return -EINVAL; | ||
| 319 | |||
| 320 | if (rule_type != LANDLOCK_RULE_PATH_BENEATH) | ||
| 321 | return -EINVAL; | ||
| 322 | |||
| 323 | /* Copies raw user space buffer, only one type for now. */ | ||
| 324 | res = copy_from_user(&path_beneath_attr, rule_attr, | ||
| 325 | sizeof(path_beneath_attr)); | ||
| 326 | if (res) | ||
| 327 | return -EFAULT; | ||
| 328 | |||
| 329 | /* Gets and checks the ruleset. */ | ||
| 330 | ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); | ||
| 331 | if (IS_ERR(ruleset)) | ||
| 332 | return PTR_ERR(ruleset); | ||
| 333 | |||
| 334 | /* | ||
| 335 | * Informs about useless rule: empty allowed_access (i.e. deny rules) | ||
| 336 | * are ignored in path walks. | ||
| 337 | */ | ||
| 338 | if (!path_beneath_attr.allowed_access) { | ||
| 339 | err = -ENOMSG; | ||
| 340 | goto out_put_ruleset; | ||
| 341 | } | ||
| 342 | /* | ||
| 343 | * Checks that allowed_access matches the @ruleset constraints | ||
| 344 | * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits). | ||
| 345 | */ | ||
| 346 | if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) != | ||
| 347 | ruleset->fs_access_masks[0]) { | ||
| 348 | err = -EINVAL; | ||
| 349 | goto out_put_ruleset; | ||
| 350 | } | ||
| 351 | |||
| 352 | /* Gets and checks the new rule. */ | ||
| 353 | err = get_path_from_fd(path_beneath_attr.parent_fd, &path); | ||
| 354 | if (err) | ||
| 355 | goto out_put_ruleset; | ||
| 356 | |||
| 357 | /* Imports the new rule. */ | ||
| 358 | err = landlock_append_fs_rule(ruleset, &path, | ||
| 359 | path_beneath_attr.allowed_access); | ||
| 360 | path_put(&path); | ||
| 361 | |||
| 362 | out_put_ruleset: | ||
| 363 | landlock_put_ruleset(ruleset); | ||
| 364 | return err; | ||
| 365 | } | ||
| 366 | |||
| 367 | /* Enforcement */ | ||
| 368 | |||
| 369 | /** | ||
| 370 | * sys_landlock_restrict_self - Enforce a ruleset on the calling thread | ||
| 371 | * | ||
| 372 | * @ruleset_fd: File descriptor tied to the ruleset to merge with the target. | ||
| 373 | * @flags: Must be 0. | ||
| 374 | * | ||
| 375 | * This system call enables to enforce a Landlock ruleset on the current | ||
| 376 | * thread. Enforcing a ruleset requires that the task has CAP_SYS_ADMIN in its | ||
| 377 | * namespace or is running with no_new_privs. This avoids scenarios where | ||
| 378 | * unprivileged tasks can affect the behavior of privileged children. | ||
| 379 | * | ||
| 380 | * Possible returned errors are: | ||
| 381 | * | ||
| 382 | * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; | ||
| 383 | * - EINVAL: @flags is not 0. | ||
| 384 | * - EBADF: @ruleset_fd is not a file descriptor for the current thread; | ||
| 385 | * - EBADFD: @ruleset_fd is not a ruleset file descriptor; | ||
| 386 | * - EPERM: @ruleset_fd has no read access to the underlying ruleset, or the | ||
| 387 | * current thread is not running with no_new_privs, or it doesn't have | ||
| 388 | * CAP_SYS_ADMIN in its namespace. | ||
| 389 | * - E2BIG: The maximum number of stacked rulesets is reached for the current | ||
| 390 | * thread. | ||
| 391 | */ | ||
| 392 | SYSCALL_DEFINE2(landlock_restrict_self, | ||
| 393 | const int, ruleset_fd, const __u32, flags) | ||
| 394 | { | ||
| 395 | struct landlock_ruleset *new_dom, *ruleset; | ||
| 396 | struct cred *new_cred; | ||
| 397 | struct landlock_cred_security *new_llcred; | ||
| 398 | int err; | ||
| 399 | |||
| 400 | if (!landlock_initialized) | ||
| 401 | return -EOPNOTSUPP; | ||
| 402 | |||
| 403 | /* No flag for now. */ | ||
| 404 | if (flags) | ||
| 405 | return -EINVAL; | ||
| 406 | |||
| 407 | /* | ||
| 408 | * Similar checks as for seccomp(2), except that an -EPERM may be | ||
| 409 | * returned. | ||
| 410 | */ | ||
| 411 | if (!task_no_new_privs(current) && | ||
| 412 | !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) | ||
| 413 | return -EPERM; | ||
| 414 | |||
| 415 | /* Gets and checks the ruleset. */ | ||
| 416 | ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ); | ||
| 417 | if (IS_ERR(ruleset)) | ||
| 418 | return PTR_ERR(ruleset); | ||
| 419 | |||
| 420 | /* Prepares new credentials. */ | ||
| 421 | new_cred = prepare_creds(); | ||
| 422 | if (!new_cred) { | ||
| 423 | err = -ENOMEM; | ||
| 424 | goto out_put_ruleset; | ||
| 425 | } | ||
| 426 | new_llcred = landlock_cred(new_cred); | ||
| 427 | |||
| 428 | /* | ||
| 429 | * There is no possible race condition while copying and manipulating | ||
| 430 | * the current credentials because they are dedicated per thread. | ||
| 431 | */ | ||
| 432 | new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset); | ||
| 433 | if (IS_ERR(new_dom)) { | ||
| 434 | err = PTR_ERR(new_dom); | ||
| 435 | goto out_put_creds; | ||
| 436 | } | ||
| 437 | |||
| 438 | /* Replaces the old (prepared) domain. */ | ||
| 439 | landlock_put_ruleset(new_llcred->domain); | ||
| 440 | new_llcred->domain = new_dom; | ||
| 441 | |||
| 442 | landlock_put_ruleset(ruleset); | ||
| 443 | return commit_creds(new_cred); | ||
| 444 | |||
| 445 | out_put_creds: | ||
| 446 | abort_creds(new_cred); | ||
| 447 | |||
| 448 | out_put_ruleset: | ||
| 449 | landlock_put_ruleset(ruleset); | ||
| 450 | return err; | ||
| 451 | } | ||
| @@ -203,6 +203,7 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) · 202 unmodified lines | |||
| 203 | lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode); | 203 | lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode); |
| 204 | lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc); | 204 | lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc); |
| 205 | lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg); | 205 | lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg); |
| 206 | lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock); | ||
| 206 | lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task); | 207 | lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task); |
| 207 | } | 208 | } |
| 208 | 209 | ||
| @@ -333,12 +334,13 @@ static void __init ordered_lsm_init(void) · 124 unmodified lines | |||
| 333 | for (lsm = ordered_lsms; *lsm; lsm++) | 334 | for (lsm = ordered_lsms; *lsm; lsm++) |
| 334 | prepare_lsm(*lsm); | 335 | prepare_lsm(*lsm); |
| 335 | 336 | ||
| 336 | init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); | 337 | init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); |
| 337 | init_debug("file blob size = %d\n", blob_sizes.lbs_file); | 338 | init_debug("file blob size = %d\n", blob_sizes.lbs_file); |
| 338 | init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); | 339 | init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); |
| 339 | init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); | 340 | init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); |
| 340 | init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); | 341 | init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); |
| 341 | init_debug("task blob size = %d\n", blob_sizes.lbs_task); | 342 | init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock); |
| 343 | init_debug("task blob size = %d\n", blob_sizes.lbs_task); | ||
| 342 | 344 | ||
| 343 | /* | 345 | /* |
| 344 | * Create any kmem_caches needed for blobs | 346 | * Create any kmem_caches needed for blobs |
| @@ -670,6 +672,27 @@ static void __init lsm_early_task(struct task_struct *task) · 325 unmodified lines | |||
| 670 | panic("%s: Early task alloc failed.\n", __func__); | 672 | panic("%s: Early task alloc failed.\n", __func__); |
| 671 | } | 673 | } |
| 672 | 674 | ||
| 675 | /** | ||
| 676 | * lsm_superblock_alloc - allocate a composite superblock blob | ||
| 677 | * @sb: the superblock that needs a blob | ||
| 678 | * | ||
| 679 | * Allocate the superblock blob for all the modules | ||
| 680 | * | ||
| 681 | * Returns 0, or -ENOMEM if memory can't be allocated. | ||
| 682 | */ | ||
| 683 | static int lsm_superblock_alloc(struct super_block *sb) | ||
| 684 | { | ||
| 685 | if (blob_sizes.lbs_superblock == 0) { | ||
| 686 | sb->s_security = NULL; | ||
| 687 | return 0; | ||
| 688 | } | ||
| 689 | |||
| 690 | sb->s_security = kzalloc(blob_sizes.lbs_superblock, GFP_KERNEL); | ||
| 691 | if (sb->s_security == NULL) | ||
| 692 | return -ENOMEM; | ||
| 693 | return 0; | ||
| 694 | } | ||
| 695 | |||
| 673 | /* | 696 | /* |
| 674 | * The default value of the LSM hook is defined in linux/lsm_hook_defs.h and | 697 | * The default value of the LSM hook is defined in linux/lsm_hook_defs.h and |
| 675 | * can be accessed with: | 698 | * can be accessed with: |
| @@ -867,12 +890,26 @@ int security_fs_context_parse_param(struct fs_context *fc, struct fs_parameter * · 191 unmodified lines | |||
| 867 | 890 | ||
| 868 | int security_sb_alloc(struct super_block *sb) | 891 | int security_sb_alloc(struct super_block *sb) |
| 869 | { | 892 | { |
| 870 | return call_int_hook(sb_alloc_security, 0, sb); | 893 | int rc = lsm_superblock_alloc(sb); |
| 894 | |||
| 895 | if (unlikely(rc)) | ||
| 896 | return rc; | ||
| 897 | rc = call_int_hook(sb_alloc_security, 0, sb); | ||
| 898 | if (unlikely(rc)) | ||
| 899 | security_sb_free(sb); | ||
| 900 | return rc; | ||
| 901 | } | ||
| 902 | |||
| 903 | void security_sb_delete(struct super_block *sb) | ||
| 904 | { | ||
| 905 | call_void_hook(sb_delete, sb); | ||
| 871 | } | 906 | } |
| 872 | 907 | ||
| 873 | void security_sb_free(struct super_block *sb) | 908 | void security_sb_free(struct super_block *sb) |
| 874 | { | 909 | { |
| 875 | call_void_hook(sb_free_security, sb); | 910 | call_void_hook(sb_free_security, sb); |
| 911 | kfree(sb->s_security); | ||
| 912 | sb->s_security = NULL; | ||
| 876 | } | 913 | } |
| 877 | 914 | ||
| 878 | void security_free_mnt_opts(void **mnt_opts) | 915 | void security_free_mnt_opts(void **mnt_opts) |
| @@ -358,7 +358,7 @@ static void inode_free_security(struct inode *inode) · 357 unmodified lines | |||
| 358 | 358 | ||
| 359 | if (!isec) | 359 | if (!isec) |
| 360 | return; | 360 | return; |
| 361 | sbsec = inode->i_sb->s_security; | 361 | sbsec = selinux_superblock(inode->i_sb); |
| 362 | /* | 362 | /* |
| 363 | * As not all inode security structures are in a list, we check for | 363 | * As not all inode security structures are in a list, we check for |
| 364 | * empty list outside of the lock to make sure that we won't waste | 364 | * empty list outside of the lock to make sure that we won't waste |
| @@ -376,13 +376,6 @@ static void inode_free_security(struct inode *inode) · 11 unmodified lines | |||
| 376 | } | 376 | } |
| 377 | } | 377 | } |
| 378 | 378 | ||
| 379 | static void superblock_free_security(struct super_block *sb) | ||
| 380 | { | ||
| 381 | struct superblock_security_struct *sbsec = sb->s_security; | ||
| 382 | sb->s_security = NULL; | ||
| 383 | kfree(sbsec); | ||
| 384 | } | ||
| 385 | |||
| 386 | struct selinux_mnt_opts { | 379 | struct selinux_mnt_opts { |
| 387 | const char *fscontext, *context, *rootcontext, *defcontext; | 380 | const char *fscontext, *context, *rootcontext, *defcontext; |
| 388 | }; | 381 | }; |
| @@ -494,7 +487,7 @@ static int selinux_is_genfs_special_handling(struct super_block *sb) · 105 unmodified lines | |||
| 494 | 487 | ||
| 495 | static int selinux_is_sblabel_mnt(struct super_block *sb) | 488 | static int selinux_is_sblabel_mnt(struct super_block *sb) |
| 496 | { | 489 | { |
| 497 | struct superblock_security_struct *sbsec = sb->s_security; | 490 | struct superblock_security_struct *sbsec = selinux_superblock(sb); |
| 498 | 491 | ||
| 499 | /* | 492 | /* |
| 500 | * IMPORTANT: Double-check logic in this function when adding a new | 493 | * IMPORTANT: Double-check logic in this function when adding a new |
| @@ -571,7 +564,7 @@ fallback: · 70 unmodified lines | |||
| 571 | 564 | ||
| 572 | static int sb_finish_set_opts(struct super_block *sb) | 565 | static int sb_finish_set_opts(struct super_block *sb) |
| 573 | { | 566 | { |
| 574 | struct superblock_security_struct *sbsec = sb->s_security; | 567 | struct superblock_security_struct *sbsec = selinux_superblock(sb); |
| 575 | struct dentry *root = sb->s_root; | 568 | struct dentry *root = sb->s_root; |
| 576 | struct inode *root_inode = d_backing_inode(root); | 569 | struct inode *root_inode = d_backing_inode(root); |
| 577 | int rc = 0; | 570 | int rc = 0; |
| @@ -662,7 +655,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, · 84 unmodified lines | |||
| 662 | unsigned long *set_kern_flags) | 655 | unsigned long *set_kern_flags) |
| 663 | { | 656 | { |
| 664 | const struct cred *cred = current_cred(); | 657 | const struct cred *cred = current_cred(); |
| 665 | struct superblock_security_struct *sbsec = sb->s_security; | 658 | struct superblock_security_struct *sbsec = selinux_superblock(sb); |
| 666 | struct dentry *root = sb->s_root; | 659 | struct dentry *root = sb->s_root; |
| 667 | struct selinux_mnt_opts *opts = mnt_opts; | 660 | struct selinux_mnt_opts *opts = mnt_opts; |
| 668 | struct inode_security_struct *root_isec; | 661 | struct inode_security_struct *root_isec; |
| @@ -900,8 +893,8 @@ out_double_mount: · 231 unmodified lines | |||
| 900 | static int selinux_cmp_sb_context(const struct super_block *oldsb, | 893 | static int selinux_cmp_sb_context(const struct super_block *oldsb, |
| 901 | const struct super_block *newsb) | 894 | const struct super_block *newsb) |
| 902 | { | 895 | { |
| 903 | struct superblock_security_struct *old = oldsb->s_security; | 896 | struct superblock_security_struct *old = selinux_superblock(oldsb); |
| 904 | struct superblock_security_struct *new = newsb->s_security; | 897 | struct superblock_security_struct *new = selinux_superblock(newsb); |
| 905 | char oldflags = old->flags & SE_MNTMASK; | 898 | char oldflags = old->flags & SE_MNTMASK; |
| 906 | char newflags = new->flags & SE_MNTMASK; | 899 | char newflags = new->flags & SE_MNTMASK; |
| 907 | 900 | ||
| @@ -933,8 +926,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, · 25 unmodified lines | |||
| 933 | unsigned long *set_kern_flags) | 926 | unsigned long *set_kern_flags) |
| 934 | { | 927 | { |
| 935 | int rc = 0; | 928 | int rc = 0; |
| 936 | const struct superblock_security_struct *oldsbsec = oldsb->s_security; | 929 | const struct superblock_security_struct *oldsbsec = |
| 937 | struct superblock_security_struct *newsbsec = newsb->s_security; | 930 | selinux_superblock(oldsb); |
| 931 | struct superblock_security_struct *newsbsec = selinux_superblock(newsb); | ||
| 938 | 932 | ||
| 939 | int set_fscontext = (oldsbsec->flags & FSCONTEXT_MNT); | 933 | int set_fscontext = (oldsbsec->flags & FSCONTEXT_MNT); |
| 940 | int set_context = (oldsbsec->flags & CONTEXT_MNT); | 934 | int set_context = (oldsbsec->flags & CONTEXT_MNT); |
| @@ -1113,7 +1107,7 @@ static int show_sid(struct seq_file *m, u32 sid) · 172 unmodified lines | |||
| 1113 | 1107 | ||
| 1114 | static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) | 1108 | static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) |
| 1115 | { | 1109 | { |
| 1116 | struct superblock_security_struct *sbsec = sb->s_security; | 1110 | struct superblock_security_struct *sbsec = selinux_superblock(sb); |
| 1117 | int rc; | 1111 | int rc; |
| 1118 | 1112 | ||
| 1119 | if (!(sbsec->flags & SE_SBINITIALIZED)) | 1113 | if (!(sbsec->flags & SE_SBINITIALIZED)) |
| @@ -1464,7 +1458,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent · 344 unmodified lines | |||
| 1464 | if (isec->sclass == SECCLASS_FILE) | 1458 | if (isec->sclass == SECCLASS_FILE) |
| 1465 | isec->sclass = inode_mode_to_security_class(inode->i_mode); | 1459 | isec->sclass = inode_mode_to_security_class(inode->i_mode); |
| 1466 | 1460 | ||
| 1467 | sbsec = inode->i_sb->s_security; | 1461 | sbsec = selinux_superblock(inode->i_sb); |
| 1468 | if (!(sbsec->flags & SE_SBINITIALIZED)) { | 1462 | if (!(sbsec->flags & SE_SBINITIALIZED)) { |
| 1469 | /* Defer initialization until selinux_complete_init, | 1463 | /* Defer initialization until selinux_complete_init, |
| 1470 | after the initial policy is loaded and the security | 1464 | after the initial policy is loaded and the security |
| @@ -1815,7 +1809,8 @@ selinux_determine_inode_label(const struct task_security_struct *tsec, · 344 unmodified lines | |||
| 1815 | const struct qstr *name, u16 tclass, | 1809 | const struct qstr *name, u16 tclass, |
| 1816 | u32 *_new_isid) | 1810 | u32 *_new_isid) |
| 1817 | { | 1811 | { |
| 1818 | const struct superblock_security_struct *sbsec = dir->i_sb->s_security; | 1812 | const struct superblock_security_struct *sbsec = |
| 1813 | selinux_superblock(dir->i_sb); | ||
| 1819 | 1814 | ||
| 1820 | if ((sbsec->flags & SE_SBINITIALIZED) && | 1815 | if ((sbsec->flags & SE_SBINITIALIZED) && |
| 1821 | (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { | 1816 | (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { |
| @@ -1846,7 +1841,7 @@ static int may_create(struct inode *dir, · 24 unmodified lines | |||
| 1846 | int rc; | 1841 | int rc; |
| 1847 | 1842 | ||
| 1848 | dsec = inode_security(dir); | 1843 | dsec = inode_security(dir); |
| 1849 | sbsec = dir->i_sb->s_security; | 1844 | sbsec = selinux_superblock(dir->i_sb); |
| 1850 | 1845 | ||
| 1851 | sid = tsec->sid; | 1846 | sid = tsec->sid; |
| 1852 | 1847 | ||
| @@ -1995,7 +1990,7 @@ static int superblock_has_perm(const struct cred *cred, · 142 unmodified lines | |||
| 1995 | struct superblock_security_struct *sbsec; | 1990 | struct superblock_security_struct *sbsec; |
| 1996 | u32 sid = cred_sid(cred); | 1991 | u32 sid = cred_sid(cred); |
| 1997 | 1992 | ||
| 1998 | sbsec = sb->s_security; | 1993 | sbsec = selinux_superblock(sb); |
| 1999 | return avc_has_perm(&selinux_state, | 1994 | return avc_has_perm(&selinux_state, |
| 2000 | sid, sbsec->sid, SECCLASS_FILESYSTEM, perms, ad); | 1995 | sid, sbsec->sid, SECCLASS_FILESYSTEM, perms, ad); |
| 2001 | } | 1996 | } |
| @@ -2617,11 +2612,7 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) · 615 unmodified lines | |||
| 2617 | 2612 | ||
| 2618 | static int selinux_sb_alloc_security(struct super_block *sb) | 2613 | static int selinux_sb_alloc_security(struct super_block *sb) |
| 2619 | { | 2614 | { |
| 2620 | struct superblock_security_struct *sbsec; | 2615 | struct superblock_security_struct *sbsec = selinux_superblock(sb); |
| 2621 | |||
| 2622 | sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); | ||
| 2623 | if (!sbsec) | ||
| 2624 | return -ENOMEM; | ||
| 2625 | 2616 | ||
| 2626 | mutex_init(&sbsec->lock); | 2617 | mutex_init(&sbsec->lock); |
| 2627 | INIT_LIST_HEAD(&sbsec->isec_head); | 2618 | INIT_LIST_HEAD(&sbsec->isec_head); |
| @@ -2629,16 +2620,10 @@ static int selinux_sb_alloc_security(struct super_block *sb) · 1 unmodified line | |||
| 2629 | sbsec->sid = SECINITSID_UNLABELED; | 2620 | sbsec->sid = SECINITSID_UNLABELED; |
| 2630 | sbsec->def_sid = SECINITSID_FILE; | 2621 | sbsec->def_sid = SECINITSID_FILE; |
| 2631 | sbsec->mntpoint_sid = SECINITSID_UNLABELED; | 2622 | sbsec->mntpoint_sid = SECINITSID_UNLABELED; |
| 2632 | sb->s_security = sbsec; | ||
| 2633 | 2623 | ||
| 2634 | return 0; | 2624 | return 0; |
| 2635 | } | 2625 | } |
| 2636 | 2626 | ||
| 2637 | static void selinux_sb_free_security(struct super_block *sb) | ||
| 2638 | { | ||
| 2639 | superblock_free_security(sb); | ||
| 2640 | } | ||
| 2641 | |||
| 2642 | static inline int opt_len(const char *s) | 2627 | static inline int opt_len(const char *s) |
| 2643 | { | 2628 | { |
| 2644 | bool open_quote = false; | 2629 | bool open_quote = false; |
| @@ -2772,7 +2757,7 @@ static int selinux_sb_mnt_opts_compat(struct super_block *sb, void *mnt_opts) · 127 unmodified lines | |||
| 2772 | static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) | 2757 | static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) |
| 2773 | { | 2758 | { |
| 2774 | struct selinux_mnt_opts *opts = mnt_opts; | 2759 | struct selinux_mnt_opts *opts = mnt_opts; |
| 2775 | struct superblock_security_struct *sbsec = sb->s_security; | 2760 | struct superblock_security_struct *sbsec = selinux_superblock(sb); |
| 2776 | u32 sid; | 2761 | u32 sid; |
| 2777 | int rc; | 2762 | int rc; |
| 2778 | 2763 | ||
| @@ -3010,7 +2995,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, · 231 unmodified lines | |||
| 3010 | int rc; | 2995 | int rc; |
| 3011 | char *context; | 2996 | char *context; |
| 3012 | 2997 | ||
| 3013 | sbsec = dir->i_sb->s_security; | 2998 | sbsec = selinux_superblock(dir->i_sb); |
| 3014 | 2999 | ||
| 3015 | newsid = tsec->create_sid; | 3000 | newsid = tsec->create_sid; |
| 3016 | 3001 | ||
| @@ -3312,7 +3297,7 @@ static int selinux_inode_setxattr(struct user_namespace *mnt_userns, · 295 unmodified lines | |||
| 3312 | if (!selinux_initialized(&selinux_state)) | 3297 | if (!selinux_initialized(&selinux_state)) |
| 3313 | return (inode_owner_or_capable(mnt_userns, inode) ? 0 : -EPERM); | 3298 | return (inode_owner_or_capable(mnt_userns, inode) ? 0 : -EPERM); |
| 3314 | 3299 | ||
| 3315 | sbsec = inode->i_sb->s_security; | 3300 | sbsec = selinux_superblock(inode->i_sb); |
| 3316 | if (!(sbsec->flags & SBLABEL_MNT)) | 3301 | if (!(sbsec->flags & SBLABEL_MNT)) |
| 3317 | return -EOPNOTSUPP; | 3302 | return -EOPNOTSUPP; |
| 3318 | 3303 | ||
| @@ -3557,13 +3542,14 @@ static int selinux_inode_setsecurity(struct inode *inode, const char *name, · 238 unmodified lines | |||
| 3557 | const void *value, size_t size, int flags) | 3542 | const void *value, size_t size, int flags) |
| 3558 | { | 3543 | { |
| 3559 | struct inode_security_struct *isec = inode_security_novalidate(inode); | 3544 | struct inode_security_struct *isec = inode_security_novalidate(inode); |
| 3560 | struct superblock_security_struct *sbsec = inode->i_sb->s_security; | 3545 | struct superblock_security_struct *sbsec; |
| 3561 | u32 newsid; | 3546 | u32 newsid; |
| 3562 | int rc; | 3547 | int rc; |
| 3563 | 3548 | ||
| 3564 | if (strcmp(name, XATTR_SELINUX_SUFFIX)) | 3549 | if (strcmp(name, XATTR_SELINUX_SUFFIX)) |
| 3565 | return -EOPNOTSUPP; | 3550 | return -EOPNOTSUPP; |
| 3566 | 3551 | ||
| 3552 | sbsec = selinux_superblock(inode->i_sb); | ||
| 3567 | if (!(sbsec->flags & SBLABEL_MNT)) | 3553 | if (!(sbsec->flags & SBLABEL_MNT)) |
| 3568 | return -EOPNOTSUPP; | 3554 | return -EOPNOTSUPP; |
| 3569 | 3555 | ||
| @@ -7065,6 +7051,7 @@ struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = { · 3495 unmodified lines | |||
| 7065 | .lbs_inode = sizeof(struct inode_security_struct), | 7051 | .lbs_inode = sizeof(struct inode_security_struct), |
| 7066 | .lbs_ipc = sizeof(struct ipc_security_struct), | 7052 | .lbs_ipc = sizeof(struct ipc_security_struct), |
| 7067 | .lbs_msg_msg = sizeof(struct msg_security_struct), | 7053 | .lbs_msg_msg = sizeof(struct msg_security_struct), |
| 7054 | .lbs_superblock = sizeof(struct superblock_security_struct), | ||
| 7068 | }; | 7055 | }; |
| 7069 | 7056 | ||
| 7070 | #ifdef CONFIG_PERF_EVENTS | 7057 | #ifdef CONFIG_PERF_EVENTS |
| @@ -7165,7 +7152,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { · 94 unmodified lines | |||
| 7165 | LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds), | 7152 | LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds), |
| 7166 | LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds), | 7153 | LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds), |
| 7167 | 7154 | ||
| 7168 | LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), | ||
| 7169 | LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts), | 7155 | LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts), |
| 7170 | LSM_HOOK_INIT(sb_mnt_opts_compat, selinux_sb_mnt_opts_compat), | 7156 | LSM_HOOK_INIT(sb_mnt_opts_compat, selinux_sb_mnt_opts_compat), |
| 7171 | LSM_HOOK_INIT(sb_remount, selinux_sb_remount), | 7157 | LSM_HOOK_INIT(sb_remount, selinux_sb_remount), |
| @@ -188,4 +188,10 @@ static inline u32 current_sid(void) · 187 unmodified lines | |||
| 188 | return tsec->sid; | 188 | return tsec->sid; |
| 189 | } | 189 | } |
| 190 | 190 | ||
| 191 | static inline struct superblock_security_struct *selinux_superblock( | ||
| 192 | const struct super_block *superblock) | ||
| 193 | { | ||
| 194 | return superblock->s_security + selinux_blob_sizes.lbs_superblock; | ||
| 195 | } | ||
| 196 | |||
| 191 | #endif /* _SELINUX_OBJSEC_H_ */ | 197 | #endif /* _SELINUX_OBJSEC_H_ */ |
| @@ -47,6 +47,7 @@ · 46 unmodified lines | |||
| 47 | #include <linux/sched.h> | 47 | #include <linux/sched.h> |
| 48 | #include <linux/audit.h> | 48 | #include <linux/audit.h> |
| 49 | #include <linux/vmalloc.h> | 49 | #include <linux/vmalloc.h> |
| 50 | #include <linux/lsm_hooks.h> | ||
| 50 | #include <net/netlabel.h> | 51 | #include <net/netlabel.h> |
| 51 | 52 | ||
| 52 | #include "flask.h" | 53 | #include "flask.h" |
| @@ -2955,7 +2956,7 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) · 2902 unmodified lines | |||
| 2955 | struct sidtab *sidtab; | 2956 | struct sidtab *sidtab; |
| 2956 | int rc; | 2957 | int rc; |
| 2957 | struct ocontext *c; | 2958 | struct ocontext *c; |
| 2958 | struct superblock_security_struct *sbsec = sb->s_security; | 2959 | struct superblock_security_struct *sbsec = selinux_superblock(sb); |
| 2959 | const char *fstype = sb->s_type->name; | 2960 | const char *fstype = sb->s_type->name; |
| 2960 | 2961 | ||
| 2961 | if (!selinux_initialized(state)) { | 2962 | if (!selinux_initialized(state)) { |
| @@ -357,6 +357,12 @@ static inline struct smack_known **smack_ipc(const struct kern_ipc_perm *ipc) · 356 unmodified lines | |||
| 357 | return ipc->security + smack_blob_sizes.lbs_ipc; | 357 | return ipc->security + smack_blob_sizes.lbs_ipc; |
| 358 | } | 358 | } |
| 359 | 359 | ||
| 360 | static inline struct superblock_smack *smack_superblock( | ||
| 361 | const struct super_block *superblock) | ||
| 362 | { | ||
| 363 | return superblock->s_security + smack_blob_sizes.lbs_superblock; | ||
| 364 | } | ||
| 365 | |||
| 360 | /* | 366 | /* |
| 361 | * Is the directory transmuting? | 367 | * Is the directory transmuting? |
| 362 | */ | 368 | */ |
| @@ -535,12 +535,7 @@ static int smack_syslog(int typefrom_file) · 534 unmodified lines | |||
| 535 | */ | 535 | */ |
| 536 | static int smack_sb_alloc_security(struct super_block *sb) | 536 | static int smack_sb_alloc_security(struct super_block *sb) |
| 537 | { | 537 | { |
| 538 | struct superblock_smack *sbsp; | 538 | struct superblock_smack *sbsp = smack_superblock(sb); |
| 539 | |||
| 540 | sbsp = kzalloc(sizeof(struct superblock_smack), GFP_KERNEL); | ||
| 541 | |||
| 542 | if (sbsp == NULL) | ||
| 543 | return -ENOMEM; | ||
| 544 | 539 | ||
| 545 | sbsp->smk_root = &smack_known_floor; | 540 | sbsp->smk_root = &smack_known_floor; |
| 546 | sbsp->smk_default = &smack_known_floor; | 541 | sbsp->smk_default = &smack_known_floor; |
| @@ -549,22 +544,10 @@ static int smack_sb_alloc_security(struct super_block *sb) · 2 unmodified lines | |||
| 549 | /* | 544 | /* |
| 550 | * SMK_SB_INITIALIZED will be zero from kzalloc. | 545 | * SMK_SB_INITIALIZED will be zero from kzalloc. |
| 551 | */ | 546 | */ |
| 552 | sb->s_security = sbsp; | ||
| 553 | 547 | ||
| 554 | return 0; | 548 | return 0; |
| 555 | } | 549 | } |
| 556 | 550 | ||
| 557 | /** | ||
| 558 | * smack_sb_free_security - free a superblock blob | ||
| 559 | * @sb: the superblock getting the blob | ||
| 560 | * | ||
| 561 | */ | ||
| 562 | static void smack_sb_free_security(struct super_block *sb) | ||
| 563 | { | ||
| 564 | kfree(sb->s_security); | ||
| 565 | sb->s_security = NULL; | ||
| 566 | } | ||
| 567 | |||
| 568 | struct smack_mnt_opts { | 551 | struct smack_mnt_opts { |
| 569 | const char *fsdefault, *fsfloor, *fshat, *fsroot, *fstransmute; | 552 | const char *fsdefault, *fsfloor, *fshat, *fsroot, *fstransmute; |
| 570 | }; | 553 | }; |
| @@ -772,7 +755,7 @@ static int smack_set_mnt_opts(struct super_block *sb, · 201 unmodified lines | |||
| 772 | { | 755 | { |
| 773 | struct dentry *root = sb->s_root; | 756 | struct dentry *root = sb->s_root; |
| 774 | struct inode *inode = d_backing_inode(root); | 757 | struct inode *inode = d_backing_inode(root); |
| 775 | struct superblock_smack *sp = sb->s_security; | 758 | struct superblock_smack *sp = smack_superblock(sb); |
| 776 | struct inode_smack *isp; | 759 | struct inode_smack *isp; |
| 777 | struct smack_known *skp; | 760 | struct smack_known *skp; |
| 778 | struct smack_mnt_opts *opts = mnt_opts; | 761 | struct smack_mnt_opts *opts = mnt_opts; |
| @@ -871,7 +854,7 @@ static int smack_set_mnt_opts(struct super_block *sb, · 92 unmodified lines | |||
| 871 | */ | 854 | */ |
| 872 | static int smack_sb_statfs(struct dentry *dentry) | 855 | static int smack_sb_statfs(struct dentry *dentry) |
| 873 | { | 856 | { |
| 874 | struct superblock_smack *sbp = dentry->d_sb->s_security; | 857 | struct superblock_smack *sbp = smack_superblock(dentry->d_sb); |
| 875 | int rc; | 858 | int rc; |
| 876 | struct smk_audit_info ad; | 859 | struct smk_audit_info ad; |
| 877 | 860 | ||
| @@ -905,7 +888,7 @@ static int smack_bprm_creds_for_exec(struct linux_binprm *bprm) · 27 unmodified lines | |||
| 905 | if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task) | 888 | if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task) |
| 906 | return 0; | 889 | return 0; |
| 907 | 890 | ||
| 908 | sbsp = inode->i_sb->s_security; | 891 | sbsp = smack_superblock(inode->i_sb); |
| 909 | if ((sbsp->smk_flags & SMK_SB_UNTRUSTED) && | 892 | if ((sbsp->smk_flags & SMK_SB_UNTRUSTED) && |
| 910 | isp->smk_task != sbsp->smk_root) | 893 | isp->smk_task != sbsp->smk_root) |
| 911 | return 0; | 894 | return 0; |
| @@ -1157,7 +1140,7 @@ static int smack_inode_rename(struct inode *old_inode, · 245 unmodified lines | |||
| 1157 | */ | 1140 | */ |
| 1158 | static int smack_inode_permission(struct inode *inode, int mask) | 1141 | static int smack_inode_permission(struct inode *inode, int mask) |
| 1159 | { | 1142 | { |
| 1160 | struct superblock_smack *sbsp = inode->i_sb->s_security; | 1143 | struct superblock_smack *sbsp = smack_superblock(inode->i_sb); |
| 1161 | struct smk_audit_info ad; | 1144 | struct smk_audit_info ad; |
| 1162 | int no_block = mask & MAY_NOT_BLOCK; | 1145 | int no_block = mask & MAY_NOT_BLOCK; |
| 1163 | int rc; | 1146 | int rc; |
| @@ -1400,7 +1383,7 @@ static int smack_inode_removexattr(struct user_namespace *mnt_userns, · 236 unmodified lines | |||
| 1400 | */ | 1383 | */ |
| 1401 | if (strcmp(name, XATTR_NAME_SMACK) == 0) { | 1384 | if (strcmp(name, XATTR_NAME_SMACK) == 0) { |
| 1402 | struct super_block *sbp = dentry->d_sb; | 1385 | struct super_block *sbp = dentry->d_sb; |
| 1403 | struct superblock_smack *sbsp = sbp->s_security; | 1386 | struct superblock_smack *sbsp = smack_superblock(sbp); |
| 1404 | 1387 | ||
| 1405 | isp->smk_inode = sbsp->smk_default; | 1388 | isp->smk_inode = sbsp->smk_default; |
| 1406 | } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) | 1389 | } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) |
| @@ -1670,7 +1653,7 @@ static int smack_mmap_file(struct file *file, · 263 unmodified lines | |||
| 1670 | isp = smack_inode(file_inode(file)); | 1653 | isp = smack_inode(file_inode(file)); |
| 1671 | if (isp->smk_mmap == NULL) | 1654 | if (isp->smk_mmap == NULL) |
| 1672 | return 0; | 1655 | return 0; |
| 1673 | sbsp = file_inode(file)->i_sb->s_security; | 1656 | sbsp = smack_superblock(file_inode(file)->i_sb); |
| 1674 | if (sbsp->smk_flags & SMK_SB_UNTRUSTED && | 1657 | if (sbsp->smk_flags & SMK_SB_UNTRUSTED && |
| 1675 | isp->smk_mmap != sbsp->smk_root) | 1658 | isp->smk_mmap != sbsp->smk_root) |
| 1676 | return -EACCES; | 1659 | return -EACCES; |
| @@ -3299,7 +3282,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) · 1622 unmodified lines | |||
| 3299 | return; | 3282 | return; |
| 3300 | 3283 | ||
| 3301 | sbp = inode->i_sb; | 3284 | sbp = inode->i_sb; |
| 3302 | sbsp = sbp->s_security; | 3285 | sbsp = smack_superblock(sbp); |
| 3303 | /* | 3286 | /* |
| 3304 | * We're going to use the superblock default label | 3287 | * We're going to use the superblock default label |
| 3305 | * if there's no label on the file. | 3288 | * if there's no label on the file. |
| @@ -4714,6 +4697,7 @@ struct lsm_blob_sizes smack_blob_sizes __lsm_ro_after_init = { · 1408 unmodified lines | |||
| 4714 | .lbs_inode = sizeof(struct inode_smack), | 4697 | .lbs_inode = sizeof(struct inode_smack), |
| 4715 | .lbs_ipc = sizeof(struct smack_known *), | 4698 | .lbs_ipc = sizeof(struct smack_known *), |
| 4716 | .lbs_msg_msg = sizeof(struct smack_known *), | 4699 | .lbs_msg_msg = sizeof(struct smack_known *), |
| 4700 | .lbs_superblock = sizeof(struct superblock_smack), | ||
| 4717 | }; | 4701 | }; |
| 4718 | 4702 | ||
| 4719 | static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { | 4703 | static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { |
| @@ -4725,7 +4709,6 @@ static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { · 5 unmodified lines | |||
| 4725 | LSM_HOOK_INIT(fs_context_parse_param, smack_fs_context_parse_param), | 4709 | LSM_HOOK_INIT(fs_context_parse_param, smack_fs_context_parse_param), |
| 4726 | 4710 | ||
| 4727 | LSM_HOOK_INIT(sb_alloc_security, smack_sb_alloc_security), | 4711 | LSM_HOOK_INIT(sb_alloc_security, smack_sb_alloc_security), |
| 4728 | LSM_HOOK_INIT(sb_free_security, smack_sb_free_security), | ||
| 4729 | LSM_HOOK_INIT(sb_free_mnt_opts, smack_free_mnt_opts), | 4712 | LSM_HOOK_INIT(sb_free_mnt_opts, smack_free_mnt_opts), |
| 4730 | LSM_HOOK_INIT(sb_eat_lsm_opts, smack_sb_eat_lsm_opts), | 4713 | LSM_HOOK_INIT(sb_eat_lsm_opts, smack_sb_eat_lsm_opts), |
| 4731 | LSM_HOOK_INIT(sb_statfs, smack_sb_statfs), | 4714 | LSM_HOOK_INIT(sb_statfs, smack_sb_statfs), |
| @@ -25,6 +25,7 @@ TARGETS += ir · 24 unmodified lines | |||
| 25 | TARGETS += kcmp | 25 | TARGETS += kcmp |
| 26 | TARGETS += kexec | 26 | TARGETS += kexec |
| 27 | TARGETS += kvm | 27 | TARGETS += kvm |
| 28 | TARGETS += landlock | ||
| 28 | TARGETS += lib | 29 | TARGETS += lib |
| 29 | TARGETS += livepatch | 30 | TARGETS += livepatch |
| 30 | TARGETS += lkdtm | 31 | TARGETS += lkdtm |
| @@ -0,0 +1,2 @@ | |||
| 1 | /*_test | ||
| 2 | /true | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0 | ||
| 2 | |||
| 3 | CFLAGS += -Wall -O2 | ||
| 4 | |||
| 5 | src_test := $(wildcard *_test.c) | ||
| 6 | |||
| 7 | TEST_GEN_PROGS := $(src_test:.c=) | ||
| 8 | |||
| 9 | TEST_GEN_PROGS_EXTENDED := true | ||
| 10 | |||
| 11 | KSFT_KHDR_INSTALL := 1 | ||
| 12 | OVERRIDE_TARGETS := 1 | ||
| 13 | include ../lib.mk | ||
| 14 | |||
| 15 | khdr_dir = $(top_srcdir)/usr/include | ||
| 16 | |||
| 17 | $(khdr_dir)/linux/landlock.h: khdr | ||
| 18 | @: | ||
| 19 | |||
| 20 | $(OUTPUT)/true: true.c | ||
| 21 | $(LINK.c) $< $(LDLIBS) -o $@ -static | ||
| 22 | |||
| 23 | $(OUTPUT)/%_test: %_test.c $(khdr_dir)/linux/landlock.h ../kselftest_harness.h common.h | ||
| 24 | $(LINK.c) $< $(LDLIBS) -o $@ -lcap -I$(khdr_dir) | ||
| @@ -0,0 +1,266 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Landlock tests - Common user space base | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2019-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #define _GNU_SOURCE | ||
| 10 | #include <errno.h> | ||
| 11 | #include <fcntl.h> | ||
| 12 | #include <linux/landlock.h> | ||
| 13 | #include <string.h> | ||
| 14 | #include <sys/prctl.h> | ||
| 15 | #include <sys/socket.h> | ||
| 16 | #include <sys/types.h> | ||
| 17 | |||
| 18 | #include "common.h" | ||
| 19 | |||
| 20 | #ifndef O_PATH | ||
| 21 | #define O_PATH 010000000 | ||
| 22 | #endif | ||
| 23 | |||
| 24 | TEST(inconsistent_attr) { | ||
| 25 | const long page_size = sysconf(_SC_PAGESIZE); | ||
| 26 | char *const buf = malloc(page_size + 1); | ||
| 27 | struct landlock_ruleset_attr *const ruleset_attr = (void *)buf; | ||
| 28 | |||
| 29 | ASSERT_NE(NULL, buf); | ||
| 30 | |||
| 31 | /* Checks copy_from_user(). */ | ||
| 32 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 0, 0)); | ||
| 33 | /* The size if less than sizeof(struct landlock_attr_enforce). */ | ||
| 34 | ASSERT_EQ(EINVAL, errno); | ||
| 35 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 1, 0)); | ||
| 36 | ASSERT_EQ(EINVAL, errno); | ||
| 37 | |||
| 38 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, 1, 0)); | ||
| 39 | /* The size if less than sizeof(struct landlock_attr_enforce). */ | ||
| 40 | ASSERT_EQ(EFAULT, errno); | ||
| 41 | |||
| 42 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, | ||
| 43 | sizeof(struct landlock_ruleset_attr), 0)); | ||
| 44 | ASSERT_EQ(EFAULT, errno); | ||
| 45 | |||
| 46 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); | ||
| 47 | ASSERT_EQ(E2BIG, errno); | ||
| 48 | |||
| 49 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, | ||
| 50 | sizeof(struct landlock_ruleset_attr), 0)); | ||
| 51 | ASSERT_EQ(ENOMSG, errno); | ||
| 52 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); | ||
| 53 | ASSERT_EQ(ENOMSG, errno); | ||
| 54 | |||
| 55 | /* Checks non-zero value. */ | ||
| 56 | buf[page_size - 2] = '.'; | ||
| 57 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); | ||
| 58 | ASSERT_EQ(E2BIG, errno); | ||
| 59 | |||
| 60 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); | ||
| 61 | ASSERT_EQ(E2BIG, errno); | ||
| 62 | |||
| 63 | free(buf); | ||
| 64 | } | ||
| 65 | |||
| 66 | TEST(abi_version) { | ||
| 67 | const struct landlock_ruleset_attr ruleset_attr = { | ||
| 68 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 69 | }; | ||
| 70 | ASSERT_EQ(1, landlock_create_ruleset(NULL, 0, | ||
| 71 | LANDLOCK_CREATE_RULESET_VERSION)); | ||
| 72 | |||
| 73 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, | ||
| 74 | LANDLOCK_CREATE_RULESET_VERSION)); | ||
| 75 | ASSERT_EQ(EINVAL, errno); | ||
| 76 | |||
| 77 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr), | ||
| 78 | LANDLOCK_CREATE_RULESET_VERSION)); | ||
| 79 | ASSERT_EQ(EINVAL, errno); | ||
| 80 | |||
| 81 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, | ||
| 82 | sizeof(ruleset_attr), | ||
| 83 | LANDLOCK_CREATE_RULESET_VERSION)); | ||
| 84 | ASSERT_EQ(EINVAL, errno); | ||
| 85 | |||
| 86 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, | ||
| 87 | LANDLOCK_CREATE_RULESET_VERSION | 1 << 31)); | ||
| 88 | ASSERT_EQ(EINVAL, errno); | ||
| 89 | } | ||
| 90 | |||
| 91 | TEST(inval_create_ruleset_flags) { | ||
| 92 | const int last_flag = LANDLOCK_CREATE_RULESET_VERSION; | ||
| 93 | const int invalid_flag = last_flag << 1; | ||
| 94 | const struct landlock_ruleset_attr ruleset_attr = { | ||
| 95 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 96 | }; | ||
| 97 | |||
| 98 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, invalid_flag)); | ||
| 99 | ASSERT_EQ(EINVAL, errno); | ||
| 100 | |||
| 101 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, invalid_flag)); | ||
| 102 | ASSERT_EQ(EINVAL, errno); | ||
| 103 | |||
| 104 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr), | ||
| 105 | invalid_flag)); | ||
| 106 | ASSERT_EQ(EINVAL, errno); | ||
| 107 | |||
| 108 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, | ||
| 109 | sizeof(ruleset_attr), invalid_flag)); | ||
| 110 | ASSERT_EQ(EINVAL, errno); | ||
| 111 | } | ||
| 112 | |||
| 113 | TEST(empty_path_beneath_attr) { | ||
| 114 | const struct landlock_ruleset_attr ruleset_attr = { | ||
| 115 | .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, | ||
| 116 | }; | ||
| 117 | const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 118 | sizeof(ruleset_attr), 0); | ||
| 119 | |||
| 120 | ASSERT_LE(0, ruleset_fd); | ||
| 121 | |||
| 122 | /* Similar to struct landlock_path_beneath_attr.parent_fd = 0 */ | ||
| 123 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 124 | NULL, 0)); | ||
| 125 | ASSERT_EQ(EFAULT, errno); | ||
| 126 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 127 | } | ||
| 128 | |||
| 129 | TEST(inval_fd_enforce) { | ||
| 130 | ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); | ||
| 131 | |||
| 132 | ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); | ||
| 133 | ASSERT_EQ(EBADF, errno); | ||
| 134 | } | ||
| 135 | |||
| 136 | TEST(unpriv_enforce_without_no_new_privs) { | ||
| 137 | int err; | ||
| 138 | |||
| 139 | drop_caps(_metadata); | ||
| 140 | err = landlock_restrict_self(-1, 0); | ||
| 141 | ASSERT_EQ(EPERM, errno); | ||
| 142 | ASSERT_EQ(err, -1); | ||
| 143 | } | ||
| 144 | |||
| 145 | TEST(ruleset_fd_io) | ||
| 146 | { | ||
| 147 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 148 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 149 | }; | ||
| 150 | int ruleset_fd; | ||
| 151 | char buf; | ||
| 152 | |||
| 153 | drop_caps(_metadata); | ||
| 154 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 155 | sizeof(ruleset_attr), 0); | ||
| 156 | ASSERT_LE(0, ruleset_fd); | ||
| 157 | |||
| 158 | ASSERT_EQ(-1, write(ruleset_fd, ".", 1)); | ||
| 159 | ASSERT_EQ(EINVAL, errno); | ||
| 160 | ASSERT_EQ(-1, read(ruleset_fd, &buf, 1)); | ||
| 161 | ASSERT_EQ(EINVAL, errno); | ||
| 162 | |||
| 163 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 164 | } | ||
| 165 | |||
| 166 | /* Tests enforcement of a ruleset FD transferred through a UNIX socket. */ | ||
| 167 | TEST(ruleset_fd_transfer) | ||
| 168 | { | ||
| 169 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 170 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, | ||
| 171 | }; | ||
| 172 | struct landlock_path_beneath_attr path_beneath_attr = { | ||
| 173 | .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, | ||
| 174 | }; | ||
| 175 | int ruleset_fd_tx, dir_fd; | ||
| 176 | union { | ||
| 177 | /* Aligned ancillary data buffer. */ | ||
| 178 | char buf[CMSG_SPACE(sizeof(ruleset_fd_tx))]; | ||
| 179 | struct cmsghdr _align; | ||
| 180 | } cmsg_tx = {}; | ||
| 181 | char data_tx = '.'; | ||
| 182 | struct iovec io = { | ||
| 183 | .iov_base = &data_tx, | ||
| 184 | .iov_len = sizeof(data_tx), | ||
| 185 | }; | ||
| 186 | struct msghdr msg = { | ||
| 187 | .msg_iov = &io, | ||
| 188 | .msg_iovlen = 1, | ||
| 189 | .msg_control = &cmsg_tx.buf, | ||
| 190 | .msg_controllen = sizeof(cmsg_tx.buf), | ||
| 191 | }; | ||
| 192 | struct cmsghdr *cmsg; | ||
| 193 | int socket_fds[2]; | ||
| 194 | pid_t child; | ||
| 195 | int status; | ||
| 196 | |||
| 197 | drop_caps(_metadata); | ||
| 198 | |||
| 199 | /* Creates a test ruleset with a simple rule. */ | ||
| 200 | ruleset_fd_tx = landlock_create_ruleset(&ruleset_attr, | ||
| 201 | sizeof(ruleset_attr), 0); | ||
| 202 | ASSERT_LE(0, ruleset_fd_tx); | ||
| 203 | path_beneath_attr.parent_fd = open("/tmp", O_PATH | O_NOFOLLOW | | ||
| 204 | O_DIRECTORY | O_CLOEXEC); | ||
| 205 | ASSERT_LE(0, path_beneath_attr.parent_fd); | ||
| 206 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd_tx, LANDLOCK_RULE_PATH_BENEATH, | ||
| 207 | &path_beneath_attr, 0)); | ||
| 208 | ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); | ||
| 209 | |||
| 210 | cmsg = CMSG_FIRSTHDR(&msg); | ||
| 211 | ASSERT_NE(NULL, cmsg); | ||
| 212 | cmsg->cmsg_len = CMSG_LEN(sizeof(ruleset_fd_tx)); | ||
| 213 | cmsg->cmsg_level = SOL_SOCKET; | ||
| 214 | cmsg->cmsg_type = SCM_RIGHTS; | ||
| 215 | memcpy(CMSG_DATA(cmsg), &ruleset_fd_tx, sizeof(ruleset_fd_tx)); | ||
| 216 | |||
| 217 | /* Sends the ruleset FD over a socketpair and then close it. */ | ||
| 218 | ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds)); | ||
| 219 | ASSERT_EQ(sizeof(data_tx), sendmsg(socket_fds[0], &msg, 0)); | ||
| 220 | ASSERT_EQ(0, close(socket_fds[0])); | ||
| 221 | ASSERT_EQ(0, close(ruleset_fd_tx)); | ||
| 222 | |||
| 223 | child = fork(); | ||
| 224 | ASSERT_LE(0, child); | ||
| 225 | if (child == 0) { | ||
| 226 | int ruleset_fd_rx; | ||
| 227 | |||
| 228 | *(char *)msg.msg_iov->iov_base = '\0'; | ||
| 229 | ASSERT_EQ(sizeof(data_tx), recvmsg(socket_fds[1], &msg, MSG_CMSG_CLOEXEC)); | ||
| 230 | ASSERT_EQ('.', *(char *)msg.msg_iov->iov_base); | ||
| 231 | ASSERT_EQ(0, close(socket_fds[1])); | ||
| 232 | cmsg = CMSG_FIRSTHDR(&msg); | ||
| 233 | ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(ruleset_fd_tx))); | ||
| 234 | memcpy(&ruleset_fd_rx, CMSG_DATA(cmsg), sizeof(ruleset_fd_tx)); | ||
| 235 | |||
| 236 | /* Enforces the received ruleset on the child. */ | ||
| 237 | ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); | ||
| 238 | ASSERT_EQ(0, landlock_restrict_self(ruleset_fd_rx, 0)); | ||
| 239 | ASSERT_EQ(0, close(ruleset_fd_rx)); | ||
| 240 | |||
| 241 | /* Checks that the ruleset enforcement. */ | ||
| 242 | ASSERT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC)); | ||
| 243 | ASSERT_EQ(EACCES, errno); | ||
| 244 | dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC); | ||
| 245 | ASSERT_LE(0, dir_fd); | ||
| 246 | ASSERT_EQ(0, close(dir_fd)); | ||
| 247 | _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); | ||
| 248 | return; | ||
| 249 | } | ||
| 250 | |||
| 251 | ASSERT_EQ(0, close(socket_fds[1])); | ||
| 252 | |||
| 253 | /* Checks that the parent is unrestricted. */ | ||
| 254 | dir_fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); | ||
| 255 | ASSERT_LE(0, dir_fd); | ||
| 256 | ASSERT_EQ(0, close(dir_fd)); | ||
| 257 | dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC); | ||
| 258 | ASSERT_LE(0, dir_fd); | ||
| 259 | ASSERT_EQ(0, close(dir_fd)); | ||
| 260 | |||
| 261 | ASSERT_EQ(child, waitpid(child, &status, 0)); | ||
| 262 | ASSERT_EQ(1, WIFEXITED(status)); | ||
| 263 | ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); | ||
| 264 | } | ||
| 265 | |||
| 266 | TEST_HARNESS_MAIN | ||
| @@ -0,0 +1,183 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0 */ | ||
| 2 | /* | ||
| 3 | * Landlock test helpers | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2019-2020 ANSSI | ||
| 7 | * Copyright © 2021 Microsoft Corporation | ||
| 8 | */ | ||
| 9 | |||
| 10 | #include <errno.h> | ||
| 11 | #include <linux/landlock.h> | ||
| 12 | #include <sys/capability.h> | ||
| 13 | #include <sys/syscall.h> | ||
| 14 | #include <sys/types.h> | ||
| 15 | #include <sys/wait.h> | ||
| 16 | #include <unistd.h> | ||
| 17 | |||
| 18 | #include "../kselftest_harness.h" | ||
| 19 | |||
| 20 | #ifndef ARRAY_SIZE | ||
| 21 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) | ||
| 22 | #endif | ||
| 23 | |||
| 24 | /* | ||
| 25 | * TEST_F_FORK() is useful when a test drop privileges but the corresponding | ||
| 26 | * FIXTURE_TEARDOWN() requires them (e.g. to remove files from a directory | ||
| 27 | * where write actions are denied). For convenience, FIXTURE_TEARDOWN() is | ||
| 28 | * also called when the test failed, but not when FIXTURE_SETUP() failed. For | ||
| 29 | * this to be possible, we must not call abort() but instead exit smoothly | ||
| 30 | * (hence the step print). | ||
| 31 | */ | ||
| 32 | #define TEST_F_FORK(fixture_name, test_name) \ | ||
| 33 | static void fixture_name##_##test_name##_child( \ | ||
| 34 | struct __test_metadata *_metadata, \ | ||
| 35 | FIXTURE_DATA(fixture_name) *self, \ | ||
| 36 | const FIXTURE_VARIANT(fixture_name) *variant); \ | ||
| 37 | TEST_F(fixture_name, test_name) \ | ||
| 38 | { \ | ||
| 39 | int status; \ | ||
| 40 | const pid_t child = fork(); \ | ||
| 41 | if (child < 0) \ | ||
| 42 | abort(); \ | ||
| 43 | if (child == 0) { \ | ||
| 44 | _metadata->no_print = 1; \ | ||
| 45 | fixture_name##_##test_name##_child(_metadata, self, variant); \ | ||
| 46 | if (_metadata->skip) \ | ||
| 47 | _exit(255); \ | ||
| 48 | if (_metadata->passed) \ | ||
| 49 | _exit(0); \ | ||
| 50 | _exit(_metadata->step); \ | ||
| 51 | } \ | ||
| 52 | if (child != waitpid(child, &status, 0)) \ | ||
| 53 | abort(); \ | ||
| 54 | if (WIFSIGNALED(status) || !WIFEXITED(status)) { \ | ||
| 55 | _metadata->passed = 0; \ | ||
| 56 | _metadata->step = 1; \ | ||
| 57 | return; \ | ||
| 58 | } \ | ||
| 59 | switch (WEXITSTATUS(status)) { \ | ||
| 60 | case 0: \ | ||
| 61 | _metadata->passed = 1; \ | ||
| 62 | break; \ | ||
| 63 | case 255: \ | ||
| 64 | _metadata->passed = 1; \ | ||
| 65 | _metadata->skip = 1; \ | ||
| 66 | break; \ | ||
| 67 | default: \ | ||
| 68 | _metadata->passed = 0; \ | ||
| 69 | _metadata->step = WEXITSTATUS(status); \ | ||
| 70 | break; \ | ||
| 71 | } \ | ||
| 72 | } \ | ||
| 73 | static void fixture_name##_##test_name##_child( \ | ||
| 74 | struct __test_metadata __attribute__((unused)) *_metadata, \ | ||
| 75 | FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ | ||
| 76 | const FIXTURE_VARIANT(fixture_name) \ | ||
| 77 | __attribute__((unused)) *variant) | ||
| 78 | |||
| 79 | #ifndef landlock_create_ruleset | ||
| 80 | static inline int landlock_create_ruleset( | ||
| 81 | const struct landlock_ruleset_attr *const attr, | ||
| 82 | const size_t size, const __u32 flags) | ||
| 83 | { | ||
| 84 | return syscall(__NR_landlock_create_ruleset, attr, size, flags); | ||
| 85 | } | ||
| 86 | #endif | ||
| 87 | |||
| 88 | #ifndef landlock_add_rule | ||
| 89 | static inline int landlock_add_rule(const int ruleset_fd, | ||
| 90 | const enum landlock_rule_type rule_type, | ||
| 91 | const void *const rule_attr, const __u32 flags) | ||
| 92 | { | ||
| 93 | return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, | ||
| 94 | rule_attr, flags); | ||
| 95 | } | ||
| 96 | #endif | ||
| 97 | |||
| 98 | #ifndef landlock_restrict_self | ||
| 99 | static inline int landlock_restrict_self(const int ruleset_fd, | ||
| 100 | const __u32 flags) | ||
| 101 | { | ||
| 102 | return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); | ||
| 103 | } | ||
| 104 | #endif | ||
| 105 | |||
| 106 | static void _init_caps(struct __test_metadata *const _metadata, bool drop_all) | ||
| 107 | { | ||
| 108 | cap_t cap_p; | ||
| 109 | /* Only these three capabilities are useful for the tests. */ | ||
| 110 | const cap_value_t caps[] = { | ||
| 111 | CAP_DAC_OVERRIDE, | ||
| 112 | CAP_MKNOD, | ||
| 113 | CAP_SYS_ADMIN, | ||
| 114 | CAP_SYS_CHROOT, | ||
| 115 | }; | ||
| 116 | |||
| 117 | cap_p = cap_get_proc(); | ||
| 118 | EXPECT_NE(NULL, cap_p) { | ||
| 119 | TH_LOG("Failed to cap_get_proc: %s", strerror(errno)); | ||
| 120 | } | ||
| 121 | EXPECT_NE(-1, cap_clear(cap_p)) { | ||
| 122 | TH_LOG("Failed to cap_clear: %s", strerror(errno)); | ||
| 123 | } | ||
| 124 | if (!drop_all) { | ||
| 125 | EXPECT_NE(-1, cap_set_flag(cap_p, CAP_PERMITTED, | ||
| 126 | ARRAY_SIZE(caps), caps, CAP_SET)) { | ||
| 127 | TH_LOG("Failed to cap_set_flag: %s", strerror(errno)); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | EXPECT_NE(-1, cap_set_proc(cap_p)) { | ||
| 131 | TH_LOG("Failed to cap_set_proc: %s", strerror(errno)); | ||
| 132 | } | ||
| 133 | EXPECT_NE(-1, cap_free(cap_p)) { | ||
| 134 | TH_LOG("Failed to cap_free: %s", strerror(errno)); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | /* We cannot put such helpers in a library because of kselftest_harness.h . */ | ||
| 139 | __attribute__((__unused__)) | ||
| 140 | static void disable_caps(struct __test_metadata *const _metadata) | ||
| 141 | { | ||
| 142 | _init_caps(_metadata, false); | ||
| 143 | } | ||
| 144 | |||
| 145 | __attribute__((__unused__)) | ||
| 146 | static void drop_caps(struct __test_metadata *const _metadata) | ||
| 147 | { | ||
| 148 | _init_caps(_metadata, true); | ||
| 149 | } | ||
| 150 | |||
| 151 | static void _effective_cap(struct __test_metadata *const _metadata, | ||
| 152 | const cap_value_t caps, const cap_flag_value_t value) | ||
| 153 | { | ||
| 154 | cap_t cap_p; | ||
| 155 | |||
| 156 | cap_p = cap_get_proc(); | ||
| 157 | EXPECT_NE(NULL, cap_p) { | ||
| 158 | TH_LOG("Failed to cap_get_proc: %s", strerror(errno)); | ||
| 159 | } | ||
| 160 | EXPECT_NE(-1, cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &caps, value)) { | ||
| 161 | TH_LOG("Failed to cap_set_flag: %s", strerror(errno)); | ||
| 162 | } | ||
| 163 | EXPECT_NE(-1, cap_set_proc(cap_p)) { | ||
| 164 | TH_LOG("Failed to cap_set_proc: %s", strerror(errno)); | ||
| 165 | } | ||
| 166 | EXPECT_NE(-1, cap_free(cap_p)) { | ||
| 167 | TH_LOG("Failed to cap_free: %s", strerror(errno)); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | __attribute__((__unused__)) | ||
| 172 | static void set_cap(struct __test_metadata *const _metadata, | ||
| 173 | const cap_value_t caps) | ||
| 174 | { | ||
| 175 | _effective_cap(_metadata, caps, CAP_SET); | ||
| 176 | } | ||
| 177 | |||
| 178 | __attribute__((__unused__)) | ||
| 179 | static void clear_cap(struct __test_metadata *const _metadata, | ||
| 180 | const cap_value_t caps) | ||
| 181 | { | ||
| 182 | _effective_cap(_metadata, caps, CAP_CLEAR); | ||
| 183 | } | ||
| @@ -0,0 +1,7 @@ | |||
| 1 | CONFIG_OVERLAY_FS=y | ||
| 2 | CONFIG_SECURITY_LANDLOCK=y | ||
| 3 | CONFIG_SECURITY_PATH=y | ||
| 4 | CONFIG_SECURITY=y | ||
| 5 | CONFIG_SHMEM=y | ||
| 6 | CONFIG_TMPFS_XATTR=y | ||
| 7 | CONFIG_TMPFS=y | ||
| @@ -0,0 +1,2791 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Landlock tests - Filesystem | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2020 ANSSI | ||
| 7 | * Copyright © 2020-2021 Microsoft Corporation | ||
| 8 | */ | ||
| 9 | |||
| 10 | #define _GNU_SOURCE | ||
| 11 | #include <fcntl.h> | ||
| 12 | #include <linux/landlock.h> | ||
| 13 | #include <sched.h> | ||
| 14 | #include <string.h> | ||
| 15 | #include <sys/capability.h> | ||
| 16 | #include <sys/mount.h> | ||
| 17 | #include <sys/prctl.h> | ||
| 18 | #include <sys/sendfile.h> | ||
| 19 | #include <sys/stat.h> | ||
| 20 | #include <sys/sysmacros.h> | ||
| 21 | #include <unistd.h> | ||
| 22 | |||
| 23 | #include "common.h" | ||
| 24 | |||
| 25 | #define TMP_DIR "tmp" | ||
| 26 | #define BINARY_PATH "./true" | ||
| 27 | |||
| 28 | /* Paths (sibling number and depth) */ | ||
| 29 | static const char dir_s1d1[] = TMP_DIR "/s1d1"; | ||
| 30 | static const char file1_s1d1[] = TMP_DIR "/s1d1/f1"; | ||
| 31 | static const char file2_s1d1[] = TMP_DIR "/s1d1/f2"; | ||
| 32 | static const char dir_s1d2[] = TMP_DIR "/s1d1/s1d2"; | ||
| 33 | static const char file1_s1d2[] = TMP_DIR "/s1d1/s1d2/f1"; | ||
| 34 | static const char file2_s1d2[] = TMP_DIR "/s1d1/s1d2/f2"; | ||
| 35 | static const char dir_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3"; | ||
| 36 | static const char file1_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f1"; | ||
| 37 | static const char file2_s1d3[] = TMP_DIR "/s1d1/s1d2/s1d3/f2"; | ||
| 38 | |||
| 39 | static const char dir_s2d1[] = TMP_DIR "/s2d1"; | ||
| 40 | static const char file1_s2d1[] = TMP_DIR "/s2d1/f1"; | ||
| 41 | static const char dir_s2d2[] = TMP_DIR "/s2d1/s2d2"; | ||
| 42 | static const char file1_s2d2[] = TMP_DIR "/s2d1/s2d2/f1"; | ||
| 43 | static const char dir_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3"; | ||
| 44 | static const char file1_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f1"; | ||
| 45 | static const char file2_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f2"; | ||
| 46 | |||
| 47 | static const char dir_s3d1[] = TMP_DIR "/s3d1"; | ||
| 48 | /* dir_s3d2 is a mount point. */ | ||
| 49 | static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2"; | ||
| 50 | static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; | ||
| 51 | |||
| 52 | /* | ||
| 53 | * layout1 hierarchy: | ||
| 54 | * | ||
| 55 | * tmp | ||
| 56 | * ├── s1d1 | ||
| 57 | * │ ├── f1 | ||
| 58 | * │ ├── f2 | ||
| 59 | * │ └── s1d2 | ||
| 60 | * │ ├── f1 | ||
| 61 | * │ ├── f2 | ||
| 62 | * │ └── s1d3 | ||
| 63 | * │ ├── f1 | ||
| 64 | * │ └── f2 | ||
| 65 | * ├── s2d1 | ||
| 66 | * │ ├── f1 | ||
| 67 | * │ └── s2d2 | ||
| 68 | * │ ├── f1 | ||
| 69 | * │ └── s2d3 | ||
| 70 | * │ ├── f1 | ||
| 71 | * │ └── f2 | ||
| 72 | * └── s3d1 | ||
| 73 | * └── s3d2 | ||
| 74 | * └── s3d3 | ||
| 75 | */ | ||
| 76 | |||
| 77 | static void mkdir_parents(struct __test_metadata *const _metadata, | ||
| 78 | const char *const path) | ||
| 79 | { | ||
| 80 | char *walker; | ||
| 81 | const char *parent; | ||
| 82 | int i, err; | ||
| 83 | |||
| 84 | ASSERT_NE(path[0], '\0'); | ||
| 85 | walker = strdup(path); | ||
| 86 | ASSERT_NE(NULL, walker); | ||
| 87 | parent = walker; | ||
| 88 | for (i = 1; walker[i]; i++) { | ||
| 89 | if (walker[i] != '/') | ||
| 90 | continue; | ||
| 91 | walker[i] = '\0'; | ||
| 92 | err = mkdir(parent, 0700); | ||
| 93 | ASSERT_FALSE(err && errno != EEXIST) { | ||
| 94 | TH_LOG("Failed to create directory \"%s\": %s", | ||
| 95 | parent, strerror(errno)); | ||
| 96 | } | ||
| 97 | walker[i] = '/'; | ||
| 98 | } | ||
| 99 | free(walker); | ||
| 100 | } | ||
| 101 | |||
| 102 | static void create_directory(struct __test_metadata *const _metadata, | ||
| 103 | const char *const path) | ||
| 104 | { | ||
| 105 | mkdir_parents(_metadata, path); | ||
| 106 | ASSERT_EQ(0, mkdir(path, 0700)) { | ||
| 107 | TH_LOG("Failed to create directory \"%s\": %s", path, | ||
| 108 | strerror(errno)); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | static void create_file(struct __test_metadata *const _metadata, | ||
| 113 | const char *const path) | ||
| 114 | { | ||
| 115 | mkdir_parents(_metadata, path); | ||
| 116 | ASSERT_EQ(0, mknod(path, S_IFREG | 0700, 0)) { | ||
| 117 | TH_LOG("Failed to create file \"%s\": %s", path, | ||
| 118 | strerror(errno)); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | static int remove_path(const char *const path) | ||
| 123 | { | ||
| 124 | char *walker; | ||
| 125 | int i, ret, err = 0; | ||
| 126 | |||
| 127 | walker = strdup(path); | ||
| 128 | if (!walker) { | ||
| 129 | err = ENOMEM; | ||
| 130 | goto out; | ||
| 131 | } | ||
| 132 | if (unlink(path) && rmdir(path)) { | ||
| 133 | if (errno != ENOENT) | ||
| 134 | err = errno; | ||
| 135 | goto out; | ||
| 136 | } | ||
| 137 | for (i = strlen(walker); i > 0; i--) { | ||
| 138 | if (walker[i] != '/') | ||
| 139 | continue; | ||
| 140 | walker[i] = '\0'; | ||
| 141 | ret = rmdir(walker); | ||
| 142 | if (ret) { | ||
| 143 | if (errno != ENOTEMPTY && errno != EBUSY) | ||
| 144 | err = errno; | ||
| 145 | goto out; | ||
| 146 | } | ||
| 147 | if (strcmp(walker, TMP_DIR) == 0) | ||
| 148 | goto out; | ||
| 149 | } | ||
| 150 | |||
| 151 | out: | ||
| 152 | free(walker); | ||
| 153 | return err; | ||
| 154 | } | ||
| 155 | |||
| 156 | static void prepare_layout(struct __test_metadata *const _metadata) | ||
| 157 | { | ||
| 158 | disable_caps(_metadata); | ||
| 159 | umask(0077); | ||
| 160 | create_directory(_metadata, TMP_DIR); | ||
| 161 | |||
| 162 | /* | ||
| 163 | * Do not pollute the rest of the system: creates a private mount point | ||
| 164 | * for tests relying on pivot_root(2) and move_mount(2). | ||
| 165 | */ | ||
| 166 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 167 | ASSERT_EQ(0, unshare(CLONE_NEWNS)); | ||
| 168 | ASSERT_EQ(0, mount("tmp", TMP_DIR, "tmpfs", 0, "size=4m,mode=700")); | ||
| 169 | ASSERT_EQ(0, mount(NULL, TMP_DIR, NULL, MS_PRIVATE | MS_REC, NULL)); | ||
| 170 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 171 | } | ||
| 172 | |||
| 173 | static void cleanup_layout(struct __test_metadata *const _metadata) | ||
| 174 | { | ||
| 175 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 176 | EXPECT_EQ(0, umount(TMP_DIR)); | ||
| 177 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 178 | EXPECT_EQ(0, remove_path(TMP_DIR)); | ||
| 179 | } | ||
| 180 | |||
| 181 | static void create_layout1(struct __test_metadata *const _metadata) | ||
| 182 | { | ||
| 183 | create_file(_metadata, file1_s1d1); | ||
| 184 | create_file(_metadata, file1_s1d2); | ||
| 185 | create_file(_metadata, file1_s1d3); | ||
| 186 | create_file(_metadata, file2_s1d1); | ||
| 187 | create_file(_metadata, file2_s1d2); | ||
| 188 | create_file(_metadata, file2_s1d3); | ||
| 189 | |||
| 190 | create_file(_metadata, file1_s2d1); | ||
| 191 | create_file(_metadata, file1_s2d2); | ||
| 192 | create_file(_metadata, file1_s2d3); | ||
| 193 | create_file(_metadata, file2_s2d3); | ||
| 194 | |||
| 195 | create_directory(_metadata, dir_s3d2); | ||
| 196 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 197 | ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700")); | ||
| 198 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 199 | |||
| 200 | ASSERT_EQ(0, mkdir(dir_s3d3, 0700)); | ||
| 201 | } | ||
| 202 | |||
| 203 | static void remove_layout1(struct __test_metadata *const _metadata) | ||
| 204 | { | ||
| 205 | EXPECT_EQ(0, remove_path(file2_s1d3)); | ||
| 206 | EXPECT_EQ(0, remove_path(file2_s1d2)); | ||
| 207 | EXPECT_EQ(0, remove_path(file2_s1d1)); | ||
| 208 | EXPECT_EQ(0, remove_path(file1_s1d3)); | ||
| 209 | EXPECT_EQ(0, remove_path(file1_s1d2)); | ||
| 210 | EXPECT_EQ(0, remove_path(file1_s1d1)); | ||
| 211 | |||
| 212 | EXPECT_EQ(0, remove_path(file2_s2d3)); | ||
| 213 | EXPECT_EQ(0, remove_path(file1_s2d3)); | ||
| 214 | EXPECT_EQ(0, remove_path(file1_s2d2)); | ||
| 215 | EXPECT_EQ(0, remove_path(file1_s2d1)); | ||
| 216 | |||
| 217 | EXPECT_EQ(0, remove_path(dir_s3d3)); | ||
| 218 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 219 | umount(dir_s3d2); | ||
| 220 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 221 | EXPECT_EQ(0, remove_path(dir_s3d2)); | ||
| 222 | } | ||
| 223 | |||
| 224 | FIXTURE(layout1) { | ||
| 225 | }; | ||
| 226 | |||
| 227 | FIXTURE_SETUP(layout1) | ||
| 228 | { | ||
| 229 | prepare_layout(_metadata); | ||
| 230 | |||
| 231 | create_layout1(_metadata); | ||
| 232 | } | ||
| 233 | |||
| 234 | FIXTURE_TEARDOWN(layout1) | ||
| 235 | { | ||
| 236 | remove_layout1(_metadata); | ||
| 237 | |||
| 238 | cleanup_layout(_metadata); | ||
| 239 | } | ||
| 240 | |||
| 241 | /* | ||
| 242 | * This helper enables to use the ASSERT_* macros and print the line number | ||
| 243 | * pointing to the test caller. | ||
| 244 | */ | ||
| 245 | static int test_open_rel(const int dirfd, const char *const path, const int flags) | ||
| 246 | { | ||
| 247 | int fd; | ||
| 248 | |||
| 249 | /* Works with file and directories. */ | ||
| 250 | fd = openat(dirfd, path, flags | O_CLOEXEC); | ||
| 251 | if (fd < 0) | ||
| 252 | return errno; | ||
| 253 | /* | ||
| 254 | * Mixing error codes from close(2) and open(2) should not lead to any | ||
| 255 | * (access type) confusion for this test. | ||
| 256 | */ | ||
| 257 | if (close(fd) != 0) | ||
| 258 | return errno; | ||
| 259 | return 0; | ||
| 260 | } | ||
| 261 | |||
| 262 | static int test_open(const char *const path, const int flags) | ||
| 263 | { | ||
| 264 | return test_open_rel(AT_FDCWD, path, flags); | ||
| 265 | } | ||
| 266 | |||
| 267 | TEST_F_FORK(layout1, no_restriction) | ||
| 268 | { | ||
| 269 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | ||
| 270 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | ||
| 271 | ASSERT_EQ(0, test_open(file2_s1d1, O_RDONLY)); | ||
| 272 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); | ||
| 273 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 274 | ASSERT_EQ(0, test_open(file2_s1d2, O_RDONLY)); | ||
| 275 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | ||
| 276 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 277 | |||
| 278 | ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY)); | ||
| 279 | ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY)); | ||
| 280 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY)); | ||
| 281 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); | ||
| 282 | ASSERT_EQ(0, test_open(dir_s2d3, O_RDONLY)); | ||
| 283 | ASSERT_EQ(0, test_open(file1_s2d3, O_RDONLY)); | ||
| 284 | |||
| 285 | ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); | ||
| 286 | ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); | ||
| 287 | ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); | ||
| 288 | } | ||
| 289 | |||
| 290 | TEST_F_FORK(layout1, inval) | ||
| 291 | { | ||
| 292 | struct landlock_path_beneath_attr path_beneath = { | ||
| 293 | .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 294 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 295 | .parent_fd = -1, | ||
| 296 | }; | ||
| 297 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 298 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 299 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 300 | }; | ||
| 301 | int ruleset_fd; | ||
| 302 | |||
| 303 | path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY | | ||
| 304 | O_CLOEXEC); | ||
| 305 | ASSERT_LE(0, path_beneath.parent_fd); | ||
| 306 | |||
| 307 | ruleset_fd = open(dir_s1d1, O_PATH | O_DIRECTORY | O_CLOEXEC); | ||
| 308 | ASSERT_LE(0, ruleset_fd); | ||
| 309 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 310 | &path_beneath, 0)); | ||
| 311 | /* Returns EBADF because ruleset_fd is not a landlock-ruleset FD. */ | ||
| 312 | ASSERT_EQ(EBADF, errno); | ||
| 313 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 314 | |||
| 315 | ruleset_fd = open(dir_s1d1, O_DIRECTORY | O_CLOEXEC); | ||
| 316 | ASSERT_LE(0, ruleset_fd); | ||
| 317 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 318 | &path_beneath, 0)); | ||
| 319 | /* Returns EBADFD because ruleset_fd is not a valid ruleset. */ | ||
| 320 | ASSERT_EQ(EBADFD, errno); | ||
| 321 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 322 | |||
| 323 | /* Gets a real ruleset. */ | ||
| 324 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 325 | sizeof(ruleset_attr), 0); | ||
| 326 | ASSERT_LE(0, ruleset_fd); | ||
| 327 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 328 | &path_beneath, 0)); | ||
| 329 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | ||
| 330 | |||
| 331 | /* Tests without O_PATH. */ | ||
| 332 | path_beneath.parent_fd = open(dir_s1d2, O_DIRECTORY | O_CLOEXEC); | ||
| 333 | ASSERT_LE(0, path_beneath.parent_fd); | ||
| 334 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 335 | &path_beneath, 0)); | ||
| 336 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | ||
| 337 | |||
| 338 | /* Tests with a ruleset FD. */ | ||
| 339 | path_beneath.parent_fd = ruleset_fd; | ||
| 340 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 341 | &path_beneath, 0)); | ||
| 342 | ASSERT_EQ(EBADFD, errno); | ||
| 343 | |||
| 344 | /* Checks unhandled allowed_access. */ | ||
| 345 | path_beneath.parent_fd = open(dir_s1d2, O_PATH | O_DIRECTORY | | ||
| 346 | O_CLOEXEC); | ||
| 347 | ASSERT_LE(0, path_beneath.parent_fd); | ||
| 348 | |||
| 349 | /* Test with legitimate values. */ | ||
| 350 | path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE; | ||
| 351 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 352 | &path_beneath, 0)); | ||
| 353 | ASSERT_EQ(EINVAL, errno); | ||
| 354 | path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE; | ||
| 355 | |||
| 356 | /* Test with unknown (64-bits) value. */ | ||
| 357 | path_beneath.allowed_access |= (1ULL << 60); | ||
| 358 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 359 | &path_beneath, 0)); | ||
| 360 | ASSERT_EQ(EINVAL, errno); | ||
| 361 | path_beneath.allowed_access &= ~(1ULL << 60); | ||
| 362 | |||
| 363 | /* Test with no access. */ | ||
| 364 | path_beneath.allowed_access = 0; | ||
| 365 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 366 | &path_beneath, 0)); | ||
| 367 | ASSERT_EQ(ENOMSG, errno); | ||
| 368 | path_beneath.allowed_access &= ~(1ULL << 60); | ||
| 369 | |||
| 370 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | ||
| 371 | |||
| 372 | /* Enforces the ruleset. */ | ||
| 373 | ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); | ||
| 374 | ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); | ||
| 375 | |||
| 376 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 377 | } | ||
| 378 | |||
| 379 | #define ACCESS_FILE ( \ | ||
| 380 | LANDLOCK_ACCESS_FS_EXECUTE | \ | ||
| 381 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | ||
| 382 | LANDLOCK_ACCESS_FS_READ_FILE) | ||
| 383 | |||
| 384 | #define ACCESS_LAST LANDLOCK_ACCESS_FS_MAKE_SYM | ||
| 385 | |||
| 386 | #define ACCESS_ALL ( \ | ||
| 387 | ACCESS_FILE | \ | ||
| 388 | LANDLOCK_ACCESS_FS_READ_DIR | \ | ||
| 389 | LANDLOCK_ACCESS_FS_REMOVE_DIR | \ | ||
| 390 | LANDLOCK_ACCESS_FS_REMOVE_FILE | \ | ||
| 391 | LANDLOCK_ACCESS_FS_MAKE_CHAR | \ | ||
| 392 | LANDLOCK_ACCESS_FS_MAKE_DIR | \ | ||
| 393 | LANDLOCK_ACCESS_FS_MAKE_REG | \ | ||
| 394 | LANDLOCK_ACCESS_FS_MAKE_SOCK | \ | ||
| 395 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ | ||
| 396 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ | ||
| 397 | ACCESS_LAST) | ||
| 398 | |||
| 399 | TEST_F_FORK(layout1, file_access_rights) | ||
| 400 | { | ||
| 401 | __u64 access; | ||
| 402 | int err; | ||
| 403 | struct landlock_path_beneath_attr path_beneath = {}; | ||
| 404 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 405 | .handled_access_fs = ACCESS_ALL, | ||
| 406 | }; | ||
| 407 | const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 408 | sizeof(ruleset_attr), 0); | ||
| 409 | |||
| 410 | ASSERT_LE(0, ruleset_fd); | ||
| 411 | |||
| 412 | /* Tests access rights for files. */ | ||
| 413 | path_beneath.parent_fd = open(file1_s1d2, O_PATH | O_CLOEXEC); | ||
| 414 | ASSERT_LE(0, path_beneath.parent_fd); | ||
| 415 | for (access = 1; access <= ACCESS_LAST; access <<= 1) { | ||
| 416 | path_beneath.allowed_access = access; | ||
| 417 | err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 418 | &path_beneath, 0); | ||
| 419 | if ((access | ACCESS_FILE) == ACCESS_FILE) { | ||
| 420 | ASSERT_EQ(0, err); | ||
| 421 | } else { | ||
| 422 | ASSERT_EQ(-1, err); | ||
| 423 | ASSERT_EQ(EINVAL, errno); | ||
| 424 | } | ||
| 425 | } | ||
| 426 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | ||
| 427 | } | ||
| 428 | |||
| 429 | static void add_path_beneath(struct __test_metadata *const _metadata, | ||
| 430 | const int ruleset_fd, const __u64 allowed_access, | ||
| 431 | const char *const path) | ||
| 432 | { | ||
| 433 | struct landlock_path_beneath_attr path_beneath = { | ||
| 434 | .allowed_access = allowed_access, | ||
| 435 | }; | ||
| 436 | |||
| 437 | path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC); | ||
| 438 | ASSERT_LE(0, path_beneath.parent_fd) { | ||
| 439 | TH_LOG("Failed to open directory \"%s\": %s", path, | ||
| 440 | strerror(errno)); | ||
| 441 | } | ||
| 442 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 443 | &path_beneath, 0)) { | ||
| 444 | TH_LOG("Failed to update the ruleset with \"%s\": %s", path, | ||
| 445 | strerror(errno)); | ||
| 446 | } | ||
| 447 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | ||
| 448 | } | ||
| 449 | |||
| 450 | struct rule { | ||
| 451 | const char *path; | ||
| 452 | __u64 access; | ||
| 453 | }; | ||
| 454 | |||
| 455 | #define ACCESS_RO ( \ | ||
| 456 | LANDLOCK_ACCESS_FS_READ_FILE | \ | ||
| 457 | LANDLOCK_ACCESS_FS_READ_DIR) | ||
| 458 | |||
| 459 | #define ACCESS_RW ( \ | ||
| 460 | ACCESS_RO | \ | ||
| 461 | LANDLOCK_ACCESS_FS_WRITE_FILE) | ||
| 462 | |||
| 463 | static int create_ruleset(struct __test_metadata *const _metadata, | ||
| 464 | const __u64 handled_access_fs, const struct rule rules[]) | ||
| 465 | { | ||
| 466 | int ruleset_fd, i; | ||
| 467 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 468 | .handled_access_fs = handled_access_fs, | ||
| 469 | }; | ||
| 470 | |||
| 471 | ASSERT_NE(NULL, rules) { | ||
| 472 | TH_LOG("No rule list"); | ||
| 473 | } | ||
| 474 | ASSERT_NE(NULL, rules[0].path) { | ||
| 475 | TH_LOG("Empty rule list"); | ||
| 476 | } | ||
| 477 | |||
| 478 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 479 | sizeof(ruleset_attr), 0); | ||
| 480 | ASSERT_LE(0, ruleset_fd) { | ||
| 481 | TH_LOG("Failed to create a ruleset: %s", strerror(errno)); | ||
| 482 | } | ||
| 483 | |||
| 484 | for (i = 0; rules[i].path; i++) { | ||
| 485 | add_path_beneath(_metadata, ruleset_fd, rules[i].access, | ||
| 486 | rules[i].path); | ||
| 487 | } | ||
| 488 | return ruleset_fd; | ||
| 489 | } | ||
| 490 | |||
| 491 | static void enforce_ruleset(struct __test_metadata *const _metadata, | ||
| 492 | const int ruleset_fd) | ||
| 493 | { | ||
| 494 | ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); | ||
| 495 | ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)) { | ||
| 496 | TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | TEST_F_FORK(layout1, proc_nsfs) | ||
| 501 | { | ||
| 502 | const struct rule rules[] = { | ||
| 503 | { | ||
| 504 | .path = "/dev/null", | ||
| 505 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 506 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 507 | }, | ||
| 508 | {} | ||
| 509 | }; | ||
| 510 | struct landlock_path_beneath_attr path_beneath; | ||
| 511 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access | | ||
| 512 | LANDLOCK_ACCESS_FS_READ_DIR, rules); | ||
| 513 | |||
| 514 | ASSERT_LE(0, ruleset_fd); | ||
| 515 | ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY)); | ||
| 516 | |||
| 517 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 518 | |||
| 519 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | ||
| 520 | ASSERT_EQ(EACCES, test_open("/dev", O_RDONLY)); | ||
| 521 | ASSERT_EQ(0, test_open("/dev/null", O_RDONLY)); | ||
| 522 | ASSERT_EQ(EACCES, test_open("/dev/full", O_RDONLY)); | ||
| 523 | |||
| 524 | ASSERT_EQ(EACCES, test_open("/proc", O_RDONLY)); | ||
| 525 | ASSERT_EQ(EACCES, test_open("/proc/self", O_RDONLY)); | ||
| 526 | ASSERT_EQ(EACCES, test_open("/proc/self/ns", O_RDONLY)); | ||
| 527 | /* | ||
| 528 | * Because nsfs is an internal filesystem, /proc/self/ns/mnt is a | ||
| 529 | * disconnected path. Such path cannot be identified and must then be | ||
| 530 | * allowed. | ||
| 531 | */ | ||
| 532 | ASSERT_EQ(0, test_open("/proc/self/ns/mnt", O_RDONLY)); | ||
| 533 | |||
| 534 | /* | ||
| 535 | * Checks that it is not possible to add nsfs-like filesystem | ||
| 536 | * references to a ruleset. | ||
| 537 | */ | ||
| 538 | path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 539 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 540 | path_beneath.parent_fd = open("/proc/self/ns/mnt", O_PATH | O_CLOEXEC); | ||
| 541 | ASSERT_LE(0, path_beneath.parent_fd); | ||
| 542 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | ||
| 543 | &path_beneath, 0)); | ||
| 544 | ASSERT_EQ(EBADFD, errno); | ||
| 545 | ASSERT_EQ(0, close(path_beneath.parent_fd)); | ||
| 546 | } | ||
| 547 | |||
| 548 | TEST_F_FORK(layout1, unpriv) { | ||
| 549 | const struct rule rules[] = { | ||
| 550 | { | ||
| 551 | .path = dir_s1d2, | ||
| 552 | .access = ACCESS_RO, | ||
| 553 | }, | ||
| 554 | {} | ||
| 555 | }; | ||
| 556 | int ruleset_fd; | ||
| 557 | |||
| 558 | drop_caps(_metadata); | ||
| 559 | |||
| 560 | ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); | ||
| 561 | ASSERT_LE(0, ruleset_fd); | ||
| 562 | ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0)); | ||
| 563 | ASSERT_EQ(EPERM, errno); | ||
| 564 | |||
| 565 | /* enforce_ruleset() calls prctl(no_new_privs). */ | ||
| 566 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 567 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 568 | } | ||
| 569 | |||
| 570 | TEST_F_FORK(layout1, effective_access) | ||
| 571 | { | ||
| 572 | const struct rule rules[] = { | ||
| 573 | { | ||
| 574 | .path = dir_s1d2, | ||
| 575 | .access = ACCESS_RO, | ||
| 576 | }, | ||
| 577 | { | ||
| 578 | .path = file1_s2d2, | ||
| 579 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 580 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 581 | }, | ||
| 582 | {} | ||
| 583 | }; | ||
| 584 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 585 | char buf; | ||
| 586 | int reg_fd; | ||
| 587 | |||
| 588 | ASSERT_LE(0, ruleset_fd); | ||
| 589 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 590 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 591 | |||
| 592 | /* Tests on a directory. */ | ||
| 593 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | ||
| 594 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); | ||
| 595 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | ||
| 596 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); | ||
| 597 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 598 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | ||
| 599 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 600 | |||
| 601 | /* Tests on a file. */ | ||
| 602 | ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY)); | ||
| 603 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); | ||
| 604 | |||
| 605 | /* Checks effective read and write actions. */ | ||
| 606 | reg_fd = open(file1_s2d2, O_RDWR | O_CLOEXEC); | ||
| 607 | ASSERT_LE(0, reg_fd); | ||
| 608 | ASSERT_EQ(1, write(reg_fd, ".", 1)); | ||
| 609 | ASSERT_LE(0, lseek(reg_fd, 0, SEEK_SET)); | ||
| 610 | ASSERT_EQ(1, read(reg_fd, &buf, 1)); | ||
| 611 | ASSERT_EQ('.', buf); | ||
| 612 | ASSERT_EQ(0, close(reg_fd)); | ||
| 613 | |||
| 614 | /* Just in case, double-checks effective actions. */ | ||
| 615 | reg_fd = open(file1_s2d2, O_RDONLY | O_CLOEXEC); | ||
| 616 | ASSERT_LE(0, reg_fd); | ||
| 617 | ASSERT_EQ(-1, write(reg_fd, &buf, 1)); | ||
| 618 | ASSERT_EQ(EBADF, errno); | ||
| 619 | ASSERT_EQ(0, close(reg_fd)); | ||
| 620 | } | ||
| 621 | |||
| 622 | TEST_F_FORK(layout1, unhandled_access) | ||
| 623 | { | ||
| 624 | const struct rule rules[] = { | ||
| 625 | { | ||
| 626 | .path = dir_s1d2, | ||
| 627 | .access = ACCESS_RO, | ||
| 628 | }, | ||
| 629 | {} | ||
| 630 | }; | ||
| 631 | /* Here, we only handle read accesses, not write accesses. */ | ||
| 632 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); | ||
| 633 | |||
| 634 | ASSERT_LE(0, ruleset_fd); | ||
| 635 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 636 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 637 | |||
| 638 | /* | ||
| 639 | * Because the policy does not handle LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 640 | * opening for write-only should be allowed, but not read-write. | ||
| 641 | */ | ||
| 642 | ASSERT_EQ(0, test_open(file1_s1d1, O_WRONLY)); | ||
| 643 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | ||
| 644 | |||
| 645 | ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY)); | ||
| 646 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); | ||
| 647 | } | ||
| 648 | |||
| 649 | TEST_F_FORK(layout1, ruleset_overlap) | ||
| 650 | { | ||
| 651 | const struct rule rules[] = { | ||
| 652 | /* These rules should be ORed among them. */ | ||
| 653 | { | ||
| 654 | .path = dir_s1d2, | ||
| 655 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 656 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 657 | }, | ||
| 658 | { | ||
| 659 | .path = dir_s1d2, | ||
| 660 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 661 | LANDLOCK_ACCESS_FS_READ_DIR, | ||
| 662 | }, | ||
| 663 | {} | ||
| 664 | }; | ||
| 665 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 666 | |||
| 667 | ASSERT_LE(0, ruleset_fd); | ||
| 668 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 669 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 670 | |||
| 671 | /* Checks s1d1 hierarchy. */ | ||
| 672 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | ||
| 673 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | ||
| 674 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | ||
| 675 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | ||
| 676 | |||
| 677 | /* Checks s1d2 hierarchy. */ | ||
| 678 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 679 | ASSERT_EQ(0, test_open(file1_s1d2, O_WRONLY)); | ||
| 680 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); | ||
| 681 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 682 | |||
| 683 | /* Checks s1d3 hierarchy. */ | ||
| 684 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 685 | ASSERT_EQ(0, test_open(file1_s1d3, O_WRONLY)); | ||
| 686 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | ||
| 687 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 688 | } | ||
| 689 | |||
| 690 | TEST_F_FORK(layout1, non_overlapping_accesses) | ||
| 691 | { | ||
| 692 | const struct rule layer1[] = { | ||
| 693 | { | ||
| 694 | .path = dir_s1d2, | ||
| 695 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | ||
| 696 | }, | ||
| 697 | {} | ||
| 698 | }; | ||
| 699 | const struct rule layer2[] = { | ||
| 700 | { | ||
| 701 | .path = dir_s1d3, | ||
| 702 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | ||
| 703 | }, | ||
| 704 | {} | ||
| 705 | }; | ||
| 706 | int ruleset_fd; | ||
| 707 | |||
| 708 | ASSERT_EQ(0, unlink(file1_s1d1)); | ||
| 709 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 710 | |||
| 711 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, | ||
| 712 | layer1); | ||
| 713 | ASSERT_LE(0, ruleset_fd); | ||
| 714 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 715 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 716 | |||
| 717 | ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); | ||
| 718 | ASSERT_EQ(EACCES, errno); | ||
| 719 | ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0)); | ||
| 720 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 721 | |||
| 722 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE, | ||
| 723 | layer2); | ||
| 724 | ASSERT_LE(0, ruleset_fd); | ||
| 725 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 726 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 727 | |||
| 728 | /* Unchanged accesses for file creation. */ | ||
| 729 | ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); | ||
| 730 | ASSERT_EQ(EACCES, errno); | ||
| 731 | ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0)); | ||
| 732 | |||
| 733 | /* Checks file removing. */ | ||
| 734 | ASSERT_EQ(-1, unlink(file1_s1d2)); | ||
| 735 | ASSERT_EQ(EACCES, errno); | ||
| 736 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 737 | } | ||
| 738 | |||
| 739 | TEST_F_FORK(layout1, interleaved_masked_accesses) | ||
| 740 | { | ||
| 741 | /* | ||
| 742 | * Checks overly restrictive rules: | ||
| 743 | * layer 1: allows R s1d1/s1d2/s1d3/file1 | ||
| 744 | * layer 2: allows RW s1d1/s1d2/s1d3 | ||
| 745 | * allows W s1d1/s1d2 | ||
| 746 | * denies R s1d1/s1d2 | ||
| 747 | * layer 3: allows R s1d1 | ||
| 748 | * layer 4: allows R s1d1/s1d2 | ||
| 749 | * denies W s1d1/s1d2 | ||
| 750 | * layer 5: allows R s1d1/s1d2 | ||
| 751 | * layer 6: allows X ---- | ||
| 752 | * layer 7: allows W s1d1/s1d2 | ||
| 753 | * denies R s1d1/s1d2 | ||
| 754 | */ | ||
| 755 | const struct rule layer1_read[] = { | ||
| 756 | /* Allows read access to file1_s1d3 with the first layer. */ | ||
| 757 | { | ||
| 758 | .path = file1_s1d3, | ||
| 759 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 760 | }, | ||
| 761 | {} | ||
| 762 | }; | ||
| 763 | /* First rule with write restrictions. */ | ||
| 764 | const struct rule layer2_read_write[] = { | ||
| 765 | /* Start by granting read-write access via its parent directory... */ | ||
| 766 | { | ||
| 767 | .path = dir_s1d3, | ||
| 768 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 769 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 770 | }, | ||
| 771 | /* ...but also denies read access via its grandparent directory. */ | ||
| 772 | { | ||
| 773 | .path = dir_s1d2, | ||
| 774 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 775 | }, | ||
| 776 | {} | ||
| 777 | }; | ||
| 778 | const struct rule layer3_read[] = { | ||
| 779 | /* Allows read access via its great-grandparent directory. */ | ||
| 780 | { | ||
| 781 | .path = dir_s1d1, | ||
| 782 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 783 | }, | ||
| 784 | {} | ||
| 785 | }; | ||
| 786 | const struct rule layer4_read_write[] = { | ||
| 787 | /* | ||
| 788 | * Try to confuse the deny access by denying write (but not | ||
| 789 | * read) access via its grandparent directory. | ||
| 790 | */ | ||
| 791 | { | ||
| 792 | .path = dir_s1d2, | ||
| 793 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 794 | }, | ||
| 795 | {} | ||
| 796 | }; | ||
| 797 | const struct rule layer5_read[] = { | ||
| 798 | /* | ||
| 799 | * Try to override layer2's deny read access by explicitly | ||
| 800 | * allowing read access via file1_s1d3's grandparent. | ||
| 801 | */ | ||
| 802 | { | ||
| 803 | .path = dir_s1d2, | ||
| 804 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 805 | }, | ||
| 806 | {} | ||
| 807 | }; | ||
| 808 | const struct rule layer6_execute[] = { | ||
| 809 | /* | ||
| 810 | * Restricts an unrelated file hierarchy with a new access | ||
| 811 | * (non-overlapping) type. | ||
| 812 | */ | ||
| 813 | { | ||
| 814 | .path = dir_s2d1, | ||
| 815 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | ||
| 816 | }, | ||
| 817 | {} | ||
| 818 | }; | ||
| 819 | const struct rule layer7_read_write[] = { | ||
| 820 | /* | ||
| 821 | * Finally, denies read access to file1_s1d3 via its | ||
| 822 | * grandparent. | ||
| 823 | */ | ||
| 824 | { | ||
| 825 | .path = dir_s1d2, | ||
| 826 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 827 | }, | ||
| 828 | {} | ||
| 829 | }; | ||
| 830 | int ruleset_fd; | ||
| 831 | |||
| 832 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 833 | layer1_read); | ||
| 834 | ASSERT_LE(0, ruleset_fd); | ||
| 835 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 836 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 837 | |||
| 838 | /* Checks that read access is granted for file1_s1d3 with layer 1. */ | ||
| 839 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | ||
| 840 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | ||
| 841 | ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); | ||
| 842 | |||
| 843 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 844 | LANDLOCK_ACCESS_FS_WRITE_FILE, layer2_read_write); | ||
| 845 | ASSERT_LE(0, ruleset_fd); | ||
| 846 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 847 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 848 | |||
| 849 | /* Checks that previous access rights are unchanged with layer 2. */ | ||
| 850 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | ||
| 851 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | ||
| 852 | ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); | ||
| 853 | |||
| 854 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 855 | layer3_read); | ||
| 856 | ASSERT_LE(0, ruleset_fd); | ||
| 857 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 858 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 859 | |||
| 860 | /* Checks that previous access rights are unchanged with layer 3. */ | ||
| 861 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); | ||
| 862 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | ||
| 863 | ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); | ||
| 864 | |||
| 865 | /* This time, denies write access for the file hierarchy. */ | ||
| 866 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 867 | LANDLOCK_ACCESS_FS_WRITE_FILE, layer4_read_write); | ||
| 868 | ASSERT_LE(0, ruleset_fd); | ||
| 869 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 870 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 871 | |||
| 872 | /* | ||
| 873 | * Checks that the only change with layer 4 is that write access is | ||
| 874 | * denied. | ||
| 875 | */ | ||
| 876 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 877 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 878 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | ||
| 879 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | ||
| 880 | |||
| 881 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 882 | layer5_read); | ||
| 883 | ASSERT_LE(0, ruleset_fd); | ||
| 884 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 885 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 886 | |||
| 887 | /* Checks that previous access rights are unchanged with layer 5. */ | ||
| 888 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 889 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 890 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | ||
| 891 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | ||
| 892 | |||
| 893 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, | ||
| 894 | layer6_execute); | ||
| 895 | ASSERT_LE(0, ruleset_fd); | ||
| 896 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 897 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 898 | |||
| 899 | /* Checks that previous access rights are unchanged with layer 6. */ | ||
| 900 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 901 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 902 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | ||
| 903 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | ||
| 904 | |||
| 905 | ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 906 | LANDLOCK_ACCESS_FS_WRITE_FILE, layer7_read_write); | ||
| 907 | ASSERT_LE(0, ruleset_fd); | ||
| 908 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 909 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 910 | |||
| 911 | /* Checks read access is now denied with layer 7. */ | ||
| 912 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); | ||
| 913 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 914 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); | ||
| 915 | ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); | ||
| 916 | } | ||
| 917 | |||
| 918 | TEST_F_FORK(layout1, inherit_subset) | ||
| 919 | { | ||
| 920 | const struct rule rules[] = { | ||
| 921 | { | ||
| 922 | .path = dir_s1d2, | ||
| 923 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 924 | LANDLOCK_ACCESS_FS_READ_DIR, | ||
| 925 | }, | ||
| 926 | {} | ||
| 927 | }; | ||
| 928 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 929 | |||
| 930 | ASSERT_LE(0, ruleset_fd); | ||
| 931 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 932 | |||
| 933 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | ||
| 934 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | ||
| 935 | |||
| 936 | /* Write access is forbidden. */ | ||
| 937 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | ||
| 938 | /* Readdir access is allowed. */ | ||
| 939 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 940 | |||
| 941 | /* Write access is forbidden. */ | ||
| 942 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 943 | /* Readdir access is allowed. */ | ||
| 944 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 945 | |||
| 946 | /* | ||
| 947 | * Tests shared rule extension: the following rules should not grant | ||
| 948 | * any new access, only remove some. Once enforced, these rules are | ||
| 949 | * ANDed with the previous ones. | ||
| 950 | */ | ||
| 951 | add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 952 | dir_s1d2); | ||
| 953 | /* | ||
| 954 | * According to ruleset_fd, dir_s1d2 should now have the | ||
| 955 | * LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE | ||
| 956 | * access rights (even if this directory is opened a second time). | ||
| 957 | * However, when enforcing this updated ruleset, the ruleset tied to | ||
| 958 | * the current process (i.e. its domain) will still only have the | ||
| 959 | * dir_s1d2 with LANDLOCK_ACCESS_FS_READ_FILE and | ||
| 960 | * LANDLOCK_ACCESS_FS_READ_DIR accesses, but | ||
| 961 | * LANDLOCK_ACCESS_FS_WRITE_FILE must not be allowed because it would | ||
| 962 | * be a privilege escalation. | ||
| 963 | */ | ||
| 964 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 965 | |||
| 966 | /* Same tests and results as above. */ | ||
| 967 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | ||
| 968 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | ||
| 969 | |||
| 970 | /* It is still forbidden to write in file1_s1d2. */ | ||
| 971 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | ||
| 972 | /* Readdir access is still allowed. */ | ||
| 973 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 974 | |||
| 975 | /* It is still forbidden to write in file1_s1d3. */ | ||
| 976 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 977 | /* Readdir access is still allowed. */ | ||
| 978 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 979 | |||
| 980 | /* | ||
| 981 | * Try to get more privileges by adding new access rights to the parent | ||
| 982 | * directory: dir_s1d1. | ||
| 983 | */ | ||
| 984 | add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1); | ||
| 985 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 986 | |||
| 987 | /* Same tests and results as above. */ | ||
| 988 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | ||
| 989 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | ||
| 990 | |||
| 991 | /* It is still forbidden to write in file1_s1d2. */ | ||
| 992 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | ||
| 993 | /* Readdir access is still allowed. */ | ||
| 994 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 995 | |||
| 996 | /* It is still forbidden to write in file1_s1d3. */ | ||
| 997 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 998 | /* Readdir access is still allowed. */ | ||
| 999 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 1000 | |||
| 1001 | /* | ||
| 1002 | * Now, dir_s1d3 get a new rule tied to it, only allowing | ||
| 1003 | * LANDLOCK_ACCESS_FS_WRITE_FILE. The (kernel internal) difference is | ||
| 1004 | * that there was no rule tied to it before. | ||
| 1005 | */ | ||
| 1006 | add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 1007 | dir_s1d3); | ||
| 1008 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1009 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1010 | |||
| 1011 | /* | ||
| 1012 | * Same tests and results as above, except for open(dir_s1d3) which is | ||
| 1013 | * now denied because the new rule mask the rule previously inherited | ||
| 1014 | * from dir_s1d2. | ||
| 1015 | */ | ||
| 1016 | |||
| 1017 | /* Same tests and results as above. */ | ||
| 1018 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | ||
| 1019 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | ||
| 1020 | |||
| 1021 | /* It is still forbidden to write in file1_s1d2. */ | ||
| 1022 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | ||
| 1023 | /* Readdir access is still allowed. */ | ||
| 1024 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 1025 | |||
| 1026 | /* It is still forbidden to write in file1_s1d3. */ | ||
| 1027 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 1028 | /* | ||
| 1029 | * Readdir of dir_s1d3 is still allowed because of the OR policy inside | ||
| 1030 | * the same layer. | ||
| 1031 | */ | ||
| 1032 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | TEST_F_FORK(layout1, inherit_superset) | ||
| 1036 | { | ||
| 1037 | const struct rule rules[] = { | ||
| 1038 | { | ||
| 1039 | .path = dir_s1d3, | ||
| 1040 | .access = ACCESS_RO, | ||
| 1041 | }, | ||
| 1042 | {} | ||
| 1043 | }; | ||
| 1044 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1045 | |||
| 1046 | ASSERT_LE(0, ruleset_fd); | ||
| 1047 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1048 | |||
| 1049 | /* Readdir access is denied for dir_s1d2. */ | ||
| 1050 | ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 1051 | /* Readdir access is allowed for dir_s1d3. */ | ||
| 1052 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 1053 | /* File access is allowed for file1_s1d3. */ | ||
| 1054 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 1055 | |||
| 1056 | /* Now dir_s1d2, parent of dir_s1d3, gets a new rule tied to it. */ | ||
| 1057 | add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 1058 | LANDLOCK_ACCESS_FS_READ_DIR, dir_s1d2); | ||
| 1059 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1060 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1061 | |||
| 1062 | /* Readdir access is still denied for dir_s1d2. */ | ||
| 1063 | ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 1064 | /* Readdir access is still allowed for dir_s1d3. */ | ||
| 1065 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 1066 | /* File access is still allowed for file1_s1d3. */ | ||
| 1067 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | TEST_F_FORK(layout1, max_layers) | ||
| 1071 | { | ||
| 1072 | int i, err; | ||
| 1073 | const struct rule rules[] = { | ||
| 1074 | { | ||
| 1075 | .path = dir_s1d2, | ||
| 1076 | .access = ACCESS_RO, | ||
| 1077 | }, | ||
| 1078 | {} | ||
| 1079 | }; | ||
| 1080 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1081 | |||
| 1082 | ASSERT_LE(0, ruleset_fd); | ||
| 1083 | for (i = 0; i < 64; i++) | ||
| 1084 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1085 | |||
| 1086 | for (i = 0; i < 2; i++) { | ||
| 1087 | err = landlock_restrict_self(ruleset_fd, 0); | ||
| 1088 | ASSERT_EQ(-1, err); | ||
| 1089 | ASSERT_EQ(E2BIG, errno); | ||
| 1090 | } | ||
| 1091 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | TEST_F_FORK(layout1, empty_or_same_ruleset) | ||
| 1095 | { | ||
| 1096 | struct landlock_ruleset_attr ruleset_attr = {}; | ||
| 1097 | int ruleset_fd; | ||
| 1098 | |||
| 1099 | /* Tests empty handled_access_fs. */ | ||
| 1100 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 1101 | sizeof(ruleset_attr), 0); | ||
| 1102 | ASSERT_LE(-1, ruleset_fd); | ||
| 1103 | ASSERT_EQ(ENOMSG, errno); | ||
| 1104 | |||
| 1105 | /* Enforces policy which deny read access to all files. */ | ||
| 1106 | ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE; | ||
| 1107 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 1108 | sizeof(ruleset_attr), 0); | ||
| 1109 | ASSERT_LE(0, ruleset_fd); | ||
| 1110 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1111 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | ||
| 1112 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | ||
| 1113 | |||
| 1114 | /* Nests a policy which deny read access to all directories. */ | ||
| 1115 | ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR; | ||
| 1116 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 1117 | sizeof(ruleset_attr), 0); | ||
| 1118 | ASSERT_LE(0, ruleset_fd); | ||
| 1119 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1120 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | ||
| 1121 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); | ||
| 1122 | |||
| 1123 | /* Enforces a second time with the same ruleset. */ | ||
| 1124 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1125 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1126 | } | ||
| 1127 | |||
| 1128 | TEST_F_FORK(layout1, rule_on_mountpoint) | ||
| 1129 | { | ||
| 1130 | const struct rule rules[] = { | ||
| 1131 | { | ||
| 1132 | .path = dir_s1d1, | ||
| 1133 | .access = ACCESS_RO, | ||
| 1134 | }, | ||
| 1135 | { | ||
| 1136 | /* dir_s3d2 is a mount point. */ | ||
| 1137 | .path = dir_s3d2, | ||
| 1138 | .access = ACCESS_RO, | ||
| 1139 | }, | ||
| 1140 | {} | ||
| 1141 | }; | ||
| 1142 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1143 | |||
| 1144 | ASSERT_LE(0, ruleset_fd); | ||
| 1145 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1146 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1147 | |||
| 1148 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | ||
| 1149 | |||
| 1150 | ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY)); | ||
| 1151 | |||
| 1152 | ASSERT_EQ(EACCES, test_open(dir_s3d1, O_RDONLY)); | ||
| 1153 | ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); | ||
| 1154 | ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | TEST_F_FORK(layout1, rule_over_mountpoint) | ||
| 1158 | { | ||
| 1159 | const struct rule rules[] = { | ||
| 1160 | { | ||
| 1161 | .path = dir_s1d1, | ||
| 1162 | .access = ACCESS_RO, | ||
| 1163 | }, | ||
| 1164 | { | ||
| 1165 | /* dir_s3d2 is a mount point. */ | ||
| 1166 | .path = dir_s3d1, | ||
| 1167 | .access = ACCESS_RO, | ||
| 1168 | }, | ||
| 1169 | {} | ||
| 1170 | }; | ||
| 1171 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1172 | |||
| 1173 | ASSERT_LE(0, ruleset_fd); | ||
| 1174 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1175 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1176 | |||
| 1177 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | ||
| 1178 | |||
| 1179 | ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY)); | ||
| 1180 | |||
| 1181 | ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); | ||
| 1182 | ASSERT_EQ(0, test_open(dir_s3d2, O_RDONLY)); | ||
| 1183 | ASSERT_EQ(0, test_open(dir_s3d3, O_RDONLY)); | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | /* | ||
| 1187 | * This test verifies that we can apply a landlock rule on the root directory | ||
| 1188 | * (which might require special handling). | ||
| 1189 | */ | ||
| 1190 | TEST_F_FORK(layout1, rule_over_root_allow_then_deny) | ||
| 1191 | { | ||
| 1192 | struct rule rules[] = { | ||
| 1193 | { | ||
| 1194 | .path = "/", | ||
| 1195 | .access = ACCESS_RO, | ||
| 1196 | }, | ||
| 1197 | {} | ||
| 1198 | }; | ||
| 1199 | int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1200 | |||
| 1201 | ASSERT_LE(0, ruleset_fd); | ||
| 1202 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1203 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1204 | |||
| 1205 | /* Checks allowed access. */ | ||
| 1206 | ASSERT_EQ(0, test_open("/", O_RDONLY)); | ||
| 1207 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | ||
| 1208 | |||
| 1209 | rules[0].access = LANDLOCK_ACCESS_FS_READ_FILE; | ||
| 1210 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1211 | ASSERT_LE(0, ruleset_fd); | ||
| 1212 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1213 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1214 | |||
| 1215 | /* Checks denied access (on a directory). */ | ||
| 1216 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | ||
| 1217 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); | ||
| 1218 | } | ||
| 1219 | |||
| 1220 | TEST_F_FORK(layout1, rule_over_root_deny) | ||
| 1221 | { | ||
| 1222 | const struct rule rules[] = { | ||
| 1223 | { | ||
| 1224 | .path = "/", | ||
| 1225 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 1226 | }, | ||
| 1227 | {} | ||
| 1228 | }; | ||
| 1229 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1230 | |||
| 1231 | ASSERT_LE(0, ruleset_fd); | ||
| 1232 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1233 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1234 | |||
| 1235 | /* Checks denied access (on a directory). */ | ||
| 1236 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | ||
| 1237 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); | ||
| 1238 | } | ||
| 1239 | |||
| 1240 | TEST_F_FORK(layout1, rule_inside_mount_ns) | ||
| 1241 | { | ||
| 1242 | const struct rule rules[] = { | ||
| 1243 | { | ||
| 1244 | .path = "s3d3", | ||
| 1245 | .access = ACCESS_RO, | ||
| 1246 | }, | ||
| 1247 | {} | ||
| 1248 | }; | ||
| 1249 | int ruleset_fd; | ||
| 1250 | |||
| 1251 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1252 | ASSERT_EQ(0, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3)) { | ||
| 1253 | TH_LOG("Failed to pivot root: %s", strerror(errno)); | ||
| 1254 | }; | ||
| 1255 | ASSERT_EQ(0, chdir("/")); | ||
| 1256 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1257 | |||
| 1258 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1259 | ASSERT_LE(0, ruleset_fd); | ||
| 1260 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1261 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1262 | |||
| 1263 | ASSERT_EQ(0, test_open("s3d3", O_RDONLY)); | ||
| 1264 | ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | TEST_F_FORK(layout1, mount_and_pivot) | ||
| 1268 | { | ||
| 1269 | const struct rule rules[] = { | ||
| 1270 | { | ||
| 1271 | .path = dir_s3d2, | ||
| 1272 | .access = ACCESS_RO, | ||
| 1273 | }, | ||
| 1274 | {} | ||
| 1275 | }; | ||
| 1276 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1277 | |||
| 1278 | ASSERT_LE(0, ruleset_fd); | ||
| 1279 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1280 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1281 | |||
| 1282 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1283 | ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL)); | ||
| 1284 | ASSERT_EQ(EPERM, errno); | ||
| 1285 | ASSERT_EQ(-1, syscall(SYS_pivot_root, dir_s3d2, dir_s3d3)); | ||
| 1286 | ASSERT_EQ(EPERM, errno); | ||
| 1287 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1288 | } | ||
| 1289 | |||
| 1290 | TEST_F_FORK(layout1, move_mount) | ||
| 1291 | { | ||
| 1292 | const struct rule rules[] = { | ||
| 1293 | { | ||
| 1294 | .path = dir_s3d2, | ||
| 1295 | .access = ACCESS_RO, | ||
| 1296 | }, | ||
| 1297 | {} | ||
| 1298 | }; | ||
| 1299 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1300 | |||
| 1301 | ASSERT_LE(0, ruleset_fd); | ||
| 1302 | |||
| 1303 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1304 | ASSERT_EQ(0, syscall(SYS_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, | ||
| 1305 | dir_s1d2, 0)) { | ||
| 1306 | TH_LOG("Failed to move mount: %s", strerror(errno)); | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | ASSERT_EQ(0, syscall(SYS_move_mount, AT_FDCWD, dir_s1d2, AT_FDCWD, | ||
| 1310 | dir_s3d2, 0)); | ||
| 1311 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1312 | |||
| 1313 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1314 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1315 | |||
| 1316 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1317 | ASSERT_EQ(-1, syscall(SYS_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, | ||
| 1318 | dir_s1d2, 0)); | ||
| 1319 | ASSERT_EQ(EPERM, errno); | ||
| 1320 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1321 | } | ||
| 1322 | |||
| 1323 | TEST_F_FORK(layout1, release_inodes) | ||
| 1324 | { | ||
| 1325 | const struct rule rules[] = { | ||
| 1326 | { | ||
| 1327 | .path = dir_s1d1, | ||
| 1328 | .access = ACCESS_RO, | ||
| 1329 | }, | ||
| 1330 | { | ||
| 1331 | .path = dir_s3d2, | ||
| 1332 | .access = ACCESS_RO, | ||
| 1333 | }, | ||
| 1334 | { | ||
| 1335 | .path = dir_s3d3, | ||
| 1336 | .access = ACCESS_RO, | ||
| 1337 | }, | ||
| 1338 | {} | ||
| 1339 | }; | ||
| 1340 | const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); | ||
| 1341 | |||
| 1342 | ASSERT_LE(0, ruleset_fd); | ||
| 1343 | /* Unmount a file hierarchy while it is being used by a ruleset. */ | ||
| 1344 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1345 | ASSERT_EQ(0, umount(dir_s3d2)); | ||
| 1346 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 1347 | |||
| 1348 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1349 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1350 | |||
| 1351 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | ||
| 1352 | ASSERT_EQ(EACCES, test_open(dir_s3d2, O_RDONLY)); | ||
| 1353 | /* This dir_s3d3 would not be allowed and does not exist anyway. */ | ||
| 1354 | ASSERT_EQ(ENOENT, test_open(dir_s3d3, O_RDONLY)); | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | enum relative_access { | ||
| 1358 | REL_OPEN, | ||
| 1359 | REL_CHDIR, | ||
| 1360 | REL_CHROOT_ONLY, | ||
| 1361 | REL_CHROOT_CHDIR, | ||
| 1362 | }; | ||
| 1363 | |||
| 1364 | static void test_relative_path(struct __test_metadata *const _metadata, | ||
| 1365 | const enum relative_access rel) | ||
| 1366 | { | ||
| 1367 | /* | ||
| 1368 | * Common layer to check that chroot doesn't ignore it (i.e. a chroot | ||
| 1369 | * is not a disconnected root directory). | ||
| 1370 | */ | ||
| 1371 | const struct rule layer1_base[] = { | ||
| 1372 | { | ||
| 1373 | .path = TMP_DIR, | ||
| 1374 | .access = ACCESS_RO, | ||
| 1375 | }, | ||
| 1376 | {} | ||
| 1377 | }; | ||
| 1378 | const struct rule layer2_subs[] = { | ||
| 1379 | { | ||
| 1380 | .path = dir_s1d2, | ||
| 1381 | .access = ACCESS_RO, | ||
| 1382 | }, | ||
| 1383 | { | ||
| 1384 | .path = dir_s2d2, | ||
| 1385 | .access = ACCESS_RO, | ||
| 1386 | }, | ||
| 1387 | {} | ||
| 1388 | }; | ||
| 1389 | int dirfd, ruleset_fd; | ||
| 1390 | |||
| 1391 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); | ||
| 1392 | ASSERT_LE(0, ruleset_fd); | ||
| 1393 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1394 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1395 | |||
| 1396 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_subs); | ||
| 1397 | |||
| 1398 | ASSERT_LE(0, ruleset_fd); | ||
| 1399 | switch (rel) { | ||
| 1400 | case REL_OPEN: | ||
| 1401 | case REL_CHDIR: | ||
| 1402 | break; | ||
| 1403 | case REL_CHROOT_ONLY: | ||
| 1404 | ASSERT_EQ(0, chdir(dir_s2d2)); | ||
| 1405 | break; | ||
| 1406 | case REL_CHROOT_CHDIR: | ||
| 1407 | ASSERT_EQ(0, chdir(dir_s1d2)); | ||
| 1408 | break; | ||
| 1409 | default: | ||
| 1410 | ASSERT_TRUE(false); | ||
| 1411 | return; | ||
| 1412 | } | ||
| 1413 | |||
| 1414 | set_cap(_metadata, CAP_SYS_CHROOT); | ||
| 1415 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1416 | |||
| 1417 | switch (rel) { | ||
| 1418 | case REL_OPEN: | ||
| 1419 | dirfd = open(dir_s1d2, O_DIRECTORY); | ||
| 1420 | ASSERT_LE(0, dirfd); | ||
| 1421 | break; | ||
| 1422 | case REL_CHDIR: | ||
| 1423 | ASSERT_EQ(0, chdir(dir_s1d2)); | ||
| 1424 | dirfd = AT_FDCWD; | ||
| 1425 | break; | ||
| 1426 | case REL_CHROOT_ONLY: | ||
| 1427 | /* Do chroot into dir_s1d2 (relative to dir_s2d2). */ | ||
| 1428 | ASSERT_EQ(0, chroot("../../s1d1/s1d2")) { | ||
| 1429 | TH_LOG("Failed to chroot: %s", strerror(errno)); | ||
| 1430 | } | ||
| 1431 | dirfd = AT_FDCWD; | ||
| 1432 | break; | ||
| 1433 | case REL_CHROOT_CHDIR: | ||
| 1434 | /* Do chroot into dir_s1d2. */ | ||
| 1435 | ASSERT_EQ(0, chroot(".")) { | ||
| 1436 | TH_LOG("Failed to chroot: %s", strerror(errno)); | ||
| 1437 | } | ||
| 1438 | dirfd = AT_FDCWD; | ||
| 1439 | break; | ||
| 1440 | } | ||
| 1441 | |||
| 1442 | ASSERT_EQ((rel == REL_CHROOT_CHDIR) ? 0 : EACCES, | ||
| 1443 | test_open_rel(dirfd, "..", O_RDONLY)); | ||
| 1444 | ASSERT_EQ(0, test_open_rel(dirfd, ".", O_RDONLY)); | ||
| 1445 | |||
| 1446 | if (rel == REL_CHROOT_ONLY) { | ||
| 1447 | /* The current directory is dir_s2d2. */ | ||
| 1448 | ASSERT_EQ(0, test_open_rel(dirfd, "./s2d3", O_RDONLY)); | ||
| 1449 | } else { | ||
| 1450 | /* The current directory is dir_s1d2. */ | ||
| 1451 | ASSERT_EQ(0, test_open_rel(dirfd, "./s1d3", O_RDONLY)); | ||
| 1452 | } | ||
| 1453 | |||
| 1454 | if (rel == REL_CHROOT_ONLY || rel == REL_CHROOT_CHDIR) { | ||
| 1455 | /* Checks the root dir_s1d2. */ | ||
| 1456 | ASSERT_EQ(0, test_open_rel(dirfd, "/..", O_RDONLY)); | ||
| 1457 | ASSERT_EQ(0, test_open_rel(dirfd, "/", O_RDONLY)); | ||
| 1458 | ASSERT_EQ(0, test_open_rel(dirfd, "/f1", O_RDONLY)); | ||
| 1459 | ASSERT_EQ(0, test_open_rel(dirfd, "/s1d3", O_RDONLY)); | ||
| 1460 | } | ||
| 1461 | |||
| 1462 | if (rel != REL_CHROOT_CHDIR) { | ||
| 1463 | ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s1d1", O_RDONLY)); | ||
| 1464 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2", O_RDONLY)); | ||
| 1465 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s1d1/s1d2/s1d3", O_RDONLY)); | ||
| 1466 | |||
| 1467 | ASSERT_EQ(EACCES, test_open_rel(dirfd, "../../s2d1", O_RDONLY)); | ||
| 1468 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2", O_RDONLY)); | ||
| 1469 | ASSERT_EQ(0, test_open_rel(dirfd, "../../s2d1/s2d2/s2d3", O_RDONLY)); | ||
| 1470 | } | ||
| 1471 | |||
| 1472 | if (rel == REL_OPEN) | ||
| 1473 | ASSERT_EQ(0, close(dirfd)); | ||
| 1474 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1475 | } | ||
| 1476 | |||
| 1477 | TEST_F_FORK(layout1, relative_open) | ||
| 1478 | { | ||
| 1479 | test_relative_path(_metadata, REL_OPEN); | ||
| 1480 | } | ||
| 1481 | |||
| 1482 | TEST_F_FORK(layout1, relative_chdir) | ||
| 1483 | { | ||
| 1484 | test_relative_path(_metadata, REL_CHDIR); | ||
| 1485 | } | ||
| 1486 | |||
| 1487 | TEST_F_FORK(layout1, relative_chroot_only) | ||
| 1488 | { | ||
| 1489 | test_relative_path(_metadata, REL_CHROOT_ONLY); | ||
| 1490 | } | ||
| 1491 | |||
| 1492 | TEST_F_FORK(layout1, relative_chroot_chdir) | ||
| 1493 | { | ||
| 1494 | test_relative_path(_metadata, REL_CHROOT_CHDIR); | ||
| 1495 | } | ||
| 1496 | |||
| 1497 | static void copy_binary(struct __test_metadata *const _metadata, | ||
| 1498 | const char *const dst_path) | ||
| 1499 | { | ||
| 1500 | int dst_fd, src_fd; | ||
| 1501 | struct stat statbuf; | ||
| 1502 | |||
| 1503 | dst_fd = open(dst_path, O_WRONLY | O_TRUNC | O_CLOEXEC); | ||
| 1504 | ASSERT_LE(0, dst_fd) { | ||
| 1505 | TH_LOG("Failed to open \"%s\": %s", dst_path, | ||
| 1506 | strerror(errno)); | ||
| 1507 | } | ||
| 1508 | src_fd = open(BINARY_PATH, O_RDONLY | O_CLOEXEC); | ||
| 1509 | ASSERT_LE(0, src_fd) { | ||
| 1510 | TH_LOG("Failed to open \"" BINARY_PATH "\": %s", | ||
| 1511 | strerror(errno)); | ||
| 1512 | } | ||
| 1513 | ASSERT_EQ(0, fstat(src_fd, &statbuf)); | ||
| 1514 | ASSERT_EQ(statbuf.st_size, sendfile(dst_fd, src_fd, 0, | ||
| 1515 | statbuf.st_size)); | ||
| 1516 | ASSERT_EQ(0, close(src_fd)); | ||
| 1517 | ASSERT_EQ(0, close(dst_fd)); | ||
| 1518 | } | ||
| 1519 | |||
| 1520 | static void test_execute(struct __test_metadata *const _metadata, | ||
| 1521 | const int err, const char *const path) | ||
| 1522 | { | ||
| 1523 | int status; | ||
| 1524 | char *const argv[] = {(char *)path, NULL}; | ||
| 1525 | const pid_t child = fork(); | ||
| 1526 | |||
| 1527 | ASSERT_LE(0, child); | ||
| 1528 | if (child == 0) { | ||
| 1529 | ASSERT_EQ(err ? -1 : 0, execve(path, argv, NULL)) { | ||
| 1530 | TH_LOG("Failed to execute \"%s\": %s", path, | ||
| 1531 | strerror(errno)); | ||
| 1532 | }; | ||
| 1533 | ASSERT_EQ(err, errno); | ||
| 1534 | _exit(_metadata->passed ? 2 : 1); | ||
| 1535 | return; | ||
| 1536 | } | ||
| 1537 | ASSERT_EQ(child, waitpid(child, &status, 0)); | ||
| 1538 | ASSERT_EQ(1, WIFEXITED(status)); | ||
| 1539 | ASSERT_EQ(err ? 2 : 0, WEXITSTATUS(status)) { | ||
| 1540 | TH_LOG("Unexpected return code for \"%s\": %s", path, | ||
| 1541 | strerror(errno)); | ||
| 1542 | }; | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | TEST_F_FORK(layout1, execute) | ||
| 1546 | { | ||
| 1547 | const struct rule rules[] = { | ||
| 1548 | { | ||
| 1549 | .path = dir_s1d2, | ||
| 1550 | .access = LANDLOCK_ACCESS_FS_EXECUTE, | ||
| 1551 | }, | ||
| 1552 | {} | ||
| 1553 | }; | ||
| 1554 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1555 | rules); | ||
| 1556 | |||
| 1557 | ASSERT_LE(0, ruleset_fd); | ||
| 1558 | copy_binary(_metadata, file1_s1d1); | ||
| 1559 | copy_binary(_metadata, file1_s1d2); | ||
| 1560 | copy_binary(_metadata, file1_s1d3); | ||
| 1561 | |||
| 1562 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1563 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1564 | |||
| 1565 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | ||
| 1566 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | ||
| 1567 | test_execute(_metadata, EACCES, file1_s1d1); | ||
| 1568 | |||
| 1569 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); | ||
| 1570 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 1571 | test_execute(_metadata, 0, file1_s1d2); | ||
| 1572 | |||
| 1573 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | ||
| 1574 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 1575 | test_execute(_metadata, 0, file1_s1d3); | ||
| 1576 | } | ||
| 1577 | |||
| 1578 | TEST_F_FORK(layout1, link) | ||
| 1579 | { | ||
| 1580 | const struct rule rules[] = { | ||
| 1581 | { | ||
| 1582 | .path = dir_s1d2, | ||
| 1583 | .access = LANDLOCK_ACCESS_FS_MAKE_REG, | ||
| 1584 | }, | ||
| 1585 | {} | ||
| 1586 | }; | ||
| 1587 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1588 | rules); | ||
| 1589 | |||
| 1590 | ASSERT_LE(0, ruleset_fd); | ||
| 1591 | |||
| 1592 | ASSERT_EQ(0, unlink(file1_s1d1)); | ||
| 1593 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 1594 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 1595 | |||
| 1596 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1597 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1598 | |||
| 1599 | ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); | ||
| 1600 | ASSERT_EQ(EACCES, errno); | ||
| 1601 | /* Denies linking because of reparenting. */ | ||
| 1602 | ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2)); | ||
| 1603 | ASSERT_EQ(EXDEV, errno); | ||
| 1604 | ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3)); | ||
| 1605 | ASSERT_EQ(EXDEV, errno); | ||
| 1606 | |||
| 1607 | ASSERT_EQ(0, link(file2_s1d2, file1_s1d2)); | ||
| 1608 | ASSERT_EQ(0, link(file2_s1d3, file1_s1d3)); | ||
| 1609 | } | ||
| 1610 | |||
| 1611 | TEST_F_FORK(layout1, rename_file) | ||
| 1612 | { | ||
| 1613 | const struct rule rules[] = { | ||
| 1614 | { | ||
| 1615 | .path = dir_s1d3, | ||
| 1616 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | ||
| 1617 | }, | ||
| 1618 | { | ||
| 1619 | .path = dir_s2d2, | ||
| 1620 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | ||
| 1621 | }, | ||
| 1622 | {} | ||
| 1623 | }; | ||
| 1624 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1625 | rules); | ||
| 1626 | |||
| 1627 | ASSERT_LE(0, ruleset_fd); | ||
| 1628 | |||
| 1629 | ASSERT_EQ(0, unlink(file1_s1d1)); | ||
| 1630 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 1631 | |||
| 1632 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1633 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1634 | |||
| 1635 | /* | ||
| 1636 | * Tries to replace a file, from a directory that allows file removal, | ||
| 1637 | * but to a different directory (which also allows file removal). | ||
| 1638 | */ | ||
| 1639 | ASSERT_EQ(-1, rename(file1_s2d3, file1_s1d3)); | ||
| 1640 | ASSERT_EQ(EXDEV, errno); | ||
| 1641 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d3, | ||
| 1642 | RENAME_EXCHANGE)); | ||
| 1643 | ASSERT_EQ(EXDEV, errno); | ||
| 1644 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3, | ||
| 1645 | RENAME_EXCHANGE)); | ||
| 1646 | ASSERT_EQ(EXDEV, errno); | ||
| 1647 | |||
| 1648 | /* | ||
| 1649 | * Tries to replace a file, from a directory that denies file removal, | ||
| 1650 | * to a different directory (which allows file removal). | ||
| 1651 | */ | ||
| 1652 | ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); | ||
| 1653 | ASSERT_EQ(EXDEV, errno); | ||
| 1654 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file1_s1d3, | ||
| 1655 | RENAME_EXCHANGE)); | ||
| 1656 | ASSERT_EQ(EXDEV, errno); | ||
| 1657 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s1d3, | ||
| 1658 | RENAME_EXCHANGE)); | ||
| 1659 | ASSERT_EQ(EXDEV, errno); | ||
| 1660 | |||
| 1661 | /* Exchanges files and directories that partially allow removal. */ | ||
| 1662 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s2d1, | ||
| 1663 | RENAME_EXCHANGE)); | ||
| 1664 | ASSERT_EQ(EACCES, errno); | ||
| 1665 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, dir_s2d2, | ||
| 1666 | RENAME_EXCHANGE)); | ||
| 1667 | ASSERT_EQ(EACCES, errno); | ||
| 1668 | |||
| 1669 | /* Renames files with different parents. */ | ||
| 1670 | ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2)); | ||
| 1671 | ASSERT_EQ(EXDEV, errno); | ||
| 1672 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 1673 | ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3)); | ||
| 1674 | ASSERT_EQ(EXDEV, errno); | ||
| 1675 | |||
| 1676 | /* Exchanges and renames files with same parent. */ | ||
| 1677 | ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s2d3, | ||
| 1678 | RENAME_EXCHANGE)); | ||
| 1679 | ASSERT_EQ(0, rename(file2_s2d3, file1_s2d3)); | ||
| 1680 | |||
| 1681 | /* Exchanges files and directories with same parent, twice. */ | ||
| 1682 | ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, | ||
| 1683 | RENAME_EXCHANGE)); | ||
| 1684 | ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s2d3, | ||
| 1685 | RENAME_EXCHANGE)); | ||
| 1686 | } | ||
| 1687 | |||
| 1688 | TEST_F_FORK(layout1, rename_dir) | ||
| 1689 | { | ||
| 1690 | const struct rule rules[] = { | ||
| 1691 | { | ||
| 1692 | .path = dir_s1d2, | ||
| 1693 | .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, | ||
| 1694 | }, | ||
| 1695 | { | ||
| 1696 | .path = dir_s2d1, | ||
| 1697 | .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, | ||
| 1698 | }, | ||
| 1699 | {} | ||
| 1700 | }; | ||
| 1701 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1702 | rules); | ||
| 1703 | |||
| 1704 | ASSERT_LE(0, ruleset_fd); | ||
| 1705 | |||
| 1706 | /* Empties dir_s1d3 to allow renaming. */ | ||
| 1707 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 1708 | ASSERT_EQ(0, unlink(file2_s1d3)); | ||
| 1709 | |||
| 1710 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1711 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1712 | |||
| 1713 | /* Exchanges and renames directory to a different parent. */ | ||
| 1714 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, | ||
| 1715 | RENAME_EXCHANGE)); | ||
| 1716 | ASSERT_EQ(EXDEV, errno); | ||
| 1717 | ASSERT_EQ(-1, rename(dir_s2d3, dir_s1d3)); | ||
| 1718 | ASSERT_EQ(EXDEV, errno); | ||
| 1719 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3, | ||
| 1720 | RENAME_EXCHANGE)); | ||
| 1721 | ASSERT_EQ(EXDEV, errno); | ||
| 1722 | |||
| 1723 | /* | ||
| 1724 | * Exchanges directory to the same parent, which doesn't allow | ||
| 1725 | * directory removal. | ||
| 1726 | */ | ||
| 1727 | ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d1, AT_FDCWD, dir_s2d1, | ||
| 1728 | RENAME_EXCHANGE)); | ||
| 1729 | ASSERT_EQ(EACCES, errno); | ||
| 1730 | ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s1d2, | ||
| 1731 | RENAME_EXCHANGE)); | ||
| 1732 | ASSERT_EQ(EACCES, errno); | ||
| 1733 | |||
| 1734 | /* | ||
| 1735 | * Exchanges and renames directory to the same parent, which allows | ||
| 1736 | * directory removal. | ||
| 1737 | */ | ||
| 1738 | ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s1d2, | ||
| 1739 | RENAME_EXCHANGE)); | ||
| 1740 | ASSERT_EQ(0, unlink(dir_s1d3)); | ||
| 1741 | ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); | ||
| 1742 | ASSERT_EQ(0, rename(file1_s1d2, dir_s1d3)); | ||
| 1743 | ASSERT_EQ(0, rmdir(dir_s1d3)); | ||
| 1744 | } | ||
| 1745 | |||
| 1746 | TEST_F_FORK(layout1, remove_dir) | ||
| 1747 | { | ||
| 1748 | const struct rule rules[] = { | ||
| 1749 | { | ||
| 1750 | .path = dir_s1d2, | ||
| 1751 | .access = LANDLOCK_ACCESS_FS_REMOVE_DIR, | ||
| 1752 | }, | ||
| 1753 | {} | ||
| 1754 | }; | ||
| 1755 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1756 | rules); | ||
| 1757 | |||
| 1758 | ASSERT_LE(0, ruleset_fd); | ||
| 1759 | |||
| 1760 | ASSERT_EQ(0, unlink(file1_s1d1)); | ||
| 1761 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 1762 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 1763 | ASSERT_EQ(0, unlink(file2_s1d3)); | ||
| 1764 | |||
| 1765 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1766 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1767 | |||
| 1768 | ASSERT_EQ(0, rmdir(dir_s1d3)); | ||
| 1769 | ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); | ||
| 1770 | ASSERT_EQ(0, unlinkat(AT_FDCWD, dir_s1d3, AT_REMOVEDIR)); | ||
| 1771 | |||
| 1772 | /* dir_s1d2 itself cannot be removed. */ | ||
| 1773 | ASSERT_EQ(-1, rmdir(dir_s1d2)); | ||
| 1774 | ASSERT_EQ(EACCES, errno); | ||
| 1775 | ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d2, AT_REMOVEDIR)); | ||
| 1776 | ASSERT_EQ(EACCES, errno); | ||
| 1777 | ASSERT_EQ(-1, rmdir(dir_s1d1)); | ||
| 1778 | ASSERT_EQ(EACCES, errno); | ||
| 1779 | ASSERT_EQ(-1, unlinkat(AT_FDCWD, dir_s1d1, AT_REMOVEDIR)); | ||
| 1780 | ASSERT_EQ(EACCES, errno); | ||
| 1781 | } | ||
| 1782 | |||
| 1783 | TEST_F_FORK(layout1, remove_file) | ||
| 1784 | { | ||
| 1785 | const struct rule rules[] = { | ||
| 1786 | { | ||
| 1787 | .path = dir_s1d2, | ||
| 1788 | .access = LANDLOCK_ACCESS_FS_REMOVE_FILE, | ||
| 1789 | }, | ||
| 1790 | {} | ||
| 1791 | }; | ||
| 1792 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1793 | rules); | ||
| 1794 | |||
| 1795 | ASSERT_LE(0, ruleset_fd); | ||
| 1796 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1797 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1798 | |||
| 1799 | ASSERT_EQ(-1, unlink(file1_s1d1)); | ||
| 1800 | ASSERT_EQ(EACCES, errno); | ||
| 1801 | ASSERT_EQ(-1, unlinkat(AT_FDCWD, file1_s1d1, 0)); | ||
| 1802 | ASSERT_EQ(EACCES, errno); | ||
| 1803 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 1804 | ASSERT_EQ(0, unlinkat(AT_FDCWD, file1_s1d3, 0)); | ||
| 1805 | } | ||
| 1806 | |||
| 1807 | static void test_make_file(struct __test_metadata *const _metadata, | ||
| 1808 | const __u64 access, const mode_t mode, const dev_t dev) | ||
| 1809 | { | ||
| 1810 | const struct rule rules[] = { | ||
| 1811 | { | ||
| 1812 | .path = dir_s1d2, | ||
| 1813 | .access = access, | ||
| 1814 | }, | ||
| 1815 | {} | ||
| 1816 | }; | ||
| 1817 | const int ruleset_fd = create_ruleset(_metadata, access, rules); | ||
| 1818 | |||
| 1819 | ASSERT_LE(0, ruleset_fd); | ||
| 1820 | |||
| 1821 | ASSERT_EQ(0, unlink(file1_s1d1)); | ||
| 1822 | ASSERT_EQ(0, unlink(file2_s1d1)); | ||
| 1823 | ASSERT_EQ(0, mknod(file2_s1d1, mode | 0400, dev)) { | ||
| 1824 | TH_LOG("Failed to make file \"%s\": %s", | ||
| 1825 | file2_s1d1, strerror(errno)); | ||
| 1826 | }; | ||
| 1827 | |||
| 1828 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 1829 | ASSERT_EQ(0, unlink(file2_s1d2)); | ||
| 1830 | |||
| 1831 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 1832 | ASSERT_EQ(0, unlink(file2_s1d3)); | ||
| 1833 | |||
| 1834 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1835 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1836 | |||
| 1837 | ASSERT_EQ(-1, mknod(file1_s1d1, mode | 0400, dev)); | ||
| 1838 | ASSERT_EQ(EACCES, errno); | ||
| 1839 | ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); | ||
| 1840 | ASSERT_EQ(EACCES, errno); | ||
| 1841 | ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); | ||
| 1842 | ASSERT_EQ(EACCES, errno); | ||
| 1843 | |||
| 1844 | ASSERT_EQ(0, mknod(file1_s1d2, mode | 0400, dev)) { | ||
| 1845 | TH_LOG("Failed to make file \"%s\": %s", | ||
| 1846 | file1_s1d2, strerror(errno)); | ||
| 1847 | }; | ||
| 1848 | ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); | ||
| 1849 | ASSERT_EQ(0, unlink(file2_s1d2)); | ||
| 1850 | ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2)); | ||
| 1851 | |||
| 1852 | ASSERT_EQ(0, mknod(file1_s1d3, mode | 0400, dev)); | ||
| 1853 | ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); | ||
| 1854 | ASSERT_EQ(0, unlink(file2_s1d3)); | ||
| 1855 | ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3)); | ||
| 1856 | } | ||
| 1857 | |||
| 1858 | TEST_F_FORK(layout1, make_char) | ||
| 1859 | { | ||
| 1860 | /* Creates a /dev/null device. */ | ||
| 1861 | set_cap(_metadata, CAP_MKNOD); | ||
| 1862 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_CHAR, S_IFCHR, | ||
| 1863 | makedev(1, 3)); | ||
| 1864 | } | ||
| 1865 | |||
| 1866 | TEST_F_FORK(layout1, make_block) | ||
| 1867 | { | ||
| 1868 | /* Creates a /dev/loop0 device. */ | ||
| 1869 | set_cap(_metadata, CAP_MKNOD); | ||
| 1870 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_BLOCK, S_IFBLK, | ||
| 1871 | makedev(7, 0)); | ||
| 1872 | } | ||
| 1873 | |||
| 1874 | TEST_F_FORK(layout1, make_reg_1) | ||
| 1875 | { | ||
| 1876 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, S_IFREG, 0); | ||
| 1877 | } | ||
| 1878 | |||
| 1879 | TEST_F_FORK(layout1, make_reg_2) | ||
| 1880 | { | ||
| 1881 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, 0, 0); | ||
| 1882 | } | ||
| 1883 | |||
| 1884 | TEST_F_FORK(layout1, make_sock) | ||
| 1885 | { | ||
| 1886 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_SOCK, S_IFSOCK, 0); | ||
| 1887 | } | ||
| 1888 | |||
| 1889 | TEST_F_FORK(layout1, make_fifo) | ||
| 1890 | { | ||
| 1891 | test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_FIFO, S_IFIFO, 0); | ||
| 1892 | } | ||
| 1893 | |||
| 1894 | TEST_F_FORK(layout1, make_sym) | ||
| 1895 | { | ||
| 1896 | const struct rule rules[] = { | ||
| 1897 | { | ||
| 1898 | .path = dir_s1d2, | ||
| 1899 | .access = LANDLOCK_ACCESS_FS_MAKE_SYM, | ||
| 1900 | }, | ||
| 1901 | {} | ||
| 1902 | }; | ||
| 1903 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1904 | rules); | ||
| 1905 | |||
| 1906 | ASSERT_LE(0, ruleset_fd); | ||
| 1907 | |||
| 1908 | ASSERT_EQ(0, unlink(file1_s1d1)); | ||
| 1909 | ASSERT_EQ(0, unlink(file2_s1d1)); | ||
| 1910 | ASSERT_EQ(0, symlink("none", file2_s1d1)); | ||
| 1911 | |||
| 1912 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 1913 | ASSERT_EQ(0, unlink(file2_s1d2)); | ||
| 1914 | |||
| 1915 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 1916 | ASSERT_EQ(0, unlink(file2_s1d3)); | ||
| 1917 | |||
| 1918 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1919 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1920 | |||
| 1921 | ASSERT_EQ(-1, symlink("none", file1_s1d1)); | ||
| 1922 | ASSERT_EQ(EACCES, errno); | ||
| 1923 | ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); | ||
| 1924 | ASSERT_EQ(EACCES, errno); | ||
| 1925 | ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1)); | ||
| 1926 | ASSERT_EQ(EACCES, errno); | ||
| 1927 | |||
| 1928 | ASSERT_EQ(0, symlink("none", file1_s1d2)); | ||
| 1929 | ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); | ||
| 1930 | ASSERT_EQ(0, unlink(file2_s1d2)); | ||
| 1931 | ASSERT_EQ(0, rename(file1_s1d2, file2_s1d2)); | ||
| 1932 | |||
| 1933 | ASSERT_EQ(0, symlink("none", file1_s1d3)); | ||
| 1934 | ASSERT_EQ(0, link(file1_s1d3, file2_s1d3)); | ||
| 1935 | ASSERT_EQ(0, unlink(file2_s1d3)); | ||
| 1936 | ASSERT_EQ(0, rename(file1_s1d3, file2_s1d3)); | ||
| 1937 | } | ||
| 1938 | |||
| 1939 | TEST_F_FORK(layout1, make_dir) | ||
| 1940 | { | ||
| 1941 | const struct rule rules[] = { | ||
| 1942 | { | ||
| 1943 | .path = dir_s1d2, | ||
| 1944 | .access = LANDLOCK_ACCESS_FS_MAKE_DIR, | ||
| 1945 | }, | ||
| 1946 | {} | ||
| 1947 | }; | ||
| 1948 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 1949 | rules); | ||
| 1950 | |||
| 1951 | ASSERT_LE(0, ruleset_fd); | ||
| 1952 | |||
| 1953 | ASSERT_EQ(0, unlink(file1_s1d1)); | ||
| 1954 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 1955 | ASSERT_EQ(0, unlink(file1_s1d3)); | ||
| 1956 | |||
| 1957 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1958 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1959 | |||
| 1960 | /* Uses file_* as directory names. */ | ||
| 1961 | ASSERT_EQ(-1, mkdir(file1_s1d1, 0700)); | ||
| 1962 | ASSERT_EQ(EACCES, errno); | ||
| 1963 | ASSERT_EQ(0, mkdir(file1_s1d2, 0700)); | ||
| 1964 | ASSERT_EQ(0, mkdir(file1_s1d3, 0700)); | ||
| 1965 | } | ||
| 1966 | |||
| 1967 | static int open_proc_fd(struct __test_metadata *const _metadata, const int fd, | ||
| 1968 | const int open_flags) | ||
| 1969 | { | ||
| 1970 | static const char path_template[] = "/proc/self/fd/%d"; | ||
| 1971 | char procfd_path[sizeof(path_template) + 10]; | ||
| 1972 | const int procfd_path_size = snprintf(procfd_path, sizeof(procfd_path), | ||
| 1973 | path_template, fd); | ||
| 1974 | |||
| 1975 | ASSERT_LT(procfd_path_size, sizeof(procfd_path)); | ||
| 1976 | return open(procfd_path, open_flags); | ||
| 1977 | } | ||
| 1978 | |||
| 1979 | TEST_F_FORK(layout1, proc_unlinked_file) | ||
| 1980 | { | ||
| 1981 | const struct rule rules[] = { | ||
| 1982 | { | ||
| 1983 | .path = file1_s1d2, | ||
| 1984 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 1985 | }, | ||
| 1986 | {} | ||
| 1987 | }; | ||
| 1988 | int reg_fd, proc_fd; | ||
| 1989 | const int ruleset_fd = create_ruleset(_metadata, | ||
| 1990 | LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 1991 | LANDLOCK_ACCESS_FS_WRITE_FILE, rules); | ||
| 1992 | |||
| 1993 | ASSERT_LE(0, ruleset_fd); | ||
| 1994 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 1995 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 1996 | |||
| 1997 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); | ||
| 1998 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 1999 | reg_fd = open(file1_s1d2, O_RDONLY | O_CLOEXEC); | ||
| 2000 | ASSERT_LE(0, reg_fd); | ||
| 2001 | ASSERT_EQ(0, unlink(file1_s1d2)); | ||
| 2002 | |||
| 2003 | proc_fd = open_proc_fd(_metadata, reg_fd, O_RDONLY | O_CLOEXEC); | ||
| 2004 | ASSERT_LE(0, proc_fd); | ||
| 2005 | ASSERT_EQ(0, close(proc_fd)); | ||
| 2006 | |||
| 2007 | proc_fd = open_proc_fd(_metadata, reg_fd, O_RDWR | O_CLOEXEC); | ||
| 2008 | ASSERT_EQ(-1, proc_fd) { | ||
| 2009 | TH_LOG("Successfully opened /proc/self/fd/%d: %s", | ||
| 2010 | reg_fd, strerror(errno)); | ||
| 2011 | } | ||
| 2012 | ASSERT_EQ(EACCES, errno); | ||
| 2013 | |||
| 2014 | ASSERT_EQ(0, close(reg_fd)); | ||
| 2015 | } | ||
| 2016 | |||
| 2017 | TEST_F_FORK(layout1, proc_pipe) | ||
| 2018 | { | ||
| 2019 | int proc_fd; | ||
| 2020 | int pipe_fds[2]; | ||
| 2021 | char buf = '\0'; | ||
| 2022 | const struct rule rules[] = { | ||
| 2023 | { | ||
| 2024 | .path = dir_s1d2, | ||
| 2025 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 2026 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2027 | }, | ||
| 2028 | {} | ||
| 2029 | }; | ||
| 2030 | /* Limits read and write access to files tied to the filesystem. */ | ||
| 2031 | const int ruleset_fd = create_ruleset(_metadata, rules[0].access, | ||
| 2032 | rules); | ||
| 2033 | |||
| 2034 | ASSERT_LE(0, ruleset_fd); | ||
| 2035 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2036 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2037 | |||
| 2038 | /* Checks enforcement for normal files. */ | ||
| 2039 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); | ||
| 2040 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDWR)); | ||
| 2041 | |||
| 2042 | /* Checks access to pipes through FD. */ | ||
| 2043 | ASSERT_EQ(0, pipe2(pipe_fds, O_CLOEXEC)); | ||
| 2044 | ASSERT_EQ(1, write(pipe_fds[1], ".", 1)) { | ||
| 2045 | TH_LOG("Failed to write in pipe: %s", strerror(errno)); | ||
| 2046 | } | ||
| 2047 | ASSERT_EQ(1, read(pipe_fds[0], &buf, 1)); | ||
| 2048 | ASSERT_EQ('.', buf); | ||
| 2049 | |||
| 2050 | /* Checks write access to pipe through /proc/self/fd . */ | ||
| 2051 | proc_fd = open_proc_fd(_metadata, pipe_fds[1], O_WRONLY | O_CLOEXEC); | ||
| 2052 | ASSERT_LE(0, proc_fd); | ||
| 2053 | ASSERT_EQ(1, write(proc_fd, ".", 1)) { | ||
| 2054 | TH_LOG("Failed to write through /proc/self/fd/%d: %s", | ||
| 2055 | pipe_fds[1], strerror(errno)); | ||
| 2056 | } | ||
| 2057 | ASSERT_EQ(0, close(proc_fd)); | ||
| 2058 | |||
| 2059 | /* Checks read access to pipe through /proc/self/fd . */ | ||
| 2060 | proc_fd = open_proc_fd(_metadata, pipe_fds[0], O_RDONLY | O_CLOEXEC); | ||
| 2061 | ASSERT_LE(0, proc_fd); | ||
| 2062 | buf = '\0'; | ||
| 2063 | ASSERT_EQ(1, read(proc_fd, &buf, 1)) { | ||
| 2064 | TH_LOG("Failed to read through /proc/self/fd/%d: %s", | ||
| 2065 | pipe_fds[1], strerror(errno)); | ||
| 2066 | } | ||
| 2067 | ASSERT_EQ(0, close(proc_fd)); | ||
| 2068 | |||
| 2069 | ASSERT_EQ(0, close(pipe_fds[0])); | ||
| 2070 | ASSERT_EQ(0, close(pipe_fds[1])); | ||
| 2071 | } | ||
| 2072 | |||
| 2073 | FIXTURE(layout1_bind) { | ||
| 2074 | }; | ||
| 2075 | |||
| 2076 | FIXTURE_SETUP(layout1_bind) | ||
| 2077 | { | ||
| 2078 | prepare_layout(_metadata); | ||
| 2079 | |||
| 2080 | create_layout1(_metadata); | ||
| 2081 | |||
| 2082 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2083 | ASSERT_EQ(0, mount(dir_s1d2, dir_s2d2, NULL, MS_BIND, NULL)); | ||
| 2084 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2085 | } | ||
| 2086 | |||
| 2087 | FIXTURE_TEARDOWN(layout1_bind) | ||
| 2088 | { | ||
| 2089 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2090 | EXPECT_EQ(0, umount(dir_s2d2)); | ||
| 2091 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2092 | |||
| 2093 | remove_layout1(_metadata); | ||
| 2094 | |||
| 2095 | cleanup_layout(_metadata); | ||
| 2096 | } | ||
| 2097 | |||
| 2098 | static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3"; | ||
| 2099 | static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1"; | ||
| 2100 | |||
| 2101 | /* | ||
| 2102 | * layout1_bind hierarchy: | ||
| 2103 | * | ||
| 2104 | * tmp | ||
| 2105 | * ├── s1d1 | ||
| 2106 | * │ ├── f1 | ||
| 2107 | * │ ├── f2 | ||
| 2108 | * │ └── s1d2 | ||
| 2109 | * │ ├── f1 | ||
| 2110 | * │ ├── f2 | ||
| 2111 | * │ └── s1d3 | ||
| 2112 | * │ ├── f1 | ||
| 2113 | * │ └── f2 | ||
| 2114 | * ├── s2d1 | ||
| 2115 | * │ ├── f1 | ||
| 2116 | * │ └── s2d2 | ||
| 2117 | * │ ├── f1 | ||
| 2118 | * │ ├── f2 | ||
| 2119 | * │ └── s1d3 | ||
| 2120 | * │ ├── f1 | ||
| 2121 | * │ └── f2 | ||
| 2122 | * └── s3d1 | ||
| 2123 | * └── s3d2 | ||
| 2124 | * └── s3d3 | ||
| 2125 | */ | ||
| 2126 | |||
| 2127 | TEST_F_FORK(layout1_bind, no_restriction) | ||
| 2128 | { | ||
| 2129 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); | ||
| 2130 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | ||
| 2131 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); | ||
| 2132 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 2133 | ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); | ||
| 2134 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 2135 | |||
| 2136 | ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY)); | ||
| 2137 | ASSERT_EQ(0, test_open(file1_s2d1, O_RDONLY)); | ||
| 2138 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY)); | ||
| 2139 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDONLY)); | ||
| 2140 | ASSERT_EQ(ENOENT, test_open(dir_s2d3, O_RDONLY)); | ||
| 2141 | ASSERT_EQ(ENOENT, test_open(file1_s2d3, O_RDONLY)); | ||
| 2142 | |||
| 2143 | ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY)); | ||
| 2144 | ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY)); | ||
| 2145 | |||
| 2146 | ASSERT_EQ(0, test_open(dir_s3d1, O_RDONLY)); | ||
| 2147 | } | ||
| 2148 | |||
| 2149 | TEST_F_FORK(layout1_bind, same_content_same_file) | ||
| 2150 | { | ||
| 2151 | /* | ||
| 2152 | * Sets access right on parent directories of both source and | ||
| 2153 | * destination mount points. | ||
| 2154 | */ | ||
| 2155 | const struct rule layer1_parent[] = { | ||
| 2156 | { | ||
| 2157 | .path = dir_s1d1, | ||
| 2158 | .access = ACCESS_RO, | ||
| 2159 | }, | ||
| 2160 | { | ||
| 2161 | .path = dir_s2d1, | ||
| 2162 | .access = ACCESS_RW, | ||
| 2163 | }, | ||
| 2164 | {} | ||
| 2165 | }; | ||
| 2166 | /* | ||
| 2167 | * Sets access rights on the same bind-mounted directories. The result | ||
| 2168 | * should be ACCESS_RW for both directories, but not both hierarchies | ||
| 2169 | * because of the first layer. | ||
| 2170 | */ | ||
| 2171 | const struct rule layer2_mount_point[] = { | ||
| 2172 | { | ||
| 2173 | .path = dir_s1d2, | ||
| 2174 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2175 | }, | ||
| 2176 | { | ||
| 2177 | .path = dir_s2d2, | ||
| 2178 | .access = ACCESS_RW, | ||
| 2179 | }, | ||
| 2180 | {} | ||
| 2181 | }; | ||
| 2182 | /* Only allow read-access to the s1d3 hierarchies. */ | ||
| 2183 | const struct rule layer3_source[] = { | ||
| 2184 | { | ||
| 2185 | .path = dir_s1d3, | ||
| 2186 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2187 | }, | ||
| 2188 | {} | ||
| 2189 | }; | ||
| 2190 | /* Removes all access rights. */ | ||
| 2191 | const struct rule layer4_destination[] = { | ||
| 2192 | { | ||
| 2193 | .path = bind_file1_s1d3, | ||
| 2194 | .access = LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2195 | }, | ||
| 2196 | {} | ||
| 2197 | }; | ||
| 2198 | int ruleset_fd; | ||
| 2199 | |||
| 2200 | /* Sets rules for the parent directories. */ | ||
| 2201 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_parent); | ||
| 2202 | ASSERT_LE(0, ruleset_fd); | ||
| 2203 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2204 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2205 | |||
| 2206 | /* Checks source hierarchy. */ | ||
| 2207 | ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); | ||
| 2208 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | ||
| 2209 | ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | ||
| 2210 | |||
| 2211 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 2212 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | ||
| 2213 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 2214 | |||
| 2215 | /* Checks destination hierarchy. */ | ||
| 2216 | ASSERT_EQ(0, test_open(file1_s2d1, O_RDWR)); | ||
| 2217 | ASSERT_EQ(0, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY)); | ||
| 2218 | |||
| 2219 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR)); | ||
| 2220 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); | ||
| 2221 | |||
| 2222 | /* Sets rules for the mount points. */ | ||
| 2223 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_mount_point); | ||
| 2224 | ASSERT_LE(0, ruleset_fd); | ||
| 2225 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2226 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2227 | |||
| 2228 | /* Checks source hierarchy. */ | ||
| 2229 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); | ||
| 2230 | ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); | ||
| 2231 | ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); | ||
| 2232 | |||
| 2233 | ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); | ||
| 2234 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | ||
| 2235 | ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 2236 | |||
| 2237 | /* Checks destination hierarchy. */ | ||
| 2238 | ASSERT_EQ(EACCES, test_open(file1_s2d1, O_RDONLY)); | ||
| 2239 | ASSERT_EQ(EACCES, test_open(file1_s2d1, O_WRONLY)); | ||
| 2240 | ASSERT_EQ(EACCES, test_open(dir_s2d1, O_RDONLY | O_DIRECTORY)); | ||
| 2241 | |||
| 2242 | ASSERT_EQ(0, test_open(file1_s2d2, O_RDWR)); | ||
| 2243 | ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); | ||
| 2244 | ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 2245 | |||
| 2246 | /* Sets a (shared) rule only on the source. */ | ||
| 2247 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_source); | ||
| 2248 | ASSERT_LE(0, ruleset_fd); | ||
| 2249 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2250 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2251 | |||
| 2252 | /* Checks source hierarchy. */ | ||
| 2253 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY)); | ||
| 2254 | ASSERT_EQ(EACCES, test_open(file1_s1d2, O_WRONLY)); | ||
| 2255 | ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); | ||
| 2256 | |||
| 2257 | ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); | ||
| 2258 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 2259 | ASSERT_EQ(EACCES, test_open(dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 2260 | |||
| 2261 | /* Checks destination hierarchy. */ | ||
| 2262 | ASSERT_EQ(EACCES, test_open(file1_s2d2, O_RDONLY)); | ||
| 2263 | ASSERT_EQ(EACCES, test_open(file1_s2d2, O_WRONLY)); | ||
| 2264 | ASSERT_EQ(EACCES, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); | ||
| 2265 | |||
| 2266 | ASSERT_EQ(0, test_open(bind_file1_s1d3, O_RDONLY)); | ||
| 2267 | ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY)); | ||
| 2268 | ASSERT_EQ(EACCES, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); | ||
| 2269 | |||
| 2270 | /* Sets a (shared) rule only on the destination. */ | ||
| 2271 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_destination); | ||
| 2272 | ASSERT_LE(0, ruleset_fd); | ||
| 2273 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2274 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2275 | |||
| 2276 | /* Checks source hierarchy. */ | ||
| 2277 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); | ||
| 2278 | ASSERT_EQ(EACCES, test_open(file1_s1d3, O_WRONLY)); | ||
| 2279 | |||
| 2280 | /* Checks destination hierarchy. */ | ||
| 2281 | ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_RDONLY)); | ||
| 2282 | ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY)); | ||
| 2283 | } | ||
| 2284 | |||
| 2285 | #define LOWER_BASE TMP_DIR "/lower" | ||
| 2286 | #define LOWER_DATA LOWER_BASE "/data" | ||
| 2287 | static const char lower_fl1[] = LOWER_DATA "/fl1"; | ||
| 2288 | static const char lower_dl1[] = LOWER_DATA "/dl1"; | ||
| 2289 | static const char lower_dl1_fl2[] = LOWER_DATA "/dl1/fl2"; | ||
| 2290 | static const char lower_fo1[] = LOWER_DATA "/fo1"; | ||
| 2291 | static const char lower_do1[] = LOWER_DATA "/do1"; | ||
| 2292 | static const char lower_do1_fo2[] = LOWER_DATA "/do1/fo2"; | ||
| 2293 | static const char lower_do1_fl3[] = LOWER_DATA "/do1/fl3"; | ||
| 2294 | |||
| 2295 | static const char (*lower_base_files[])[] = { | ||
| 2296 | &lower_fl1, | ||
| 2297 | &lower_fo1, | ||
| 2298 | NULL | ||
| 2299 | }; | ||
| 2300 | static const char (*lower_base_directories[])[] = { | ||
| 2301 | &lower_dl1, | ||
| 2302 | &lower_do1, | ||
| 2303 | NULL | ||
| 2304 | }; | ||
| 2305 | static const char (*lower_sub_files[])[] = { | ||
| 2306 | &lower_dl1_fl2, | ||
| 2307 | &lower_do1_fo2, | ||
| 2308 | &lower_do1_fl3, | ||
| 2309 | NULL | ||
| 2310 | }; | ||
| 2311 | |||
| 2312 | #define UPPER_BASE TMP_DIR "/upper" | ||
| 2313 | #define UPPER_DATA UPPER_BASE "/data" | ||
| 2314 | #define UPPER_WORK UPPER_BASE "/work" | ||
| 2315 | static const char upper_fu1[] = UPPER_DATA "/fu1"; | ||
| 2316 | static const char upper_du1[] = UPPER_DATA "/du1"; | ||
| 2317 | static const char upper_du1_fu2[] = UPPER_DATA "/du1/fu2"; | ||
| 2318 | static const char upper_fo1[] = UPPER_DATA "/fo1"; | ||
| 2319 | static const char upper_do1[] = UPPER_DATA "/do1"; | ||
| 2320 | static const char upper_do1_fo2[] = UPPER_DATA "/do1/fo2"; | ||
| 2321 | static const char upper_do1_fu3[] = UPPER_DATA "/do1/fu3"; | ||
| 2322 | |||
| 2323 | static const char (*upper_base_files[])[] = { | ||
| 2324 | &upper_fu1, | ||
| 2325 | &upper_fo1, | ||
| 2326 | NULL | ||
| 2327 | }; | ||
| 2328 | static const char (*upper_base_directories[])[] = { | ||
| 2329 | &upper_du1, | ||
| 2330 | &upper_do1, | ||
| 2331 | NULL | ||
| 2332 | }; | ||
| 2333 | static const char (*upper_sub_files[])[] = { | ||
| 2334 | &upper_du1_fu2, | ||
| 2335 | &upper_do1_fo2, | ||
| 2336 | &upper_do1_fu3, | ||
| 2337 | NULL | ||
| 2338 | }; | ||
| 2339 | |||
| 2340 | #define MERGE_BASE TMP_DIR "/merge" | ||
| 2341 | #define MERGE_DATA MERGE_BASE "/data" | ||
| 2342 | static const char merge_fl1[] = MERGE_DATA "/fl1"; | ||
| 2343 | static const char merge_dl1[] = MERGE_DATA "/dl1"; | ||
| 2344 | static const char merge_dl1_fl2[] = MERGE_DATA "/dl1/fl2"; | ||
| 2345 | static const char merge_fu1[] = MERGE_DATA "/fu1"; | ||
| 2346 | static const char merge_du1[] = MERGE_DATA "/du1"; | ||
| 2347 | static const char merge_du1_fu2[] = MERGE_DATA "/du1/fu2"; | ||
| 2348 | static const char merge_fo1[] = MERGE_DATA "/fo1"; | ||
| 2349 | static const char merge_do1[] = MERGE_DATA "/do1"; | ||
| 2350 | static const char merge_do1_fo2[] = MERGE_DATA "/do1/fo2"; | ||
| 2351 | static const char merge_do1_fl3[] = MERGE_DATA "/do1/fl3"; | ||
| 2352 | static const char merge_do1_fu3[] = MERGE_DATA "/do1/fu3"; | ||
| 2353 | |||
| 2354 | static const char (*merge_base_files[])[] = { | ||
| 2355 | &merge_fl1, | ||
| 2356 | &merge_fu1, | ||
| 2357 | &merge_fo1, | ||
| 2358 | NULL | ||
| 2359 | }; | ||
| 2360 | static const char (*merge_base_directories[])[] = { | ||
| 2361 | &merge_dl1, | ||
| 2362 | &merge_du1, | ||
| 2363 | &merge_do1, | ||
| 2364 | NULL | ||
| 2365 | }; | ||
| 2366 | static const char (*merge_sub_files[])[] = { | ||
| 2367 | &merge_dl1_fl2, | ||
| 2368 | &merge_du1_fu2, | ||
| 2369 | &merge_do1_fo2, | ||
| 2370 | &merge_do1_fl3, | ||
| 2371 | &merge_do1_fu3, | ||
| 2372 | NULL | ||
| 2373 | }; | ||
| 2374 | |||
| 2375 | /* | ||
| 2376 | * layout2_overlay hierarchy: | ||
| 2377 | * | ||
| 2378 | * tmp | ||
| 2379 | * ├── lower | ||
| 2380 | * │ └── data | ||
| 2381 | * │ ├── dl1 | ||
| 2382 | * │ │ └── fl2 | ||
| 2383 | * │ ├── do1 | ||
| 2384 | * │ │ ├── fl3 | ||
| 2385 | * │ │ └── fo2 | ||
| 2386 | * │ ├── fl1 | ||
| 2387 | * │ └── fo1 | ||
| 2388 | * ├── merge | ||
| 2389 | * │ └── data | ||
| 2390 | * │ ├── dl1 | ||
| 2391 | * │ │ └── fl2 | ||
| 2392 | * │ ├── do1 | ||
| 2393 | * │ │ ├── fl3 | ||
| 2394 | * │ │ ├── fo2 | ||
| 2395 | * │ │ └── fu3 | ||
| 2396 | * │ ├── du1 | ||
| 2397 | * │ │ └── fu2 | ||
| 2398 | * │ ├── fl1 | ||
| 2399 | * │ ├── fo1 | ||
| 2400 | * │ └── fu1 | ||
| 2401 | * └── upper | ||
| 2402 | * ├── data | ||
| 2403 | * │ ├── do1 | ||
| 2404 | * │ │ ├── fo2 | ||
| 2405 | * │ │ └── fu3 | ||
| 2406 | * │ ├── du1 | ||
| 2407 | * │ │ └── fu2 | ||
| 2408 | * │ ├── fo1 | ||
| 2409 | * │ └── fu1 | ||
| 2410 | * └── work | ||
| 2411 | * └── work | ||
| 2412 | */ | ||
| 2413 | |||
| 2414 | FIXTURE(layout2_overlay) { | ||
| 2415 | }; | ||
| 2416 | |||
| 2417 | FIXTURE_SETUP(layout2_overlay) | ||
| 2418 | { | ||
| 2419 | prepare_layout(_metadata); | ||
| 2420 | |||
| 2421 | create_directory(_metadata, LOWER_BASE); | ||
| 2422 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2423 | /* Creates tmpfs mount points to get deterministic overlayfs. */ | ||
| 2424 | ASSERT_EQ(0, mount("tmp", LOWER_BASE, "tmpfs", 0, "size=4m,mode=700")); | ||
| 2425 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2426 | create_file(_metadata, lower_fl1); | ||
| 2427 | create_file(_metadata, lower_dl1_fl2); | ||
| 2428 | create_file(_metadata, lower_fo1); | ||
| 2429 | create_file(_metadata, lower_do1_fo2); | ||
| 2430 | create_file(_metadata, lower_do1_fl3); | ||
| 2431 | |||
| 2432 | create_directory(_metadata, UPPER_BASE); | ||
| 2433 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2434 | ASSERT_EQ(0, mount("tmp", UPPER_BASE, "tmpfs", 0, "size=4m,mode=700")); | ||
| 2435 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2436 | create_file(_metadata, upper_fu1); | ||
| 2437 | create_file(_metadata, upper_du1_fu2); | ||
| 2438 | create_file(_metadata, upper_fo1); | ||
| 2439 | create_file(_metadata, upper_do1_fo2); | ||
| 2440 | create_file(_metadata, upper_do1_fu3); | ||
| 2441 | ASSERT_EQ(0, mkdir(UPPER_WORK, 0700)); | ||
| 2442 | |||
| 2443 | create_directory(_metadata, MERGE_DATA); | ||
| 2444 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2445 | set_cap(_metadata, CAP_DAC_OVERRIDE); | ||
| 2446 | ASSERT_EQ(0, mount("overlay", MERGE_DATA, "overlay", 0, | ||
| 2447 | "lowerdir=" LOWER_DATA | ||
| 2448 | ",upperdir=" UPPER_DATA | ||
| 2449 | ",workdir=" UPPER_WORK)); | ||
| 2450 | clear_cap(_metadata, CAP_DAC_OVERRIDE); | ||
| 2451 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2452 | } | ||
| 2453 | |||
| 2454 | FIXTURE_TEARDOWN(layout2_overlay) | ||
| 2455 | { | ||
| 2456 | EXPECT_EQ(0, remove_path(lower_do1_fl3)); | ||
| 2457 | EXPECT_EQ(0, remove_path(lower_dl1_fl2)); | ||
| 2458 | EXPECT_EQ(0, remove_path(lower_fl1)); | ||
| 2459 | EXPECT_EQ(0, remove_path(lower_do1_fo2)); | ||
| 2460 | EXPECT_EQ(0, remove_path(lower_fo1)); | ||
| 2461 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2462 | EXPECT_EQ(0, umount(LOWER_BASE)); | ||
| 2463 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2464 | EXPECT_EQ(0, remove_path(LOWER_BASE)); | ||
| 2465 | |||
| 2466 | EXPECT_EQ(0, remove_path(upper_do1_fu3)); | ||
| 2467 | EXPECT_EQ(0, remove_path(upper_du1_fu2)); | ||
| 2468 | EXPECT_EQ(0, remove_path(upper_fu1)); | ||
| 2469 | EXPECT_EQ(0, remove_path(upper_do1_fo2)); | ||
| 2470 | EXPECT_EQ(0, remove_path(upper_fo1)); | ||
| 2471 | EXPECT_EQ(0, remove_path(UPPER_WORK "/work")); | ||
| 2472 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2473 | EXPECT_EQ(0, umount(UPPER_BASE)); | ||
| 2474 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2475 | EXPECT_EQ(0, remove_path(UPPER_BASE)); | ||
| 2476 | |||
| 2477 | set_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2478 | EXPECT_EQ(0, umount(MERGE_DATA)); | ||
| 2479 | clear_cap(_metadata, CAP_SYS_ADMIN); | ||
| 2480 | EXPECT_EQ(0, remove_path(MERGE_DATA)); | ||
| 2481 | |||
| 2482 | cleanup_layout(_metadata); | ||
| 2483 | } | ||
| 2484 | |||
| 2485 | TEST_F_FORK(layout2_overlay, no_restriction) | ||
| 2486 | { | ||
| 2487 | ASSERT_EQ(0, test_open(lower_fl1, O_RDONLY)); | ||
| 2488 | ASSERT_EQ(0, test_open(lower_dl1, O_RDONLY)); | ||
| 2489 | ASSERT_EQ(0, test_open(lower_dl1_fl2, O_RDONLY)); | ||
| 2490 | ASSERT_EQ(0, test_open(lower_fo1, O_RDONLY)); | ||
| 2491 | ASSERT_EQ(0, test_open(lower_do1, O_RDONLY)); | ||
| 2492 | ASSERT_EQ(0, test_open(lower_do1_fo2, O_RDONLY)); | ||
| 2493 | ASSERT_EQ(0, test_open(lower_do1_fl3, O_RDONLY)); | ||
| 2494 | |||
| 2495 | ASSERT_EQ(0, test_open(upper_fu1, O_RDONLY)); | ||
| 2496 | ASSERT_EQ(0, test_open(upper_du1, O_RDONLY)); | ||
| 2497 | ASSERT_EQ(0, test_open(upper_du1_fu2, O_RDONLY)); | ||
| 2498 | ASSERT_EQ(0, test_open(upper_fo1, O_RDONLY)); | ||
| 2499 | ASSERT_EQ(0, test_open(upper_do1, O_RDONLY)); | ||
| 2500 | ASSERT_EQ(0, test_open(upper_do1_fo2, O_RDONLY)); | ||
| 2501 | ASSERT_EQ(0, test_open(upper_do1_fu3, O_RDONLY)); | ||
| 2502 | |||
| 2503 | ASSERT_EQ(0, test_open(merge_fl1, O_RDONLY)); | ||
| 2504 | ASSERT_EQ(0, test_open(merge_dl1, O_RDONLY)); | ||
| 2505 | ASSERT_EQ(0, test_open(merge_dl1_fl2, O_RDONLY)); | ||
| 2506 | ASSERT_EQ(0, test_open(merge_fu1, O_RDONLY)); | ||
| 2507 | ASSERT_EQ(0, test_open(merge_du1, O_RDONLY)); | ||
| 2508 | ASSERT_EQ(0, test_open(merge_du1_fu2, O_RDONLY)); | ||
| 2509 | ASSERT_EQ(0, test_open(merge_fo1, O_RDONLY)); | ||
| 2510 | ASSERT_EQ(0, test_open(merge_do1, O_RDONLY)); | ||
| 2511 | ASSERT_EQ(0, test_open(merge_do1_fo2, O_RDONLY)); | ||
| 2512 | ASSERT_EQ(0, test_open(merge_do1_fl3, O_RDONLY)); | ||
| 2513 | ASSERT_EQ(0, test_open(merge_do1_fu3, O_RDONLY)); | ||
| 2514 | } | ||
| 2515 | |||
| 2516 | #define for_each_path(path_list, path_entry, i) \ | ||
| 2517 | for (i = 0, path_entry = *path_list[i]; path_list[i]; \ | ||
| 2518 | path_entry = *path_list[++i]) | ||
| 2519 | |||
| 2520 | TEST_F_FORK(layout2_overlay, same_content_different_file) | ||
| 2521 | { | ||
| 2522 | /* Sets access right on parent directories of both layers. */ | ||
| 2523 | const struct rule layer1_base[] = { | ||
| 2524 | { | ||
| 2525 | .path = LOWER_BASE, | ||
| 2526 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2527 | }, | ||
| 2528 | { | ||
| 2529 | .path = UPPER_BASE, | ||
| 2530 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2531 | }, | ||
| 2532 | { | ||
| 2533 | .path = MERGE_BASE, | ||
| 2534 | .access = ACCESS_RW, | ||
| 2535 | }, | ||
| 2536 | {} | ||
| 2537 | }; | ||
| 2538 | const struct rule layer2_data[] = { | ||
| 2539 | { | ||
| 2540 | .path = LOWER_DATA, | ||
| 2541 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2542 | }, | ||
| 2543 | { | ||
| 2544 | .path = UPPER_DATA, | ||
| 2545 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2546 | }, | ||
| 2547 | { | ||
| 2548 | .path = MERGE_DATA, | ||
| 2549 | .access = ACCESS_RW, | ||
| 2550 | }, | ||
| 2551 | {} | ||
| 2552 | }; | ||
| 2553 | /* Sets access right on directories inside both layers. */ | ||
| 2554 | const struct rule layer3_subdirs[] = { | ||
| 2555 | { | ||
| 2556 | .path = lower_dl1, | ||
| 2557 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2558 | }, | ||
| 2559 | { | ||
| 2560 | .path = lower_do1, | ||
| 2561 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2562 | }, | ||
| 2563 | { | ||
| 2564 | .path = upper_du1, | ||
| 2565 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2566 | }, | ||
| 2567 | { | ||
| 2568 | .path = upper_do1, | ||
| 2569 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2570 | }, | ||
| 2571 | { | ||
| 2572 | .path = merge_dl1, | ||
| 2573 | .access = ACCESS_RW, | ||
| 2574 | }, | ||
| 2575 | { | ||
| 2576 | .path = merge_du1, | ||
| 2577 | .access = ACCESS_RW, | ||
| 2578 | }, | ||
| 2579 | { | ||
| 2580 | .path = merge_do1, | ||
| 2581 | .access = ACCESS_RW, | ||
| 2582 | }, | ||
| 2583 | {} | ||
| 2584 | }; | ||
| 2585 | /* Tighten access rights to the files. */ | ||
| 2586 | const struct rule layer4_files[] = { | ||
| 2587 | { | ||
| 2588 | .path = lower_dl1_fl2, | ||
| 2589 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2590 | }, | ||
| 2591 | { | ||
| 2592 | .path = lower_do1_fo2, | ||
| 2593 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2594 | }, | ||
| 2595 | { | ||
| 2596 | .path = lower_do1_fl3, | ||
| 2597 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2598 | }, | ||
| 2599 | { | ||
| 2600 | .path = upper_du1_fu2, | ||
| 2601 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2602 | }, | ||
| 2603 | { | ||
| 2604 | .path = upper_do1_fo2, | ||
| 2605 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2606 | }, | ||
| 2607 | { | ||
| 2608 | .path = upper_do1_fu3, | ||
| 2609 | .access = LANDLOCK_ACCESS_FS_READ_FILE, | ||
| 2610 | }, | ||
| 2611 | { | ||
| 2612 | .path = merge_dl1_fl2, | ||
| 2613 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 2614 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2615 | }, | ||
| 2616 | { | ||
| 2617 | .path = merge_du1_fu2, | ||
| 2618 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 2619 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2620 | }, | ||
| 2621 | { | ||
| 2622 | .path = merge_do1_fo2, | ||
| 2623 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 2624 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2625 | }, | ||
| 2626 | { | ||
| 2627 | .path = merge_do1_fl3, | ||
| 2628 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 2629 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2630 | }, | ||
| 2631 | { | ||
| 2632 | .path = merge_do1_fu3, | ||
| 2633 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 2634 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2635 | }, | ||
| 2636 | {} | ||
| 2637 | }; | ||
| 2638 | const struct rule layer5_merge_only[] = { | ||
| 2639 | { | ||
| 2640 | .path = MERGE_DATA, | ||
| 2641 | .access = LANDLOCK_ACCESS_FS_READ_FILE | | ||
| 2642 | LANDLOCK_ACCESS_FS_WRITE_FILE, | ||
| 2643 | }, | ||
| 2644 | {} | ||
| 2645 | }; | ||
| 2646 | int ruleset_fd; | ||
| 2647 | size_t i; | ||
| 2648 | const char *path_entry; | ||
| 2649 | |||
| 2650 | /* Sets rules on base directories (i.e. outside overlay scope). */ | ||
| 2651 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); | ||
| 2652 | ASSERT_LE(0, ruleset_fd); | ||
| 2653 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2654 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2655 | |||
| 2656 | /* Checks lower layer. */ | ||
| 2657 | for_each_path(lower_base_files, path_entry, i) { | ||
| 2658 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | ||
| 2659 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | ||
| 2660 | } | ||
| 2661 | for_each_path(lower_base_directories, path_entry, i) { | ||
| 2662 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | ||
| 2663 | } | ||
| 2664 | for_each_path(lower_sub_files, path_entry, i) { | ||
| 2665 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | ||
| 2666 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | ||
| 2667 | } | ||
| 2668 | /* Checks upper layer. */ | ||
| 2669 | for_each_path(upper_base_files, path_entry, i) { | ||
| 2670 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | ||
| 2671 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | ||
| 2672 | } | ||
| 2673 | for_each_path(upper_base_directories, path_entry, i) { | ||
| 2674 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | ||
| 2675 | } | ||
| 2676 | for_each_path(upper_sub_files, path_entry, i) { | ||
| 2677 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | ||
| 2678 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | ||
| 2679 | } | ||
| 2680 | /* | ||
| 2681 | * Checks that access rights are independent from the lower and upper | ||
| 2682 | * layers: write access to upper files viewed through the merge point | ||
| 2683 | * is still allowed, and write access to lower file viewed (and copied) | ||
| 2684 | * through the merge point is still allowed. | ||
| 2685 | */ | ||
| 2686 | for_each_path(merge_base_files, path_entry, i) { | ||
| 2687 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | ||
| 2688 | } | ||
| 2689 | for_each_path(merge_base_directories, path_entry, i) { | ||
| 2690 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | ||
| 2691 | } | ||
| 2692 | for_each_path(merge_sub_files, path_entry, i) { | ||
| 2693 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | ||
| 2694 | } | ||
| 2695 | |||
| 2696 | /* Sets rules on data directories (i.e. inside overlay scope). */ | ||
| 2697 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_data); | ||
| 2698 | ASSERT_LE(0, ruleset_fd); | ||
| 2699 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2700 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2701 | |||
| 2702 | /* Checks merge. */ | ||
| 2703 | for_each_path(merge_base_files, path_entry, i) { | ||
| 2704 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | ||
| 2705 | } | ||
| 2706 | for_each_path(merge_base_directories, path_entry, i) { | ||
| 2707 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | ||
| 2708 | } | ||
| 2709 | for_each_path(merge_sub_files, path_entry, i) { | ||
| 2710 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | ||
| 2711 | } | ||
| 2712 | |||
| 2713 | /* Same checks with tighter rules. */ | ||
| 2714 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_subdirs); | ||
| 2715 | ASSERT_LE(0, ruleset_fd); | ||
| 2716 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2717 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2718 | |||
| 2719 | /* Checks changes for lower layer. */ | ||
| 2720 | for_each_path(lower_base_files, path_entry, i) { | ||
| 2721 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | ||
| 2722 | } | ||
| 2723 | /* Checks changes for upper layer. */ | ||
| 2724 | for_each_path(upper_base_files, path_entry, i) { | ||
| 2725 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | ||
| 2726 | } | ||
| 2727 | /* Checks all merge accesses. */ | ||
| 2728 | for_each_path(merge_base_files, path_entry, i) { | ||
| 2729 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); | ||
| 2730 | } | ||
| 2731 | for_each_path(merge_base_directories, path_entry, i) { | ||
| 2732 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | ||
| 2733 | } | ||
| 2734 | for_each_path(merge_sub_files, path_entry, i) { | ||
| 2735 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | ||
| 2736 | } | ||
| 2737 | |||
| 2738 | /* Sets rules directly on overlayed files. */ | ||
| 2739 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_files); | ||
| 2740 | ASSERT_LE(0, ruleset_fd); | ||
| 2741 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2742 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2743 | |||
| 2744 | /* Checks unchanged accesses on lower layer. */ | ||
| 2745 | for_each_path(lower_sub_files, path_entry, i) { | ||
| 2746 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | ||
| 2747 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | ||
| 2748 | } | ||
| 2749 | /* Checks unchanged accesses on upper layer. */ | ||
| 2750 | for_each_path(upper_sub_files, path_entry, i) { | ||
| 2751 | ASSERT_EQ(0, test_open(path_entry, O_RDONLY)); | ||
| 2752 | ASSERT_EQ(EACCES, test_open(path_entry, O_WRONLY)); | ||
| 2753 | } | ||
| 2754 | /* Checks all merge accesses. */ | ||
| 2755 | for_each_path(merge_base_files, path_entry, i) { | ||
| 2756 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); | ||
| 2757 | } | ||
| 2758 | for_each_path(merge_base_directories, path_entry, i) { | ||
| 2759 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | ||
| 2760 | } | ||
| 2761 | for_each_path(merge_sub_files, path_entry, i) { | ||
| 2762 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | ||
| 2763 | } | ||
| 2764 | |||
| 2765 | /* Only allowes access to the merge hierarchy. */ | ||
| 2766 | ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer5_merge_only); | ||
| 2767 | ASSERT_LE(0, ruleset_fd); | ||
| 2768 | enforce_ruleset(_metadata, ruleset_fd); | ||
| 2769 | ASSERT_EQ(0, close(ruleset_fd)); | ||
| 2770 | |||
| 2771 | /* Checks new accesses on lower layer. */ | ||
| 2772 | for_each_path(lower_sub_files, path_entry, i) { | ||
| 2773 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | ||
| 2774 | } | ||
| 2775 | /* Checks new accesses on upper layer. */ | ||
| 2776 | for_each_path(upper_sub_files, path_entry, i) { | ||
| 2777 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY)); | ||
| 2778 | } | ||
| 2779 | /* Checks all merge accesses. */ | ||
| 2780 | for_each_path(merge_base_files, path_entry, i) { | ||
| 2781 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDWR)); | ||
| 2782 | } | ||
| 2783 | for_each_path(merge_base_directories, path_entry, i) { | ||
| 2784 | ASSERT_EQ(EACCES, test_open(path_entry, O_RDONLY | O_DIRECTORY)); | ||
| 2785 | } | ||
| 2786 | for_each_path(merge_sub_files, path_entry, i) { | ||
| 2787 | ASSERT_EQ(0, test_open(path_entry, O_RDWR)); | ||
| 2788 | } | ||
| 2789 | } | ||
| 2790 | |||
| 2791 | TEST_HARNESS_MAIN | ||
| @@ -0,0 +1,337 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Landlock tests - Ptrace | ||
| 4 | * | ||
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> | ||
| 6 | * Copyright © 2019-2020 ANSSI | ||
| 7 | */ | ||
| 8 | |||
| 9 | #define _GNU_SOURCE | ||
| 10 | #include <errno.h> | ||
| 11 | #include <fcntl.h> | ||
| 12 | #include <linux/landlock.h> | ||
| 13 | #include <signal.h> | ||
| 14 | #include <sys/prctl.h> | ||
| 15 | #include <sys/ptrace.h> | ||
| 16 | #include <sys/types.h> | ||
| 17 | #include <sys/wait.h> | ||
| 18 | #include <unistd.h> | ||
| 19 | |||
| 20 | #include "common.h" | ||
| 21 | |||
| 22 | static void create_domain(struct __test_metadata *const _metadata) | ||
| 23 | { | ||
| 24 | int ruleset_fd; | ||
| 25 | struct landlock_ruleset_attr ruleset_attr = { | ||
| 26 | .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK, | ||
| 27 | }; | ||
| 28 | |||
| 29 | ruleset_fd = landlock_create_ruleset(&ruleset_attr, | ||
| 30 | sizeof(ruleset_attr), 0); | ||
| 31 | EXPECT_LE(0, ruleset_fd) { | ||
| 32 | TH_LOG("Failed to create a ruleset: %s", strerror(errno)); | ||
| 33 | } | ||
| 34 | EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); | ||
| 35 | EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); | ||
| 36 | EXPECT_EQ(0, close(ruleset_fd)); | ||
| 37 | } | ||
| 38 | |||
| 39 | static int test_ptrace_read(const pid_t pid) | ||
| 40 | { | ||
| 41 | static const char path_template[] = "/proc/%d/environ"; | ||
| 42 | char procenv_path[sizeof(path_template) + 10]; | ||
| 43 | int procenv_path_size, fd; | ||
| 44 | |||
| 45 | procenv_path_size = snprintf(procenv_path, sizeof(procenv_path), | ||
| 46 | path_template, pid); | ||
| 47 | if (procenv_path_size >= sizeof(procenv_path)) | ||
| 48 | return E2BIG; | ||
| 49 | |||
| 50 | fd = open(procenv_path, O_RDONLY | O_CLOEXEC); | ||
| 51 | if (fd < 0) | ||
| 52 | return errno; | ||
| 53 | /* | ||
| 54 | * Mixing error codes from close(2) and open(2) should not lead to any | ||
| 55 | * (access type) confusion for this test. | ||
| 56 | */ | ||
| 57 | if (close(fd) != 0) | ||
| 58 | return errno; | ||
| 59 | return 0; | ||
| 60 | } | ||
| 61 | |||
| 62 | FIXTURE(hierarchy) { }; | ||
| 63 | |||
| 64 | FIXTURE_VARIANT(hierarchy) { | ||
| 65 | const bool domain_both; | ||
| 66 | const bool domain_parent; | ||
| 67 | const bool domain_child; | ||
| 68 | }; | ||
| 69 | |||
| 70 | /* | ||
| 71 | * Test multiple tracing combinations between a parent process P1 and a child | ||
| 72 | * process P2. | ||
| 73 | * | ||
| 74 | * Yama's scoped ptrace is presumed disabled. If enabled, this optional | ||
| 75 | * restriction is enforced in addition to any Landlock check, which means that | ||
| 76 | * all P2 requests to trace P1 would be denied. | ||
| 77 | */ | ||
| 78 | |||
| 79 | /* | ||
| 80 | * No domain | ||
| 81 | * | ||
| 82 | * P1-. P1 -> P2 : allow | ||
| 83 | * \ P2 -> P1 : allow | ||
| 84 | * 'P2 | ||
| 85 | */ | ||
| 86 | FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) { | ||
| 87 | .domain_both = false, | ||
| 88 | .domain_parent = false, | ||
| 89 | .domain_child = false, | ||
| 90 | }; | ||
| 91 | |||
| 92 | /* | ||
| 93 | * Child domain | ||
| 94 | * | ||
| 95 | * P1--. P1 -> P2 : allow | ||
| 96 | * \ P2 -> P1 : deny | ||
| 97 | * .'-----. | ||
| 98 | * | P2 | | ||
| 99 | * '------' | ||
| 100 | */ | ||
| 101 | FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) { | ||
| 102 | .domain_both = false, | ||
| 103 | .domain_parent = false, | ||
| 104 | .domain_child = true, | ||
| 105 | }; | ||
| 106 | |||
| 107 | /* | ||
| 108 | * Parent domain | ||
| 109 | * .------. | ||
| 110 | * | P1 --. P1 -> P2 : deny | ||
| 111 | * '------' \ P2 -> P1 : allow | ||
| 112 | * ' | ||
| 113 | * P2 | ||
| 114 | */ | ||
| 115 | FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) { | ||
| 116 | .domain_both = false, | ||
| 117 | .domain_parent = true, | ||
| 118 | .domain_child = false, | ||
| 119 | }; | ||
| 120 | |||
| 121 | /* | ||
| 122 | * Parent + child domain (siblings) | ||
| 123 | * .------. | ||
| 124 | * | P1 ---. P1 -> P2 : deny | ||
| 125 | * '------' \ P2 -> P1 : deny | ||
| 126 | * .---'--. | ||
| 127 | * | P2 | | ||
| 128 | * '------' | ||
| 129 | */ | ||
| 130 | FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) { | ||
| 131 | .domain_both = false, | ||
| 132 | .domain_parent = true, | ||
| 133 | .domain_child = true, | ||
| 134 | }; | ||
| 135 | |||
| 136 | /* | ||
| 137 | * Same domain (inherited) | ||
| 138 | * .-------------. | ||
| 139 | * | P1----. | P1 -> P2 : allow | ||
| 140 | * | \ | P2 -> P1 : allow | ||
| 141 | * | ' | | ||
| 142 | * | P2 | | ||
| 143 | * '-------------' | ||
| 144 | */ | ||
| 145 | FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) { | ||
| 146 | .domain_both = true, | ||
| 147 | .domain_parent = false, | ||
| 148 | .domain_child = false, | ||
| 149 | }; | ||
| 150 | |||
| 151 | /* | ||
| 152 | * Inherited + child domain | ||
| 153 | * .-----------------. | ||
| 154 | * | P1----. | P1 -> P2 : allow | ||
| 155 | * | \ | P2 -> P1 : deny | ||
| 156 | * | .-'----. | | ||
| 157 | * | | P2 | | | ||
| 158 | * | '------' | | ||
| 159 | * '-----------------' | ||
| 160 | */ | ||
| 161 | FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) { | ||
| 162 | .domain_both = true, | ||
| 163 | .domain_parent = false, | ||
| 164 | .domain_child = true, | ||
| 165 | }; | ||
| 166 | |||
| 167 | /* | ||
| 168 | * Inherited + parent domain | ||
| 169 | * .-----------------. | ||
| 170 | * |.------. | P1 -> P2 : deny | ||
| 171 | * || P1 ----. | P2 -> P1 : allow | ||
| 172 | * |'------' \ | | ||
| 173 | * | ' | | ||
| 174 | * | P2 | | ||
| 175 | * '-----------------' | ||
| 176 | */ | ||
| 177 | FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) { | ||
| 178 | .domain_both = true, | ||
| 179 | .domain_parent = true, | ||
| 180 | .domain_child = false, | ||
| 181 | }; | ||
| 182 | |||
| 183 | /* | ||
| 184 | * Inherited + parent and child domain (siblings) | ||
| 185 | * .-----------------. | ||
| 186 | * | .------. | P1 -> P2 : deny | ||
| 187 | * | | P1 . | P2 -> P1 : deny | ||
| 188 | * | '------'\ | | ||
| 189 | * | \ | | ||
| 190 | * | .--'---. | | ||
| 191 | * | | P2 | | | ||
| 192 | * | '------' | | ||
| 193 | * '-----------------' | ||
| 194 | */ | ||
| 195 | FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) { | ||
| 196 | .domain_both = true, | ||
| 197 | .domain_parent = true, | ||
| 198 | .domain_child = true, | ||
| 199 | }; | ||
| 200 | |||
| 201 | FIXTURE_SETUP(hierarchy) | ||
| 202 | { } | ||
| 203 | |||
| 204 | FIXTURE_TEARDOWN(hierarchy) | ||
| 205 | { } | ||
| 206 | |||
| 207 | /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */ | ||
| 208 | TEST_F(hierarchy, trace) | ||
| 209 | { | ||
| 210 | pid_t child, parent; | ||
| 211 | int status, err_proc_read; | ||
| 212 | int pipe_child[2], pipe_parent[2]; | ||
| 213 | char buf_parent; | ||
| 214 | long ret; | ||
| 215 | |||
| 216 | /* | ||
| 217 | * Removes all effective and permitted capabilities to not interfere | ||
| 218 | * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS. | ||
| 219 | */ | ||
| 220 | drop_caps(_metadata); | ||
| 221 | |||
| 222 | parent = getpid(); | ||
| 223 | ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); | ||
| 224 | ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); | ||
| 225 | if (variant->domain_both) { | ||
| 226 | create_domain(_metadata); | ||
| 227 | if (!_metadata->passed) | ||
| 228 | /* Aborts before forking. */ | ||
| 229 | return; | ||
| 230 | } | ||
| 231 | |||
| 232 | child = fork(); | ||
| 233 | ASSERT_LE(0, child); | ||
| 234 | if (child == 0) { | ||
| 235 | char buf_child; | ||
| 236 | |||
| 237 | ASSERT_EQ(0, close(pipe_parent[1])); | ||
| 238 | ASSERT_EQ(0, close(pipe_child[0])); | ||
| 239 | if (variant->domain_child) | ||
| 240 | create_domain(_metadata); | ||
| 241 | |||
| 242 | /* Waits for the parent to be in a domain, if any. */ | ||
| 243 | ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); | ||
| 244 | |||
| 245 | /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the parent. */ | ||
| 246 | err_proc_read = test_ptrace_read(parent); | ||
| 247 | ret = ptrace(PTRACE_ATTACH, parent, NULL, 0); | ||
| 248 | if (variant->domain_child) { | ||
| 249 | EXPECT_EQ(-1, ret); | ||
| 250 | EXPECT_EQ(EPERM, errno); | ||
| 251 | EXPECT_EQ(EACCES, err_proc_read); | ||
| 252 | } else { | ||
| 253 | EXPECT_EQ(0, ret); | ||
| 254 | EXPECT_EQ(0, err_proc_read); | ||
| 255 | } | ||
| 256 | if (ret == 0) { | ||
| 257 | ASSERT_EQ(parent, waitpid(parent, &status, 0)); | ||
| 258 | ASSERT_EQ(1, WIFSTOPPED(status)); | ||
| 259 | ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0)); | ||
| 260 | } | ||
| 261 | |||
| 262 | /* Tests child PTRACE_TRACEME. */ | ||
| 263 | ret = ptrace(PTRACE_TRACEME); | ||
| 264 | if (variant->domain_parent) { | ||
| 265 | EXPECT_EQ(-1, ret); | ||
| 266 | EXPECT_EQ(EPERM, errno); | ||
| 267 | } else { | ||
| 268 | EXPECT_EQ(0, ret); | ||
| 269 | } | ||
| 270 | |||
| 271 | /* | ||
| 272 | * Signals that the PTRACE_ATTACH test is done and the | ||
| 273 | * PTRACE_TRACEME test is ongoing. | ||
| 274 | */ | ||
| 275 | ASSERT_EQ(1, write(pipe_child[1], ".", 1)); | ||
| 276 | |||
| 277 | if (!variant->domain_parent) { | ||
| 278 | ASSERT_EQ(0, raise(SIGSTOP)); | ||
| 279 | } | ||
| 280 | |||
| 281 | /* Waits for the parent PTRACE_ATTACH test. */ | ||
| 282 | ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); | ||
| 283 | _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); | ||
| 284 | return; | ||
| 285 | } | ||
| 286 | |||
| 287 | ASSERT_EQ(0, close(pipe_child[1])); | ||
| 288 | ASSERT_EQ(0, close(pipe_parent[0])); | ||
| 289 | if (variant->domain_parent) | ||
| 290 | create_domain(_metadata); | ||
| 291 | |||
| 292 | /* Signals that the parent is in a domain, if any. */ | ||
| 293 | ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); | ||
| 294 | |||
| 295 | /* | ||
| 296 | * Waits for the child to test PTRACE_ATTACH on the parent and start | ||
| 297 | * testing PTRACE_TRACEME. | ||
| 298 | */ | ||
| 299 | ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); | ||
| 300 | |||
| 301 | /* Tests child PTRACE_TRACEME. */ | ||
| 302 | if (!variant->domain_parent) { | ||
| 303 | ASSERT_EQ(child, waitpid(child, &status, 0)); | ||
| 304 | ASSERT_EQ(1, WIFSTOPPED(status)); | ||
| 305 | ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0)); | ||
| 306 | } else { | ||
| 307 | /* The child should not be traced by the parent. */ | ||
| 308 | EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0)); | ||
| 309 | EXPECT_EQ(ESRCH, errno); | ||
| 310 | } | ||
| 311 | |||
| 312 | /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the child. */ | ||
| 313 | err_proc_read = test_ptrace_read(child); | ||
| 314 | ret = ptrace(PTRACE_ATTACH, child, NULL, 0); | ||
| 315 | if (variant->domain_parent) { | ||
| 316 | EXPECT_EQ(-1, ret); | ||
| 317 | EXPECT_EQ(EPERM, errno); | ||
| 318 | EXPECT_EQ(EACCES, err_proc_read); | ||
| 319 | } else { | ||
| 320 | EXPECT_EQ(0, ret); | ||
| 321 | EXPECT_EQ(0, err_proc_read); | ||
| 322 | } | ||
| 323 | if (ret == 0) { | ||
| 324 | ASSERT_EQ(child, waitpid(child, &status, 0)); | ||
| 325 | ASSERT_EQ(1, WIFSTOPPED(status)); | ||
| 326 | ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0)); | ||
| 327 | } | ||
| 328 | |||
| 329 | /* Signals that the parent PTRACE_ATTACH test is done. */ | ||
| 330 | ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); | ||
| 331 | ASSERT_EQ(child, waitpid(child, &status, 0)); | ||
| 332 | if (WIFSIGNALED(status) || !WIFEXITED(status) || | ||
| 333 | WEXITSTATUS(status) != EXIT_SUCCESS) | ||
| 334 | _metadata->passed = 0; | ||
| 335 | } | ||
| 336 | |||
| 337 | TEST_HARNESS_MAIN | ||
| @@ -0,0 +1,5 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | int main(void) | ||
| 3 | { | ||
| 4 | return 0; | ||
| 5 | } | ||