1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
14 */
15
16 /*
17 * Two privilege sets are maintained. One which is the minimal privileges
18 * necessary to run bhyve - 'bhyve_priv_min' - and one which is the maximum
19 * privileges required - 'bhyve_priv_max'. This second set starts off identical
20 * to the first, and is added to by modules during initialisation depending on
21 * their requirements and their configuration.
22 * Once all modules are initialised, and before the VM is set running, the
23 * privileges are locked by setting the 'Permitted' privilege set from the
24 * contents of the maximum set, which is now the upper bound, and dropping back
25 * to the minimum set.
26 *
27 * Privileges are process wide, and this framework does not support threaded
28 * access. The ID of the thread that first initialises privileges is recorded,
29 * and all subsequent privilege operations must be done by the same thread.
30 */
31
32 #include <err.h>
33 #include <pthread.h>
34 #include <priv.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <upanic.h>
38 #include <sys/debug.h>
39 #include "config.h"
40 #include "debug.h"
41 #include "privileges.h"
42
43 static pthread_t priv_thread;
44 static priv_set_t *bhyve_priv_init;
45 static priv_set_t *bhyve_priv_min;
46 static priv_set_t *bhyve_priv_max;
47 static bool priv_debug = false;
48
49 #define DPRINTF(params) \
50 if (priv_debug) do { PRINTLN params; fflush(stdout); } while (0)
51
52 static void
illumos_priv_printset(const char * tag,priv_set_t * set)53 illumos_priv_printset(const char *tag, priv_set_t *set)
54 {
55 char *s;
56
57 s = priv_set_to_str(set, ',', PRIV_STR_LIT);
58 if (s == NULL) {
59 warn("priv_set_to_str(%s) failed", tag);
60 return;
61 }
62 DPRINTF((" + %s: %s", tag, s));
63 free(s);
64 }
65
66 void
illumos_priv_reduce(void)67 illumos_priv_reduce(void)
68 {
69 VERIFY3S(pthread_equal(pthread_self(), priv_thread), !=, 0);
70 DPRINTF((" + Reducing privileges to minimum"));
71 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, bhyve_priv_min) != 0)
72 err(4, "failed to reduce privileges");
73 }
74
75 static void
illumos_priv_add_set(priv_set_t * set,const char * priv,const char * src)76 illumos_priv_add_set(priv_set_t *set, const char *priv, const char *src)
77 {
78 VERIFY3S(pthread_equal(pthread_self(), priv_thread), !=, 0);
79 DPRINTF((" + Adding privilege %s (%s)", priv, src));
80 if (!priv_ismember(bhyve_priv_init, priv))
81 errx(4, "Privilege %s (for %s) not in initial set", priv, src);
82 if (priv_addset(set, priv) != 0)
83 err(4, "Failed to add %s privilege for %s", priv, src);
84 }
85 void
illumos_priv_add(const char * priv,const char * src)86 illumos_priv_add(const char *priv, const char *src)
87 {
88 illumos_priv_add_set(bhyve_priv_max, priv, src);
89 }
90
91 void
illumos_priv_add_min(const char * priv,const char * src)92 illumos_priv_add_min(const char *priv, const char *src)
93 {
94 illumos_priv_add_set(bhyve_priv_min, priv, src);
95 illumos_priv_add_set(bhyve_priv_max, priv, src);
96 }
97
98 void
illumos_priv_init(void)99 illumos_priv_init(void)
100 {
101 priv_debug = get_config_bool_default("privileges.debug", false);
102
103 DPRINTF((" + Initialising privileges."));
104
105 if (priv_debug)
106 (void) setpflags(PRIV_DEBUG, 1);
107
108 priv_thread = pthread_self();
109
110 if ((bhyve_priv_init = priv_allocset()) == NULL)
111 err(4, "failed to allocate memory for initial priv set");
112 if ((bhyve_priv_min = priv_allocset()) == NULL)
113 err(4, "failed to allocate memory for minimum priv set");
114 if ((bhyve_priv_max = priv_allocset()) == NULL)
115 err(4, "failed to allocate memory for maximum priv set");
116
117 if (getppriv(PRIV_EFFECTIVE, bhyve_priv_init) != 0)
118 err(4, "failed to fetch current privileges");
119
120 if (priv_debug)
121 illumos_priv_printset("initial", bhyve_priv_init);
122
123 /*
124 * file_read is left in the minimum set to allow for lazy library
125 * loading.
126 */
127 priv_basicset(bhyve_priv_min);
128 VERIFY0(priv_delset(bhyve_priv_min, PRIV_FILE_LINK_ANY));
129 VERIFY0(priv_delset(bhyve_priv_min, PRIV_FILE_WRITE));
130 VERIFY0(priv_delset(bhyve_priv_min, PRIV_NET_ACCESS));
131 VERIFY0(priv_delset(bhyve_priv_min, PRIV_PROC_EXEC));
132 VERIFY0(priv_delset(bhyve_priv_min, PRIV_PROC_FORK));
133 VERIFY0(priv_delset(bhyve_priv_min, PRIV_PROC_INFO));
134 VERIFY0(priv_delset(bhyve_priv_min, PRIV_PROC_SECFLAGS));
135 VERIFY0(priv_delset(bhyve_priv_min, PRIV_PROC_SESSION));
136
137 priv_intersect(bhyve_priv_init, bhyve_priv_min);
138 priv_copyset(bhyve_priv_min, bhyve_priv_max);
139
140 /*
141 * These are privileges that we know will always be needed.
142 * Other privileges may be added by modules as necessary during
143 * initialisation.
144 */
145
146 illumos_priv_add(PRIV_FILE_WRITE, "init");
147
148 /*
149 * bhyve can work without proc_clock_highres so don't enforce that
150 * it is present.
151 */
152 if (priv_ismember(bhyve_priv_init, PRIV_PROC_CLOCK_HIGHRES))
153 illumos_priv_add_min(PRIV_PROC_CLOCK_HIGHRES, "init");
154 else
155 warnx("The 'proc_clock_highres' privilege is not available");
156 }
157
158 void
illumos_priv_lock(void)159 illumos_priv_lock(void)
160 {
161 VERIFY3S(pthread_equal(pthread_self(), priv_thread), !=, 0);
162
163 if (bhyve_priv_init == NULL) {
164 const char *msg = "attempted to re-lock privileges";
165 upanic(msg, strlen(msg));
166 }
167
168 priv_intersect(bhyve_priv_init, bhyve_priv_max);
169
170 if (priv_debug) {
171 DPRINTF((" + Locking privileges"));
172
173 illumos_priv_printset("min", bhyve_priv_min);
174 illumos_priv_printset("max", bhyve_priv_max);
175 }
176
177 if (setppriv(PRIV_SET, PRIV_PERMITTED, bhyve_priv_max) != 0) {
178 const char *fail = "failed to reduce permitted privileges";
179 upanic(fail, strlen(fail));
180 }
181
182 if (setppriv(PRIV_SET, PRIV_LIMIT, bhyve_priv_max) != 0) {
183 const char *fail = "failed to reduce limit privileges";
184 upanic(fail, strlen(fail));
185 }
186
187 illumos_priv_reduce();
188
189 priv_freeset(bhyve_priv_init);
190 bhyve_priv_init = NULL;
191 }
192