Skip to content

Commit 21d52e2

Browse files
tahifahimil0kod
authored andcommitted
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: landlock-lsm/linux#7 Signed-off-by: Tahera 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: Mickaël Salaün <mic@digikod.net> Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent a430d95 commit 21d52e2

File tree

7 files changed

+208
-9
lines changed

7 files changed

+208
-9
lines changed

include/uapi/linux/landlock.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ struct landlock_ruleset_attr {
4444
* flags`_).
4545
*/
4646
__u64 handled_access_net;
47+
/**
48+
* @scoped: Bitmask of scopes (cf. `Scope flags`_)
49+
* restricting a Landlock domain from accessing outside
50+
* resources (e.g. IPCs).
51+
*/
52+
__u64 scoped;
4753
};
4854

4955
/*
@@ -274,4 +280,25 @@ struct landlock_net_port_attr {
274280
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
275281
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
276282
/* clang-format on */
283+
284+
/**
285+
* DOC: scope
286+
*
287+
* Scope flags
288+
* ~~~~~~~~~~~
289+
*
290+
* These flags enable to isolate a sandboxed process from a set of IPC actions.
291+
* Setting a flag for a ruleset will isolate the Landlock domain to forbid
292+
* connections to resources outside the domain.
293+
*
294+
* Scopes:
295+
*
296+
* - %LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process from
297+
* connecting to an abstract UNIX socket created by a process outside the
298+
* related Landlock domain (e.g. a parent domain or a non-sandboxed process).
299+
*/
300+
/* clang-format off */
301+
#define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0)
302+
/* clang-format on*/
303+
277304
#endif /* _UAPI_LINUX_LANDLOCK_H */

security/landlock/limits.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
2727
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
2828

29+
#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
30+
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
31+
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
2932
/* clang-format on */
3033

3134
#endif /* _SECURITY_LANDLOCK_LIMITS_H */

security/landlock/ruleset.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
5252

5353
struct landlock_ruleset *
5454
landlock_create_ruleset(const access_mask_t fs_access_mask,
55-
const access_mask_t net_access_mask)
55+
const access_mask_t net_access_mask,
56+
const access_mask_t scope_mask)
5657
{
5758
struct landlock_ruleset *new_ruleset;
5859

5960
/* Informs about useless ruleset. */
60-
if (!fs_access_mask && !net_access_mask)
61+
if (!fs_access_mask && !net_access_mask && !scope_mask)
6162
return ERR_PTR(-ENOMSG);
6263
new_ruleset = create_ruleset(1);
6364
if (IS_ERR(new_ruleset))
@@ -66,6 +67,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,
6667
landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
6768
if (net_access_mask)
6869
landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
70+
if (scope_mask)
71+
landlock_add_scope_mask(new_ruleset, scope_mask, 0);
6972
return new_ruleset;
7073
}
7174

security/landlock/ruleset.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@ typedef u16 access_mask_t;
3535
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
3636
/* Makes sure all network access rights can be stored. */
3737
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
38+
/* Makes sure all scoped rights can be stored. */
39+
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
3840
/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
3941
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
4042

4143
/* Ruleset access masks. */
4244
struct access_masks {
4345
access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
4446
access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
47+
access_mask_t scope : LANDLOCK_NUM_SCOPE;
4548
};
4649

4750
typedef u16 layer_mask_t;
@@ -233,7 +236,8 @@ struct landlock_ruleset {
233236

234237
struct landlock_ruleset *
235238
landlock_create_ruleset(const access_mask_t access_mask_fs,
236-
const access_mask_t access_mask_net);
239+
const access_mask_t access_mask_net,
240+
const access_mask_t scope_mask);
237241

238242
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
239243
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@@ -280,6 +284,17 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
280284
ruleset->access_masks[layer_level].net |= net_mask;
281285
}
282286

287+
static inline void
288+
landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
289+
const access_mask_t scope_mask, const u16 layer_level)
290+
{
291+
access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
292+
293+
/* Should already be checked in sys_landlock_create_ruleset(). */
294+
WARN_ON_ONCE(scope_mask != mask);
295+
ruleset->access_masks[layer_level].scope |= mask;
296+
}
297+
283298
static inline access_mask_t
284299
landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
285300
const u16 layer_level)
@@ -303,6 +318,13 @@ landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
303318
return ruleset->access_masks[layer_level].net;
304319
}
305320

321+
static inline access_mask_t
322+
landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
323+
const u16 layer_level)
324+
{
325+
return ruleset->access_masks[layer_level].scope;
326+
}
327+
306328
bool landlock_unmask_layers(const struct landlock_rule *const rule,
307329
const access_mask_t access_request,
308330
layer_mask_t (*const layer_masks)[],

security/landlock/syscalls.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ static void build_check_abi(void)
9797
*/
9898
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
9999
ruleset_size += sizeof(ruleset_attr.handled_access_net);
100+
ruleset_size += sizeof(ruleset_attr.scoped);
100101
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
101-
BUILD_BUG_ON(sizeof(ruleset_attr) != 16);
102+
BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
102103

103104
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
104105
path_beneath_size += sizeof(path_beneath_attr.parent_fd);
@@ -149,7 +150,7 @@ static const struct file_operations ruleset_fops = {
149150
.write = fop_dummy_write,
150151
};
151152

152-
#define LANDLOCK_ABI_VERSION 5
153+
#define LANDLOCK_ABI_VERSION 6
153154

154155
/**
155156
* sys_landlock_create_ruleset - Create a new ruleset
@@ -170,8 +171,9 @@ static const struct file_operations ruleset_fops = {
170171
* Possible returned errors are:
171172
*
172173
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
173-
* - %EINVAL: unknown @flags, or unknown access, or too small @size;
174-
* - %E2BIG or %EFAULT: @attr or @size inconsistencies;
174+
* - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;
175+
* - %E2BIG: @attr or @size inconsistencies;
176+
* - %EFAULT: @attr or @size inconsistencies;
175177
* - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
176178
*/
177179
SYSCALL_DEFINE3(landlock_create_ruleset,
@@ -213,9 +215,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
213215
LANDLOCK_MASK_ACCESS_NET)
214216
return -EINVAL;
215217

218+
/* Checks IPC scoping content (and 32-bits cast). */
219+
if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
220+
return -EINVAL;
221+
216222
/* Checks arguments and transforms to kernel struct. */
217223
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
218-
ruleset_attr.handled_access_net);
224+
ruleset_attr.handled_access_net,
225+
ruleset_attr.scoped);
219226
if (IS_ERR(ruleset))
220227
return PTR_ERR(ruleset);
221228

security/landlock/task.c

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <linux/lsm_hooks.h>
1414
#include <linux/rcupdate.h>
1515
#include <linux/sched.h>
16+
#include <net/af_unix.h>
17+
#include <net/sock.h>
1618

1719
#include "common.h"
1820
#include "cred.h"
@@ -108,9 +110,144 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
108110
return task_ptrace(parent, current);
109111
}
110112

113+
/**
114+
* domain_is_scoped - Checks if the client domain is scoped in the same
115+
* domain as the server.
116+
*
117+
* @client: IPC sender domain.
118+
* @server: IPC receiver domain.
119+
* @scope: The scope restriction criteria.
120+
*
121+
* Returns: True if the @client domain is scoped to access the @server,
122+
* unless the @server is also scoped in the same domain as @client.
123+
*/
124+
static bool domain_is_scoped(const struct landlock_ruleset *const client,
125+
const struct landlock_ruleset *const server,
126+
access_mask_t scope)
127+
{
128+
int client_layer, server_layer;
129+
struct landlock_hierarchy *client_walker, *server_walker;
130+
131+
/* Quick return if client has no domain */
132+
if (WARN_ON_ONCE(!client))
133+
return false;
134+
135+
client_layer = client->num_layers - 1;
136+
client_walker = client->hierarchy;
137+
/*
138+
* client_layer must be a signed integer with greater capacity
139+
* than client->num_layers to ensure the following loop stops.
140+
*/
141+
BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
142+
143+
server_layer = server ? (server->num_layers - 1) : -1;
144+
server_walker = server ? server->hierarchy : NULL;
145+
146+
/*
147+
* Walks client's parent domains down to the same hierarchy level
148+
* as the server's domain, and checks that none of these client's
149+
* parent domains are scoped.
150+
*/
151+
for (; client_layer > server_layer; client_layer--) {
152+
if (landlock_get_scope_mask(client, client_layer) & scope)
153+
return true;
154+
155+
client_walker = client_walker->parent;
156+
}
157+
/*
158+
* Walks server's parent domains down to the same hierarchy level as
159+
* the client's domain.
160+
*/
161+
for (; server_layer > client_layer; server_layer--)
162+
server_walker = server_walker->parent;
163+
164+
for (; client_layer >= 0; client_layer--) {
165+
if (landlock_get_scope_mask(client, client_layer) & scope) {
166+
/*
167+
* Client and server are at the same level in the
168+
* hierarchy. If the client is scoped, the request is
169+
* only allowed if this domain is also a server's
170+
* ancestor.
171+
*/
172+
return server_walker != client_walker;
173+
}
174+
client_walker = client_walker->parent;
175+
server_walker = server_walker->parent;
176+
}
177+
return false;
178+
}
179+
180+
static bool sock_is_scoped(struct sock *const other,
181+
const struct landlock_ruleset *const domain)
182+
{
183+
const struct landlock_ruleset *dom_other;
184+
185+
/* The credentials will not change. */
186+
lockdep_assert_held(&unix_sk(other)->lock);
187+
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
188+
return domain_is_scoped(domain, dom_other,
189+
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
190+
}
191+
192+
static bool is_abstract_socket(struct sock *const sock)
193+
{
194+
struct unix_address *addr = unix_sk(sock)->addr;
195+
196+
if (!addr)
197+
return false;
198+
199+
if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
200+
addr->name->sun_path[0] == '\0')
201+
return true;
202+
203+
return false;
204+
}
205+
206+
static int hook_unix_stream_connect(struct sock *const sock,
207+
struct sock *const other,
208+
struct sock *const newsk)
209+
{
210+
const struct landlock_ruleset *const dom =
211+
landlock_get_current_domain();
212+
213+
/* Quick return for non-landlocked tasks. */
214+
if (!dom)
215+
return 0;
216+
217+
if (is_abstract_socket(other) && sock_is_scoped(other, dom))
218+
return -EPERM;
219+
220+
return 0;
221+
}
222+
223+
static int hook_unix_may_send(struct socket *const sock,
224+
struct socket *const other)
225+
{
226+
const struct landlock_ruleset *const dom =
227+
landlock_get_current_domain();
228+
229+
if (!dom)
230+
return 0;
231+
232+
/*
233+
* Checks if this datagram socket was already allowed to be connected
234+
* to other.
235+
*/
236+
if (unix_peer(sock->sk) == other->sk)
237+
return 0;
238+
239+
if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom))
240+
return -EPERM;
241+
242+
return 0;
243+
}
244+
111245
static struct security_hook_list landlock_hooks[] __ro_after_init = {
112246
LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
113247
LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
248+
249+
LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect),
250+
LSM_HOOK_INIT(unix_may_send, hook_unix_may_send),
114251
};
115252

116253
__init void landlock_add_task_hooks(void)

tools/testing/selftests/landlock/base_test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ TEST(abi_version)
7676
const struct landlock_ruleset_attr ruleset_attr = {
7777
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
7878
};
79-
ASSERT_EQ(5, landlock_create_ruleset(NULL, 0,
79+
ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
8080
LANDLOCK_CREATE_RULESET_VERSION));
8181

8282
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,

0 commit comments

Comments
 (0)