| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * Landlock - Ptrace and scope hooks |
| 4 | * |
| 5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> |
| 6 | * Copyright © 2019-2020 ANSSI |
| 7 | * Copyright © 2024-2025 Microsoft Corporation |
| 8 | */ |
| 9 | |
| 10 | #include <asm/current.h> |
| 11 | #include <linux/cleanup.h> |
| 12 | #include <linux/cred.h> |
| 13 | #include <linux/errno.h> |
| 14 | #include <linux/kernel.h> |
| 15 | #include <linux/lsm_audit.h> |
| 16 | #include <linux/lsm_hooks.h> |
| 17 | #include <linux/rcupdate.h> |
| 18 | #include <linux/sched.h> |
| 19 | #include <linux/sched/signal.h> |
| 20 | #include <net/af_unix.h> |
| 21 | #include <net/sock.h> |
| 22 | |
| 23 | #include "audit.h" |
| 24 | #include "common.h" |
| 25 | #include "cred.h" |
| 26 | #include "domain.h" |
| 27 | #include "fs.h" |
| 28 | #include "ruleset.h" |
| 29 | #include "setup.h" |
| 30 | #include "task.h" |
| 31 | |
| 32 | /** |
| 33 | * domain_scope_le - Checks domain ordering for scoped ptrace |
| 34 | * |
| 35 | * @parent: Parent domain. |
| 36 | * @child: Potential child of @parent. |
| 37 | * |
| 38 | * Checks if the @parent domain is less or equal to (i.e. an ancestor, which |
| 39 | * means a subset of) the @child domain. |
| 40 | */ |
| 41 | static bool domain_scope_le(const struct landlock_ruleset *const parent, |
| 42 | const struct landlock_ruleset *const child) |
| 43 | { |
| 44 | const struct landlock_hierarchy *walker; |
| 45 | |
| 46 | /* Quick return for non-landlocked tasks. */ |
| 47 | if (!parent) |
| 48 | return true; |
| 49 | |
| 50 | if (!child) |
| 51 | return false; |
| 52 | |
| 53 | for (walker = child->hierarchy; walker; walker = walker->parent) { |
| 54 | if (walker == parent->hierarchy) |
| 55 | /* @parent is in the scoped hierarchy of @child. */ |
| 56 | return true; |
| 57 | } |
| 58 | |
| 59 | /* There is no relationship between @parent and @child. */ |
| 60 | return false; |
| 61 | } |
| 62 | |
| 63 | static int domain_ptrace(const struct landlock_ruleset *const parent, |
| 64 | const struct landlock_ruleset *const child) |
| 65 | { |
| 66 | if (domain_scope_le(parent, child)) |
| 67 | return 0; |
| 68 | |
| 69 | return -EPERM; |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * hook_ptrace_access_check - Determines whether the current process may access |
| 74 | * another |
| 75 | * |
| 76 | * @child: Process to be accessed. |
| 77 | * @mode: Mode of attachment. |
| 78 | * |
| 79 | * If the current task has Landlock rules, then the child must have at least |
| 80 | * the same rules. Else denied. |
| 81 | * |
| 82 | * Determines whether a process may access another, returning 0 if permission |
| 83 | * granted, -errno if denied. |
| 84 | */ |
| 85 | static int hook_ptrace_access_check(struct task_struct *const child, |
| 86 | const unsigned int mode) |
| 87 | { |
| 88 | const struct landlock_cred_security *parent_subject; |
| 89 | int err; |
| 90 | |
| 91 | /* Quick return for non-landlocked tasks. */ |
| 92 | parent_subject = landlock_cred(current_cred()); |
| 93 | if (!parent_subject) |
| 94 | return 0; |
| 95 | |
| 96 | scoped_guard(rcu) |
| 97 | { |
| 98 | const struct landlock_ruleset *const child_dom = |
| 99 | landlock_get_task_domain(task: child); |
| 100 | err = domain_ptrace(parent: parent_subject->domain, child: child_dom); |
| 101 | } |
| 102 | |
| 103 | if (!err) |
| 104 | return 0; |
| 105 | |
| 106 | /* |
| 107 | * For the ptrace_access_check case, we log the current/parent domain |
| 108 | * and the child task. |
| 109 | */ |
| 110 | if (!(mode & PTRACE_MODE_NOAUDIT)) |
| 111 | landlock_log_denial(subject: parent_subject, request: &(struct landlock_request) { |
| 112 | .type = LANDLOCK_REQUEST_PTRACE, |
| 113 | .audit = { |
| 114 | .type = LSM_AUDIT_DATA_TASK, |
| 115 | .u.tsk = child, |
| 116 | }, |
| 117 | .layer_plus_one = parent_subject->domain->num_layers, |
| 118 | }); |
| 119 | |
| 120 | return err; |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * hook_ptrace_traceme - Determines whether another process may trace the |
| 125 | * current one |
| 126 | * |
| 127 | * @parent: Task proposed to be the tracer. |
| 128 | * |
| 129 | * If the parent has Landlock rules, then the current task must have the same |
| 130 | * or more rules. Else denied. |
| 131 | * |
| 132 | * Determines whether the nominated task is permitted to trace the current |
| 133 | * process, returning 0 if permission is granted, -errno if denied. |
| 134 | */ |
| 135 | static int hook_ptrace_traceme(struct task_struct *const parent) |
| 136 | { |
| 137 | const struct landlock_cred_security *parent_subject; |
| 138 | const struct landlock_ruleset *child_dom; |
| 139 | int err; |
| 140 | |
| 141 | child_dom = landlock_get_current_domain(); |
| 142 | |
| 143 | guard(rcu)(); |
| 144 | parent_subject = landlock_cred(__task_cred(parent)); |
| 145 | err = domain_ptrace(parent: parent_subject->domain, child: child_dom); |
| 146 | |
| 147 | if (!err) |
| 148 | return 0; |
| 149 | |
| 150 | /* |
| 151 | * For the ptrace_traceme case, we log the domain which is the cause of |
| 152 | * the denial, which means the parent domain instead of the current |
| 153 | * domain. This may look unusual because the ptrace_traceme action is a |
| 154 | * request to be traced, but the semantic is consistent with |
| 155 | * hook_ptrace_access_check(). |
| 156 | */ |
| 157 | landlock_log_denial(subject: parent_subject, request: &(struct landlock_request) { |
| 158 | .type = LANDLOCK_REQUEST_PTRACE, |
| 159 | .audit = { |
| 160 | .type = LSM_AUDIT_DATA_TASK, |
| 161 | .u.tsk = current, |
| 162 | }, |
| 163 | .layer_plus_one = parent_subject->domain->num_layers, |
| 164 | }); |
| 165 | return err; |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * domain_is_scoped - Check if an interaction from a client/sender to a |
| 170 | * server/receiver should be restricted based on scope controls. |
| 171 | * |
| 172 | * @client: IPC sender domain. |
| 173 | * @server: IPC receiver domain. |
| 174 | * @scope: The scope restriction criteria. |
| 175 | * |
| 176 | * Returns: True if @server is in a different domain from @client, and @client |
| 177 | * is scoped to access @server (i.e. access should be denied). |
| 178 | */ |
| 179 | static bool domain_is_scoped(const struct landlock_ruleset *const client, |
| 180 | const struct landlock_ruleset *const server, |
| 181 | access_mask_t scope) |
| 182 | { |
| 183 | int client_layer, server_layer; |
| 184 | const struct landlock_hierarchy *client_walker, *server_walker; |
| 185 | |
| 186 | /* Quick return if client has no domain */ |
| 187 | if (WARN_ON_ONCE(!client)) |
| 188 | return false; |
| 189 | |
| 190 | client_layer = client->num_layers - 1; |
| 191 | client_walker = client->hierarchy; |
| 192 | /* |
| 193 | * client_layer must be a signed integer with greater capacity |
| 194 | * than client->num_layers to ensure the following loop stops. |
| 195 | */ |
| 196 | BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); |
| 197 | |
| 198 | server_layer = server ? (server->num_layers - 1) : -1; |
| 199 | server_walker = server ? server->hierarchy : NULL; |
| 200 | |
| 201 | /* |
| 202 | * Walks client's parent domains down to the same hierarchy level |
| 203 | * as the server's domain, and checks that none of these client's |
| 204 | * parent domains are scoped. |
| 205 | */ |
| 206 | for (; client_layer > server_layer; client_layer--) { |
| 207 | if (landlock_get_scope_mask(ruleset: client, layer_level: client_layer) & scope) |
| 208 | return true; |
| 209 | |
| 210 | client_walker = client_walker->parent; |
| 211 | } |
| 212 | /* |
| 213 | * Walks server's parent domains down to the same hierarchy level as |
| 214 | * the client's domain. |
| 215 | */ |
| 216 | for (; server_layer > client_layer; server_layer--) |
| 217 | server_walker = server_walker->parent; |
| 218 | |
| 219 | for (; client_layer >= 0; client_layer--) { |
| 220 | if (landlock_get_scope_mask(ruleset: client, layer_level: client_layer) & scope) { |
| 221 | /* |
| 222 | * Client and server are at the same level in the |
| 223 | * hierarchy. If the client is scoped, the request is |
| 224 | * only allowed if this domain is also a server's |
| 225 | * ancestor. |
| 226 | */ |
| 227 | return server_walker != client_walker; |
| 228 | } |
| 229 | client_walker = client_walker->parent; |
| 230 | server_walker = server_walker->parent; |
| 231 | } |
| 232 | return false; |
| 233 | } |
| 234 | |
| 235 | static bool sock_is_scoped(struct sock *const other, |
| 236 | const struct landlock_ruleset *const domain) |
| 237 | { |
| 238 | const struct landlock_ruleset *dom_other; |
| 239 | |
| 240 | /* The credentials will not change. */ |
| 241 | lockdep_assert_held(&unix_sk(other)->lock); |
| 242 | dom_other = landlock_cred(cred: other->sk_socket->file->f_cred)->domain; |
| 243 | return domain_is_scoped(client: domain, server: dom_other, |
| 244 | LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); |
| 245 | } |
| 246 | |
| 247 | static bool is_abstract_socket(struct sock *const sock) |
| 248 | { |
| 249 | struct unix_address *addr = unix_sk(sock)->addr; |
| 250 | |
| 251 | if (!addr) |
| 252 | return false; |
| 253 | |
| 254 | if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 && |
| 255 | addr->name->sun_path[0] == '\0') |
| 256 | return true; |
| 257 | |
| 258 | return false; |
| 259 | } |
| 260 | |
| 261 | static const struct access_masks unix_scope = { |
| 262 | .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, |
| 263 | }; |
| 264 | |
| 265 | static int hook_unix_stream_connect(struct sock *const sock, |
| 266 | struct sock *const other, |
| 267 | struct sock *const newsk) |
| 268 | { |
| 269 | size_t handle_layer; |
| 270 | const struct landlock_cred_security *const subject = |
| 271 | landlock_get_applicable_subject(current_cred(), masks: unix_scope, |
| 272 | handle_layer: &handle_layer); |
| 273 | |
| 274 | /* Quick return for non-landlocked tasks. */ |
| 275 | if (!subject) |
| 276 | return 0; |
| 277 | |
| 278 | if (!is_abstract_socket(sock: other)) |
| 279 | return 0; |
| 280 | |
| 281 | if (!sock_is_scoped(other, domain: subject->domain)) |
| 282 | return 0; |
| 283 | |
| 284 | landlock_log_denial(subject, request: &(struct landlock_request) { |
| 285 | .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, |
| 286 | .audit = { |
| 287 | .type = LSM_AUDIT_DATA_NET, |
| 288 | .u.net = &(struct lsm_network_audit) { |
| 289 | .sk = other, |
| 290 | }, |
| 291 | }, |
| 292 | .layer_plus_one = handle_layer + 1, |
| 293 | }); |
| 294 | return -EPERM; |
| 295 | } |
| 296 | |
| 297 | static int hook_unix_may_send(struct socket *const sock, |
| 298 | struct socket *const other) |
| 299 | { |
| 300 | size_t handle_layer; |
| 301 | const struct landlock_cred_security *const subject = |
| 302 | landlock_get_applicable_subject(current_cred(), masks: unix_scope, |
| 303 | handle_layer: &handle_layer); |
| 304 | |
| 305 | if (!subject) |
| 306 | return 0; |
| 307 | |
| 308 | /* |
| 309 | * Checks if this datagram socket was already allowed to be connected |
| 310 | * to other. |
| 311 | */ |
| 312 | if (unix_peer(sock->sk) == other->sk) |
| 313 | return 0; |
| 314 | |
| 315 | if (!is_abstract_socket(sock: other->sk)) |
| 316 | return 0; |
| 317 | |
| 318 | if (!sock_is_scoped(other: other->sk, domain: subject->domain)) |
| 319 | return 0; |
| 320 | |
| 321 | landlock_log_denial(subject, request: &(struct landlock_request) { |
| 322 | .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, |
| 323 | .audit = { |
| 324 | .type = LSM_AUDIT_DATA_NET, |
| 325 | .u.net = &(struct lsm_network_audit) { |
| 326 | .sk = other->sk, |
| 327 | }, |
| 328 | }, |
| 329 | .layer_plus_one = handle_layer + 1, |
| 330 | }); |
| 331 | return -EPERM; |
| 332 | } |
| 333 | |
| 334 | static const struct access_masks signal_scope = { |
| 335 | .scope = LANDLOCK_SCOPE_SIGNAL, |
| 336 | }; |
| 337 | |
| 338 | static int hook_task_kill(struct task_struct *const p, |
| 339 | struct kernel_siginfo *const info, const int sig, |
| 340 | const struct cred *cred) |
| 341 | { |
| 342 | bool is_scoped; |
| 343 | size_t handle_layer; |
| 344 | const struct landlock_cred_security *subject; |
| 345 | |
| 346 | if (!cred) { |
| 347 | /* |
| 348 | * Always allow sending signals between threads of the same process. |
| 349 | * This is required for process credential changes by the Native POSIX |
| 350 | * Threads Library and implemented by the set*id(2) wrappers and |
| 351 | * libcap(3) with tgkill(2). See nptl(7) and libpsx(3). |
| 352 | * |
| 353 | * This exception is similar to the __ptrace_may_access() one. |
| 354 | */ |
| 355 | if (same_thread_group(p1: p, current)) |
| 356 | return 0; |
| 357 | |
| 358 | /* Not dealing with USB IO. */ |
| 359 | cred = current_cred(); |
| 360 | } |
| 361 | |
| 362 | subject = landlock_get_applicable_subject(cred, masks: signal_scope, |
| 363 | handle_layer: &handle_layer); |
| 364 | |
| 365 | /* Quick return for non-landlocked tasks. */ |
| 366 | if (!subject) |
| 367 | return 0; |
| 368 | |
| 369 | scoped_guard(rcu) |
| 370 | { |
| 371 | is_scoped = domain_is_scoped(client: subject->domain, |
| 372 | server: landlock_get_task_domain(task: p), |
| 373 | scope: signal_scope.scope); |
| 374 | } |
| 375 | |
| 376 | if (!is_scoped) |
| 377 | return 0; |
| 378 | |
| 379 | landlock_log_denial(subject, request: &(struct landlock_request) { |
| 380 | .type = LANDLOCK_REQUEST_SCOPE_SIGNAL, |
| 381 | .audit = { |
| 382 | .type = LSM_AUDIT_DATA_TASK, |
| 383 | .u.tsk = p, |
| 384 | }, |
| 385 | .layer_plus_one = handle_layer + 1, |
| 386 | }); |
| 387 | return -EPERM; |
| 388 | } |
| 389 | |
| 390 | static int hook_file_send_sigiotask(struct task_struct *tsk, |
| 391 | struct fown_struct *fown, int signum) |
| 392 | { |
| 393 | const struct landlock_cred_security *subject; |
| 394 | bool is_scoped = false; |
| 395 | |
| 396 | /* Lock already held by send_sigio() and send_sigurg(). */ |
| 397 | lockdep_assert_held(&fown->lock); |
| 398 | subject = &landlock_file(file: fown->file)->fown_subject; |
| 399 | |
| 400 | /* |
| 401 | * Quick return for unowned socket. |
| 402 | * |
| 403 | * subject->domain has already been filtered when saved by |
| 404 | * hook_file_set_fowner(), so there is no need to call |
| 405 | * landlock_get_applicable_subject() here. |
| 406 | */ |
| 407 | if (!subject->domain) |
| 408 | return 0; |
| 409 | |
| 410 | scoped_guard(rcu) |
| 411 | { |
| 412 | is_scoped = domain_is_scoped(client: subject->domain, |
| 413 | server: landlock_get_task_domain(task: tsk), |
| 414 | scope: signal_scope.scope); |
| 415 | } |
| 416 | |
| 417 | if (!is_scoped) |
| 418 | return 0; |
| 419 | |
| 420 | landlock_log_denial(subject, request: &(struct landlock_request) { |
| 421 | .type = LANDLOCK_REQUEST_SCOPE_SIGNAL, |
| 422 | .audit = { |
| 423 | .type = LSM_AUDIT_DATA_TASK, |
| 424 | .u.tsk = tsk, |
| 425 | }, |
| 426 | #ifdef CONFIG_AUDIT |
| 427 | .layer_plus_one = landlock_file(file: fown->file)->fown_layer + 1, |
| 428 | #endif /* CONFIG_AUDIT */ |
| 429 | }); |
| 430 | return -EPERM; |
| 431 | } |
| 432 | |
| 433 | static struct security_hook_list landlock_hooks[] __ro_after_init = { |
| 434 | LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), |
| 435 | LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), |
| 436 | |
| 437 | LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect), |
| 438 | LSM_HOOK_INIT(unix_may_send, hook_unix_may_send), |
| 439 | |
| 440 | LSM_HOOK_INIT(task_kill, hook_task_kill), |
| 441 | LSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask), |
| 442 | }; |
| 443 | |
| 444 | __init void landlock_add_task_hooks(void) |
| 445 | { |
| 446 | security_add_hooks(hooks: landlock_hooks, ARRAY_SIZE(landlock_hooks), |
| 447 | lsmid: &landlock_lsmid); |
| 448 | } |
| 449 | |