linux-security-module.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 1/2] Landlock: Add signal control
@ 2024-07-05 21:21 Tahera Fahimi
  2024-07-05 21:21 ` [PATCH v1 2/2] Landlock: Signal scoping tests Tahera Fahimi
  2024-07-22 12:45 ` [PATCH v1 1/2] Landlock: Add signal control Jann Horn
  0 siblings, 2 replies; 4+ messages in thread
From: Tahera Fahimi @ 2024-07-05 21:21 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, outreachy, netdev
  Cc: Tahera Fahimi

Currently, a sandbox process is not restricted to send a signal
(e.g. SIGKILL) to a process outside of the sandbox environment.
Ability to sending a signal for a sandboxed process should be
scoped the same way abstract unix sockets are scoped.

The same way as abstract unix socket, we extend "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).

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
 include/uapi/linux/landlock.h |  3 +++
 security/landlock/limits.h    |  2 +-
 security/landlock/task.c      | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 010aaca5b05a..878479a1b9dd 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -291,8 +291,11 @@ 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 process
  *   which is not sandboxed).
+ * - %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/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 acc6e0fbc111..caee485b97b2 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -168,11 +168,60 @@ static int hook_unix_may_send(struct socket *const sock,
 	return -EPERM;
 }
 
+static bool signal_is_scoped(const struct landlock_ruleset *const sender_dom,
+			     struct task_struct *const target)
+{
+	const struct landlock_ruleset *target_dom =
+		landlock_get_task_domain(target);
+
+	/* quick return if there is no domain or .scoped is not set */
+	if (!sender_dom || !get_scoped_accesses(sender_dom))
+		return true;
+
+	if (!target_dom || !get_scoped_accesses(target_dom))
+		return false;
+
+	/* other is scoped, they connect if they are in the same domain */
+	return domain_scope_le(sender_dom, target_dom);
+}
+
+static int hook_task_kill(struct task_struct *const p,
+			  struct kernel_siginfo *const info, const int sig,
+			  const struct cred *const cred)
+{
+	const struct landlock_ruleset *const dom =
+		landlock_get_current_domain();
+	bool ret = false;
+
+	if (!cred)
+		ret = signal_is_scoped(dom, p);
+	else
+		ret = signal_is_scoped(landlock_cred(cred)->domain, p);
+	if (ret)
+		return 0;
+	return EPERM;
+}
+
+static int hook_file_send_sigiotask(struct task_struct *tsk,
+				    struct fown_struct *fown, int signum)
+{
+	const struct task_struct *result =
+		get_pid_task(fown->pid, fown->pid_type);
+
+	const struct landlock_ruleset *const dom =
+		landlock_get_task_domain(result);
+	if (signal_is_scoped(dom, tsk))
+		return 0;
+	return EPERM;
+}
+
 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)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v1 2/2] Landlock: Signal scoping tests
  2024-07-05 21:21 [PATCH v1 1/2] Landlock: Add signal control Tahera Fahimi
