* [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction
@ 2024-08-20 4:08 Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 1/6] " Tahera Fahimi
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Tahera Fahimi @ 2024-08-20 4:08 UTC (permalink / raw)
To: outreachy
Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi
This patch series adds scoping mechanism for abstract UNIX sockets.
Closes: https://github.com/landlock-lsm/linux/issues/7
Problem
=======
Abstract UNIX sockets are used for local inter-process communications
independent of the filesystem. Currently, a sandboxed process can
connect to a socket outside of the sandboxed environment, since Landlock
has no restriction for connecting to an abstract socket address(see more
details in [1,2]). Access to such sockets for a sandboxed process should
be scoped the same way ptrace is limited.
[1] https://lore.kernel.org/all/20231023.ahphah4Wii4v@digikod.net/
[2] https://lore.kernel.org/all/20231102.MaeWaepav8nu@digikod.net/
Solution
========
To solve this issue, we extend the user space interface by adding a new
"scoped" field to Landlock ruleset attribute structure. This field can
contains different rights to restrict different functionalities. For
abstract unix sockets, we introduce
"LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" field to specify that a ruleset
will deny any connection from within the sandbox domain to its parent
(i.e. any parent sandbox or non-sandbox processes).
Example
=======
Starting a listening socket with socat(1):
socat abstract-listen:mysocket -
Starting a sandboxed shell from $HOME with samples/landlock/sandboxer:
LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash
If we try to connect to the listening socket, the connection would be
refused.
socat - abstract-connect:mysocket --> fails
Notes of Implementation
=======================
* Using the "scoped" field provides enough compatibility and flexibility
to extend the scoping mechanism for other IPCs(e.g. signals).
* To access the domain of a socket, we use its credentials of the file's FD
which point to the credentials of the process that created the socket.
(see more details in [3]). Cases where the process using the socket has
a different domain than the process created it are covered in the
outside_socket test.
[3]https://lore.kernel.org/all/20240611.Pi8Iph7ootae@digikod.net/
Previous Versions
=================
v9: https://lore.kernel.org/all/cover.1723615689.git.fahimitahera@gmail.com/
v8: https://lore.kernel.org/all/cover.1722570749.git.fahimitahera@gmail.com/
v7: https://lore.kernel.org/all/cover.1721269836.git.fahimitahera@gmail.com/
v6: https://lore.kernel.org/all/Zn32CYZiu7pY+rdI@tahera-OptiPlex-5000/
and https://lore.kernel.org/all/Zn32KKIJrY7Zi51K@tahera-OptiPlex-5000/
v5: https://lore.kernel.org/all/ZnSZnhGBiprI6FRk@tahera-OptiPlex-5000/
v4: https://lore.kernel.org/all/ZnNcE3ph2SWi1qmd@tahera-OptiPlex-5000/
v3: https://lore.kernel.org/all/ZmJJ7lZdQuQop7e5@tahera-OptiPlex-5000/
v2: https://lore.kernel.org/all/ZgX5TRTrSDPrJFfF@tahera-OptiPlex-5000/
v1: https://lore.kernel.org/all/ZgXN5fi6A1YQKiAQ@tahera-OptiPlex-5000/
Tahera Fahimi (6):
Landlock: Add abstract unix socket connect restriction
selftests/Landlock: general scoped restriction tests
selftests/Landlock: Abstract UNIX socket restriction tests
selftests/Landlock: Add pathname UNIX socket tests
sample/Landlock: Support abstract unix socket restriction
Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI
versioning
Documentation/userspace-api/landlock.rst | 33 +-
include/uapi/linux/landlock.h | 27 +
samples/landlock/sandboxer.c | 56 +-
security/landlock/limits.h | 3 +
security/landlock/ruleset.c | 7 +-
security/landlock/ruleset.h | 24 +-
security/landlock/syscalls.c | 17 +-
security/landlock/task.c | 127 ++
tools/testing/selftests/landlock/base_test.c | 2 +-
tools/testing/selftests/landlock/common.h | 38 +
tools/testing/selftests/landlock/net_test.c | 31 +-
.../landlock/scoped_abstract_unix_test.c | 1130 +++++++++++++++++
.../testing/selftests/landlock/scoped_test.c | 33 +
13 files changed, 1483 insertions(+), 45 deletions(-)
create mode 100644 tools/testing/selftests/landlock/scoped_abstract_unix_test.c
create mode 100644 tools/testing/selftests/landlock/scoped_test.c
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v10 1/6] Landlock: Add abstract UNIX socket connect restriction
2024-08-20 4:08 [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction Tahera Fahimi
@ 2024-08-20 4:08 ` Tahera Fahimi
2024-08-20 19:14 ` Simon Horman
2024-08-20 4:08 ` [PATCH v10 2/6] selftests/Landlock: general scoped restriction tests Tahera Fahimi
` (4 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Tahera Fahimi @ 2024-08-20 4:08 UTC (permalink / raw)
To: outreachy
Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi
This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
abstract UNIX sockets from connecting to a process outside of
the same Landlock domain. It implements two hooks, unix_stream_connect
and unix_may_send to enforce this restriction.
Closes: https://github.com/landlock-lsm/linux/issues/7
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
v10:
- Minor code improvement based on reviews on v9.
v9:
- Editting inline comments.
- Major refactoring in domain_is_scoped() and is_abstract_socket
v8:
- Code refactoring (improve code readability, renaming variable, etc.) based
on reviews by Mickaël Salaün on version 7.
- Adding warn_on_once to check (impossible) inconsistencies.
- Adding inline comments.
- Adding check_unix_address_format to check if the scoping socket is an abstract
unix sockets.
v7:
- Using socket's file credentials for both connected(STREAM) and
non-connected(DGRAM) sockets.
- Adding "domain_sock_scope" instead of the domain scoping mechanism used in
ptrace ensures that if a server's domain is accessible from the client's
domain (where the client is more privileged than the server), the client
can connect to the server in all edge cases.
- Removing debug codes.
v6:
- Removing curr_ruleset from landlock_hierarchy, and switching back to use
the same domain scoping as ptrace.
- code clean up.
v5:
- Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE"
- Adding curr_ruleset to hierarachy_ruleset structure to have access from
landlock_hierarchy to its respective landlock_ruleset.
- Using curr_ruleset to check if a domain is scoped while walking in the
hierarchy of domains.
- Modifying inline comments.
V4:
- Rebased on Günther's Patch:
https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/
so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed.
- Adding get_scope_accesses function to check all scoped access masks in a ruleset.
- Using socket's file credentials instead of credentials stored in peer_cred
for datagram sockets. (see discussion in [1])
- Modifying inline comments.
V3:
- Improving commit description.
- Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping
purpose, and adding related functions.
- Changing structure of ruleset based on "scoped".
- Removing rcu lock and using unix_sk lock instead.
- Introducing scoping for datagram sockets in unix_may_send.
V2:
- Removing wrapper functions
[1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/
---
include/uapi/linux/landlock.h | 27 ++++
security/landlock/limits.h | 3 +
security/landlock/ruleset.c | 7 +-
security/landlock/ruleset.h | 24 +++-
security/landlock/syscalls.c | 17 ++-
security/landlock/task.c | 127 +++++++++++++++++++
tools/testing/selftests/landlock/base_test.c | 2 +-
7 files changed, 198 insertions(+), 9 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 2c8dbc74b955..da8ee1bd4bee 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -44,6 +44,12 @@ struct landlock_ruleset_attr {
* flags`_).
*/
__u64 handled_access_net;
+ /**
+ * @scoped: Bitmask of scopes (cf. `Scope flags`_)
+ * restricting a Landlock domain from accessing outside
+ * resources(e.g. IPCs).
+ */
+ __u64 scoped;
};
/*
@@ -274,4 +280,25 @@ struct landlock_net_port_attr {
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
/* clang-format on */
+
+/**
+ * DOC: scope
+ *
+ * Scope flags
+ * ~~~~~~~~~~~
+ *
+ * These flags enable to restrict a sandboxed process from a set of IPC
+ * actions. Setting a flag for a ruleset will isolate the Landlock domain
+ * to forbid connections to resources outside the domain.
+ *
+ * IPCs with scoped actions:
+ *
+ * - %LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process
+ * 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).
+ */
+/* clang-format off */
+#define LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET (1ULL << 0)
+/* clang-format on*/
#endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 4eb643077a2a..eb01d0fb2165 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -26,6 +26,9 @@
#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_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
+#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
/* clang-format on */
#endif /* _SECURITY_LANDLOCK_LIMITS_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 6ff232f58618..a93bdbf52fff 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -52,12 +52,13 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t fs_access_mask,
- const access_mask_t net_access_mask)
+ const access_mask_t net_access_mask,
+ const access_mask_t scope_mask)
{
struct landlock_ruleset *new_ruleset;
/* Informs about useless ruleset. */
- if (!fs_access_mask && !net_access_mask)
+ if (!fs_access_mask && !net_access_mask && !scope_mask)
return ERR_PTR(-ENOMSG);
new_ruleset = create_ruleset(1);
if (IS_ERR(new_ruleset))
@@ -66,6 +67,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,
landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
if (net_access_mask)
landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
+ if (scope_mask)
+ landlock_add_scope_mask(new_ruleset, scope_mask, 0);
return new_ruleset;
}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0f1b5b4c8f6b..a9ac2a0487d1 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -35,6 +35,8 @@ typedef u16 access_mask_t;
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
/* Makes sure all network access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
+/* Makes sure all scoped rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
@@ -42,6 +44,7 @@ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
struct access_masks {
access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
+ access_mask_t scoped : LANDLOCK_NUM_SCOPE;
};
typedef u16 layer_mask_t;
@@ -233,7 +236,8 @@ struct landlock_ruleset {
struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t access_mask_fs,
- const access_mask_t access_mask_net);
+ const access_mask_t access_mask_net,
+ const access_mask_t scope_mask);
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@@ -280,6 +284,17 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
ruleset->access_masks[layer_level].net |= net_mask;
}
+static inline void
+landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
+ const access_mask_t scope_mask, const u16 layer_level)
+{
+ access_mask_t scoped_mask = scope_mask & LANDLOCK_MASK_SCOPE;
+
+ /* Should already be checked in sys_landlock_create_ruleset(). */
+ WARN_ON_ONCE(scope_mask != scoped_mask);
+ ruleset->access_masks[layer_level].scoped |= scoped_mask;
+}
+
static inline access_mask_t
landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level)
@@ -303,6 +318,13 @@ landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
return ruleset->access_masks[layer_level].net;
}
+static inline access_mask_t
+landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ return ruleset->access_masks[layer_level].scoped;
+}
+
bool landlock_unmask_layers(const struct landlock_rule *const rule,
const access_mask_t access_request,
layer_mask_t (*const layer_masks)[],
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index ccc8bc6c1584..c67836841e46 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -97,8 +97,9 @@ static void build_check_abi(void)
*/
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
ruleset_size += sizeof(ruleset_attr.handled_access_net);
+ ruleset_size += sizeof(ruleset_attr.scoped);
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
- BUILD_BUG_ON(sizeof(ruleset_attr) != 16);
+ BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
path_beneath_size += sizeof(path_beneath_attr.parent_fd);
@@ -149,7 +150,7 @@ static const struct file_operations ruleset_fops = {
.write = fop_dummy_write,
};
-#define LANDLOCK_ABI_VERSION 5
+#define LANDLOCK_ABI_VERSION 6
/**
* sys_landlock_create_ruleset - Create a new ruleset
@@ -170,8 +171,9 @@ static const struct file_operations ruleset_fops = {
* Possible returned errors are:
*
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
- * - %EINVAL: unknown @flags, or unknown access, or too small @size;
- * - %E2BIG or %EFAULT: @attr or @size inconsistencies;
+ * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;
+ * - %E2BIG: @attr or @size inconsistencies;
+ * - %EFAULT: @attr or @size inconsistencies;
* - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
*/
SYSCALL_DEFINE3(landlock_create_ruleset,
@@ -213,9 +215,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
LANDLOCK_MASK_ACCESS_NET)
return -EINVAL;
+ /* Checks IPC scoping content (and 32-bits cast). */
+ if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
+ return -EINVAL;
+
/* Checks arguments and transforms to kernel struct. */
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
- ruleset_attr.handled_access_net);
+ ruleset_attr.handled_access_net,
+ ruleset_attr.scoped);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 849f5123610b..aaa490cb3d88 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -13,6 +13,8 @@
#include <linux/lsm_hooks.h>
#include <linux/rcupdate.h>
#include <linux/sched.h>
+#include <net/af_unix.h>
+#include <net/sock.h>
#include "common.h"
#include "cred.h"
@@ -108,9 +110,134 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
return task_ptrace(parent, current);
}
+/**
+ * domain_is_scoped - Checks if the client domain is scoped in the same
+ * domain as the server.
+ *
+ * @client: IPC sender domain.
+ * @server: IPC receiver domain.
+ *
+ * Return true if the @client domain is scoped to access the @server,
+ * unless the @server is also scoped in the same domain as @client.
+ */
+static bool domain_is_scoped(const struct landlock_ruleset *const client,
+ const struct landlock_ruleset *const server,
+ access_mask_t scope)
+{
+ int client_layer, server_layer;
+ struct landlock_hierarchy *client_walker, *server_walker;
+
+ /* Quick return if client has no domain */
+ if (WARN_ON_ONCE(!client))
+ return false;
+
+ client_layer = client->num_layers - 1;
+ client_walker = client->hierarchy;
+ /*
+ * client_layer must be a signed integer with greater capacity
+ * than client->num_layers to ensure the following loop stops.
+ */
+ BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
+
+ server_layer = server ? (server->num_layers - 1) : -1;
+ server_walker = server ? server->hierarchy : NULL;
+
+ /*
+ * Walks client's parent domains down to the same hierarchy level
+ * as the server's domain, and checks that none of these client's
+ * parent domains are scoped.
+ */
+ for (; client_layer > server_layer; client_layer--) {
+ if (landlock_get_scope_mask(client, client_layer) & scope)
+ return true;
+ client_walker = client_walker->parent;
+ }
+ /*
+ * Walks server's parent domains down to the same hierarchy level as
+ * the client's domain.
+ */
+ for (; server_layer > client_layer; server_layer--)
+ server_walker = server_walker->parent;
+
+ for (; client_layer >= 0; client_layer--) {
+ if (landlock_get_scope_mask(client, client_layer) & scope) {
+ /*
+ * Client and server are at the same level in the
+ * hierarchy. If the client is scoped, the request is
+ * only allowed if this domain is also a server's
+ * ancestor.
+ */
+ return server_walker != client_walker;
+ }
+ client_walker = client_walker->parent;
+ server_walker = server_walker->parent;
+ }
+ return false;
+}
+
+static bool sock_is_scoped(struct sock *const other,
+ const struct landlock_ruleset *const domain)
+{
+ const struct landlock_ruleset *dom_other;
+
+ /* the credentials will not change */
+ lockdep_assert_held(&unix_sk(other)->lock);
+ dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
+ return domain_is_scoped(domain, dom_other,
+ LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET);
+}
+
+static bool is_abstract_socket(struct sock *const sock)
+{
+ struct unix_address *addr = unix_sk(sock)->addr;
+
+ if (!addr)
+ return false;
+
+ if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
+ addr->name[0].sun_path[0] == '\0')
+ return true;
+
+ return false;
+}
+
+static int hook_unix_stream_connect(struct sock *const sock,
+ struct sock *const other,
+ struct sock *const newsk)
+{
+ const struct landlock_ruleset *const dom =
+ landlock_get_current_domain();
+
+ /* quick return for non-sandboxed processes */
+ if (!dom)
+ return 0;
+
+ if (is_abstract_socket(other) && sock_is_scoped(other, dom))
+ return -EPERM;
+
+ return 0;
+}
+
+static int hook_unix_may_send(struct socket *const sock,
+ struct socket *const other)
+{
+ const struct landlock_ruleset *const dom =
+ landlock_get_current_domain();
+
+ if (!dom)
+ return 0;
+
+ if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom))
+ return -EPERM;
+
+ 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),
};
__init void landlock_add_task_hooks(void)
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 3b26bf3cf5b9..1bc16fde2e8a 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
- ASSERT_EQ(5, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v10 2/6] selftests/Landlock: general scoped restriction tests
2024-08-20 4:08 [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 1/6] " Tahera Fahimi
@ 2024-08-20 4:08 ` Tahera Fahimi
2024-08-20 15:58 ` Mickaël Salaün
2024-08-20 4:08 ` [PATCH v10 3/6] selftests/Landlock: Abstract UNIX socket " Tahera Fahimi
` (3 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Tahera Fahimi @ 2024-08-20 4:08 UTC (permalink / raw)
To: outreachy
Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi
The test function, "ruleset_with_unknown_scoped", is designed to
validate the behaviour of the "landlock_create_ruleset" function
when it is provided with an unsupported or unknown scoped mask.
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
.../testing/selftests/landlock/scoped_test.c | 33 +++++++++++++++++++
1 file changed, 33 insertions(+)
create mode 100644 tools/testing/selftests/landlock/scoped_test.c
diff --git a/tools/testing/selftests/landlock/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c
new file mode 100644
index 000000000000..aee853582451
--- /dev/null
+++ b/tools/testing/selftests/landlock/scoped_test.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - Scope Restriction
+ *
+ * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <linux/landlock.h>
+#include <sys/prctl.h>
+
+#include "common.h"
+
+#define ACCESS_LAST LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET
+
+TEST(ruleset_with_unknown_scoped)
+{
+ __u64 scoped_mask;
+
+ for (scoped_mask = 1ULL << 63; scoped_mask != ACCESS_LAST;
+ scoped_mask >>= 1) {
+ struct landlock_ruleset_attr ruleset_attr = {
+ .scoped = scoped_mask,
+ };
+
+ ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
+ sizeof(ruleset_attr), 0));
+ ASSERT_EQ(EINVAL, errno);
+ }
+}
+
+TEST_HARNESS_MAIN
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v10 3/6] selftests/Landlock: Abstract UNIX socket restriction tests
2024-08-20 4:08 [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 1/6] " Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 2/6] selftests/Landlock: general scoped restriction tests Tahera Fahimi
@ 2024-08-20 4:08 ` Tahera Fahimi
2024-08-20 16:00 ` Mickaël Salaün
2024-08-20 4:08 ` [PATCH v10 4/6] selftests/Landlock: Add pathname UNIX socket tests Tahera Fahimi
` (2 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Tahera Fahimi @ 2024-08-20 4:08 UTC (permalink / raw)
To: outreachy
Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi
The patch introduces Landlock ABI version 6 and adds three types of tests
that examines different scenarios for abstract unix socket:
1) unix_socket: base tests of the abstract socket scoping mechanism for a
landlocked process, same as the ptrace test.
2) optional_scoping: generates three processes with different domains and
tests if a process with a non-scoped domain can connect to other
processes.
3) outside_socket: since the socket's creator credentials are used
for scoping sockets, this test examines the cases where the socket's
credentials are different from the process using it.
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
Changes in versions:
v10:
- Code improvements by changing fixture variables to local ones.
- Rename "unix_sock_special_cases" to "outside_socket"
v9:
- Move pathname_address_sockets to a different patch.
- Extend optional_scoping test scenarios.
- Removing hardcoded numbers and using "backlog" instead.
V8:
- Move tests to scoped_abstract_unix_test.c file.
- To avoid potential conflicts among Unix socket names in different tests,
set_unix_address is added to common.h to set different sun_path for Unix sockets.
- protocol_variant and service_fixture structures are also moved to common.h
- Adding pathname_address_sockets to cover all types of address formats
for unix sockets, and moving remove_path() to common.h to reuse in this test.
V7:
- Introducing landlock ABI version 6.
- Adding some edge test cases to optional_scoping test.
- Using `enum` for different domains in optional_scoping tests.
- Extend unix_sock_special_cases test cases for connected(SOCK_STREAM) sockets.
- Modifying inline comments.
V6:
- Introducing optional_scoping test which ensures a sandboxed process with a
non-scoped domain can still connect to another abstract unix socket(either
sandboxed or non-sandboxed).
- Introducing unix_sock_special_cases test which tests examines scenarios where
the connecting sockets have different domain than the process using them.
V4:
- Introducing unix_socket to evaluate the basic scoping mechanism for abstract
unix sockets.
---
tools/testing/selftests/landlock/common.h | 38 +
tools/testing/selftests/landlock/net_test.c | 31 +-
.../landlock/scoped_abstract_unix_test.c | 931 ++++++++++++++++++
3 files changed, 970 insertions(+), 30 deletions(-)
create mode 100644 tools/testing/selftests/landlock/scoped_abstract_unix_test.c
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 7e2b431b9f90..cca387df86c2 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -7,6 +7,7 @@
* Copyright © 2021 Microsoft Corporation
*/
+#include <arpa/inet.h>
#include <errno.h>
#include <linux/landlock.h>
#include <linux/securebits.h>
@@ -14,10 +15,12 @@
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
+#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../kselftest_harness.h"
+#define TMP_DIR "tmp"
#ifndef __maybe_unused
#define __maybe_unused __attribute__((__unused__))
@@ -226,3 +229,38 @@ enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
}
}
+
+struct protocol_variant {
+ int domain;
+ int type;
+};
+
+struct service_fixture {
+ struct protocol_variant protocol;
+ /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
+ unsigned short port;
+ union {
+ struct sockaddr_in ipv4_addr;
+ struct sockaddr_in6 ipv6_addr;
+ struct {
+ struct sockaddr_un unix_addr;
+ socklen_t unix_addr_len;
+ };
+ };
+};
+
+static pid_t __maybe_unused sys_gettid(void)
+{
+ return syscall(__NR_gettid);
+}
+
+static void __maybe_unused set_unix_address(struct service_fixture *const srv,
+ const unsigned short index)
+{
+ srv->unix_addr.sun_family = AF_UNIX;
+ sprintf(srv->unix_addr.sun_path,
+ "_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(),
+ index);
+ srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
+ srv->unix_addr.sun_path[0] = '\0';
+}
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index f21cfbbc3638..4e0aeb53b225 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -36,30 +36,6 @@ enum sandbox_type {
TCP_SANDBOX,
};
-struct protocol_variant {
- int domain;
- int type;
-};
-
-struct service_fixture {
- struct protocol_variant protocol;
- /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
- unsigned short port;
- union {
- struct sockaddr_in ipv4_addr;
- struct sockaddr_in6 ipv6_addr;
- struct {
- struct sockaddr_un unix_addr;
- socklen_t unix_addr_len;
- };
- };
-};
-
-static pid_t sys_gettid(void)
-{
- return syscall(__NR_gettid);
-}
-
static int set_service(struct service_fixture *const srv,
const struct protocol_variant prot,
const unsigned short index)
@@ -92,12 +68,7 @@ static int set_service(struct service_fixture *const srv,
return 0;
case AF_UNIX:
- srv->unix_addr.sun_family = prot.domain;
- sprintf(srv->unix_addr.sun_path,
- "_selftests-landlock-net-tid%d-index%d", sys_gettid(),
- index);
- srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
- srv->unix_addr.sun_path[0] = '\0';
+ set_unix_address(srv, index);
return 0;
}
return 1;
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
new file mode 100644
index 000000000000..65c1ac2895a9
--- /dev/null
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -0,0 +1,931 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - Abstract Unix Socket
+ *
+ * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <sched.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "common.h"
+
+/* Number pending connections queue to be hold. */
+const short backlog = 10;
+
+static void create_fs_domain(struct __test_metadata *const _metadata)
+{
+ int ruleset_fd;
+ struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+ };
+
+ 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));
+ }
+ EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+ EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
+ EXPECT_EQ(0, close(ruleset_fd));
+}
+
+static void create_unix_domain(struct __test_metadata *const _metadata)
+{
+ int ruleset_fd;
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .scoped = LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET,
+ };
+
+ 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));
+}
+
+/* clang-format off */
+FIXTURE(unix_socket) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(unix_socket)
+{
+ bool domain_both;
+ bool domain_parent;
+ bool domain_child;
+ bool connect_to_parent;
+};
+
+FIXTURE_SETUP(unix_socket)
+{
+}
+
+FIXTURE_TEARDOWN(unix_socket)
+{
+}
+
+/*
+ * No domain
+ *
+ * P1-. P1 -> P2 : allow
+ * \ P2 -> P1 : allow
+ * 'P2
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_without_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = false,
+ .domain_child = false,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_without_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = false,
+ .domain_child = false,
+ .connect_to_parent = false,
+};
+
+/*
+ * Child domain
+ *
+ * P1--. P1 -> P2 : allow
+ * \ P2 -> P1 : deny
+ * .'-----.
+ * | P2 |
+ * '------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_one_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = false,
+ .domain_child = true,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_with_one_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = false,
+ .domain_child = true,
+ .connect_to_parent = false,
+};
+
+/*
+ * Parent domain
+ * .------.
+ * | P1 --. P1 -> P2 : deny
+ * '------' \ P2 -> P1 : allow
+ * '
+ * P2
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_with_parent_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = true,
+ .domain_child = false,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_parent_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = true,
+ .domain_child = false,
+ .connect_to_parent = false,
+};
+
+/*
+ * Parent + child domain (siblings)
+ * .------.
+ * | P1 ---. P1 -> P2 : deny
+ * '------' \ P2 -> P1 : deny
+ * .---'--.
+ * | P2 |
+ * '------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_sibling_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = true,
+ .domain_child = true,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_sibling_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = false,
+ .domain_parent = true,
+ .domain_child = true,
+ .connect_to_parent = false,
+};
+
+/*
+ * Same domain (inherited)
+ * .-------------.
+ * | P1----. | P1 -> P2 : allow
+ * | \ | P2 -> P1 : allow
+ * | ' |
+ * | P2 |
+ * '-------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_inherited_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = false,
+ .domain_child = false,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_inherited_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = false,
+ .domain_child = false,
+ .connect_to_parent = false,
+};
+
+/*
+ * Inherited + child domain
+ * .-----------------.
+ * | P1----. | P1 -> P2 : allow
+ * | \ | P2 -> P1 : deny
+ * | .-'----. |
+ * | | P2 | |
+ * | '------' |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_nested_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = false,
+ .domain_child = true,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_nested_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = false,
+ .domain_child = true,
+ .connect_to_parent = false,
+};
+
+/*
+ * Inherited + parent domain
+ * .-----------------.
+ * |.------. | P1 -> P2 : deny
+ * || P1 ----. | P2 -> P1 : allow
+ * |'------' \ |
+ * | ' |
+ * | P2 |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_with_nested_and_parent_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = true,
+ .domain_child = false,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_nested_and_parent_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = true,
+ .domain_child = false,
+ .connect_to_parent = false,
+};
+
+/*
+ * Inherited + parent and child domain (siblings)
+ * .-----------------.
+ * | .------. | P1 -> P2 : deny
+ * | | P1 . | P2 -> P1 : deny
+ * | '------'\ |
+ * | \ |
+ * | .--'---. |
+ * | | P2 | |
+ * | '------' |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_forked_domain_connect_to_parent) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = true,
+ .domain_child = true,
+ .connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_forked_domain_connect_to_child) {
+ /* clang-format on */
+ .domain_both = true,
+ .domain_parent = true,
+ .domain_child = true,
+ .connect_to_parent = false,
+};
+
+/*
+ * Test unix_stream_connect() and unix_may_send() for parent and child,
+ * when they have scoped domain or no domain.
+ */
+TEST_F(unix_socket, abstract_unix_socket)
+{
+ struct service_fixture stream_address, dgram_address;
+ pid_t child;
+ bool can_connect_to_parent, can_connect_to_child;
+ int err, err_dgram, status;
+ int pipe_child[2], pipe_parent[2];
+ char buf_parent;
+
+ memset(&stream_address, 0, sizeof(stream_address));
+ memset(&dgram_address, 0, sizeof(dgram_address));
+ set_unix_address(&stream_address, 0);
+ set_unix_address(&dgram_address, 1);
+ /*
+ * can_connect_to_child is true if a parent process can connect to its
+ * child process. The parent process is not isolated from the child
+ * with a dedicated Landlock domain.
+ */
+ can_connect_to_child = !variant->domain_parent;
+ /*
+ * can_connect_to_parent is true if a child process can connect to its
+ * parent process. This depends on the child process is not isolated from
+ * the parent with a dedicated Landlock domain.
+ */
+ can_connect_to_parent = !variant->domain_child;
+
+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+ if (variant->domain_both) {
+ create_unix_domain(_metadata);
+ if (!__test_passed(_metadata))
+ return;
+ }
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ char buf_child;
+
+ ASSERT_EQ(0, close(pipe_parent[1]));
+ ASSERT_EQ(0, close(pipe_child[0]));
+ if (variant->domain_child)
+ create_unix_domain(_metadata);
+
+ /* Waits for the parent to be in a domain, if any. */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+ if (variant->connect_to_parent) {
+ int client, dgram_client;
+
+ client = socket(AF_UNIX, SOCK_STREAM, 0);
+ dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
+
+ ASSERT_NE(-1, client);
+ ASSERT_NE(-1, dgram_client);
+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+ err = connect(client, &stream_address.unix_addr,
+ stream_address.unix_addr_len);
+ err_dgram = connect(dgram_client,
+ &dgram_address.unix_addr,
+ dgram_address.unix_addr_len);
+
+ if (can_connect_to_parent) {
+ EXPECT_EQ(0, err);
+ EXPECT_EQ(0, err_dgram);
+ } else {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(-1, err_dgram);
+ EXPECT_EQ(EPERM, errno);
+ }
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+ ASSERT_EQ(0, close(client));
+ ASSERT_EQ(0, close(dgram_client));
+ } else {
+ int server, dgram_server;
+
+ server = socket(AF_UNIX, SOCK_STREAM, 0);
+ dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_NE(-1, server);
+ ASSERT_NE(-1, dgram_server);
+
+ ASSERT_EQ(0, bind(server, &stream_address.unix_addr,
+ stream_address.unix_addr_len));
+ ASSERT_EQ(0,
+ bind(dgram_server, &dgram_address.unix_addr,
+ dgram_address.unix_addr_len));
+ ASSERT_EQ(0, listen(server, backlog));
+
+ /* signal to parent that child is listening */
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+ /* wait to connect */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+ ASSERT_EQ(0, close(server));
+ ASSERT_EQ(0, close(dgram_server));
+ }
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ ASSERT_EQ(0, close(pipe_child[1]));
+ ASSERT_EQ(0, close(pipe_parent[0]));
+
+ if (variant->domain_parent)
+ create_unix_domain(_metadata);
+
+ /* Signals that the parent is in a domain, if any. */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+ if (!variant->connect_to_parent) {
+ int client, dgram_client;
+
+ client = socket(AF_UNIX, SOCK_STREAM, 0);
+ dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_NE(-1, client);
+ ASSERT_NE(-1, dgram_client);
+
+ /* Waits for the child to listen */
+ ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+ err = connect(client, &stream_address.unix_addr,
+ stream_address.unix_addr_len);
+ err_dgram = connect(dgram_client, &dgram_address.unix_addr,
+ dgram_address.unix_addr_len);
+
+ if (can_connect_to_child) {
+ EXPECT_EQ(0, err);
+ EXPECT_EQ(0, err_dgram);
+ } else {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(-1, err_dgram);
+ EXPECT_EQ(EPERM, errno);
+ }
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(0, close(client));
+ ASSERT_EQ(0, close(dgram_client));
+ } else {
+ int server, dgram_server;
+
+ server = socket(AF_UNIX, SOCK_STREAM, 0);
+ dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_NE(-1, server);
+ ASSERT_NE(-1, dgram_server);
+ ASSERT_EQ(0, bind(server, &stream_address.unix_addr,
+ stream_address.unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server, &dgram_address.unix_addr,
+ dgram_address.unix_addr_len));
+ ASSERT_EQ(0, listen(server, backlog));
+
+ /* signal to child that parent is listening */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+
+ ASSERT_EQ(0, close(server));
+ ASSERT_EQ(0, close(dgram_server));
+ }
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+}
+
+enum sandbox_type {
+ NO_SANDBOX,
+ SCOPE_SANDBOX,
+ /* Any other type of sandboxing domain */
+ OTHER_SANDBOX,
+};
+
+/* clang-format off */
+FIXTURE(optional_scoping) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(optional_scoping)
+{
+ const int domain_all;
+ const int domain_parent;
+ const int domain_children;
+ const int domain_child;
+ const int domain_grand_child;
+ const int type;
+};
+
+FIXTURE_SETUP(optional_scoping)
+{
+}
+
+FIXTURE_TEARDOWN(optional_scoping)
+{
+}
+
+/*
+ * .-----------------.
+ * | ####### | P3 -> P2 : allow
+ * | P1----# P2 # | P3 -> P1 : deny
+ * | # | # |
+ * | # P3 # |
+ * | ####### |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, deny_scoped) {
+ .domain_all = OTHER_SANDBOX,
+ .domain_parent = NO_SANDBOX,
+ .domain_children = SCOPE_SANDBOX,
+ .domain_child = NO_SANDBOX,
+ .domain_grand_child = NO_SANDBOX,
+ .type = SOCK_DGRAM,
+ /* clang-format on */
+};
+
+/*
+ * ###################
+ * # ####### # P3 -> P2 : allow
+ * # P1----# P2 # # P3 -> P1 : deny
+ * # # | # #
+ * # # P3 # #
+ * # ####### #
+ * ###################
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, all_scoped) {
+ .domain_all = SCOPE_SANDBOX,
+ .domain_parent = NO_SANDBOX,
+ .domain_children = SCOPE_SANDBOX,
+ .domain_child = NO_SANDBOX,
+ .domain_grand_child = NO_SANDBOX,
+ .type = SOCK_DGRAM,
+ /* clang-format on */
+};
+
+/*
+ * .-----------------.
+ * | .-----. | P3 -> P2 : allow
+ * | P1----| P2 | | P3 -> P1 : allow
+ * | | | |
+ * | | P3 | |
+ * | '-----' |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, allow_with_other_domain) {
+ .domain_all = OTHER_SANDBOX,
+ .domain_parent = NO_SANDBOX,
+ .domain_children = OTHER_SANDBOX,
+ .domain_child = NO_SANDBOX,
+ .domain_grand_child = NO_SANDBOX,
+ .type = SOCK_DGRAM,
+ /* clang-format on */
+};
+
+/*
+ * .----. ###### P3 -> P2 : allow
+ * | P1 |----# P2 # P3 -> P1 : allow
+ * '----' ######
+ * |
+ * P3
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, allow_with_one_domain) {
+ .domain_all = NO_SANDBOX,
+ .domain_parent = OTHER_SANDBOX,
+ .domain_children = NO_SANDBOX,
+ .domain_child = SCOPE_SANDBOX,
+ .domain_grand_child = NO_SANDBOX,
+ .type = SOCK_DGRAM,
+ /* clang-format on */
+};
+
+/*
+ * ###### .-----. P3 -> P2 : allow
+ * # P1 #----| P2 | P3 -> P1 : allow
+ * ###### '-----'
+ * |
+ * P3
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, allow_with_grand_parent_scoped) {
+ .domain_all = NO_SANDBOX,
+ .domain_parent = SCOPE_SANDBOX,
+ .domain_children = NO_SANDBOX,
+ .domain_child = OTHER_SANDBOX,
+ .domain_grand_child = NO_SANDBOX,
+ .type = SOCK_STREAM,
+ /* clang-format on */
+};
+
+/*
+ * ###### ###### P3 -> P2 : allow
+ * # P1 #----# P2 # P3 -> P1 : allow
+ * ###### ######
+ * |
+ * .----.
+ * | P3 |
+ * '----'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, allow_with_parents_domain) {
+ .domain_all = NO_SANDBOX,
+ .domain_parent = SCOPE_SANDBOX,
+ .domain_children = NO_SANDBOX,
+ .domain_child = SCOPE_SANDBOX,
+ .domain_grand_child = NO_SANDBOX,
+ .type = SOCK_STREAM,
+ /* clang-format on */
+};
+
+/*
+ * ###### P3 -> P2 : deny
+ * # P1 #----P2 P3 -> P1 : deny
+ * ###### |
+ * |
+ * ######
+ * # P3 #
+ * ######
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, deny_with_self_and_parents_domain) {
+ .domain_all = NO_SANDBOX,
+ .domain_parent = SCOPE_SANDBOX,
+ .domain_children = NO_SANDBOX,
+ .domain_child = NO_SANDBOX,
+ .domain_grand_child = SCOPE_SANDBOX,
+ .type = SOCK_STREAM,
+ /* clang-format on */
+};
+
+/*
+ * Test UNIX_STREAM_CONNECT and UNIX_MAY_SEND for parent, child
+ * and grand child processes when they can have scoped or non-scoped
+ * domains.
+ */
+TEST_F(optional_scoping, unix_scoping)
+{
+ struct service_fixture parent_address;
+ pid_t child;
+ int status;
+ bool can_connect_to_parent, can_connect_to_child;
+ int pipe_parent[2];
+
+ memset(&parent_address, 0, sizeof(parent_address));
+ set_unix_address(&parent_address, 0);
+
+ can_connect_to_child = (variant->domain_grand_child != SCOPE_SANDBOX);
+ can_connect_to_parent = (can_connect_to_child &&
+ (variant->domain_children != SCOPE_SANDBOX));
+
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+ if (variant->domain_all == OTHER_SANDBOX)
+ create_fs_domain(_metadata);
+ else if (variant->domain_all == SCOPE_SANDBOX)
+ create_unix_domain(_metadata);
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ int pipe_child[2];
+ pid_t grand_child;
+ struct service_fixture child_address;
+
+ memset(&child_address, 0, sizeof(child_address));
+ set_unix_address(&child_address, 1);
+
+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+
+ if (variant->domain_children == OTHER_SANDBOX)
+ create_fs_domain(_metadata);
+ else if (variant->domain_children == SCOPE_SANDBOX)
+ create_unix_domain(_metadata);
+
+ grand_child = fork();
+ ASSERT_LE(0, grand_child);
+ if (grand_child == 0) {
+ char buf1, buf2;
+ int err, client;
+
+ ASSERT_EQ(0, close(pipe_parent[1]));
+ ASSERT_EQ(0, close(pipe_child[1]));
+
+ if (variant->domain_grand_child == OTHER_SANDBOX)
+ create_fs_domain(_metadata);
+ else if (variant->domain_grand_child == SCOPE_SANDBOX)
+ create_unix_domain(_metadata);
+
+ client = socket(AF_UNIX, variant->type, 0);
+ ASSERT_NE(-1, client);
+
+ ASSERT_EQ(1, read(pipe_child[0], &buf2, 1));
+ err = connect(client, &child_address.unix_addr,
+ child_address.unix_addr_len);
+ if (can_connect_to_child) {
+ EXPECT_EQ(0, err);
+ } else {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ }
+
+ if (variant->type == SOCK_STREAM) {
+ EXPECT_EQ(0, close(client));
+ client = socket(AF_UNIX, variant->type, 0);
+ ASSERT_NE(-1, client);
+ }
+ ASSERT_EQ(1, read(pipe_parent[0], &buf1, 1));
+ err = connect(client, &parent_address.unix_addr,
+ parent_address.unix_addr_len);
+ if (can_connect_to_parent) {
+ EXPECT_EQ(0, err);
+ } else {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ }
+ EXPECT_EQ(0, close(client));
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+ int child_server;
+
+ ASSERT_EQ(0, close(pipe_child[0]));
+ if (variant->domain_child == OTHER_SANDBOX)
+ create_fs_domain(_metadata);
+ else if (variant->domain_child == SCOPE_SANDBOX)
+ create_unix_domain(_metadata);
+
+ child_server = socket(AF_UNIX, variant->type, 0);
+ ASSERT_NE(-1, child_server);
+ ASSERT_EQ(0, bind(child_server, &child_address.unix_addr,
+ child_address.unix_addr_len));
+ if (variant->type == SOCK_STREAM)
+ ASSERT_EQ(0, listen(child_server, backlog));
+
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+ ASSERT_EQ(grand_child, waitpid(grand_child, &status, 0));
+ ASSERT_EQ(0, close(child_server));
+ return;
+ }
+ int parent_server;
+
+ ASSERT_EQ(0, close(pipe_parent[0]));
+
+ if (variant->domain_parent == OTHER_SANDBOX)
+ create_fs_domain(_metadata);
+ else if (variant->domain_parent == SCOPE_SANDBOX)
+ create_unix_domain(_metadata);
+
+ parent_server = socket(AF_UNIX, variant->type, 0);
+ ASSERT_NE(-1, parent_server);
+ ASSERT_EQ(0, bind(parent_server, &parent_address.unix_addr,
+ parent_address.unix_addr_len));
+
+ if (variant->type == SOCK_STREAM)
+ ASSERT_EQ(0, listen(parent_server, backlog));
+
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_EQ(0, close(parent_server));
+
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+}
+
+/* clang-format off */
+FIXTURE(outside_socket) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(outside_socket)
+{
+ const bool domain_server;
+ const bool domain_server_socket;
+ const int type;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, allow_dgram_server_sock_domain) {
+ /* clang-format on */
+ .domain_server = false,
+ .domain_server_socket = true,
+ .type = SOCK_DGRAM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, deny_dgram_server_domain) {
+ /* clang-format on */
+ .domain_server = true,
+ .domain_server_socket = false,
+ .type = SOCK_DGRAM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, allow_stream_server_sock_domain) {
+ /* clang-format on */
+ .domain_server = false,
+ .domain_server_socket = true,
+ .type = SOCK_STREAM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, deny_stream_server_domain) {
+ /* clang-format on */
+ .domain_server = true,
+ .domain_server_socket = false,
+ .type = SOCK_STREAM,
+};
+
+FIXTURE_SETUP(outside_socket)
+{
+}
+
+FIXTURE_TEARDOWN(outside_socket)
+{
+}
+
+/* Test UNIX_STREAM_CONNECT and UNIX_MAY_SEND for parent and
+ * child processes when connecting socket has different domain
+ * than the process using it.
+ **/
+TEST_F(outside_socket, socket_with_different_domain)
+{
+ pid_t child;
+ int err, status;
+ int pipe_child[2], pipe_parent[2];
+ char buf_parent;
+ struct service_fixture address, transit_address;
+
+ ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+ memset(&transit_address, 0, sizeof(transit_address));
+ memset(&address, 0, sizeof(address));
+ set_unix_address(&transit_address, 0);
+ set_unix_address(&address, 1);
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ char buf_child;
+ int stream_server, client;
+
+ ASSERT_EQ(0, close(pipe_parent[1]));
+ ASSERT_EQ(0, close(pipe_child[0]));
+
+ /* client always has domain */
+ create_unix_domain(_metadata);
+
+ if (variant->domain_server_socket) {
+ int data_socket;
+ int fd_sock = socket(AF_UNIX, variant->type, 0);
+
+ ASSERT_NE(-1, fd_sock);
+
+ stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ ASSERT_NE(-1, stream_server);
+ ASSERT_EQ(0, bind(stream_server,
+ &transit_address.unix_addr,
+ transit_address.unix_addr_len));
+ ASSERT_EQ(0, listen(stream_server, backlog));
+
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+
+ data_socket = accept(stream_server, NULL, NULL);
+
+ ASSERT_EQ(0, send_fd(data_socket, fd_sock));
+ ASSERT_EQ(0, close(fd_sock));
+ ASSERT_EQ(0, close(stream_server));
+ }
+
+ client = socket(AF_UNIX, variant->type, 0);
+ ASSERT_NE(-1, client);
+ /* wait for parent signal for connection */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+ err = connect(client, &address.unix_addr,
+ address.unix_addr_len);
+ if (!variant->domain_server_socket) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(0, err);
+ }
+ ASSERT_EQ(0, close(client));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ int server_socket;
+
+ ASSERT_EQ(0, close(pipe_child[1]));
+ ASSERT_EQ(0, close(pipe_parent[0]));
+
+ if (!variant->domain_server_socket) {
+ server_socket = socket(AF_UNIX, variant->type, 0);
+ } else {
+ int cli = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ ASSERT_NE(-1, cli);
+ ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+ ASSERT_EQ(0, connect(cli, &transit_address.unix_addr,
+ transit_address.unix_addr_len));
+
+ server_socket = recv_fd(cli);
+ ASSERT_LE(0, server_socket);
+ ASSERT_EQ(0, close(cli));
+ }
+
+ ASSERT_NE(-1, server_socket);
+
+ if (variant->domain_server)
+ create_unix_domain(_metadata);
+
+ ASSERT_EQ(0, bind(server_socket, &address.unix_addr,
+ address.unix_addr_len));
+ if (variant->type == SOCK_STREAM)
+ ASSERT_EQ(0, listen(server_socket, backlog));
+ /* signal to child that parent is listening */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_EQ(0, close(server_socket));
+
+ 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] 11+ messages in thread
* [PATCH v10 4/6] selftests/Landlock: Add pathname UNIX socket tests
2024-08-20 4:08 [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction Tahera Fahimi
` (2 preceding siblings ...)
2024-08-20 4:08 ` [PATCH v10 3/6] selftests/Landlock: Abstract UNIX socket " Tahera Fahimi
@ 2024-08-20 4:08 ` Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 5/6] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 6/6] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning Tahera Fahimi
5 siblings, 0 replies; 11+ messages in thread
From: Tahera Fahimi @ 2024-08-20 4:08 UTC (permalink / raw)
To: outreachy
Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi
This patch expands abstract UNIX socket restriction tests by
testing pathname sockets connection with scoped domain.
pathname_address_sockets ensures that UNIX sockets bound to
a filesystem path name can still connect to a socket outside
of their scoped domain. This means that even if the domain
is scoped with LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET, the
socket can connect to a socket outside the scoped domain.
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
changes in versions:
v10:
- Code improvements by changing fixture variables to local ones.
- Commit improvement.
v9:
- Moving remove_path() back to fs_test.c, and using unlink(2)
and rmdir(2) instead.
- Removing hard-coded numbers and using "backlog" instead.
V8:
- Adding pathname_address_sockets to cover all types of address
formats for unix sockets, and moving remove_path() to
common.h to reuse in this test.
---
.../landlock/scoped_abstract_unix_test.c | 199 ++++++++++++++++++
1 file changed, 199 insertions(+)
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index 65c1ac2895a9..401e0d2e7025 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -928,4 +928,203 @@ TEST_F(outside_socket, socket_with_different_domain)
_metadata->exit_code = KSFT_FAIL;
}
+static const char path1[] = TMP_DIR "/s1_variant1";
+static const char path2[] = TMP_DIR "/s2_variant1";
+
+/* clang-format off */
+FIXTURE(pathname_address_sockets) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(pathname_address_sockets)
+{
+ const int domain;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(pathname_address_sockets, pathname_socket_scoped_domain) {
+ /* clang-format on */
+ .domain = SCOPE_SANDBOX,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(pathname_address_sockets, pathname_socket_other_domain) {
+ /* clang-format on */
+ .domain = OTHER_SANDBOX,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(pathname_address_sockets, pathname_socket_no_domain) {
+ /* clang-format on */
+ .domain = NO_SANDBOX,
+};
+
+FIXTURE_SETUP(pathname_address_sockets)
+{
+ disable_caps(_metadata);
+ umask(0077);
+ ASSERT_EQ(0, mkdir(TMP_DIR, 0700));
+
+ ASSERT_EQ(0, mknod(path1, S_IFREG | 0700, 0))
+ {
+ TH_LOG("Failed to create file \"%s\": %s", path1,
+ strerror(errno));
+ ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR));
+ }
+ ASSERT_EQ(0, mknod(path2, S_IFREG | 0700, 0))
+ {
+ TH_LOG("Failed to create file \"%s\": %s", path2,
+ strerror(errno));
+ ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR));
+ }
+}
+
+FIXTURE_TEARDOWN(pathname_address_sockets)
+{
+ ASSERT_EQ(0, unlink(path1) & rmdir(path1));
+ ASSERT_EQ(0, unlink(path2) & rmdir(path2));
+ ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR));
+}
+
+TEST_F(pathname_address_sockets, scoped_pathname_sockets)
+{
+ struct service_fixture stream_address, dgram_address;
+ const char *const stream_path = path1;
+ const char *const dgram_path = path2;
+ socklen_t size, size_dg;
+ struct sockaddr_un srv_un, srv_un_dg;
+ int pipe_parent[2];
+ pid_t child;
+ int status;
+ char buf_child;
+ int socket_fds_stream[2];
+
+ /* setup abstract addresses */
+ memset(&stream_address, 0, sizeof(stream_address));
+ set_unix_address(&stream_address, 0);
+ memset(&dgram_address, 0, sizeof(dgram_address));
+ set_unix_address(&dgram_address, 0);
+
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
+ socket_fds_stream));
+
+ srv_un.sun_family = AF_UNIX;
+ snprintf(srv_un.sun_path, sizeof(srv_un.sun_path), "%s", stream_path);
+ size = offsetof(struct sockaddr_un, sun_path) + strlen(srv_un.sun_path);
+
+ srv_un_dg.sun_family = AF_UNIX;
+ snprintf(srv_un_dg.sun_path, sizeof(srv_un_dg.sun_path), "%s",
+ dgram_path);
+ size_dg = offsetof(struct sockaddr_un, sun_path) +
+ strlen(srv_un_dg.sun_path);
+
+ ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ int cli_fd, cli_fd_dg;
+ int err, err_dg;
+ int client, dgram_client;
+ int sample = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ ASSERT_LE(0, sample);
+ ASSERT_EQ(0, close(pipe_parent[1]));
+
+ /* scope the domain */
+ if (variant->domain == SCOPE_SANDBOX)
+ create_unix_domain(_metadata);
+ else if (variant->domain == OTHER_SANDBOX)
+ create_fs_domain(_metadata);
+
+ ASSERT_EQ(0, close(socket_fds_stream[1]));
+ ASSERT_EQ(0, send_fd(socket_fds_stream[0], sample));
+ ASSERT_EQ(0, close(sample));
+ ASSERT_EQ(0, close(socket_fds_stream[0]));
+
+ /* wait for server to listen */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+ /* connect with pathname sockets */
+ cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, cli_fd);
+ ASSERT_EQ(0, connect(cli_fd, &srv_un, size));
+ ASSERT_EQ(0, close(cli_fd));
+
+ cli_fd_dg = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_LE(0, cli_fd_dg);
+ ASSERT_EQ(0, connect(cli_fd_dg, &srv_un_dg, size_dg));
+
+ ASSERT_EQ(0, close(cli_fd_dg));
+
+ /* check connection with abstract sockets */
+ client = socket(AF_UNIX, SOCK_STREAM, 0);
+ dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
+
+ ASSERT_NE(-1, client);
+ ASSERT_NE(-1, dgram_client);
+
+ err = connect(client, &stream_address.unix_addr,
+ stream_address.unix_addr_len);
+ err_dg = connect(dgram_client, &dgram_address.unix_addr,
+ dgram_address.unix_addr_len);
+ if (variant->domain == SCOPE_SANDBOX) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(-1, err_dg);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(0, err);
+ EXPECT_EQ(0, err_dg);
+ }
+ ASSERT_EQ(0, close(client));
+ ASSERT_EQ(0, close(dgram_client));
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+ int srv_fd, srv_fd_dg, server, dgram_server;
+ int recv_data;
+
+ ASSERT_EQ(0, close(pipe_parent[0]));
+
+ recv_data = recv_fd(socket_fds_stream[1]);
+ ASSERT_LE(0, recv_data);
+ ASSERT_LE(0, close(socket_fds_stream[1]));
+
+ /* Sets up a server */
+ srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, srv_fd);
+ ASSERT_EQ(0, unlink(stream_path));
+ ASSERT_EQ(0, bind(srv_fd, &srv_un, size));
+ ASSERT_EQ(0, listen(srv_fd, backlog));
+
+ /* set up a datagram server */
+ ASSERT_EQ(0, unlink(dgram_path));
+ srv_fd_dg = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_LE(0, srv_fd_dg);
+ ASSERT_EQ(0, bind(srv_fd_dg, (struct sockaddr *)&srv_un_dg, size_dg));
+
+ /*set up abstract servers */
+ server = socket(AF_UNIX, SOCK_STREAM, 0);
+ dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_NE(-1, server);
+ ASSERT_NE(-1, dgram_server);
+ ASSERT_EQ(0, bind(server, &stream_address.unix_addr,
+ stream_address.unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server, &dgram_address.unix_addr,
+ dgram_address.unix_addr_len));
+ ASSERT_EQ(0, listen(server, backlog));
+
+ /* servers are listening, signal to child */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_EQ(0, close(srv_fd));
+ ASSERT_EQ(0, close(srv_fd_dg));
+ ASSERT_EQ(0, close(server));
+ ASSERT_EQ(0, close(dgram_server));
+
+ 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] 11+ messages in thread
* [PATCH v10 5/6] sample/Landlock: Support abstract unix socket restriction
2024-08-20 4:08 [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction Tahera Fahimi
` (3 preceding siblings ...)
2024-08-20 4:08 ` [PATCH v10 4/6] selftests/Landlock: Add pathname UNIX socket tests Tahera Fahimi
@ 2024-08-20 4:08 ` Tahera Fahimi
2024-08-21 15:59 ` Mickaël Salaün
2024-08-20 4:08 ` [PATCH v10 6/6] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning Tahera Fahimi
5 siblings, 1 reply; 11+ messages in thread
From: Tahera Fahimi @ 2024-08-20 4:08 UTC (permalink / raw)
To: outreachy
Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi
A sandboxer can receive the character "a" as input from the environment
variable LL_SCOPE to restrict the abstract UNIX sockets from connecting
to a process outside its scoped domain.
Example
=======
Create an abstract unix socket to listen with socat(1):
socat abstract-listen:mysocket -
Create a sandboxed shell and pass the character "a" to LL_SCOPED:
LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash
If the sandboxed process tries to connect to the listening socket
with command "socat - abstract-connect:mysocket", the connection
will fail.
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
v10:
- Minor improvement in code based on v9.
v9:
- Add a restrict approach on input of LL_SCOPED, so it only allows zero
or one "a" to be the input.
v8:
- Adding check_ruleset_scope function to parse the scope environment
variable and update the landlock attribute based on the restriction
provided by the user.
- Adding Mickaël Salaün reviews on version 7.
v7:
- Adding IPC scoping to the sandbox demo by defining a new "LL_SCOPED"
environment variable. "LL_SCOPED" gets value "a" to restrict abstract
unix sockets.
- Change LANDLOCK_ABI_LAST to 6.
---
samples/landlock/sandboxer.c | 56 +++++++++++++++++++++++++++++++++---
1 file changed, 52 insertions(+), 4 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e8223c3e781a..0564d0a40c67 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -14,6 +14,7 @@
#include <fcntl.h>
#include <linux/landlock.h>
#include <linux/prctl.h>
+#include <linux/socket.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@@ -22,6 +23,7 @@
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
+#include <stdbool.h>
#ifndef landlock_create_ruleset
static inline int
@@ -55,6 +57,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_FS_RW_NAME "LL_FS_RW"
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
+#define ENV_SCOPED_NAME "LL_SCOPED"
#define ENV_DELIMITER ":"
static int parse_path(char *env_path, const char ***const path_list)
@@ -184,6 +187,40 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
return ret;
}
+static bool check_ruleset_scope(const char *const env_var,
+ struct landlock_ruleset_attr *ruleset_attr)
+{
+ bool abstract_scoping = false;
+ bool ret = true;
+ char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
+
+ ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
+ env_type_scope = getenv(env_var);
+ /* scoping is not supported by the user */
+ if (!env_type_scope || strcmp("", env_type_scope) == 0)
+ return true;
+
+ env_type_scope = strdup(env_type_scope);
+ unsetenv(env_var);
+ env_type_scope_next = env_type_scope;
+ while ((ipc_scoping_name =
+ strsep(&env_type_scope_next, ENV_DELIMITER))) {
+ if (strcmp("a", ipc_scoping_name) == 0 && !abstract_scoping) {
+ abstract_scoping = true;
+ ruleset_attr->scoped |=
+ LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
+ } else {
+ fprintf(stderr, "Unsupported scoping \"%s\"\n",
+ ipc_scoping_name);
+ ret = false;
+ goto out_free_name;
+ }
+ }
+out_free_name:
+ free(env_type_scope);
+ return ret;
+}
+
/* clang-format off */
#define ACCESS_FS_ROUGHLY_READ ( \
@@ -208,7 +245,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
/* clang-format on */
-#define LANDLOCK_ABI_LAST 5
+#define LANDLOCK_ABI_LAST 6
int main(const int argc, char *const argv[], char *const *const envp)
{
@@ -223,14 +260,15 @@ int main(const int argc, char *const argv[], char *const *const envp)
.handled_access_fs = access_fs_rw,
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .scoped = LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET,
};
if (argc < 2) {
fprintf(stderr,
- "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
+ "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s "
"<cmd> [args]...\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
- ENV_TCP_CONNECT_NAME, argv[0]);
+ ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
fprintf(stderr,
"Execute a command in a restricted environment.\n\n");
fprintf(stderr,
@@ -251,15 +289,18 @@ int main(const int argc, char *const argv[], char *const *const envp)
fprintf(stderr,
"* %s: list of ports allowed to connect (client).\n",
ENV_TCP_CONNECT_NAME);
+ fprintf(stderr, "* %s: list of restrictions on IPCs.\n",
+ ENV_SCOPED_NAME);
fprintf(stderr,
"\nexample:\n"
"%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
"%s=\"9418\" "
"%s=\"80:443\" "
+ "%s=\"a\" "
"%s bash -i\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
- ENV_TCP_CONNECT_NAME, argv[0]);
+ ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
fprintf(stderr,
"This sandboxer can use Landlock features "
"up to ABI version %d.\n",
@@ -327,6 +368,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ __attribute__((fallthrough));
+ case 5:
+ /* Removes LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
+ ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
fprintf(stderr,
"Hint: You should update the running kernel "
"to leverage Landlock features "
@@ -358,6 +403,9 @@ int main(const int argc, char *const argv[], char *const *const envp)
~LANDLOCK_ACCESS_NET_CONNECT_TCP;
}
+ if (abi >= 6 && !check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
+ return 1;
+
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v10 6/6] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning
2024-08-20 4:08 [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction Tahera Fahimi
` (4 preceding siblings ...)
2024-08-20 4:08 ` [PATCH v10 5/6] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
@ 2024-08-20 4:08 ` Tahera Fahimi
5 siblings, 0 replies; 11+ messages in thread
From: Tahera Fahimi @ 2024-08-20 4:08 UTC (permalink / raw)
To: outreachy
Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi
Introducing LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET as an IPC scoping
mechanism in Landlock ABI version 6, and updating ruleset_attr,
Landlock ABI version, and access rights code blocks based on that.
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
v10:
- Update date.
v8:
- Improving documentation by specifying differences between scoped and
non-scoped domains.
- Adding review notes of version 7.
- Update date.
v7:
- Add "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" explanation to IPC scoping
section and updating ABI to version 6.
- Adding "scoped" attribute to the Access rights section.
- In current limitation, unnamed sockets are specified as sockets that
are not restricted.
- Update date.
---
Documentation/userspace-api/landlock.rst | 33 ++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 37dafce8038b..89a2580a2bbf 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -8,7 +8,7 @@ Landlock: unprivileged access control
=====================================
:Author: Mickaël Salaün
-:Date: July 2024
+:Date: August 2024
The goal of Landlock is to enable to restrict ambient rights (e.g. global
filesystem or network access) for a set of processes. Because Landlock
@@ -81,6 +81,8 @@ to be explicit about the denied-by-default access rights.
.handled_access_net =
LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .scoped =
+ LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET,
};
Because we may not know on which kernel version an application will be
@@ -119,6 +121,9 @@ version, and only use the available subset of access rights:
case 4:
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ case 5:
+ /* Removes LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
+ ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
}
This enables to create an inclusive ruleset that will contain our rules.
@@ -306,6 +311,23 @@ To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target
process, a sandboxed process should have a subset of the target process rules,
which means the tracee must be in a sub-domain of the tracer.
+IPC Scoping
+-----------
+
+Similar to the implicit `Ptrace restrictions`_, we may want to further restrict
+interactions between sandboxes. Each Landlock domain can be explicitly scoped
+for a set of actions by specifying it on a ruleset. For example, if a sandboxed
+process should not be able to :manpage:`connect(2)` to a non-sandboxed process
+through abstract :manpage:`unix(7)` sockets, we can specify such restriction
+with ``LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET``.
+
+A sandboxed process can connect to a non-sandboxed process when its domain is
+not scoped. If a process's domain is scoped, it can only connect to sockets
+created by processes in the same scoped domain.
+
+IPC scoping does not support Landlock rules, so if a domain is scoped, no rules
+can be added to allow accessing to a resource outside of the scoped domain.
+
Truncating files
----------------
@@ -404,7 +426,7 @@ Access rights
-------------
.. kernel-doc:: include/uapi/linux/landlock.h
- :identifiers: fs_access net_access
+ :identifiers: fs_access net_access scope
Creating a new ruleset
----------------------
@@ -541,6 +563,13 @@ earlier ABI.
Starting with the Landlock ABI version 5, it is possible to restrict the use of
:manpage:`ioctl(2)` using the new ``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right.
+Abstract UNIX sockets Restriction (ABI < 6)
+--------------------------------------------
+
+With ABI version 6, it is possible to restrict connection to an abstract Unix socket
+through ``LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET``, thanks to the ``scoped`` ruleset
+attribute.
+
.. _kernel_support:
Kernel support
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v10 2/6] selftests/Landlock: general scoped restriction tests
2024-08-20 4:08 ` [PATCH v10 2/6] selftests/Landlock: general scoped restriction tests Tahera Fahimi
@ 2024-08-20 15:58 ` Mickaël Salaün
0 siblings, 0 replies; 11+ messages in thread
From: Mickaël Salaün @ 2024-08-20 15:58 UTC (permalink / raw)
To: Tahera Fahimi
Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev
"Re: [PATCH v10 2/6] selftests/Landlock: general scoped restriction"
This subject is still incorrect, please use this instead:
"selftests/landlock: Add common scope tests"
The same rule for the subject prefix should be followed for all other
commits (see my previous review).
On Mon, Aug 19, 2024 at 10:08:52PM -0600, Tahera Fahimi wrote:
> The test function, "ruleset_with_unknown_scoped", is designed to
> validate the behaviour of the "landlock_create_ruleset" function
> when it is provided with an unsupported or unknown scoped mask.
>
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> ---
> .../testing/selftests/landlock/scoped_test.c | 33 +++++++++++++++++++
> 1 file changed, 33 insertions(+)
> create mode 100644 tools/testing/selftests/landlock/scoped_test.c
>
> diff --git a/tools/testing/selftests/landlock/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c
> new file mode 100644
> index 000000000000..aee853582451
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/scoped_test.c
> @@ -0,0 +1,33 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Landlock tests - Scope Restriction
Landlock tests - Common scope restrictions
> + *
> + * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
> + */
> +
> +#define _GNU_SOURCE
> +#include <errno.h>
> +#include <linux/landlock.h>
> +#include <sys/prctl.h>
> +
> +#include "common.h"
> +
> +#define ACCESS_LAST LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET
> +
> +TEST(ruleset_with_unknown_scoped)
"ruleset_with_unknown_scope" makes more sense (also in the commit
message).
> +{
> + __u64 scoped_mask;
> +
> + for (scoped_mask = 1ULL << 63; scoped_mask != ACCESS_LAST;
> + scoped_mask >>= 1) {
> + struct landlock_ruleset_attr ruleset_attr = {
> + .scoped = scoped_mask,
> + };
> +
> + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
> + sizeof(ruleset_attr), 0));
> + ASSERT_EQ(EINVAL, errno);
> + }
> +}
Good!
> +
> +TEST_HARNESS_MAIN
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v10 3/6] selftests/Landlock: Abstract UNIX socket restriction tests
2024-08-20 4:08 ` [PATCH v10 3/6] selftests/Landlock: Abstract UNIX socket " Tahera Fahimi
@ 2024-08-20 16:00 ` Mickaël Salaün
0 siblings, 0 replies; 11+ messages in thread
From: Mickaël Salaün @ 2024-08-20 16:00 UTC (permalink / raw)
To: Tahera Fahimi
Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev
On Mon, Aug 19, 2024 at 10:08:53PM -0600, Tahera Fahimi wrote:
> The patch introduces Landlock ABI version 6 and adds three types of tests
> that examines different scenarios for abstract unix socket:
> 1) unix_socket: base tests of the abstract socket scoping mechanism for a
> landlocked process, same as the ptrace test.
> 2) optional_scoping: generates three processes with different domains and
> tests if a process with a non-scoped domain can connect to other
> processes.
> 3) outside_socket: since the socket's creator credentials are used
> for scoping sockets, this test examines the cases where the socket's
> credentials are different from the process using it.
>
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> ---
> Changes in versions:
> v10:
> - Code improvements by changing fixture variables to local ones.
> - Rename "unix_sock_special_cases" to "outside_socket"
> v9:
> - Move pathname_address_sockets to a different patch.
> - Extend optional_scoping test scenarios.
> - Removing hardcoded numbers and using "backlog" instead.
> V8:
> - Move tests to scoped_abstract_unix_test.c file.
> - To avoid potential conflicts among Unix socket names in different tests,
> set_unix_address is added to common.h to set different sun_path for Unix sockets.
> - protocol_variant and service_fixture structures are also moved to common.h
> - Adding pathname_address_sockets to cover all types of address formats
> for unix sockets, and moving remove_path() to common.h to reuse in this test.
> V7:
> - Introducing landlock ABI version 6.
> - Adding some edge test cases to optional_scoping test.
> - Using `enum` for different domains in optional_scoping tests.
> - Extend unix_sock_special_cases test cases for connected(SOCK_STREAM) sockets.
> - Modifying inline comments.
> V6:
> - Introducing optional_scoping test which ensures a sandboxed process with a
> non-scoped domain can still connect to another abstract unix socket(either
> sandboxed or non-sandboxed).
> - Introducing unix_sock_special_cases test which tests examines scenarios where
> the connecting sockets have different domain than the process using them.
> V4:
> - Introducing unix_socket to evaluate the basic scoping mechanism for abstract
> unix sockets.
> ---
> tools/testing/selftests/landlock/common.h | 38 +
> tools/testing/selftests/landlock/net_test.c | 31 +-
> .../landlock/scoped_abstract_unix_test.c | 931 ++++++++++++++++++
> 3 files changed, 970 insertions(+), 30 deletions(-)
> create mode 100644 tools/testing/selftests/landlock/scoped_abstract_unix_test.c
> diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
> new file mode 100644
> index 000000000000..65c1ac2895a9
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
> @@ -0,0 +1,931 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Landlock tests - Abstract Unix Socket
> + *
> + * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
> + */
> +
> +#define _GNU_SOURCE
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <linux/landlock.h>
> +#include <sched.h>
> +#include <signal.h>
> +#include <stddef.h>
> +#include <sys/prctl.h>
> +#include <sys/socket.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <sys/un.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include "common.h"
> +
> +/* Number pending connections queue to be hold. */
> +const short backlog = 10;
> +
> +static void create_fs_domain(struct __test_metadata *const _metadata)
> +{
> + int ruleset_fd;
> + struct landlock_ruleset_attr ruleset_attr = {
> + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
> + };
> +
> + 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));
> + }
> + EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> + EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
> + EXPECT_EQ(0, close(ruleset_fd));
> +}
> +
> +static void create_unix_domain(struct __test_metadata *const _metadata)
> +{
> + int ruleset_fd;
> + const struct landlock_ruleset_attr ruleset_attr = {
> + .scoped = LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET,
> + };
> +
> + 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));
> +}
> +
> +/* clang-format off */
> +FIXTURE(unix_socket) {};
> +/* clang-format on */
> +
> +FIXTURE_VARIANT(unix_socket)
These variant fixture should be renamed to
FIXTURE_VARIANT(scoped_domains) to be usable for all scoped tests (e.g.
abstract unix socket and signal). You can define them in a
scoped_common.h file which will be included by
scoped_abstract_unix_test.c and scoped_signal_test.c
> +{
> + bool domain_both;
> + bool domain_parent;
> + bool domain_child;
> + bool connect_to_parent;
connect_to_parent should not be part of a variant. In this case, we can
create two TEST_F(): a TEST_F(to_parent) like when connect_to_parent is
true, and a TEST_F(to_child) otherwise.
scoped_signal_test.c should have the same TEST_F names with slightly
different implementation but still testing the same semantic.
> +};
> +
> +FIXTURE_SETUP(unix_socket)
The self->stream_address and self->dgram_address initializations were
good. The issue was about socket's file descriptors, but if we have raw
data common to all tests, it makes sense to initialize them here.
> +{
> +}
> +
> +FIXTURE_TEARDOWN(unix_socket)
> +{
> +}
> +
> +/*
> + * No domain
> + *
> + * P1-. P1 -> P2 : allow
> + * \ P2 -> P1 : allow
> + * 'P2
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_without_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = false,
> + .domain_child = false,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_without_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = false,
> + .domain_child = false,
> + .connect_to_parent = false,
We can see that the picture describing the domains is the same for both
of these variants, which means something is wrong. connect_to_parent
should not be part of the variant definitions.
> +};
> +
> +/*
> + * Child domain
> + *
> + * P1--. P1 -> P2 : allow
> + * \ P2 -> P1 : deny
> + * .'-----.
> + * | P2 |
> + * '------'
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_with_one_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = false,
> + .domain_child = true,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_with_one_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = false,
> + .domain_child = true,
> + .connect_to_parent = false,
> +};
> +
> +/*
> + * Parent domain
> + * .------.
> + * | P1 --. P1 -> P2 : deny
> + * '------' \ P2 -> P1 : allow
> + * '
> + * P2
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_with_parent_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = true,
> + .domain_child = false,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_with_parent_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = true,
> + .domain_child = false,
> + .connect_to_parent = false,
> +};
> +
> +/*
> + * Parent + child domain (siblings)
> + * .------.
> + * | P1 ---. P1 -> P2 : deny
> + * '------' \ P2 -> P1 : deny
> + * .---'--.
> + * | P2 |
> + * '------'
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_with_sibling_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = true,
> + .domain_child = true,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_with_sibling_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = false,
> + .domain_parent = true,
> + .domain_child = true,
> + .connect_to_parent = false,
> +};
> +
> +/*
> + * Same domain (inherited)
> + * .-------------.
> + * | P1----. | P1 -> P2 : allow
> + * | \ | P2 -> P1 : allow
> + * | ' |
> + * | P2 |
> + * '-------------'
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_inherited_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = false,
> + .domain_child = false,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_inherited_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = false,
> + .domain_child = false,
> + .connect_to_parent = false,
> +};
> +
> +/*
> + * Inherited + child domain
> + * .-----------------.
> + * | P1----. | P1 -> P2 : allow
> + * | \ | P2 -> P1 : deny
> + * | .-'----. |
> + * | | P2 | |
> + * | '------' |
> + * '-----------------'
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_nested_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = false,
> + .domain_child = true,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_nested_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = false,
> + .domain_child = true,
> + .connect_to_parent = false,
> +};
> +
> +/*
> + * Inherited + parent domain
> + * .-----------------.
> + * |.------. | P1 -> P2 : deny
> + * || P1 ----. | P2 -> P1 : allow
> + * |'------' \ |
> + * | ' |
> + * | P2 |
> + * '-----------------'
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, allow_with_nested_and_parent_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = true,
> + .domain_child = false,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_with_nested_and_parent_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = true,
> + .domain_child = false,
> + .connect_to_parent = false,
> +};
> +
> +/*
> + * Inherited + parent and child domain (siblings)
> + * .-----------------.
> + * | .------. | P1 -> P2 : deny
> + * | | P1 . | P2 -> P1 : deny
> + * | '------'\ |
> + * | \ |
> + * | .--'---. |
> + * | | P2 | |
> + * | '------' |
> + * '-----------------'
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_with_forked_domain_connect_to_parent) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = true,
> + .domain_child = true,
> + .connect_to_parent = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(unix_socket, deny_with_forked_domain_connect_to_child) {
> + /* clang-format on */
> + .domain_both = true,
> + .domain_parent = true,
> + .domain_child = true,
> + .connect_to_parent = false,
> +};
> +
> +/*
> + * Test unix_stream_connect() and unix_may_send() for parent and child,
> + * when they have scoped domain or no domain.
> + */
> +TEST_F(unix_socket, abstract_unix_socket)
> +{
> + struct service_fixture stream_address, dgram_address;
> + pid_t child;
> + bool can_connect_to_parent, can_connect_to_child;
> + int err, err_dgram, status;
> + int pipe_child[2], pipe_parent[2];
> + char buf_parent;
For all TEST() and TEST_F(), in this patch series and the signal one, we
need to first drop capabilities:
drop_caps(_metadata);
> +
> + memset(&stream_address, 0, sizeof(stream_address));
> + memset(&dgram_address, 0, sizeof(dgram_address));
> + set_unix_address(&stream_address, 0);
> + set_unix_address(&dgram_address, 1);
> + /*
> + * can_connect_to_child is true if a parent process can connect to its
> + * child process. The parent process is not isolated from the child
> + * with a dedicated Landlock domain.
> + */
> + can_connect_to_child = !variant->domain_parent;
> + /*
> + * can_connect_to_parent is true if a child process can connect to its
> + * parent process. This depends on the child process is not isolated from
> + * the parent with a dedicated Landlock domain.
> + */
> + can_connect_to_parent = !variant->domain_child;
> +
> + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> + if (variant->domain_both) {
> + create_unix_domain(_metadata);
> + if (!__test_passed(_metadata))
> + return;
> + }
> +
> + child = fork();
> + ASSERT_LE(0, child);
> + if (child == 0) {
> + char buf_child;
> +
> + ASSERT_EQ(0, close(pipe_parent[1]));
> + ASSERT_EQ(0, close(pipe_child[0]));
> + if (variant->domain_child)
> + create_unix_domain(_metadata);
> +
> + /* Waits for the parent to be in a domain, if any. */
> + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
> +
> + if (variant->connect_to_parent) {
> + int client, dgram_client;
It looks like you missed some of my previous reviews (e.g. variable
names). Please read back again *all* my reviews/emails for at least the
last two versions.
> +
> + client = socket(AF_UNIX, SOCK_STREAM, 0);
> + dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
> +
> +TEST_F(outside_socket, socket_with_different_domain)
> +{
> + pid_t child;
> + int err, status;
> + int pipe_child[2], pipe_parent[2];
> + char buf_parent;
> + struct service_fixture address, transit_address;
> +
> + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> +
> + memset(&transit_address, 0, sizeof(transit_address));
> + memset(&address, 0, sizeof(address));
> + set_unix_address(&transit_address, 0);
> + set_unix_address(&address, 1);
> +
> + child = fork();
> + ASSERT_LE(0, child);
> + if (child == 0) {
> + char buf_child;
> + int stream_server, client;
> +
> + ASSERT_EQ(0, close(pipe_parent[1]));
> + ASSERT_EQ(0, close(pipe_child[0]));
> +
> + /* client always has domain */
> + create_unix_domain(_metadata);
> +
> + if (variant->domain_server_socket) {
> + int data_socket;
> + int fd_sock = socket(AF_UNIX, variant->type, 0);
> +
> + ASSERT_NE(-1, fd_sock);
> +
> + stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
> +
> + ASSERT_NE(-1, stream_server);
> + ASSERT_EQ(0, bind(stream_server,
> + &transit_address.unix_addr,
> + transit_address.unix_addr_len));
> + ASSERT_EQ(0, listen(stream_server, backlog));
> +
> + ASSERT_EQ(1, write(pipe_child[1], ".", 1));
> +
> + data_socket = accept(stream_server, NULL, NULL);
> +
> + ASSERT_EQ(0, send_fd(data_socket, fd_sock));
> + ASSERT_EQ(0, close(fd_sock));
> + ASSERT_EQ(0, close(stream_server));
> + }
> +
> + client = socket(AF_UNIX, variant->type, 0);
> + ASSERT_NE(-1, client);
> + /* wait for parent signal for connection */
> + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
> +
> + err = connect(client, &address.unix_addr,
> + address.unix_addr_len);
> + if (!variant->domain_server_socket) {
> + EXPECT_EQ(-1, err);
> + EXPECT_EQ(EPERM, errno);
> + } else {
> + EXPECT_EQ(0, err);
> + }
> + ASSERT_EQ(0, close(client));
> + _exit(_metadata->exit_code);
> + return;
> + }
> + int server_socket;
Variable declarations go at the top of the scope where other variable
are declared.
> +
> + ASSERT_EQ(0, close(pipe_child[1]));
> + ASSERT_EQ(0, close(pipe_parent[0]));
> +
> + if (!variant->domain_server_socket) {
> + server_socket = socket(AF_UNIX, variant->type, 0);
> + } else {
> + int cli = socket(AF_UNIX, SOCK_STREAM, 0);
> +
> + ASSERT_NE(-1, cli);
> + ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
> + ASSERT_EQ(0, connect(cli, &transit_address.unix_addr,
> + transit_address.unix_addr_len));
> +
> + server_socket = recv_fd(cli);
> + ASSERT_LE(0, server_socket);
> + ASSERT_EQ(0, close(cli));
> + }
> +
> + ASSERT_NE(-1, server_socket);
> +
> + if (variant->domain_server)
> + create_unix_domain(_metadata);
> +
> + ASSERT_EQ(0, bind(server_socket, &address.unix_addr,
> + address.unix_addr_len));
> + if (variant->type == SOCK_STREAM)
> + ASSERT_EQ(0, listen(server_socket, backlog));
> + /* signal to child that parent is listening */
> + ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> +
> + ASSERT_EQ(child, waitpid(child, &status, 0));
> + ASSERT_EQ(0, close(server_socket));
> +
> + if (WIFSIGNALED(status) || !WIFEXITED(status) ||
> + WEXITSTATUS(status) != EXIT_SUCCESS)
> + _metadata->exit_code = KSFT_FAIL;
> +}
> +
> +TEST_HARNESS_MAIN
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v10 1/6] Landlock: Add abstract UNIX socket connect restriction
2024-08-20 4:08 ` [PATCH v10 1/6] " Tahera Fahimi
@ 2024-08-20 19:14 ` Simon Horman
0 siblings, 0 replies; 11+ messages in thread
From: Simon Horman @ 2024-08-20 19:14 UTC (permalink / raw)
To: Tahera Fahimi
Cc: outreachy, mic, gnoack, paul, jmorris, serge,
linux-security-module, linux-kernel, bjorn3_gh, jannh, netdev
On Mon, Aug 19, 2024 at 10:08:51PM -0600, Tahera Fahimi wrote:
> This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> abstract UNIX sockets from connecting to a process outside of
> the same Landlock domain. It implements two hooks, unix_stream_connect
> and unix_may_send to enforce this restriction.
>
> Closes: https://github.com/landlock-lsm/linux/issues/7
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
...
> diff --git a/security/landlock/task.c b/security/landlock/task.c
...
> @@ -108,9 +110,134 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
> return task_ptrace(parent, current);
> }
>
> +/**
> + * domain_is_scoped - Checks if the client domain is scoped in the same
> + * domain as the server.
> + *
> + * @client: IPC sender domain.
> + * @server: IPC receiver domain.
nit: @scope should be documented here.
> + *
> + * Return true if the @client domain is scoped to access the @server,
nit: Kernel doc returns sections start with "Return:" or "Returns:".
It might be worth using that syntax here.
> + * unless the @server is also scoped in the same domain as @client.
> + */
> +static bool domain_is_scoped(const struct landlock_ruleset *const client,
> + const struct landlock_ruleset *const server,
> + access_mask_t scope)
...
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v10 5/6] sample/Landlock: Support abstract unix socket restriction
2024-08-20 4:08 ` [PATCH v10 5/6] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
@ 2024-08-21 15:59 ` Mickaël Salaün
0 siblings, 0 replies; 11+ messages in thread
From: Mickaël Salaün @ 2024-08-21 15:59 UTC (permalink / raw)
To: Tahera Fahimi
Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
linux-kernel, bjorn3_gh, jannh, netdev
On Mon, Aug 19, 2024 at 10:08:55PM -0600, Tahera Fahimi wrote:
> A sandboxer can receive the character "a" as input from the environment
> variable LL_SCOPE to restrict the abstract UNIX sockets from connecting
> to a process outside its scoped domain.
>
> Example
> =======
> Create an abstract unix socket to listen with socat(1):
> socat abstract-listen:mysocket -
>
> Create a sandboxed shell and pass the character "a" to LL_SCOPED:
> LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash
>
> If the sandboxed process tries to connect to the listening socket
> with command "socat - abstract-connect:mysocket", the connection
> will fail.
>
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> ---
> v10:
> - Minor improvement in code based on v9.
> v9:
> - Add a restrict approach on input of LL_SCOPED, so it only allows zero
> or one "a" to be the input.
> v8:
> - Adding check_ruleset_scope function to parse the scope environment
> variable and update the landlock attribute based on the restriction
> provided by the user.
> - Adding Mickaël Salaün reviews on version 7.
>
> v7:
> - Adding IPC scoping to the sandbox demo by defining a new "LL_SCOPED"
> environment variable. "LL_SCOPED" gets value "a" to restrict abstract
> unix sockets.
> - Change LANDLOCK_ABI_LAST to 6.
> ---
> samples/landlock/sandboxer.c | 56 +++++++++++++++++++++++++++++++++---
> 1 file changed, 52 insertions(+), 4 deletions(-)
>
> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> index e8223c3e781a..0564d0a40c67 100644
> --- a/samples/landlock/sandboxer.c
> +++ b/samples/landlock/sandboxer.c
> @@ -14,6 +14,7 @@
> #include <fcntl.h>
> #include <linux/landlock.h>
> #include <linux/prctl.h>
> +#include <linux/socket.h>
> #include <stddef.h>
> #include <stdio.h>
> #include <stdlib.h>
> @@ -22,6 +23,7 @@
> #include <sys/stat.h>
> #include <sys/syscall.h>
> #include <unistd.h>
> +#include <stdbool.h>
>
> #ifndef landlock_create_ruleset
> static inline int
> @@ -55,6 +57,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
> #define ENV_FS_RW_NAME "LL_FS_RW"
> #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
> #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
> +#define ENV_SCOPED_NAME "LL_SCOPED"
> #define ENV_DELIMITER ":"
>
> static int parse_path(char *env_path, const char ***const path_list)
> @@ -184,6 +187,40 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
> return ret;
> }
>
> +static bool check_ruleset_scope(const char *const env_var,
> + struct landlock_ruleset_attr *ruleset_attr)
> +{
> + bool abstract_scoping = false;
> + bool ret = true;
> + char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
> +
> + ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
This is bug prone because it removes the scope flags but doesn't store
the initial state. It would be better to use the abstract_scoping
variable to unset the related flag at the end of this function.
> + env_type_scope = getenv(env_var);
> + /* scoping is not supported by the user */
> + if (!env_type_scope || strcmp("", env_type_scope) == 0)
> + return true;
> +
> + env_type_scope = strdup(env_type_scope);
> + unsetenv(env_var);
> + env_type_scope_next = env_type_scope;
> + while ((ipc_scoping_name =
> + strsep(&env_type_scope_next, ENV_DELIMITER))) {
> + if (strcmp("a", ipc_scoping_name) == 0 && !abstract_scoping) {
> + abstract_scoping = true;
> + ruleset_attr->scoped |=
> + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> + } else {
> + fprintf(stderr, "Unsupported scoping \"%s\"\n",
> + ipc_scoping_name);
> + ret = false;
> + goto out_free_name;
> + }
> + }
> +out_free_name:
> + free(env_type_scope);
> + return ret;
> +}
> +
> /* clang-format off */
>
> #define ACCESS_FS_ROUGHLY_READ ( \
> @@ -208,7 +245,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>
> /* clang-format on */
>
> -#define LANDLOCK_ABI_LAST 5
> +#define LANDLOCK_ABI_LAST 6
>
> int main(const int argc, char *const argv[], char *const *const envp)
> {
> @@ -223,14 +260,15 @@ int main(const int argc, char *const argv[], char *const *const envp)
> .handled_access_fs = access_fs_rw,
> .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .scoped = LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET,
> };
>
> if (argc < 2) {
> fprintf(stderr,
> - "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
> + "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s "
> "<cmd> [args]...\n\n",
> ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
> - ENV_TCP_CONNECT_NAME, argv[0]);
> + ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
> fprintf(stderr,
> "Execute a command in a restricted environment.\n\n");
> fprintf(stderr,
> @@ -251,15 +289,18 @@ int main(const int argc, char *const argv[], char *const *const envp)
> fprintf(stderr,
> "* %s: list of ports allowed to connect (client).\n",
> ENV_TCP_CONNECT_NAME);
> + fprintf(stderr, "* %s: list of restrictions on IPCs.\n",
> + ENV_SCOPED_NAME);
> fprintf(stderr,
> "\nexample:\n"
> "%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
> "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
> "%s=\"9418\" "
> "%s=\"80:443\" "
> + "%s=\"a\" "
> "%s bash -i\n\n",
> ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
> - ENV_TCP_CONNECT_NAME, argv[0]);
> + ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
> fprintf(stderr,
> "This sandboxer can use Landlock features "
> "up to ABI version %d.\n",
> @@ -327,6 +368,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
> /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
> ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
>
> + __attribute__((fallthrough));
> + case 5:
> + /* Removes LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
> + ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> fprintf(stderr,
> "Hint: You should update the running kernel "
> "to leverage Landlock features "
> @@ -358,6 +403,9 @@ int main(const int argc, char *const argv[], char *const *const envp)
> ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
> }
>
> + if (abi >= 6 && !check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
Instead of explicitly re-checking the ABI, check_ruleset_scope() should
check ruleset_attr.scoped & LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET
> + return 1;
> +
> ruleset_fd =
> landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> if (ruleset_fd < 0) {
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2024-08-21 15:59 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-08-20 4:08 [PATCH v10 0/6] Landlock: Add abstract UNIX socket connect restriction Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 1/6] " Tahera Fahimi
2024-08-20 19:14 ` Simon Horman
2024-08-20 4:08 ` [PATCH v10 2/6] selftests/Landlock: general scoped restriction tests Tahera Fahimi
2024-08-20 15:58 ` Mickaël Salaün
2024-08-20 4:08 ` [PATCH v10 3/6] selftests/Landlock: Abstract UNIX socket " Tahera Fahimi
2024-08-20 16:00 ` Mickaël Salaün
2024-08-20 4:08 ` [PATCH v10 4/6] selftests/Landlock: Add pathname UNIX socket tests Tahera Fahimi
2024-08-20 4:08 ` [PATCH v10 5/6] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
2024-08-21 15:59 ` Mickaël Salaün
2024-08-20 4:08 ` [PATCH v10 6/6] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning Tahera Fahimi
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).