summaryrefslogtreecommitdiff
path: root/security/landlock/net.c
blob: cbff59ec3abaa0ed267fe5ea5560453e7201c3bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Landlock - Network management and hooks
 *
 * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
 * Copyright © 2022-2025 Microsoft Corporation
 */

#include <linux/in.h>
#include <linux/lsm_audit.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <net/ipv6.h>

#include "audit.h"
#include "common.h"
#include "cred.h"
#include "limits.h"
#include "net.h"
#include "ruleset.h"

int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
			     const u16 port, access_mask_t access_rights,
			     const u32 flags)
{
	int err;
	const struct landlock_id id = {
		.key.data = (__force uintptr_t)htons(port),
		.type = LANDLOCK_KEY_NET_PORT,
	};

	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));

	/* Transforms relative access rights to absolute ones. */
	access_rights |= LANDLOCK_MASK_ACCESS_NET &
			 ~landlock_get_net_access_mask(ruleset, 0);

	mutex_lock(&ruleset->lock);
	err = landlock_insert_rule(ruleset, id, access_rights, flags);
	mutex_unlock(&ruleset->lock);

	return err;
}

static int current_check_access_socket(struct socket *const sock,
				       struct sockaddr *const address,
				       const int addrlen,
				       access_mask_t access_request,
				       bool connecting)
{
	unsigned short sock_family;
	__be16 port;
	struct layer_masks layer_masks = {};
	const struct landlock_rule *rule;
	struct landlock_id id = {
		.type = LANDLOCK_KEY_NET_PORT,
	};
	const struct access_masks masks = {
		.net = access_request,
	};
	const struct landlock_cred_security *const subject =
		landlock_get_applicable_subject(current_cred(), masks, NULL);
	struct lsm_network_audit audit_net = {};

	if (!subject)
		return 0;

	/* Checks for minimal header length to safely read sa_family. */
	if (addrlen < offsetofend(typeof(*address), sa_family))
		return -EINVAL;

	/*
	 * The socket is not locked, so sk_family can change concurrently due to
	 * e.g. setsockopt(IPV6_ADDRFORM).
	 */
	sock_family = READ_ONCE(sock->sk->sk_family);

	switch (address->sa_family) {
	case AF_UNSPEC:
		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
		    (access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP &&
		     connecting)) {
			/*
			 * Connecting to an address with AF_UNSPEC dissolves the
			 * remote association while retaining the socket object
			 * (i.e., the file descriptor). For TCP, it has the same
			 * effect as closing the connection. For UDP, it removes
			 * any preset remote address. As for dropping
			 * privileges, these actions are always allowed.  Let
			 * the network stack handle potential inconsistencies
			 * and return -EINVAL if needed.
			 */
			return 0;
		} else if (access_request ==
			   LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
			if (sock_family == AF_INET6) {
				/*
				 * We cannot allow sending UDP datagrams to an
				 * explicit AF_UNSPEC address on IPv6 sockets,
				 * even if AF_UNSPEC is treated as "no address"
				 * on such sockets (so it should always be
				 * allowed).  That's because the socket's family
				 * can change under our feet (if another thread
				 * calls setsockopt(IPV6_ADDRFORM)) to IPv4,
				 * which would then treat AF_UNSPEC as AF_INET.
				 */
				audit_net.family = AF_UNSPEC;
				audit_net.sk = sock->sk;
				landlock_init_layer_masks(
					subject->domain, access_request,
					&layer_masks, LANDLOCK_KEY_NET_PORT);
				landlock_log_denial(
					subject,
					&(struct landlock_request){
						.type = LANDLOCK_REQUEST_NET_ACCESS,
						.audit.type =
							LSM_AUDIT_DATA_NET,
						.audit.u.net = &audit_net,
						.access = access_request,
						.layer_masks = &layer_masks,
					});
				return -EACCES;
			}
		} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
			   access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
			/*
			 * Binding to an AF_UNSPEC address is treated
			 * differently by IPv4 and IPv6 sockets. The socket's
			 * family may change under our feet due to
			 * setsockopt(IPV6_ADDRFORM), but that's ok: we either
			 * reject entirely for IPv6 or require
			 * %LANDLOCK_ACCESS_NET_BIND_TCP or
			 * %LANDLOCK_ACCESS_NET_BIND_UDP for IPv4, so it cannot
			 * be used to bypass the policy.
			 *
			 * IPv4 sockets map AF_UNSPEC to AF_INET for
			 * retrocompatibility for bind accesses, only if the
			 * address is INADDR_ANY (cf. __inet_bind). IPv6
			 * sockets always reject it.
			 *
			 * Checking the address is required to not wrongfully
			 * return -EACCES instead of -EAFNOSUPPORT or -EINVAL.
			 * We could return 0 and let the network stack handle
			 * these checks, but it is safer to return a proper
			 * error and test consistency thanks to kselftest.
			 */
			if (sock_family == AF_INET) {
				const struct sockaddr_in *const sockaddr =
					(struct sockaddr_in *)address;

				if (addrlen < sizeof(struct sockaddr_in))
					return -EINVAL;

				if (sockaddr->sin_addr.s_addr !=
				    htonl(INADDR_ANY))
					return -EAFNOSUPPORT;
			} else {
				if (addrlen < SIN6_LEN_RFC2133)
					return -EINVAL;
				else
					return -EAFNOSUPPORT;
			}
		} else {
			WARN_ON_ONCE(1);
		}
		/*
		 * AF_UNSPEC is treated as AF_INET only in
		 * bind(AF_UNSPEC+INADDR_ANY) on IPv4 sockets and when sending
		 * to AF_UNSPEC addresses on IPv4 sockets.
		 */
		fallthrough;
	case AF_INET: {
		const struct sockaddr_in *addr4;

		if (addrlen < sizeof(struct sockaddr_in))
			return -EINVAL;

		addr4 = (struct sockaddr_in *)address;
		port = addr4->sin_port;

		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
		    access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
			audit_net.dport = port;
			audit_net.v4info.daddr = addr4->sin_addr.s_addr;
		} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
			   access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
			audit_net.sport = port;
			audit_net.v4info.saddr = addr4->sin_addr.s_addr;
		} else {
			WARN_ON_ONCE(1);
		}
		break;
	}

