secmodel_extensions.c revision 1.14
1/* $NetBSD: secmodel_extensions.c,v 1.14 2022/03/28 19:08:43 rillig Exp $ */
2/*-
3 * Copyright (c) 2011 Elad Efrat <elad@NetBSD.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: secmodel_extensions.c,v 1.14 2022/03/28 19:08:43 rillig Exp $");
31
32#include <sys/types.h>
33#include <sys/param.h>
34#include <sys/kauth.h>
35
36#include <sys/mount.h>
37#include <sys/vnode.h>
38#include <sys/socketvar.h>
39#include <sys/sysctl.h>
40#include <sys/proc.h>
41#include <sys/ptrace.h>
42#include <sys/module.h>
43
44#include <secmodel/secmodel.h>
45#include <secmodel/extensions/extensions.h>
46
47MODULE(MODULE_CLASS_SECMODEL, extensions, NULL);
48
49static int dovfsusermount;
50static int curtain;
51static int user_set_cpu_affinity;
52static int hardlink_check_uid;
53static int hardlink_check_gid;
54
55#ifdef PT_SETDBREGS
56int user_set_dbregs;
57#endif
58
59static kauth_listener_t l_system, l_process, l_network, l_vnode;
60
61static secmodel_t extensions_sm;
62
63static void secmodel_extensions_init(void);
64static void secmodel_extensions_start(void);
65static void secmodel_extensions_stop(void);
66
67static void sysctl_security_extensions_setup(struct sysctllog **);
68static int  sysctl_extensions_user_handler(SYSCTLFN_PROTO);
69static int  sysctl_extensions_curtain_handler(SYSCTLFN_PROTO);
70static bool is_securelevel_above(int);
71
72static int secmodel_extensions_system_cb(kauth_cred_t, kauth_action_t,
73    void *, void *, void *, void *, void *);
74static int secmodel_extensions_process_cb(kauth_cred_t, kauth_action_t,
75    void *, void *, void *, void *, void *);
76static int secmodel_extensions_network_cb(kauth_cred_t, kauth_action_t,
77    void *, void *, void *, void *, void *);
78static int secmodel_extensions_vnode_cb(kauth_cred_t, kauth_action_t,
79    void *, void *, void *, void *, void *);
80
81SYSCTL_SETUP(sysctl_security_extensions_setup,
82    "security extensions sysctl")
83{
84	const struct sysctlnode *rnode, *rnode2;
85
86	sysctl_createv(clog, 0, NULL, &rnode,
87		       CTLFLAG_PERMANENT,
88		       CTLTYPE_NODE, "models", NULL,
89		       NULL, 0, NULL, 0,
90		       CTL_SECURITY, CTL_CREATE, CTL_EOL);
91
92	/* Compatibility: security.models.bsd44 */
93	rnode2 = rnode;
94	sysctl_createv(clog, 0, &rnode2, &rnode2,
95		       CTLFLAG_PERMANENT,
96		       CTLTYPE_NODE, "bsd44", NULL,
97		       NULL, 0, NULL, 0,
98		       CTL_CREATE, CTL_EOL);
99
100        /* Compatibility: security.models.bsd44.curtain */
101	sysctl_createv(clog, 0, &rnode2, NULL,
102		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
103		       CTLTYPE_INT, "curtain",
104		       SYSCTL_DESCR("Curtain information about objects to "\
105		       		    "users not owning them."),
106		       sysctl_extensions_curtain_handler, 0, &curtain, 0,
107		       CTL_CREATE, CTL_EOL);
108
109	sysctl_createv(clog, 0, &rnode, &rnode,
110		       CTLFLAG_PERMANENT,
111		       CTLTYPE_NODE, "extensions", NULL,
112		       NULL, 0, NULL, 0,
113		       CTL_CREATE, CTL_EOL);
114
115	sysctl_createv(clog, 0, &rnode, NULL,
116		       CTLFLAG_PERMANENT,
117		       CTLTYPE_STRING, "name", NULL,
118		       NULL, 0, __UNCONST(SECMODEL_EXTENSIONS_NAME), 0,
119		       CTL_CREATE, CTL_EOL);
120
121	sysctl_createv(clog, 0, &rnode, NULL,
122		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
123		       CTLTYPE_INT, "usermount",
124		       SYSCTL_DESCR("Whether unprivileged users may mount "
125				    "filesystems"),
126		       sysctl_extensions_user_handler, 0, &dovfsusermount, 0,
127		       CTL_CREATE, CTL_EOL);
128
129	sysctl_createv(clog, 0, &rnode, NULL,
130		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
131		       CTLTYPE_INT, "curtain",
132		       SYSCTL_DESCR("Curtain information about objects to "\
133		       		    "users not owning them."),
134		       sysctl_extensions_curtain_handler, 0, &curtain, 0,
135		       CTL_CREATE, CTL_EOL);
136
137	sysctl_createv(clog, 0, &rnode, NULL,
138		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
139		       CTLTYPE_INT, "user_set_cpu_affinity",
140		       SYSCTL_DESCR("Whether unprivileged users may control "\
141		       		    "CPU affinity."),
142		       sysctl_extensions_user_handler, 0,
143		       &user_set_cpu_affinity, 0,
144		       CTL_CREATE, CTL_EOL);
145
146#ifdef PT_SETDBREGS
147	sysctl_createv(clog, 0, &rnode, NULL,
148		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
149		       CTLTYPE_INT, "user_set_dbregs",
150		       SYSCTL_DESCR("Whether unprivileged users may set "\
151		       		    "CPU Debug Registers."),
152		       sysctl_extensions_user_handler, 0,
153		       &user_set_dbregs, 0,
154		       CTL_CREATE, CTL_EOL);
155#endif
156
157	sysctl_createv(clog, 0, &rnode, NULL,
158		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
159		       CTLTYPE_INT, "hardlink_check_uid",
160		       SYSCTL_DESCR("Whether unprivileged users can hardlink "\
161			    "to files they don't own"),
162		       sysctl_extensions_user_handler, 0,
163		       &hardlink_check_uid, 0,
164		       CTL_CREATE, CTL_EOL);
165
166	sysctl_createv(clog, 0, &rnode, NULL,
167		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
168		       CTLTYPE_INT, "hardlink_check_gid",
169		       SYSCTL_DESCR("Whether unprivileged users can hardlink "\
170			    "to files that are not in their " \
171			    "group membership"),
172		       sysctl_extensions_user_handler, 0,
173		       &hardlink_check_gid, 0,
174		       CTL_CREATE, CTL_EOL);
175
176	/* Compatibility: vfs.generic.usermount */
177	sysctl_createv(clog, 0, NULL, NULL,
178		       CTLFLAG_PERMANENT,
179		       CTLTYPE_NODE, "generic",
180		       SYSCTL_DESCR("Non-specific vfs related information"),
181		       NULL, 0, NULL, 0,
182		       CTL_VFS, VFS_GENERIC, CTL_EOL);
183
184	sysctl_createv(clog, 0, NULL, NULL,
185		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
186		       CTLTYPE_INT, "usermount",
187		       SYSCTL_DESCR("Whether unprivileged users may mount "
188				    "filesystems"),
189		       sysctl_extensions_user_handler, 0, &dovfsusermount, 0,
190		       CTL_VFS, VFS_GENERIC, VFS_USERMOUNT, CTL_EOL);
191
192	/* Compatibility: security.curtain */
193	sysctl_createv(clog, 0, NULL, NULL,
194		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
195		       CTLTYPE_INT, "curtain",
196		       SYSCTL_DESCR("Curtain information about objects to "\
197		       		    "users not owning them."),
198		       sysctl_extensions_curtain_handler, 0, &curtain, 0,
199		       CTL_SECURITY, CTL_CREATE, CTL_EOL);
200}
201
202static int
203sysctl_extensions_curtain_handler(SYSCTLFN_ARGS)
204{
205	struct sysctlnode node;
206	int val, error;
207
208	val = *(int *)rnode->sysctl_data;
209
210	node = *rnode;
211	node.sysctl_data = &val;
212
213	error = sysctl_lookup(SYSCTLFN_CALL(&node));
214	if (error || newp == NULL)
215		return error;
216
217	/* shortcut */
218	if (val == *(int *)rnode->sysctl_data)
219		return 0;
220
221	/* curtain cannot be disabled when securelevel is above 0 */
222	if (val == 0 && is_securelevel_above(0)) {
223		return EPERM;
224	}
225
226	*(int *)rnode->sysctl_data = val;
227	return 0;
228}
229
230/*
231 * Generic sysctl extensions handler for user mount and set CPU affinity
232 * rights. Checks the following conditions:
233 * - setting value to 0 is always permitted (decrease user rights)
234 * - setting value != 0 is not permitted when securelevel is above 0 (increase
235 *   user rights).
236 */
237static int
238sysctl_extensions_user_handler(SYSCTLFN_ARGS)
239{
240	struct sysctlnode node;
241	int val, error;
242
243	val = *(int *)rnode->sysctl_data;
244
245	node = *rnode;
246	node.sysctl_data = &val;
247
248	error = sysctl_lookup(SYSCTLFN_CALL(&node));
249	if (error || newp == NULL)
250		return error;
251
252	/* shortcut */
253	if (val == *(int *)rnode->sysctl_data)
254		return 0;
255
256	/* we cannot grant more rights to users when securelevel is above 0 */
257	if (val != 0 && is_securelevel_above(0)) {
258		return EPERM;
259	}
260
261	*(int *)rnode->sysctl_data = val;
262	return 0;
263}
264
265/*
266 * Query secmodel_securelevel(9) to know whether securelevel is strictly
267 * above 'level' or not.
268 * Returns true if it is, false otherwise (when securelevel is absent or
269 * securelevel is at or below 'level').
270 */
271static bool
272is_securelevel_above(int level)
273{
274	bool above;
275	int error;
276
277	error = secmodel_eval("org.netbsd.secmodel.securelevel",
278	    "is-securelevel-above", KAUTH_ARG(level), &above);
279	if (error == 0 && above)
280		return true;
281	else
282		return false;
283}
284
285static void
286secmodel_extensions_init(void)
287{
288
289	curtain = 0;
290	user_set_cpu_affinity = 0;
291#ifdef PT_SETDBREGS
292	user_set_dbregs = 0;
293#endif
294}
295
296static void
297secmodel_extensions_start(void)
298{
299
300	l_system = kauth_listen_scope(KAUTH_SCOPE_SYSTEM,
301	    secmodel_extensions_system_cb, NULL);
302	l_process = kauth_listen_scope(KAUTH_SCOPE_PROCESS,
303	    secmodel_extensions_process_cb, NULL);
304	l_network = kauth_listen_scope(KAUTH_SCOPE_NETWORK,
305	    secmodel_extensions_network_cb, NULL);
306	l_vnode = kauth_listen_scope(KAUTH_SCOPE_VNODE,
307	    secmodel_extensions_vnode_cb, NULL);
308}
309
310static void
311secmodel_extensions_stop(void)
312{
313
314	kauth_unlisten_scope(l_system);
315	kauth_unlisten_scope(l_process);
316	kauth_unlisten_scope(l_network);
317	kauth_unlisten_scope(l_vnode);
318}
319
320static int
321extensions_modcmd(modcmd_t cmd, void *arg)
322{
323	int error = 0;
324
325	switch (cmd) {
326	case MODULE_CMD_INIT:
327		error = secmodel_register(&extensions_sm,
328		    SECMODEL_EXTENSIONS_ID, SECMODEL_EXTENSIONS_NAME,
329		    NULL, NULL, NULL);
330		if (error != 0)
331			printf("extensions_modcmd::init: secmodel_register "
332			    "returned %d\n", error);
333
334		secmodel_extensions_init();
335		secmodel_extensions_start();
336		break;
337
338	case MODULE_CMD_FINI:
339		secmodel_extensions_stop();
340
341		error = secmodel_deregister(extensions_sm);
342		if (error != 0)
343			printf("extensions_modcmd::fini: secmodel_deregister "
344			    "returned %d\n", error);
345
346		break;
347
348	case MODULE_CMD_AUTOUNLOAD:
349		error = EPERM;
350		break;
351
352	default:
353		error = ENOTTY;
354		break;
355	}
356
357	return (error);
358}
359
360static int
361secmodel_extensions_system_cb(kauth_cred_t cred, kauth_action_t action,
362    void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
363{
364	vnode_t *vp;
365	struct vattr va;
366	struct mount *mp;
367	u_long flags;
368	int result;
369	enum kauth_system_req req;
370	int error;
371
372	req = (enum kauth_system_req)(uintptr_t)arg0;
373	result = KAUTH_RESULT_DEFER;
374
375	switch (action) {
376	case KAUTH_SYSTEM_MOUNT:
377		if (dovfsusermount == 0)
378			break;
379		switch (req) {
380		case KAUTH_REQ_SYSTEM_MOUNT_NEW:
381			vp = (vnode_t *)arg1;
382			mp = vp->v_mount;
383			flags = (u_long)arg2;
384
385			/*
386			 * Ensure that the user owns the directory onto which
387			 * the mount is attempted.
388			 */
389			vn_lock(vp, LK_SHARED | LK_RETRY);
390			error = VOP_GETATTR(vp, &va, cred);
391			VOP_UNLOCK(vp);
392			if (error)
393				break;
394
395			if (va.va_uid != kauth_cred_geteuid(cred))
396				break;
397
398			error = usermount_common_policy(mp, flags);
399			if (error)
400				break;
401
402			result = KAUTH_RESULT_ALLOW;
403
404			break;
405
406		case KAUTH_REQ_SYSTEM_MOUNT_UNMOUNT:
407			mp = arg1;
408
409			/* Must own the mount. */
410			if (mp->mnt_stat.f_owner == kauth_cred_geteuid(cred))
411				result = KAUTH_RESULT_ALLOW;
412
413			break;
414
415		case KAUTH_REQ_SYSTEM_MOUNT_UPDATE:
416			mp = arg1;
417			flags = (u_long)arg2;
418
419			/* Must own the mount. */
420			if (mp->mnt_stat.f_owner == kauth_cred_geteuid(cred) &&
421				usermount_common_policy(mp, flags) == 0)
422				result = KAUTH_RESULT_ALLOW;
423
424			break;
425
426		default:
427			break;
428		}
429		break;
430
431	default:
432		break;
433	}
434
435	return (result);
436}
437
438static int
439secmodel_extensions_process_cb(kauth_cred_t cred, kauth_action_t action,
440    void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
441{
442	int result;
443	enum kauth_process_req req;
444
445	result = KAUTH_RESULT_DEFER;
446	req = (enum kauth_process_req)(uintptr_t)arg1;
447
448	switch (action) {
449	case KAUTH_PROCESS_CANSEE:
450		switch (req) {
451		case KAUTH_REQ_PROCESS_CANSEE_ARGS:
452		case KAUTH_REQ_PROCESS_CANSEE_ENTRY:
453		case KAUTH_REQ_PROCESS_CANSEE_OPENFILES:
454		case KAUTH_REQ_PROCESS_CANSEE_EPROC:
455			if (curtain != 0) {
456				struct proc *p = arg0;
457
458				/*
459				 * Only process' owner and root can see
460				 * through curtain
461				 */
462				if (!kauth_cred_uidmatch(cred, p->p_cred)) {
463					int error;
464					bool isroot = false;
465
466					error = secmodel_eval(
467					    "org.netbsd.secmodel.suser",
468					    "is-root", cred, &isroot);
469					if (error == 0 && !isroot)
470						result = KAUTH_RESULT_DENY;
471				}
472			}
473
474			break;
475
476		case KAUTH_REQ_PROCESS_CANSEE_KPTR:
477		default:
478			break;
479		}
480
481		break;
482
483	case KAUTH_PROCESS_SCHEDULER_SETAFFINITY:
484		if (user_set_cpu_affinity != 0) {
485			struct proc *p = arg0;
486
487			if (kauth_cred_uidmatch(cred, p->p_cred))
488				result = KAUTH_RESULT_ALLOW;
489		}
490		break;
491
492	default:
493		break;
494	}
495
496	return (result);
497}
498
499static int
500secmodel_extensions_network_cb(kauth_cred_t cred, kauth_action_t action,
501    void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
502{
503	int result;
504	enum kauth_network_req req;
505
506	result = KAUTH_RESULT_DEFER;
507	req = (enum kauth_network_req)(uintptr_t)arg0;
508
509	if (action != KAUTH_NETWORK_SOCKET ||
510	    req != KAUTH_REQ_NETWORK_SOCKET_CANSEE)
511		return result;
512
513	if (curtain != 0) {
514		struct socket *so = (struct socket *)arg1;
515
516		if (__predict_false(so == NULL || so->so_cred == NULL))
517			return KAUTH_RESULT_DENY;
518
519		if (!kauth_cred_uidmatch(cred, so->so_cred)) {
520			int error;
521			bool isroot = false;
522
523			error = secmodel_eval("org.netbsd.secmodel.suser",
524			    "is-root", cred, &isroot);
525			if (error == 0 && !isroot)
526				result = KAUTH_RESULT_DENY;
527		}
528	}
529
530	return (result);
531}
532
533static int
534secmodel_extensions_vnode_cb(kauth_cred_t cred, kauth_action_t action,
535    void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
536{
537	int error, isroot;
538	struct vattr va;
539
540	if ((action & KAUTH_VNODE_ADD_LINK) == 0)
541		return KAUTH_RESULT_DEFER;
542
543	error = VOP_GETATTR((vnode_t *)arg0, &va, cred);
544	if (error)
545		goto checkroot;
546
547	if (hardlink_check_uid && kauth_cred_geteuid(cred) != va.va_uid)
548		goto checkroot;
549
550	if (hardlink_check_gid && kauth_cred_groupmember(cred, va.va_gid) != 0)
551		goto checkroot;
552
553	return KAUTH_RESULT_DEFER;
554checkroot:
555	error = secmodel_eval("org.netbsd.secmodel.suser", "is-root",
556	    cred, &isroot);
557	if (error || !isroot)
558		return KAUTH_RESULT_DENY;
559
560	return KAUTH_RESULT_DEFER;
561}
562