[PATCH v4 1/6] landlock: Add signal scoping control

Tahera Fahimi
2024-09-06T21:30:17Z

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_SCOPED_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 procsses).

This patch adds two new hooks, "hook_file_set_fowner" and
"hook_file_free_security", 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. Also, it updates the function "ruleset_with_unknown_scope", to
support the scope mask of signal "LANDLOCK_SCOPED_SIGNAL".

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
Changes in versions:
V4:
* Merging file_send_sigiotask and task_kill patches into one.
* Commit improvement.
* Applying feedback of V3 on managing fown_domain pointer.
V3:
* Moving file_send_sigiotask to another patch.
* Minor code refactoring.
V2:
* Remove signal_is_scoped function
* Applying reviews of V1
V1:
* Introducing LANDLOCK_SCOPE_SIGNAL
* Adding two hooks, hook_task_kill and hook_file_send_sigiotask for
signal scoping.
---
include/uapi/linux/landlock.h | 3 +
security/landlock/fs.c | 17 ++++++
security/landlock/fs.h | 6 ++
security/landlock/limits.h | 2 +-
security/landlock/task.c | 59 +++++++++++++++++++
.../testing/selftests/landlock/scoped_test.c | 2 +-
6 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index dfd48d722834..197da0c5c264 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -297,9 +297,12 @@ struct landlock_net_port_attr {
* from connecting to an abstract unix socket created by a process
* outside the related Landlock domain (e.g. a parent domain or a
* non-sandboxed process).
+ * - %LANDLOCK_SCOPED_SIGNAL: Restrict a sandboxed process from sending
+ * a signal to another process outside sandbox domain.
*/
/* clang-format off */
#define LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET (1ULL << 0)
+#define LANDLOCK_SCOPED_SIGNAL (1ULL << 1)
/* clang-format on*/

#endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 7877a64cc6b8..b1207f0a8cd4 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1636,6 +1636,20 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
return -EACCES;
}

+static void hook_file_set_fowner(struct file *file)
+{
+ write_lock_irq(&file->f_owner.lock);
+ landlock_put_ruleset_deferred(landlock_file(file)->fown_domain);
+ landlock_file(file)->fown_domain = landlock_get_current_domain();
+ landlock_get_ruleset(landlock_file(file)->fown_domain);
+ write_unlock_irq(&file->f_owner.lock);
+}
+
+static void hook_file_free_security(struct file *file)
+{
+ landlock_put_ruleset_deferred(landlock_file(file)->fown_domain);
+}
+
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_free_security, hook_inode_free_security),

@@ -1660,6 +1674,9 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(file_truncate, hook_file_truncate),
LSM_HOOK_INIT(file_ioctl, hook_file_ioctl),
LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat),
+
+ LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner),
+ LSM_HOOK_INIT(file_free_security, hook_file_free_security),
};

__init void landlock_add_fs_hooks(void)
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index 488e4813680a..9a97f9285b90 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -52,6 +52,12 @@ struct landlock_file_security {
* needed to authorize later operations on the open file.
*/
access_mask_t allowed_access;
+ /**
+ * @fown_domain: A pointer to a &landlock_ruleset of the process owns
+ * the file. This ruleset is protected by fowner_struct.lock same as
+ * pid, uid, euid fields in fown_struct.
+ */
+ struct landlock_ruleset *fown_domain;
};

/**
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index eb01d0fb2165..fa28f9236407 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -26,7 +26,7 @@
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)

-#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET
+#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPED_SIGNAL
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
/* clang-format on */
diff --git a/security/landlock/task.c b/security/landlock/task.c
index b9390445d242..a72a61e7e6c3 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -18,6 +18,7 @@

#include "common.h"
#include "cred.h"
+#include "fs.h"
#include "ruleset.h"
#include "setup.h"
#include "task.h"
@@ -242,11 +243,69 @@ static int hook_unix_may_send(struct socket *const sock,
return 0;
}

+static int hook_task_kill(struct task_struct *const p,
+ struct kernel_siginfo *const info, const int sig,
+ const struct cred *const cred)
+{
+ bool is_scoped;
+ const struct landlock_ruleset *target_dom, *dom;
+
+ dom = landlock_get_current_domain();
+ rcu_read_lock();
+ target_dom = landlock_get_task_domain(p);
+ if (cred)
+ /* dealing with USB IO */
+ is_scoped = domain_is_scoped(landlock_cred(cred)->domain,
+ target_dom,
+ LANDLOCK_SCOPED_SIGNAL);
+ else
+ is_scoped = (!dom) ? false :
+ domain_is_scoped(dom, target_dom,
+ LANDLOCK_SCOPED_SIGNAL);
+ rcu_read_unlock();
+ if (is_scoped)
+ return -EPERM;
+
+ return 0;
+}
+
+static int hook_file_send_sigiotask(struct task_struct *tsk,
+ struct fown_struct *fown, int signum)
+{
+ struct file *file;
+ bool is_scoped;
+ struct landlock_ruleset *dom;
+
+ /* struct fown_struct is never outside the context of a struct file */
+ file = container_of(fown, struct file, f_owner);
+
+ read_lock_irq(&file->f_owner.lock);
+ dom = landlock_file(file)->fown_domain;
+ landlock_get_ruleset(dom);
+ read_unlock_irq(&file->f_owner.lock);
+ if (!dom)
+ goto out_unlock;
+
+ rcu_read_lock();
+ is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk),
+ LANDLOCK_SCOPED_SIGNAL);
+ rcu_read_unlock();
+ if (is_scoped) {
+ landlock_put_ruleset(dom);
+ return -EPERM;
+ }
+out_unlock:
+ landlock_put_ruleset(dom);
+ return 0;
+}
+
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect),
LSM_HOOK_INIT(unix_may_send, hook_unix_may_send),
+ LSM_HOOK_INIT(task_kill, hook_task_kill),
+ LSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask),
};

__init void landlock_add_task_hooks(void)
diff --git a/tools/testing/selftests/landlock/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c
index 36d7266de9dc..237f98369b25 100644
--- a/tools/testing/selftests/landlock/scoped_test.c
+++ b/tools/testing/selftests/landlock/scoped_test.c
@@ -12,7 +12,7 @@

#include "common.h"

-#define ACCESS_LAST LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET
+#define ACCESS_LAST LANDLOCK_SCOPED_SIGNAL

TEST(ruleset_with_unknown_scope)
{
--
2.34.1