#if IS_ENABLED(CONFIG_IPV6)
	case AF_INET6: {
		const struct sockaddr_in6 *addr6;

		if (addrlen < SIN6_LEN_RFC2133)
			return -EINVAL;

		addr6 = (struct sockaddr_in6 *)address;
		port = addr6->sin6_port;

		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
		    access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
			audit_net.dport = port;
			audit_net.v6info.daddr = addr6->sin6_addr;
		} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
			   access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
			audit_net.sport = port;
			audit_net.v6info.saddr = addr6->sin6_addr;
		} else {
			WARN_ON_ONCE(1);
		}
		break;
	}
#endif /* IS_ENABLED(CONFIG_IPV6) */

	default:
		return 0;
	}

	/*
	 * Checks sa_family consistency to not wrongfully return
	 * -EACCES instead of -EINVAL.  Valid sa_family changes are
	 * only (from AF_INET or AF_INET6) to AF_UNSPEC.
	 *
	 * We could return 0 and let the network stack handle this
	 * check, but it is safer to return a proper error and test
	 * consistency thanks to kselftest.
	 */
	if (address->sa_family != sock_family &&
	    address->sa_family != AF_UNSPEC)
		return -EINVAL;

	id.key.data = (__force uintptr_t)port;
	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));

	rule = landlock_find_rule(subject->domain, id);
	access_request = landlock_init_layer_masks(subject->domain,
						   access_request, &layer_masks,
						   LANDLOCK_KEY_NET_PORT);
	if (!access_request)
		return 0;

	if (landlock_unmask_layers(rule, &layer_masks))
		return 0;

	audit_net.family = address->sa_family;
	audit_net.sk = sock->sk;
	landlock_log_denial(subject,
			    &(struct landlock_request){
				    .type = LANDLOCK_REQUEST_NET_ACCESS,
				    .audit.type = LSM_AUDIT_DATA_NET,
				    .audit.u.net = &audit_net,
				    .access = access_request,
				    .layer_masks = &layer_masks,
			    });
	return -EACCES;
}

