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 | 16 | siphash |
| 17 | 17 | tpm/index |
| 18 | 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 | 19 | no_new_privs |
| 20 | 20 | seccomp_filter |
| 21 | + landlock | |
| 21 | 22 | unshare |
| 22 | 23 | spec_ctrl |
| 23 | 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 | 10191 | F: net/ipv4/tcp_bpf.c |
| 10192 | 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 | 10209 | LANTIQ / INTEL Ethernet drivers |
| 10195 | 10210 | M: Hauke Mehrtens <hauke@hauke-m.de> |
| 10196 | 10211 | L: netdev@vger.kernel.org |
| @@ -1068,6 +1068,13 @@ config COMPAT_32BIT_TIME · 1067 unmodified lines | ||
| 1068 | 1068 | config ARCH_NO_PREEMPT |
| 1069 | 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 | 1078 | config ARCH_SUPPORTS_RT |
| 1072 | 1079 | bool |
| 1073 | 1080 | |
| @@ -483,3 +483,6 @@ · 482 unmodified lines | ||
| 483 | 483 | 551 common epoll_pwait2 sys_epoll_pwait2 |
| 484 | 484 | 552 common mount_setattr sys_mount_setattr |
| 485 | 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 | 457 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 458 | 458 | 442 common mount_setattr sys_mount_setattr |
| 459 | 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 | 38 | #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5) |
| 39 | 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 | 42 | #endif |
| 43 | 43 | |
| 44 | 44 | #define __ARCH_WANT_SYS_CLONE |
| @@ -895,6 +895,12 @@ __SYSCALL(__NR_epoll_pwait2, compat_sys_epoll_pwait2) · 894 unmodified lines | ||
| 895 | 895 | __SYSCALL(__NR_mount_setattr, sys_mount_setattr) |
| 896 | 896 | #define __NR_quotactl_path 443 |
| 897 | 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 | 906 | * Please add new compat syscalls above this comment and update |
| @@ -364,3 +364,6 @@ · 363 unmodified lines | ||
| 364 | 364 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 365 | 365 | 442 common mount_setattr sys_mount_setattr |
| 366 | 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 | 443 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 444 | 444 | 442 common mount_setattr sys_mount_setattr |
| 445 | 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 | 449 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 450 | 450 | 442 common mount_setattr sys_mount_setattr |
| 451 | 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 | 382 | 441 n32 epoll_pwait2 compat_sys_epoll_pwait2 |
| 383 | 383 | 442 n32 mount_setattr sys_mount_setattr |
| 384 | 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 | 358 | 441 n64 epoll_pwait2 sys_epoll_pwait2 |
| 359 | 359 | 442 n64 mount_setattr sys_mount_setattr |
| 360 | 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 | 431 | 441 o32 epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 432 | 432 | 442 o32 mount_setattr sys_mount_setattr |
| 433 | 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 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 442 | 442 | 442 common mount_setattr sys_mount_setattr |
| 443 | 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 | 523 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 524 | 524 | 442 common mount_setattr sys_mount_setattr |
| 525 | 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 | 446 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 447 | 447 | 442 common mount_setattr sys_mount_setattr sys_mount_setattr |
| 448 | 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 | 446 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 447 | 447 | 442 common mount_setattr sys_mount_setattr |
| 448 | 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 | 489 | 441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 490 | 490 | 442 common mount_setattr sys_mount_setattr |
| 491 | 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 | 5 | config UML |
| 6 | 6 | bool |
| 7 | 7 | default y |
| 8 | + select ARCH_EPHEMERAL_INODES | |
| 8 | 9 | select ARCH_HAS_KCOV |
| 9 | 10 | select ARCH_NO_PREEMPT |
| 10 | 11 | select HAVE_ARCH_AUDITSYSCALL |
| @@ -448,3 +448,6 @@ · 447 unmodified lines | ||
| 448 | 448 | 441 i386 epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 |
| 449 | 449 | 442 i386 mount_setattr sys_mount_setattr |
| 450 | 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 | 365 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 366 | 366 | 442 common mount_setattr sys_mount_setattr |
| 367 | 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 | 373 | # Due to a historical design error, certain syscalls are numbered differently |
| @@ -414,3 +414,6 @@ · 413 unmodified lines | ||
| 414 | 414 | 441 common epoll_pwait2 sys_epoll_pwait2 |
| 415 | 415 | 442 common mount_setattr sys_mount_setattr |
| 416 | 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 | 454 | evict_inodes(sb); |
| 455 | 455 | /* only nonzero refcount inodes can have marks */ |
| 456 | 456 | fsnotify_sb_delete(sb); |
| 457 | + security_sb_delete(sb); | |
| 457 | 458 | |
| 458 | 459 | if (sb->s_dio_done_wq) { |
| 459 | 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 | 59 | LSM_HOOK(int, -ENOPARAM, fs_context_parse_param, struct fs_context *fc, |
| 60 | 60 | struct fs_parameter *param) |
| 61 | 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 | 63 | LSM_HOOK(void, LSM_RET_VOID, sb_free_security, struct super_block *sb) |
| 63 | 64 | LSM_HOOK(void, LSM_RET_VOID, sb_free_mnt_opts, void *mnt_opts) |
| 64 | 65 | LSM_HOOK(int, 0, sb_eat_lsm_opts, char *orig, void **mnt_opts) |
| @@ -108,6 +108,9 @@ · 107 unmodified lines | ||
| 108 | 108 | * allocated. |
| 109 | 109 | * @sb contains the super_block structure to be modified. |
| 110 | 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 | 114 | * @sb_free_security: |
| 112 | 115 | * Deallocate and clear the sb->s_security field. |
| 113 | 116 | * @sb contains the super_block structure to be modified. |
| @@ -1585,6 +1588,7 @@ struct lsm_blob_sizes { · 1471 unmodified lines | ||
| 1585 | 1588 | int lbs_cred; |
| 1586 | 1589 | int lbs_file; |
| 1587 | 1590 | int lbs_inode; |
| 1591 | + int lbs_superblock; | |
| 1588 | 1592 | int lbs_ipc; |
| 1589 | 1593 | int lbs_msg_msg; |
| 1590 | 1594 | int lbs_task; |
| @@ -291,6 +291,7 @@ void security_bprm_committed_creds(struct linux_binprm *bprm); · 290 unmodified lines | ||
| 291 | 291 | int security_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc); |
| 292 | 292 | int security_fs_context_parse_param(struct fs_context *fc, struct fs_parameter *param); |
| 293 | 293 | int security_sb_alloc(struct super_block *sb); |
| 294 | +void security_sb_delete(struct super_block *sb); | |
| 294 | 295 | void security_sb_free(struct super_block *sb); |
| 295 | 296 | void security_free_mnt_opts(void **mnt_opts); |
| 296 | 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 | 634 | return 0; |
| 634 | 635 | } |
| 635 | 636 | |
| 637 | +static inline void security_sb_delete(struct super_block *sb) | |
| 638 | +{ } | |
| 639 | + | |
| 636 | 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 | 69 | struct clone_args; |
| 70 | 70 | struct open_how; |
| 71 | 71 | struct mount_attr; |
| 72 | +struct landlock_ruleset_attr; | |
| 73 | +enum landlock_rule_type; | |
| 72 | 74 | |
| 73 | 75 | #include <linux/types.h> |
| 74 | 76 | #include <linux/aio_abi.h> |
| @@ -1043,6 +1045,11 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig, · 968 unmodified lines | ||
| 1043 | 1045 | siginfo_t __user *info, |
| 1044 | 1046 | unsigned int flags); |
| 1045 | 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 | 1055 | * Architecture-specific system calls |
| @@ -866,8 +866,15 @@ __SYSCALL(__NR_mount_setattr, sys_mount_setattr) · 865 unmodified lines | ||
| 866 | 866 | #define __NR_quotactl_path 443 |
| 867 | 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 | 876 | #undef __NR_syscalls |
| 870 | −#define __NR_syscalls 444 | |
| 877 | +#define __NR_syscalls 447 | |
| 871 | 878 | |
| 872 | 879 | /* |
| 873 | 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 | 267 | COND_SYSCALL(keyctl); |
| 268 | 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 | 275 | /* arch/example/kernel/sys_example.c */ |
| 271 | 276 | |
| 272 | 277 | /* mm/fadvise.c */ |
| @@ -124,6 +124,13 @@ config SAMPLE_HIDRAW · 123 unmodified lines | ||
| 124 | 124 | bool "hidraw sample" |
| 125 | 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 | 134 | config SAMPLE_PIDFD |
| 128 | 135 | bool "pidfd sample" |
| 129 | 136 | depends on CC_CAN_LINK && HEADERS_INSTALL |
| @@ -11,6 +11,7 @@ obj-$(CONFIG_SAMPLE_KDB) += kdb/ · 10 unmodified lines | ||
| 11 | 11 | obj-$(CONFIG_SAMPLE_KFIFO) += kfifo/ |
| 12 | 12 | obj-$(CONFIG_SAMPLE_KOBJECT) += kobject/ |
| 13 | 13 | obj-$(CONFIG_SAMPLE_KPROBES) += kprobes/ |
| 14 | +subdir-$(CONFIG_SAMPLE_LANDLOCK) += landlock | |
| 14 | 15 | obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch/ |
| 15 | 16 | subdir-$(CONFIG_SAMPLE_PIDFD) += pidfd |
| 16 | 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 | 238 | source "security/yama/Kconfig" |
| 239 | 239 | source "security/safesetid/Kconfig" |
| 240 | 240 | source "security/lockdown/Kconfig" |
| 241 | +source "security/landlock/Kconfig" | |
| 241 | 242 | |
| 242 | 243 | source "security/integrity/Kconfig" |
| 243 | 244 | |
| @@ -277,11 +278,11 @@ endchoice · 33 unmodified lines | ||
| 277 | 278 | |
| 278 | 279 | config LSM |
| 279 | 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 "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 "lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC | |
| 284 | − default "lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf" | |
| 281 | + default "landlock,lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK | |
| 282 | + default "landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR | |
| 283 | + default "landlock,lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO | |
| 284 | + default "landlock,lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC | |
| 285 | + default "landlock,lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf" | |
| 285 | 286 | help |
| 286 | 287 | A comma-separated list of LSMs, in initialization order. |
| 287 | 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 | 13 | subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid |
| 14 | 14 | subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown |
| 15 | 15 | subdir-$(CONFIG_BPF_LSM) += bpf |
| 16 | +subdir-$(CONFIG_SECURITY_LANDLOCK) += landlock | |
| 16 | 17 | |
| 17 | 18 | # always enable default capabilities |
| 18 | 19 | obj-y += commoncap.o |
| @@ -32,6 +33,7 @@ obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/ · 13 unmodified lines | ||
| 32 | 33 | obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ |
| 33 | 34 | obj-$(CONFIG_CGROUPS) += device_cgroup.o |
| 34 | 35 | obj-$(CONFIG_BPF_LSM) += bpf/ |
| 36 | +obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ | |
| 35 | 37 | |
| 36 | 38 | # Object integrity file lists |
| 37 | 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 | 203 | lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode); |
| 204 | 204 | lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc); |
| 205 | 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 | 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 | 334 | for (lsm = ordered_lsms; *lsm; lsm++) |
| 334 | 335 | prepare_lsm(*lsm); |
| 335 | 336 | |
| 336 | − 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("inode blob size = %d\n", blob_sizes.lbs_inode); | |
| 339 | − 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("task blob size = %d\n", blob_sizes.lbs_task); | |
| 337 | + init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); | |
| 338 | + init_debug("file blob size = %d\n", blob_sizes.lbs_file); | |
| 339 | + init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); | |
| 340 | + init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); | |
| 341 | + init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); | |
| 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 | 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 | 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 | 697 | * The default value of the LSM hook is defined in linux/lsm_hook_defs.h and |
| 675 | 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 | 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 | 908 | void security_sb_free(struct super_block *sb) |
| 874 | 909 | { |
| 875 | 910 | call_void_hook(sb_free_security, sb); |
| 911 | + kfree(sb->s_security); | |
| 912 | + sb->s_security = NULL; | |
| 876 | 913 | } |
| 877 | 914 | |
| 878 | 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 | 359 | if (!isec) |
| 360 | 360 | return; |
| 361 | − sbsec = inode->i_sb->s_security; | |
| 361 | + sbsec = selinux_superblock(inode->i_sb); | |
| 362 | 362 | /* |
| 363 | 363 | * As not all inode security structures are in a list, we check for |
| 364 | 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 | 379 | struct selinux_mnt_opts { |
| 387 | 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 | 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 | 493 | * IMPORTANT: Double-check logic in this function when adding a new |
| @@ -571,7 +564,7 @@ fallback: · 70 unmodified lines | ||
| 571 | 564 | |
| 572 | 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 | 568 | struct dentry *root = sb->s_root; |
| 576 | 569 | struct inode *root_inode = d_backing_inode(root); |
| 577 | 570 | int rc = 0; |
| @@ -662,7 +655,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, · 84 unmodified lines | ||
| 662 | 655 | unsigned long *set_kern_flags) |
| 663 | 656 | { |
| 664 | 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 | 659 | struct dentry *root = sb->s_root; |
| 667 | 660 | struct selinux_mnt_opts *opts = mnt_opts; |
| 668 | 661 | struct inode_security_struct *root_isec; |
| @@ -900,8 +893,8 @@ out_double_mount: · 231 unmodified lines | ||
| 900 | 893 | static int selinux_cmp_sb_context(const struct super_block *oldsb, |
| 901 | 894 | const struct super_block *newsb) |
| 902 | 895 | { |
| 903 | − struct superblock_security_struct *old = oldsb->s_security; | |
| 904 | − struct superblock_security_struct *new = newsb->s_security; | |
| 896 | + struct superblock_security_struct *old = selinux_superblock(oldsb); | |
| 897 | + struct superblock_security_struct *new = selinux_superblock(newsb); | |
| 905 | 898 | char oldflags = old->flags & SE_MNTMASK; |
| 906 | 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 | 926 | unsigned long *set_kern_flags) |
| 934 | 927 | { |
| 935 | 928 | int rc = 0; |
| 936 | − const struct superblock_security_struct *oldsbsec = oldsb->s_security; | |
| 937 | − struct superblock_security_struct *newsbsec = newsb->s_security; | |
| 929 | + const struct superblock_security_struct *oldsbsec = | |
| 930 | + selinux_superblock(oldsb); | |
| 931 | + struct superblock_security_struct *newsbsec = selinux_superblock(newsb); | |
| 938 | 932 | |
| 939 | 933 | int set_fscontext = (oldsbsec->flags & FSCONTEXT_MNT); |
| 940 | 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 | 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 | 1111 | int rc; |
| 1118 | 1112 | |
| 1119 | 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 | 1458 | if (isec->sclass == SECCLASS_FILE) |
| 1465 | 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 | 1462 | if (!(sbsec->flags & SE_SBINITIALIZED)) { |
| 1469 | 1463 | /* Defer initialization until selinux_complete_init, |
| 1470 | 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 | 1809 | const struct qstr *name, u16 tclass, |
| 1816 | 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 | 1815 | if ((sbsec->flags & SE_SBINITIALIZED) && |
| 1821 | 1816 | (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { |
| @@ -1846,7 +1841,7 @@ static int may_create(struct inode *dir, · 24 unmodified lines | ||
| 1846 | 1841 | int rc; |
| 1847 | 1842 | |
| 1848 | 1843 | dsec = inode_security(dir); |
| 1849 | − sbsec = dir->i_sb->s_security; | |
| 1844 | + sbsec = selinux_superblock(dir->i_sb); | |
| 1850 | 1845 | |
| 1851 | 1846 | sid = tsec->sid; |
| 1852 | 1847 | |
| @@ -1995,7 +1990,7 @@ static int superblock_has_perm(const struct cred *cred, · 142 unmodified lines | ||
| 1995 | 1990 | struct superblock_security_struct *sbsec; |
| 1996 | 1991 | u32 sid = cred_sid(cred); |
| 1997 | 1992 | |
| 1998 | − sbsec = sb->s_security; | |
| 1993 | + sbsec = selinux_superblock(sb); | |
| 1999 | 1994 | return avc_has_perm(&selinux_state, |
| 2000 | 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 | 2613 | static int selinux_sb_alloc_security(struct super_block *sb) |
| 2619 | 2614 | { |
| 2620 | − struct superblock_security_struct *sbsec; | |
| 2621 | − | |
| 2622 | − sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); | |
| 2623 | − if (!sbsec) | |
| 2624 | − return -ENOMEM; | |
| 2615 | + struct superblock_security_struct *sbsec = selinux_superblock(sb); | |
| 2625 | 2616 | |
| 2626 | 2617 | mutex_init(&sbsec->lock); |
| 2627 | 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 | 2620 | sbsec->sid = SECINITSID_UNLABELED; |
| 2630 | 2621 | sbsec->def_sid = SECINITSID_FILE; |
| 2631 | 2622 | sbsec->mntpoint_sid = SECINITSID_UNLABELED; |
| 2632 | − sb->s_security = sbsec; | |
| 2633 | 2623 | |
| 2634 | 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 | 2627 | static inline int opt_len(const char *s) |
| 2643 | 2628 | { |
| 2644 | 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 | 2757 | static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) |
| 2773 | 2758 | { |
| 2774 | 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 | 2761 | u32 sid; |
| 2777 | 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 | 2995 | int rc; |
| 3011 | 2996 | char *context; |
| 3012 | 2997 | |
| 3013 | − sbsec = dir->i_sb->s_security; | |
| 2998 | + sbsec = selinux_superblock(dir->i_sb); | |
| 3014 | 2999 | |
| 3015 | 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 | 3297 | if (!selinux_initialized(&selinux_state)) |
| 3313 | 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 | 3301 | if (!(sbsec->flags & SBLABEL_MNT)) |
| 3317 | 3302 | return -EOPNOTSUPP; |
| 3318 | 3303 | |
| @@ -3557,13 +3542,14 @@ static int selinux_inode_setsecurity(struct inode *inode, const char *name, · 238 unmodified lines | ||
| 3557 | 3542 | const void *value, size_t size, int flags) |
| 3558 | 3543 | { |
| 3559 | 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 | 3546 | u32 newsid; |
| 3562 | 3547 | int rc; |
| 3563 | 3548 | |
| 3564 | 3549 | if (strcmp(name, XATTR_SELINUX_SUFFIX)) |
| 3565 | 3550 | return -EOPNOTSUPP; |
| 3566 | 3551 | |
| 3552 | + sbsec = selinux_superblock(inode->i_sb); | |
| 3567 | 3553 | if (!(sbsec->flags & SBLABEL_MNT)) |
| 3568 | 3554 | return -EOPNOTSUPP; |
| 3569 | 3555 | |
| @@ -7065,6 +7051,7 @@ struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = { · 3495 unmodified lines | ||
| 7065 | 7051 | .lbs_inode = sizeof(struct inode_security_struct), |
| 7066 | 7052 | .lbs_ipc = sizeof(struct ipc_security_struct), |
| 7067 | 7053 | .lbs_msg_msg = sizeof(struct msg_security_struct), |
| 7054 | + .lbs_superblock = sizeof(struct superblock_security_struct), | |
| 7068 | 7055 | }; |
| 7069 | 7056 | |
| 7070 | 7057 | #ifdef CONFIG_PERF_EVENTS |
| @@ -7165,7 +7152,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { · 94 unmodified lines | ||
| 7165 | 7152 | LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds), |
| 7166 | 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 | 7155 | LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts), |
| 7170 | 7156 | LSM_HOOK_INIT(sb_mnt_opts_compat, selinux_sb_mnt_opts_compat), |
| 7171 | 7157 | LSM_HOOK_INIT(sb_remount, selinux_sb_remount), |
| @@ -188,4 +188,10 @@ static inline u32 current_sid(void) · 187 unmodified lines | ||
| 188 | 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 | 197 | #endif /* _SELINUX_OBJSEC_H_ */ |
| @@ -47,6 +47,7 @@ · 46 unmodified lines | ||
| 47 | 47 | #include <linux/sched.h> |
| 48 | 48 | #include <linux/audit.h> |
| 49 | 49 | #include <linux/vmalloc.h> |
| 50 | +#include <linux/lsm_hooks.h> | |
| 50 | 51 | #include <net/netlabel.h> |
| 51 | 52 | |
| 52 | 53 | #include "flask.h" |
| @@ -2955,7 +2956,7 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) · 2902 unmodified lines | ||
| 2955 | 2956 | struct sidtab *sidtab; |
| 2956 | 2957 | int rc; |
| 2957 | 2958 | struct ocontext *c; |
| 2958 | − struct superblock_security_struct *sbsec = sb->s_security; | |
| 2959 | + struct superblock_security_struct *sbsec = selinux_superblock(sb); | |
| 2959 | 2960 | const char *fstype = sb->s_type->name; |
| 2960 | 2961 | |
| 2961 | 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 | 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 | 367 | * Is the directory transmuting? |
| 362 | 368 | */ |
| @@ -535,12 +535,7 @@ static int smack_syslog(int typefrom_file) · 534 unmodified lines | ||
| 535 | 535 | */ |
| 536 | 536 | static int smack_sb_alloc_security(struct super_block *sb) |
| 537 | 537 | { |
| 538 | − struct superblock_smack *sbsp; | |
| 539 | − | |
| 540 | − sbsp = kzalloc(sizeof(struct superblock_smack), GFP_KERNEL); | |
| 541 | − | |
| 542 | − if (sbsp == NULL) | |
| 543 | − return -ENOMEM; | |
| 538 | + struct superblock_smack *sbsp = smack_superblock(sb); | |
| 544 | 539 | |
| 545 | 540 | sbsp->smk_root = &smack_known_floor; |
| 546 | 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 | 545 | * SMK_SB_INITIALIZED will be zero from kzalloc. |
| 551 | 546 | */ |
| 552 | − sb->s_security = sbsp; | |
| 553 | 547 | |
| 554 | 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 | 551 | struct smack_mnt_opts { |
| 569 | 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 | 756 | struct dentry *root = sb->s_root; |
| 774 | 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 | 759 | struct inode_smack *isp; |
| 777 | 760 | struct smack_known *skp; |
| 778 | 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 | 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 | 858 | int rc; |
| 876 | 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 | 888 | if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task) |
| 906 | 889 | return 0; |
| 907 | 890 | |
| 908 | − sbsp = inode->i_sb->s_security; | |
| 891 | + sbsp = smack_superblock(inode->i_sb); | |
| 909 | 892 | if ((sbsp->smk_flags & SMK_SB_UNTRUSTED) && |
| 910 | 893 | isp->smk_task != sbsp->smk_root) |
| 911 | 894 | return 0; |
| @@ -1157,7 +1140,7 @@ static int smack_inode_rename(struct inode *old_inode, · 245 unmodified lines | ||
| 1157 | 1140 | */ |
| 1158 | 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 | 1144 | struct smk_audit_info ad; |
| 1162 | 1145 | int no_block = mask & MAY_NOT_BLOCK; |
| 1163 | 1146 | int rc; |
| @@ -1400,7 +1383,7 @@ static int smack_inode_removexattr(struct user_namespace *mnt_userns, · 236 unmodified lines | ||
| 1400 | 1383 | */ |
| 1401 | 1384 | if (strcmp(name, XATTR_NAME_SMACK) == 0) { |
| 1402 | 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 | 1388 | isp->smk_inode = sbsp->smk_default; |
| 1406 | 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 | 1653 | isp = smack_inode(file_inode(file)); |
| 1671 | 1654 | if (isp->smk_mmap == NULL) |
| 1672 | 1655 | return 0; |
| 1673 | − sbsp = file_inode(file)->i_sb->s_security; | |
| 1656 | + sbsp = smack_superblock(file_inode(file)->i_sb); | |
| 1674 | 1657 | if (sbsp->smk_flags & SMK_SB_UNTRUSTED && |
| 1675 | 1658 | isp->smk_mmap != sbsp->smk_root) |
| 1676 | 1659 | return -EACCES; |
| @@ -3299,7 +3282,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) · 1622 unmodified lines | ||
| 3299 | 3282 | return; |
| 3300 | 3283 | |
| 3301 | 3284 | sbp = inode->i_sb; |
| 3302 | − sbsp = sbp->s_security; | |
| 3285 | + sbsp = smack_superblock(sbp); | |
| 3303 | 3286 | /* |
| 3304 | 3287 | * We're going to use the superblock default label |
| 3305 | 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 | 4697 | .lbs_inode = sizeof(struct inode_smack), |
| 4715 | 4698 | .lbs_ipc = sizeof(struct smack_known *), |
| 4716 | 4699 | .lbs_msg_msg = sizeof(struct smack_known *), |
| 4700 | + .lbs_superblock = sizeof(struct superblock_smack), | |
| 4717 | 4701 | }; |
| 4718 | 4702 | |
| 4719 | 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 | 4709 | LSM_HOOK_INIT(fs_context_parse_param, smack_fs_context_parse_param), |
| 4726 | 4710 | |
| 4727 | 4711 | LSM_HOOK_INIT(sb_alloc_security, smack_sb_alloc_security), |
| 4728 | − LSM_HOOK_INIT(sb_free_security, smack_sb_free_security), | |
| 4729 | 4712 | LSM_HOOK_INIT(sb_free_mnt_opts, smack_free_mnt_opts), |
| 4730 | 4713 | LSM_HOOK_INIT(sb_eat_lsm_opts, smack_sb_eat_lsm_opts), |
| 4731 | 4714 | LSM_HOOK_INIT(sb_statfs, smack_sb_statfs), |
| @@ -25,6 +25,7 @@ TARGETS += ir · 24 unmodified lines | ||
| 25 | 25 | TARGETS += kcmp |
| 26 | 26 | TARGETS += kexec |
| 27 | 27 | TARGETS += kvm |
| 28 | +TARGETS += landlock | |
| 28 | 29 | TARGETS += lib |
| 29 | 30 | TARGETS += livepatch |
| 30 | 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 | +} | |