Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

landlock: Add signal scoping

Currently, a sandbox process is not restricted to sending a signal (e.g.
SIGKILL) to a process outside the sandbox environment. The ability to
send a signal for a sandboxed process should be scoped the same way
abstract UNIX sockets are scoped. Therefore, we extend the "scoped"
field in a ruleset with LANDLOCK_SCOPE_SIGNAL to specify that a ruleset
will deny sending any signal from within a sandbox process to its parent
(i.e. any parent sandbox or non-sandboxed processes).

This patch adds file_set_fowner and file_free_security hooks to set and
release a pointer to the file owner's domain. This pointer, fown_domain
in landlock_file_security will be used in file_send_sigiotask to check
if the process can send a signal.

The ruleset_with_unknown_scope test is updated to support
LANDLOCK_SCOPE_SIGNAL.

This depends on two new changes:
- commit 1934b212615d ("file: reclaim 24 bytes from f_owner"): replace
container_of(fown, struct file, f_owner) with fown->file .
- commit 26f204380a3c ("fs: Fix file_set_fowner LSM hook
inconsistencies"): lock before calling the hook.

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
Closes: https://github.com/landlock-lsm/linux/issues/8
Link: https://lore.kernel.org/r/df2b4f880a2ed3042992689a793ea0951f6798a5.1725657727.git.fahimitahera@gmail.com
[mic: Update landlock_get_current_domain()'s return type, improve and
fix locking in hook_file_set_fowner(), simplify and fix sleepable call
and locking issue in hook_file_send_sigiotask() and rebase on the latest
VFS tree, simplify hook_task_kill() and quickly return when not
sandboxed, improve comments, rename LANDLOCK_SCOPED_SIGNAL]
Co-developed-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Mickaël Salaün <mic@digikod.net>

authored by

Tahera Fahimi and committed by
Mickaël Salaün
54a6e6bb dba40c77

+94 -3
+3
include/uapi/linux/landlock.h
··· 296 296 * - %LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process from 297 297 * connecting to an abstract UNIX socket created by a process outside the 298 298 * related Landlock domain (e.g. a parent domain or a non-sandboxed process). 299 + * - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal 300 + * to another process outside the domain. 299 301 */ 300 302 /* clang-format off */ 301 303 #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0) 304 + #define LANDLOCK_SCOPE_SIGNAL (1ULL << 1) 302 305 /* clang-format on*/ 303 306 304 307 #endif /* _UAPI_LINUX_LANDLOCK_H */
+1 -1
security/landlock/cred.h
··· 26 26 return cred->security + landlock_blob_sizes.lbs_cred; 27 27 } 28 28 29 - static inline const struct landlock_ruleset *landlock_get_current_domain(void) 29 + static inline struct landlock_ruleset *landlock_get_current_domain(void) 30 30 { 31 31 return landlock_cred(current_cred())->domain; 32 32 }
+25
security/landlock/fs.c
··· 1639 1639 return -EACCES; 1640 1640 } 1641 1641 1642 + static void hook_file_set_fowner(struct file *file) 1643 + { 1644 + struct landlock_ruleset *new_dom, *prev_dom; 1645 + 1646 + /* 1647 + * Lock already held by __f_setown(), see commit 26f204380a3c ("fs: Fix 1648 + * file_set_fowner LSM hook inconsistencies"). 1649 + */ 1650 + lockdep_assert_held(&file_f_owner(file)->lock); 1651 + new_dom = landlock_get_current_domain(); 1652 + landlock_get_ruleset(new_dom); 1653 + prev_dom = landlock_file(file)->fown_domain; 1654 + landlock_file(file)->fown_domain = new_dom; 1655 + 1656 + /* Called in an RCU read-side critical section. */ 1657 + landlock_put_ruleset_deferred(prev_dom); 1658 + } 1659 + 1660 + static void hook_file_free_security(struct file *file) 1661 + { 1662 + landlock_put_ruleset_deferred(landlock_file(file)->fown_domain); 1663 + } 1664 + 1642 1665 static struct security_hook_list landlock_hooks[] __ro_after_init = { 1643 1666 LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu), 1644 1667 ··· 1686 1663 LSM_HOOK_INIT(file_truncate, hook_file_truncate), 1687 1664 LSM_HOOK_INIT(file_ioctl, hook_file_ioctl), 1688 1665 LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat), 1666 + LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner), 1667 + LSM_HOOK_INIT(file_free_security, hook_file_free_security), 1689 1668 }; 1690 1669 1691 1670 __init void landlock_add_fs_hooks(void)
+7
security/landlock/fs.h
··· 52 52 * needed to authorize later operations on the open file. 53 53 */ 54 54 access_mask_t allowed_access; 55 + /** 56 + * @fown_domain: Domain of the task that set the PID that may receive a 57 + * signal e.g., SIGURG when writing MSG_OOB to the related socket. 58 + * This pointer is protected by the related file->f_owner->lock, as for 59 + * fown_struct's members: pid, uid, and euid. 60 + */ 61 + struct landlock_ruleset *fown_domain; 55 62 }; 56 63 57 64 /**
+1 -1
security/landlock/limits.h
··· 26 26 #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) 27 27 #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) 28 28 29 - #define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET 29 + #define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL 30 30 #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) 31 31 #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) 32 32 /* clang-format on */
+56
security/landlock/task.c
··· 18 18 19 19 #include "common.h" 20 20 #include "cred.h" 21 + #include "fs.h" 21 22 #include "ruleset.h" 22 23 #include "setup.h" 23 24 #include "task.h" ··· 243 242 return 0; 244 243 } 245 244 245 + static int hook_task_kill(struct task_struct *const p, 246 + struct kernel_siginfo *const info, const int sig, 247 + const struct cred *const cred) 248 + { 249 + bool is_scoped; 250 + const struct landlock_ruleset *dom; 251 + 252 + if (cred) { 253 + /* Dealing with USB IO. */ 254 + dom = landlock_cred(cred)->domain; 255 + } else { 256 + dom = landlock_get_current_domain(); 257 + } 258 + 259 + /* Quick return for non-landlocked tasks. */ 260 + if (!dom) 261 + return 0; 262 + 263 + rcu_read_lock(); 264 + is_scoped = domain_is_scoped(dom, landlock_get_task_domain(p), 265 + LANDLOCK_SCOPE_SIGNAL); 266 + rcu_read_unlock(); 267 + if (is_scoped) 268 + return -EPERM; 269 + 270 + return 0; 271 + } 272 + 273 + static int hook_file_send_sigiotask(struct task_struct *tsk, 274 + struct fown_struct *fown, int signum) 275 + { 276 + const struct landlock_ruleset *dom; 277 + bool is_scoped = false; 278 + 279 + /* Lock already held by send_sigio() and send_sigurg(). */ 280 + lockdep_assert_held(&fown->lock); 281 + dom = landlock_file(fown->file)->fown_domain; 282 + 283 + /* Quick return for unowned socket. */ 284 + if (!dom) 285 + return 0; 286 + 287 + rcu_read_lock(); 288 + is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk), 289 + LANDLOCK_SCOPE_SIGNAL); 290 + rcu_read_unlock(); 291 + if (is_scoped) 292 + return -EPERM; 293 + 294 + return 0; 295 + } 296 + 246 297 static struct security_hook_list landlock_hooks[] __ro_after_init = { 247 298 LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), 248 299 LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), 249 300 250 301 LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect), 251 302 LSM_HOOK_INIT(unix_may_send, hook_unix_may_send), 303 + 304 + LSM_HOOK_INIT(task_kill, hook_task_kill), 305 + LSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask), 252 306 }; 253 307 254 308 __init void landlock_add_task_hooks(void)
+1 -1
tools/testing/selftests/landlock/scoped_test.c
··· 12 12 13 13 #include "common.h" 14 14 15 - #define ACCESS_LAST LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET 15 + #define ACCESS_LAST LANDLOCK_SCOPE_SIGNAL 16 16 17 17 TEST(ruleset_with_unknown_scope) 18 18 {