static int current_check_autobind_udp_socket(struct socket *const sock)
{
	const struct access_masks bind_udp = {
		.net = LANDLOCK_ACCESS_NET_BIND_UDP,
	};
	struct sockaddr_storage port0 = {};
	unsigned short num;
	bool slow;

	/* Quick return for non-Landlocked tasks. */
	if (!landlock_get_applicable_subject(current_cred(), bind_udp, NULL))
		return 0;

	/*
	 * On UDP sockets, if a local port has not already been bound, calling
	 * connect() or sending a first datagram has the side effect of
	 * autobinding an ephemeral port: we also have to check that the process
	 * would have had the right to bind(0) explicitly.  Hold the socket lock
	 * around the inet_num read to exclude udp_lib_get_port()'s transient
	 * inet_num = snum write that is reverted to 0 on a failing reuseport
	 * bind.
	 */
	slow = lock_sock_fast(sock->sk);
	num = inet_sk(sock->sk)->inet_num;
	unlock_sock_fast(sock->sk, slow);
	if (num != 0)
		return 0;

	/*
	 * Construct a struct sockaddr* with port 0 to pretend the process tried
	 * to bind() on that address.
	 */
	port0.ss_family = READ_ONCE(sock->sk->sk_family);

	return current_check_access_socket(sock, (struct sockaddr *)&port0,
					   sizeof(port0), bind_udp.net, false);
}

static int hook_socket_bind(struct socket *const sock,
			    struct sockaddr *const address, const int addrlen)
{
	access_mask_t access_request;

	if (sk_is_tcp(sock->sk))
		access_request = LANDLOCK_ACCESS_NET_BIND_TCP;
	else if (sk_is_udp(sock->sk))
		access_request = LANDLOCK_ACCESS_NET_BIND_UDP;
	else
		return 0;

	return current_check_access_socket(sock, address, addrlen,
					   access_request, false);
}

static int hook_socket_connect(struct socket *const sock,
			       struct sockaddr *const address,
			       const int addrlen)
{
	access_mask_t access_request;
	int ret = 0;

	if (sk_is_tcp(sock->sk))
		access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP;
	else if (sk_is_udp(sock->sk))
		access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
	else
		return 0;

	ret = current_check_access_socket(sock, address, addrlen,
					  access_request, true);

	/*
	 * connect()ing to an AF_UNSPEC address does not trigger an autobind and
	 * should never be restricted.
	 */
	if (ret == 0 && sk_is_udp(sock->sk) &&
	    addrlen >= offsetofend(typeof(*address), sa_family) &&
	    address->sa_family != AF_UNSPEC)
		ret = current_check_autobind_udp_socket(sock);

	return ret;
}

static int hook_socket_sendmsg(struct socket *const sock,
			       struct msghdr *const msg, const int size)
{
	struct sockaddr *const address = msg->msg_name;
	const int addrlen = msg->msg_namelen;
	access_mask_t access_request;
	int ret = 0;

	if (sk_is_udp(sock->sk))
		access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
	else
		return 0;

	if (address != NULL)
		ret = current_check_access_socket(sock, address, addrlen,
						  access_request, false);

	if (ret == 0)
		ret = current_check_autobind_udp_socket(sock);

	return ret;
}

static struct security_hook_list landlock_hooks[] __ro_after_init = {
	LSM_HOOK_INIT(socket_bind, hook_socket_bind),
	LSM_HOOK_INIT(socket_connect, hook_socket_connect),
	LSM_HOOK_INIT(socket_sendmsg, hook_socket_sendmsg),
};

__init void landlock_add_net_hooks(void)
{
	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
			   &landlock_lsmid);
}