@ 2024-07-05 21:21 ` Tahera Fahimi
  2024-07-09 15:18   ` Günther Noack
  2024-07-22 12:45 ` [PATCH v1 1/2] Landlock: Add signal control Jann Horn
  1 sibling, 1 reply; 4+ messages in thread
From: Tahera Fahimi @ 2024-07-05 21:21 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, outreachy, netdev
  Cc: Tahera Fahimi

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
 .../testing/selftests/landlock/ptrace_test.c  | 216 ++++++++++++++++++
 1 file changed, 216 insertions(+)

diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c
index a19db4d0b3bd..e092b67f8b67 100644
--- a/tools/testing/selftests/landlock/ptrace_test.c
+++ b/tools/testing/selftests/landlock/ptrace_test.c
@@ -17,6 +17,8 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <signal.h>
+
 #include "common.h"
 
 /* Copied from security/yama/yama_lsm.c */
@@ -25,6 +27,8 @@
 #define YAMA_SCOPE_CAPABILITY 2
 #define YAMA_SCOPE_NO_ATTACH 3
 
+static sig_atomic_t signaled;
+
 static void create_domain(struct __test_metadata *const _metadata)
 {
 	int ruleset_fd;
@@ -436,4 +440,216 @@ TEST_F(hierarchy, trace)
 		_metadata->exit_code = KSFT_FAIL;
 }
 
+static void create_sig_domain(struct __test_metadata *const _metadata)
+{
+	int ruleset_fd;
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.scoped = LANDLOCK_SCOPED_SIGNAL,
+	};
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	EXPECT_LE(0, ruleset_fd)
+	{
+		TH_LOG("Failed to create a ruleset: %s", strerror(errno));
+	}
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
+static void scope_signal_handler(int sig, siginfo_t *info, void *ucontext)
+{
+	if (sig == SIGHUP || sig == SIGURG || sig == SIGTSTP || sig == SIGTRAP)
+		signaled = 1;
+
+	// signal process group
+	//kill(-(t->pid), SIGKILL);
+}
+
+/* clang-format off */
+FIXTURE(signal_scoping) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(signal_scoping)
+{
+	const int sig;
+	const bool domain_both;
+	const bool domain_parent;
+	const bool domain_child;
+};
+
+/* Default Action: Terminate*/
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, deny_with_forked_domain) {
+	/* clang-format on */
+	.sig = SIGHUP,
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, allow_with_forked_domain) {
+	/* clang-format on */
+	.sig = SIGHUP,
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = false,
+};
+
+/* Default Action: Ignore*/
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, deny_with_forked_domain_SIGURG) {
+	/* clang-format on */
+	.sig = SIGURG,
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, allow_with_forked_domain_SIGURG) {
+	/* clang-format on */
+	.sig = SIGURG,
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = false,
+};
+
+/* Default Action: Stop*/
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, deny_with_forked_domain_SIGTSTP) {
+	/* clang-format on */
+	.sig = SIGTSTP,
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, allow_with_forked_domain_SIGTSTP) {
+	/* clang-format on */
+	.sig = SIGTSTP,
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = false,
+};
+
+/* Default Action: Coredump*/
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, deny_with_forked_domain_SIGTRAP) {
+	/* clang-format on */
+	.sig = SIGTRAP,
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(signal_scoping, allow_with_forked_domain_SIGTRAP) {
+	/* clang-format on */
+	.sig = SIGTRAP,
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = false,
+};
+
+FIXTURE_SETUP(signal_scoping)
+{
+}
+
+FIXTURE_TEARDOWN(signal_scoping)
+{
+}
+
+TEST_F(signal_scoping, test_signal)
+{
+	pid_t child;
+	pid_t parent = getpid();
+	int status;
+	bool can_signal;
+	int pipe_child[2], pipe_parent[2];
+	//char buf_parent;
+
+	struct sigaction action = {
+		.sa_sigaction = scope_signal_handler,
+		.sa_flags = SA_SIGINFO,
+
+	};
+
+	can_signal = !variant->domain_child;
+
+	//sigemptyset(&act.sa_mask);
+
+	ASSERT_LE(0, sigaction(variant->sig, &action, NULL))
+	{
+		TH_LOG("ERROR in sigaction %s", strerror(errno));
+	}
+
+	if (variant->domain_both) {
+		create_sig_domain(_metadata);
+		if (!__test_passed(_metadata))
+			/* Aborts before forking. */
+			return;
+	}
+	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		char buf_child;
+		int err;
+
+		ASSERT_EQ(0, close(pipe_parent[1]));
+		ASSERT_EQ(0, close(pipe_child[0]));
+
+		if (variant->domain_child)
+			create_sig_domain(_metadata);
+
+		/* Waits for the parent to be in a domain, if any. */
+		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+		//err = raise(SIGHUP);
+		err = kill(parent, variant->sig);
+		if (can_signal) {
+			ASSERT_EQ(0, err);
+		} else {
+			ASSERT_EQ(EPERM, errno)
+			{
+				TH_LOG("Invalid error cached: %s",
+				       strerror(errno));
+			}
+		}
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	ASSERT_EQ(0, close(pipe_child[1]));
+	ASSERT_EQ(0, close(pipe_parent[0]));
+	if (variant->domain_parent)
+		create_sig_domain(_metadata);
+
+	/* Signals that the parent is in a domain, if any. */
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	if (can_signal) {
+		ASSERT_EQ(-1, pause());
+		ASSERT_EQ(EINTR, errno);
+		ASSERT_EQ(1, signaled);
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+
+	if (WIFEXITED(status)) {
+		TH_LOG("Exited with code %d:", WEXITSTATUS(status));
+		if (!can_signal)
+			ASSERT_NE(1, signaled);
+	}
+
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
 TEST_HARNESS_MAIN
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v1 2/2] Landlock: Signal scoping tests
  2024-07-05 21:21 ` [PATCH v1 2/2] Landlock: Signal scoping tests Tahera Fahimi
