Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/security/mac_grantbylabel/mac_grantbylabel.c
39478 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2018-2023, Juniper Networks, Inc.
5
* All rights reserved.
6
*
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
9
* are met:
10
* 1. Redistributions of source code must retain the above copyright
11
* notice, this list of conditions and the following disclaimer.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
*
16
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26
* SUCH DAMAGE.
27
*/
28
29
#include <sys/cdefs.h>
30
31
#include "opt_mac.h"
32
33
#include <sys/param.h>
34
#include <sys/capsicum.h>
35
#include <sys/proc.h>
36
#include <sys/vnode.h>
37
#include <sys/kernel.h>
38
#include <sys/module.h>
39
#include <sys/mac.h>
40
#include <sys/namei.h>
41
#include <sys/priv.h>
42
#include <sys/imgact.h>
43
#include <sys/sysctl.h>
44
#include <sys/syslog.h>
45
#include <security/mac/mac_policy.h>
46
47
#include "mac_grantbylabel.h"
48
#include <security/mac_veriexec/mac_veriexec_internal.h>
49
50
#define MAC_GRANTBYLABEL_FULLNAME "MAC/grantbylabel"
51
52
SYSCTL_NODE(_security_mac, OID_AUTO, grantbylabel, CTLFLAG_RW, 0,
53
"MAC/grantbylabel policy controls");
54
55
#ifdef MAC_DEBUG
56
static int mac_grantbylabel_debug;
57
58
SYSCTL_INT(_security_mac_grantbylabel, OID_AUTO, debug, CTLFLAG_RW,
59
&mac_grantbylabel_debug, 0, "Debug mac_grantbylabel");
60
61
#define GRANTBYLABEL_DEBUG(n, x) if (mac_grantbylabel_debug >= (n)) printf x
62
63
#define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...) \
64
do { \
65
GRANTBYLABEL_DEBUG((_lvl), (MAC_GRANTBYLABEL_FULLNAME ": " \
66
_fmt "\n", ##__VA_ARGS__)); \
67
} while(0)
68
#else
69
#define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...)
70
#endif
71
72
73
/* label token prefix */
74
#define GBL_PREFIX "gbl/"
75
76
static int mac_grantbylabel_slot;
77
78
#define SLOT(l) \
79
mac_label_get((l), mac_grantbylabel_slot)
80
#define SLOT_SET(l, v) \
81
mac_label_set((l), mac_grantbylabel_slot, (v))
82
83
84
/**
85
* @brief parse label into bitmask
86
*
87
* We are only interested in tokens prefixed by GBL_PREFIX ("gbl/").
88
*
89
* @return 32bit mask
90
*/
91
static gbl_label_t
92
gbl_parse_label(const char *label)
93
{
94
gbl_label_t gbl;
95
char *cp;
96
97
if (!(label && *label))
98
return GBL_EMPTY;
99
gbl = 0;
100
for (cp = strstr(label, GBL_PREFIX); cp; cp = strstr(cp, GBL_PREFIX)) {
101
/* check we didn't find "fugbl/" */
102
if (cp > label && cp[-1] != ',') {
103
cp += sizeof(GBL_PREFIX);
104
continue;
105
}
106
cp += sizeof(GBL_PREFIX) - 1;
107
switch (*cp) {
108
case 'b':
109
if (strncmp(cp, "bind", 4) == 0)
110
gbl |= GBL_BIND;
111
break;
112
case 'd':
113
if (strncmp(cp, "daemon", 6) == 0)
114
gbl |= (GBL_BIND|GBL_IPC|GBL_NET|GBL_PROC|
115
GBL_SYSCTL|GBL_VACCESS);
116
break;
117
case 'i':
118
if (strncmp(cp, "ipc", 3) == 0)
119
gbl |= GBL_IPC;
120
break;
121
case 'k':
122
if (strncmp(cp, "kmem", 4) == 0)
123
gbl |= GBL_KMEM;
124
break;
125
case 'n':
126
if (strncmp(cp, "net", 3) == 0)
127
gbl |= GBL_NET;
128
break;
129
case 'p':
130
if (strncmp(cp, "proc", 4) == 0)
131
gbl |= GBL_PROC;
132
break;
133
case 'r':
134
if (strncmp(cp, "rtsock", 6) == 0)
135
gbl |= GBL_RTSOCK;
136
break;
137
case 's':
138
if (strncmp(cp, "sysctl", 6) == 0)
139
gbl |= GBL_SYSCTL;
140
break;
141
case 'v':
142
if (strncmp(cp, "vaccess", 7) == 0)
143
gbl |= GBL_VACCESS;
144
else if (strncmp(cp, "veriexec", 8) == 0)
145
gbl |= GBL_VERIEXEC;
146
break;
147
default: /* ignore unknown? */
148
MAC_GRANTBYLABEL_DBG(1,
149
"ignoring unknown token at %s/%s",
150
GBL_PREFIX, cp);
151
break;
152
}
153
}
154
155
return gbl;
156
}
157
158
159
/**
160
* @brief get the v_label for a vnode
161
*
162
* Lookup the label if not already set in v_label
163
*
164
* @return 32bit mask or 0 on error
165
*/
166
static gbl_label_t
167
gbl_get_vlabel(struct vnode *vp, struct ucred *cred)
168
{
169
struct vattr va;
170
const char *label;
171
gbl_label_t gbl;
172
int error;
173
174
gbl = SLOT(vp->v_label);
175
if (gbl == 0) {
176
error = VOP_GETATTR(vp, &va, cred);
177
if (error == 0) {
178
label = mac_veriexec_metadata_get_file_label(va.va_fsid,
179
va.va_fileid, va.va_gen, FALSE);
180
if (label) {
181
MAC_GRANTBYLABEL_DBG(1,
182
"label=%s dev=%ju, file %ju.%lu",
183
label,
184
(uintmax_t)va.va_fsid,
185
(uintmax_t)va.va_fileid,
186
va.va_gen);
187
gbl = gbl_parse_label(label);
188
} else {
189
gbl = GBL_EMPTY;
190
MAC_GRANTBYLABEL_DBG(2, "no label dev=%ju, file %ju.%lu",
191
(uintmax_t)va.va_fsid,
192
(uintmax_t)va.va_fileid,
193
va.va_gen);
194
}
195
}
196
}
197
return gbl;
198
}
199
200
201
/**
202
* @brief grant priv if warranted
203
*
204
* If the cred is root, we have nothing to do.
205
* Otherwise see if the current process has a label
206
* that grants it the requested priv.
207
*/
208
static int
209
mac_grantbylabel_priv_grant(struct ucred *cred, int priv)
210
{
211
gbl_label_t label;
212
int rc;
213
214
rc = EPERM; /* default response */
215
216
if ((curproc->p_flag & (P_KPROC|P_SYSTEM)))
217
return rc; /* not interested */
218
219
switch (priv) {
220
case PRIV_PROC_MEM_WRITE:
221
case PRIV_KMEM_READ:
222
case PRIV_KMEM_WRITE:
223
break;
224
case PRIV_VERIEXEC_DIRECT:
225
case PRIV_VERIEXEC_NOVERIFY:
226
/* XXX might want to skip in FIPS mode */
227
break;
228
default:
229
if (cred->cr_uid == 0)
230
return rc; /* not interested */
231
break;
232
}
233
234
label = (gbl_label_t)(SLOT(curproc->p_textvp->v_label) |
235
SLOT(curproc->p_label));
236
237
/*
238
* We look at the extra privs granted
239
* via process label.
240
*/
241
switch (priv) {
242
case PRIV_IPC_READ:
243
case PRIV_IPC_WRITE:
244
if (label & GBL_IPC)
245
rc = 0;
246
break;
247
case PRIV_PROC_MEM_WRITE:
248
case PRIV_KMEM_READ:
249
case PRIV_KMEM_WRITE:
250
if (label & GBL_KMEM)
251
rc = 0;
252
break;
253
case PRIV_NETINET_BINDANY:
254
case PRIV_NETINET_RESERVEDPORT: /* socket bind low port */
255
case PRIV_NETINET_REUSEPORT:
256
if (label & GBL_BIND)
257
rc = 0;
258
break;
259
case PRIV_NETINET_ADDRCTRL6:
260
case PRIV_NET_LAGG:
261
case PRIV_NET_SETIFFIB:
262
case PRIV_NET_SETIFVNET:
263
case PRIV_NETINET_SETHDROPTS:
264
case PRIV_NET_VXLAN:
265
case PRIV_NETINET_GETCRED:
266
case PRIV_NETINET_IPSEC:
267
case PRIV_NETINET_RAW:
268
if (label & GBL_NET)
269
rc = 0;
270
break;
271
case PRIV_NETINET_MROUTE:
272
case PRIV_NET_ROUTE:
273
if (label & GBL_RTSOCK)
274
rc = 0;
275
break;
276
case PRIV_PROC_LIMIT:
277
case PRIV_PROC_SETRLIMIT:
278
if (label & GBL_PROC)
279
rc = 0;
280
break;
281
case PRIV_SYSCTL_WRITE:
282
if (label & GBL_SYSCTL)
283
rc = 0;
284
break;
285
case PRIV_VFS_READ:
286
case PRIV_VFS_WRITE:
287
if (label & GBL_KMEM)
288
rc = 0;
289
/* FALLTHROUGH */
290
case PRIV_VFS_ADMIN:
291
case PRIV_VFS_BLOCKRESERVE:
292
case PRIV_VFS_CHOWN:
293
case PRIV_VFS_EXEC: /* vaccess file and accmode & VEXEC */
294
case PRIV_VFS_GENERATION:
295
case PRIV_VFS_LOOKUP: /* vaccess DIR */
296
if (label & GBL_VACCESS)
297
rc = 0;
298
break;
299
case PRIV_VERIEXEC_DIRECT:
300
/*
301
* We are here because we are attempting to direct exec
302
* something with the 'indirect' flag set.
303
* We need to check parent label for this one.
304
*/
305
PROC_LOCK(curproc);
306
label = (gbl_label_t)SLOT(curproc->p_pptr->p_textvp->v_label);
307
if (label & GBL_VERIEXEC) {
308
rc = 0;
309
/*
310
* Of course the only reason to be running an
311
* interpreter this way is to bypass O_VERIFY
312
* so we can run unsigned script.
313
* We set GBL_VERIEXEC on p_label for
314
* PRIV_VERIEXEC_NOVERIFY below
315
*/
316
SLOT_SET(curproc->p_label, GBL_VERIEXEC);
317
}
318
PROC_UNLOCK(curproc);
319
break;
320
case PRIV_VERIEXEC_NOVERIFY:
321
/* we look at p_label! see above */
322
label = (gbl_label_t)SLOT(curproc->p_label);
323
if (label & GBL_VERIEXEC)
324
rc = 0;
325
break;
326
default:
327
break;
328
}
329
MAC_GRANTBYLABEL_DBG(rc ? 1 : 2,
330
"pid=%d priv=%d, label=%#o rc=%d",
331
curproc->p_pid, priv, label, rc);
332
333
return rc;
334
}
335
336
337
/*
338
* If proc->p_textvp does not yet have a label,
339
* fetch file info from mac_veriexec
340
* and set label (if any) else set.
341
* If there is no label set it to GBL_EMPTY.
342
*/
343
static int
344
mac_grantbylabel_proc_check_resource(struct ucred *cred,
345
struct proc *proc)
346
{
347
gbl_label_t gbl;
348
349
if (!SLOT(proc->p_textvp->v_label)) {
350
gbl = gbl_get_vlabel(proc->p_textvp, cred);
351
if (gbl == 0)
352
gbl = GBL_EMPTY;
353
SLOT_SET(proc->p_textvp->v_label, gbl);
354
}
355
return 0;
356
}
357
358
static int
359
mac_grantbylabel_syscall(struct thread *td, int call, void *arg)
360
{
361
cap_rights_t rights;
362
struct mac_grantbylabel_fetch_gbl_args gbl_args;
363
struct file *fp;
364
struct proc *proc;
365
int error;
366
int proc_locked;
367
368
switch (call) {
369
case MAC_GRANTBYLABEL_FETCH_GBL:
370
case MAC_GRANTBYLABEL_FETCH_PID_GBL:
371
error = copyin(arg, &gbl_args, sizeof(gbl_args));
372
if (error)
373
return error;
374
gbl_args.gbl = 0;
375
break;
376
default:
377
return EOPNOTSUPP;
378
break;
379
}
380
proc_locked = 0;
381
switch (call) {
382
case MAC_GRANTBYLABEL_FETCH_GBL:
383
error = getvnode(td, gbl_args.u.fd,
384
cap_rights_init(&rights), &fp);
385
if (error)
386
return (error);
387
388
if (fp->f_type != DTYPE_VNODE) {
389
error = EINVAL;
390
goto cleanup_file;
391
}
392
393
vn_lock(fp->f_vnode, LK_SHARED | LK_RETRY);
394
gbl_args.gbl = gbl_get_vlabel(fp->f_vnode, td->td_ucred);
395
if (gbl_args.gbl == 0)
396
error = EOPNOTSUPP;
397
else
398
error = 0;
399
VOP_UNLOCK(fp->f_vnode);
400
cleanup_file:
401
fdrop(fp, td);
402
break;
403
case MAC_GRANTBYLABEL_FETCH_PID_GBL:
404
error = 0;
405
if (gbl_args.u.pid == 0
406
|| gbl_args.u.pid == curproc->p_pid) {
407
proc = curproc;
408
} else {
409
proc = pfind(gbl_args.u.pid);
410
if (proc == NULL)
411
return (EINVAL);
412
else if (proc->p_textvp == NULL) {
413
PROC_UNLOCK(proc);
414
return (EINVAL);
415
}
416
proc_locked = 1;
417
}
418
gbl_args.gbl = (SLOT(proc->p_textvp->v_label) |
419
SLOT(proc->p_label));
420
if (proc_locked)
421
PROC_UNLOCK(proc);
422
break;
423
}
424
if (error == 0) {
425
error = copyout(&gbl_args, arg, sizeof(gbl_args));
426
}
427
return error;
428
}
429
430
431
static void
432
mac_grantbylabel_proc_init_label(struct label *label)
433
{
434
435
SLOT_SET(label, 0); /* not yet set! */
436
}
437
438
static void
439
mac_grantbylabel_vnode_init_label(struct label *label)
440
{
441
442
SLOT_SET(label, 0); /* not yet set! */
443
}
444
445
/**
446
* @brief set v_label if needed
447
*/
448
static int
449
mac_grantbylabel_vnode_check_exec(struct ucred *cred __unused,
450
struct vnode *vp __unused, struct label *label __unused,
451
struct image_params *imgp, struct label *execlabel __unused)
452
{
453
gbl_label_t gbl;
454
455
gbl = SLOT(vp->v_label);
456
if (gbl == 0) {
457
gbl = gbl_get_vlabel(vp, cred);
458
if (gbl == 0)
459
gbl = GBL_EMPTY;
460
MAC_GRANTBYLABEL_DBG(1, "vnode_check_exec label=%#o", gbl);
461
SLOT_SET(vp->v_label, gbl);
462
}
463
return 0;
464
}
465
466
static void
467
mac_grantbylabel_copy_label(struct label *src, struct label *dest)
468
{
469
SLOT_SET(dest, SLOT(src));
470
}
471
472
/**
473
* @brief if interpreting copy script v_label to proc p_label
474
*/
475
static int
476
mac_grantbylabel_vnode_execve_will_transition(struct ucred *old,
477
struct vnode *vp, struct label *vplabel,
478
struct label *interpvplabel, struct image_params *imgp,
479
struct label *execlabel)
480
{
481
gbl_label_t gbl;
482
483
if (imgp->interpreted) {
484
gbl = SLOT(interpvplabel);
485
if (gbl) {
486
SLOT_SET(imgp->proc->p_label, gbl);
487
}
488
MAC_GRANTBYLABEL_DBG(1, "execve_will_transition label=%#o", gbl);
489
}
490
return 0;
491
}
492
493
494
static struct mac_policy_ops mac_grantbylabel_ops =
495
{
496
.mpo_proc_check_resource = mac_grantbylabel_proc_check_resource,
497
.mpo_priv_grant = mac_grantbylabel_priv_grant,
498
.mpo_syscall = mac_grantbylabel_syscall,
499
.mpo_proc_init_label = mac_grantbylabel_proc_init_label,
500
.mpo_vnode_check_exec = mac_grantbylabel_vnode_check_exec,
501
.mpo_vnode_copy_label = mac_grantbylabel_copy_label,
502
.mpo_vnode_execve_will_transition = mac_grantbylabel_vnode_execve_will_transition,
503
.mpo_vnode_init_label = mac_grantbylabel_vnode_init_label,
504
};
505
506
MAC_POLICY_SET(&mac_grantbylabel_ops, mac_grantbylabel,
507
MAC_GRANTBYLABEL_FULLNAME,
508
MPC_LOADTIME_FLAG_NOTLATE, &mac_grantbylabel_slot);
509
MODULE_VERSION(mac_grantbylabel, 1);
510
MODULE_DEPEND(mac_grantbylabel, mac_veriexec, MAC_VERIEXEC_VERSION,
511
MAC_VERIEXEC_VERSION, MAC_VERIEXEC_VERSION);
512
513