xref: /freebsd/sys/security/mac_ddb/mac_ddb.c (revision 4e2121c10afc3d9273368eae776fe31d0c68ba6a)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2022 Klara Systems
5  *
6  * This software was developed by Mitchell Horne <mhorne@FreeBSD.org>
7  * under sponsorship from Juniper Networks and Klara Systems.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/param.h>
32 #include <sys/jail.h>
33 #include <sys/kdb.h>
34 #include <sys/module.h>
35 #include <sys/mount.h>
36 #include <sys/proc.h>
37 #include <sys/queue.h>
38 #include <sys/rman.h>
39 #include <sys/sysctl.h>
40 
41 #include <net/vnet.h>
42 
43 #include <ddb/ddb.h>
44 #include <ddb/db_command.h>
45 
46 #include <security/mac/mac_policy.h>
47 
48 /*
49  * This module provides a limited interface to the ddb(4) kernel debugger. The
50  * intent is to allow execution of useful debugging commands while disallowing
51  * the execution of commands which may be used to inspect/modify arbitrary
52  * system memory.
53  *
54  * Commands which are deterministic in their output or effect and that have
55  * been flagged with DB_CMD_MEMSAFE in their definition will be allowed.
56  *
57  * Other commands are valid within this context so long as there is some
58  * constraint placed on their input arguments. This applies to most 'show'
59  * commands which accept an arbitrary address. If the provided address can be
60  * validated as a real instance of the object (e.g. the 'show proc' address
61  * points to a real struct proc in the process list), then the command may be
62  * executed. This module defines several validation functions which are used to
63  * conditionally allow or block the execution of some commands. For these
64  * commands we define and apply the DB_CMD_VALIDATE flag.
65  *
66  * Any other commands not flagged with DM_CMD_MEMSAFE or DB_CMD_VALIDATE are
67  * considered unsafe for execution.
68  */
69 
70 #define	DB_CMD_VALIDATE		DB_MAC1
71 
72 typedef int db_validation_fn_t(db_expr_t addr, bool have_addr, db_expr_t count,
73     char *modif);
74 
75 static db_validation_fn_t	db_thread_valid;
76 static db_validation_fn_t	db_show_ffs_valid;
77 static db_validation_fn_t	db_show_prison_valid;
78 static db_validation_fn_t	db_show_proc_valid;
79 static db_validation_fn_t	db_show_rman_valid;
80 static db_validation_fn_t	db_show_vnet_valid;
81 
82 struct cmd_list_item {
83 	const char *name;
84 	db_validation_fn_t *validate_fn;
85 };
86 
87 /* List of top-level ddb(4) commands which are allowed by this policy. */
88 static const struct cmd_list_item command_list[] = {
89 	{ "thread",	db_thread_valid },
90 };
91 
92 /* List of ddb(4) 'show' commands which are allowed by this policy. */
93 static const struct cmd_list_item show_command_list[] = {
94 	{ "ffs",	db_show_ffs_valid },
95 	{ "prison",	db_show_prison_valid },
96 	{ "proc",	db_show_proc_valid },
97 	{ "rman",	db_show_rman_valid },
98 	{ "thread",	db_thread_valid },
99 	{ "vnet",	db_show_vnet_valid },
100 };
101 
102 static int
103 db_thread_valid(db_expr_t addr, bool have_addr, db_expr_t count, char *modif)
104 {
105 	struct thread *thr;
106 	lwpid_t tid;
107 
108 	/* Default will show the current proc. */
109 	if (!have_addr)
110 		return (0);
111 
112 	/* Validate the provided addr OR tid against the thread list. */
113 	tid = db_hex2dec(addr);
114 	for (thr = kdb_thr_first(); thr != NULL; thr = kdb_thr_next(thr)) {
115 		if ((void *)thr == (void *)addr || tid == thr->td_tid)
116 			return (0);
117 	}
118 
119 	return (EACCES);
120 }
121 
122 static int
123 db_show_ffs_valid(db_expr_t addr, bool have_addr, db_expr_t count, char *modif)
124 {
125 	struct mount *mp;
126 
127 	/* No addr will show all mounts. */
128 	if (!have_addr)
129 		return (0);
130 
131 	TAILQ_FOREACH(mp, &mountlist, mnt_list)
132 		if ((void *)mp == (void *)addr)
133 			return (0);
134 
135 	return (EACCES);
136 }
137 
138 static int
139 db_show_prison_valid(db_expr_t addr, bool have_addr, db_expr_t count,
140     char *modif)
141 {
142 	struct prison *pr;
143 	int pr_id;
144 
145 	if (!have_addr || addr == 0)
146 		return (0);
147 
148 	/* prison can match by pointer address or ID. */
149 	pr_id = (int)addr;
150 	TAILQ_FOREACH(pr, &allprison, pr_list)
151 		if (pr->pr_id == pr_id || (void *)pr == (void *)addr)
152 			return (0);
153 
154 	return (EACCES);
155 }
156 
157 static int
158 db_show_proc_valid(db_expr_t addr, bool have_addr, db_expr_t count,
159     char *modif)
160 {
161 	struct proc *p;
162 	int i;
163 
164 	/* Default will show the current proc. */
165 	if (!have_addr)
166 		return (0);
167 
168 	for (i = 0; i <= pidhash; i++) {
169 		LIST_FOREACH(p, &pidhashtbl[i], p_hash) {
170 			if ((void *)p == (void *)addr)
171 				return (0);
172 		}
173 	}
174 
175 	return (EACCES);
176 }
177 
178 static int
179 db_show_rman_valid(db_expr_t addr, bool have_addr, db_expr_t count, char *modif)
180 {
181 	struct rman *rm;
182 
183 	TAILQ_FOREACH(rm, &rman_head, rm_link) {
184 		if ((void *)rm == (void *)rm)
185 			return (0);
186 	}
187 
188 	return (EACCES);
189 }
190 
191 static int
192 db_show_vnet_valid(db_expr_t addr, bool have_addr, db_expr_t count, char *modif)
193 {
194 	VNET_ITERATOR_DECL(vnet);
195 
196 	if (!have_addr)
197 		return (0);
198 
199 	VNET_FOREACH(vnet) {
200 		if ((void *)vnet == (void *)addr)
201 			return (0);
202 	}
203 
204 	return (EACCES);
205 }
206 
207 static int
208 command_match(struct db_command *cmd, struct cmd_list_item item)
209 {
210 	db_validation_fn_t *vfn;
211 	int n;
212 
213 	n = strcmp(cmd->name, item.name);
214 	if (n != 0)
215 		return (n);
216 
217 	/* Got an exact match. Update the command struct */
218 	vfn = item.validate_fn;
219 	if (vfn != NULL) {
220 		cmd->flag |= DB_CMD_VALIDATE;
221 		cmd->mac_priv = vfn;
222 	}
223 	return (0);
224 }
225 
226 static void
227 mac_ddb_init(struct mac_policy_conf *conf)
228 {
229 	struct db_command *cmd, *prev;
230 	int i, n;
231 
232 	/* The command lists are sorted lexographically, as are our arrays. */
233 
234 	/* Register basic commands. */
235 	for (i = 0, cmd = prev = NULL; i < nitems(command_list); i++) {
236 		LIST_FOREACH_FROM(cmd, &db_cmd_table, next) {
237 			n = command_match(cmd, command_list[i]);
238 			if (n == 0) {
239 				/* Got an exact match. */
240 				prev = cmd;
241 				break;
242 			} else if (n > 0) {
243 				/* Desired command is not registered. */
244 				break;
245 			}
246 		}
247 
248 		/* Next search begins at the previous match. */
249 		cmd = prev;
250 	}
251 
252 	/* Register 'show' commands which require validation. */
253 	for (i = 0, cmd = prev = NULL; i < nitems(show_command_list); i++) {
254 		LIST_FOREACH_FROM(cmd, &db_show_table, next) {
255 			n = command_match(cmd, show_command_list[i]);
256 			if (n == 0) {
257 				/* Got an exact match. */
258 				prev = cmd;
259 				break;
260 			} else if (n > 0) {
261 				/* Desired command is not registered. */
262 				break;
263 			}
264 		}
265 
266 		/* Next search begins at the previous match. */
267 		cmd = prev;
268 	}
269 
270 #ifdef INVARIANTS
271 	/* Verify the lists are sorted correctly. */
272 	const char *a, *b;
273 
274 	for (i = 0; i < nitems(command_list) - 1; i++) {
275 		a = command_list[i].name;
276 		b = command_list[i + 1].name;
277 		if (strcmp(a, b) > 0)
278 			panic("%s: command_list[] not alphabetical: %s,%s",
279 			    __func__, a, b);
280 	}
281 	for (i = 0; i < nitems(show_command_list) - 1; i++) {
282 		a = show_command_list[i].name;
283 		b = show_command_list[i + 1].name;
284 		if (strcmp(a, b) > 0)
285 			panic("%s: show_command_list[] not alphabetical: %s,%s",
286 			    __func__, a, b);
287 	}
288 #endif
289 }
290 
291 static int
292 mac_ddb_command_register(struct db_command_table *table,
293     struct db_command *cmd)
294 {
295 	int i, n;
296 
297 	if ((cmd->flag & DB_CMD_MEMSAFE) != 0)
298 		return (0);
299 
300 	/* For other commands, search the allow-lists. */
301 	if (table == &db_show_table) {
302 		for (i = 0; i < nitems(show_command_list); i++) {
303 			n = command_match(cmd, show_command_list[i]);
304 			if (n == 0)
305 				/* Got an exact match. */
306 				return (0);
307 			else if (n > 0)
308 				/* Command is not in the policy list. */
309 				break;
310 		}
311 	} else if (table == &db_cmd_table) {
312 		for (i = 0; i < nitems(command_list); i++) {
313 			n = command_match(cmd, command_list[i]);
314 			if (n == 0)
315 				/* Got an exact match. */
316 				return (0);
317 			else if (n > 0)
318 				/* Command is not in the policy list. */
319 				break;
320 		}
321 	}
322 
323 	/* The command will not be registered. */
324 	return (EACCES);
325 }
326 
327 static int
328 mac_ddb_command_exec(struct db_command *cmd, db_expr_t addr,
329     bool have_addr, db_expr_t count, char *modif)
330 {
331 	db_validation_fn_t *vfn = cmd->mac_priv;
332 
333 	/* Validate the command and args based on policy. */
334 	if ((cmd->flag & DB_CMD_VALIDATE) != 0) {
335 		MPASS(vfn != NULL);
336 		if (vfn(addr, have_addr, count, modif) == 0)
337 			return (0);
338 	} else if ((cmd->flag & DB_CMD_MEMSAFE) != 0)
339 		return (0);
340 
341 	return (EACCES);
342 }
343 
344 static int
345 mac_ddb_check_backend(struct kdb_dbbe *be)
346 {
347 
348 	/* Only allow DDB backend to execute. */
349 	if (strcmp(be->dbbe_name, "ddb") == 0)
350 		return (0);
351 
352 	return (EACCES);
353 }
354 
355 /*
356  * Register functions with MAC Framework policy entry points.
357  */
358 static struct mac_policy_ops mac_ddb_ops =
359 {
360 	.mpo_init = mac_ddb_init,
361 
362 	.mpo_ddb_command_register = mac_ddb_command_register,
363 	.mpo_ddb_command_exec = mac_ddb_command_exec,
364 
365 	.mpo_kdb_check_backend = mac_ddb_check_backend,
366 };
367 MAC_POLICY_SET(&mac_ddb_ops, mac_ddb, "MAC/DDB", 0, NULL);
368