@ 2024-07-09 15:18   ` Günther Noack
  0 siblings, 0 replies; 4+ messages in thread
From: Günther Noack @ 2024-07-09 15:18 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: mic, paul, jmorris, serge, linux-security-module, linux-kernel,
	bjorn3_gh, jannh, outreachy, netdev

On Fri, Jul 05, 2024 at 03:21:43PM -0600, Tahera Fahimi wrote:
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> ---
>  .../testing/selftests/landlock/ptrace_test.c  | 216 ++++++++++++++++++
>  1 file changed, 216 insertions(+)
> 
> diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c
> index a19db4d0b3bd..e092b67f8b67 100644
> --- a/tools/testing/selftests/landlock/ptrace_test.c
> +++ b/tools/testing/selftests/landlock/ptrace_test.c
> [...]

> +static void scope_signal_handler(int sig, siginfo_t *info, void *ucontext)
> +{
> +	if (sig == SIGHUP || sig == SIGURG || sig == SIGTSTP || sig == SIGTRAP)
> +		signaled = 1;
> +
> +	// signal process group
> +	//kill(-(t->pid), SIGKILL);

There is commented-out code like this in various places in this patch.

I am pretty sure that scripts/checkpatch.pl should flag that.
See https://docs.kernel.org/dev-tools/checkpatch.html
and https://docs.kernel.org/process/submitting-patches.html#style-check-your-changes

I personally just keep a checklist of things to remember before sending a patch.
(rebase as needed, clang-format -i (for Landlock files), run tests, check commit
metadata, git format-patch with -v and --cover-letter, scripts/checkpatch.pl,
edit cover letter, git send-email)

—Günther

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v1 1/2] Landlock: Add signal control
  2024-07-05 21:21 [PATCH v1 1/2] Landlock: Add signal control Tahera Fahimi
  2024-07-05 21:21 ` [PATCH v1 2/2] Landlock: Signal scoping tests Tahera Fahimi
@ 2024-07-22 12:45 ` Jann Horn
  1 sibling, 0 replies; 4+ messages in thread
From: Jann Horn @ 2024-07-22 12:45 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, outreachy, netdev

On Fri, Jul 5, 2024 at 11:22 PM Tahera Fahimi <fahimitahera@gmail.com> wrote:
> Currently, a sandbox process is not restricted to send a signal
> (e.g. SIGKILL) to a process outside of the sandbox environment.
> Ability to sending a signal for a sandboxed process should be
> scoped the same way abstract unix sockets are scoped.
>
> The same way as abstract unix socket, we extend "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).
>
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
[...]
> +static int hook_file_send_sigiotask(struct task_struct *tsk,
> +                                   struct fown_struct *fown, int signum)
> +{
> +       const struct task_struct *result =
> +               get_pid_task(fown->pid, fown->pid_type);

get_pid_task() returns a refcounted reference; you'll have to call
put_task_struct(result) to drop this reference at the end of the
function.

> +       const struct landlock_ruleset *const dom =
> +               landlock_get_task_domain(result);
> +       if (signal_is_scoped(dom, tsk))
> +               return 0;
> +       return EPERM;
> +}

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2024-07-22 12:46 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-07-05 21:21 [PATCH v1 1/2] Landlock: Add signal control Tahera Fahimi
2024-07-05 21:21 ` [PATCH v1 2/2] Landlock: Signal scoping tests Tahera Fahimi
2024-07-09 15:18   ` Günther Noack
2024-07-22 12:45 ` [PATCH v1 1/2] Landlock: Add signal control Jann Horn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).