* [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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.