Unverified Commit 21d52e29 authored by Tahera Fahimi's avatar Tahera Fahimi Committed by Mickaël Salaün
Browse files

landlock: Add abstract UNIX socket scoping

Introduce a new "scoped" member to landlock_ruleset_attr that can
specify LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET to restrict connection to
abstract UNIX sockets from a process outside of the socket's domain.

Two hooks are implemented to enforce these restrictions:
unix_stream_connect and unix_may_send.

Closes: https://github.com/landlock-lsm/linux/issues/7


Signed-off-by: default avatarTahera Fahimi <fahimitahera@gmail.com>
Link: https://lore.kernel.org/r/5f7ad85243b78427242275b93481cfc7c127764b.1725494372.git.fahimitahera@gmail.com


[mic: Fix commit message formatting, improve documentation, simplify
hook_unix_may_send(), and cosmetic fixes including rename of
LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET]
Co-developed-by: default avatarMickaël Salaün <mic@digikod.net>
Signed-off-by: default avatarMickaël Salaün <mic@digikod.net>
parent a430d95c
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -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 isolate 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.
 *
 * Scopes:
 *
 * - %LANDLOCK_SCOPE_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_SCOPE_ABSTRACT_UNIX_SOCKET		(1ULL << 0)
/* clang-format on*/

#endif /* _UAPI_LINUX_LANDLOCK_H */
+3 −0
Original line number Diff line number Diff line
@@ -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_SCOPE_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 */
+5 −2
Original line number Diff line number Diff line
@@ -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;
}

+23 −1
Original line number Diff line number Diff line
@@ -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 scope : 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 mask = scope_mask & LANDLOCK_MASK_SCOPE;

	/* Should already be checked in sys_landlock_create_ruleset(). */
	WARN_ON_ONCE(scope_mask != mask);
	ruleset->access_masks[layer_level].scope |= 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].scope;
}

bool landlock_unmask_layers(const struct landlock_rule *const rule,
			    const access_mask_t access_request,
			    layer_mask_t (*const layer_masks)[],
+12 −5
Original line number Diff line number Diff line
@@ -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);

Loading