1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2013, 2018 The FreeBSD Foundation
5  * All rights reserved.
6  *
7  * This software was developed by Pawel Jakub Dawidek under sponsorship from
8  * the FreeBSD Foundation.
9  *
10  * Portions of this software were developed by Mark Johnston
11  * under sponsorship from the FreeBSD Foundation.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 #include <sys/param.h>
39 #include <sys/cnv.h>
40 #include <sys/dnv.h>
41 #include <sys/nv.h>
42 #include <sys/sysctl.h>
43 
44 #include <assert.h>
45 #include <errno.h>
46 #include <stdlib.h>
47 #include <string.h>
48 
49 #include <libcasper.h>
50 #include <libcasper_service.h>
51 
52 #include "cap_sysctl.h"
53 
54 /*
55  * Limit interface.
56  */
57 
58 struct cap_sysctl_limit {
59 	cap_channel_t *chan;
60 	nvlist_t *nv;
61 };
62 
63 cap_sysctl_limit_t *
64 cap_sysctl_limit_init(cap_channel_t *chan)
65 {
66 	cap_sysctl_limit_t *limit;
67 	int error;
68 
69 	limit = malloc(sizeof(*limit));
70 	if (limit != NULL) {
71 		limit->chan = chan;
72 		limit->nv = nvlist_create(NV_FLAG_NO_UNIQUE);
73 		if (limit->nv == NULL) {
74 			error = errno;
75 			free(limit);
76 			limit = NULL;
77 			errno = error;
78 		}
79 	}
80 	return (limit);
81 }
82 
83 cap_sysctl_limit_t *
84 cap_sysctl_limit_name(cap_sysctl_limit_t *limit, const char *name, int flags)
85 {
86 	nvlist_t *lnv;
87 	size_t mibsz;
88 	int error, mib[CTL_MAXNAME];
89 
90 	lnv = nvlist_create(0);
91 	if (lnv == NULL) {
92 		error = errno;
93 		if (limit->nv != NULL)
94 			nvlist_destroy(limit->nv);
95 		free(limit);
96 		errno = error;
97 		return (NULL);
98 	}
99 	nvlist_add_string(lnv, "name", name);
100 	nvlist_add_number(lnv, "operation", flags);
101 
102 	mibsz = nitems(mib);
103 	error = cap_sysctlnametomib(limit->chan, name, mib, &mibsz);
104 	if (error == 0)
105 		nvlist_add_binary(lnv, "mib", mib, mibsz * sizeof(int));
106 
107 	nvlist_move_nvlist(limit->nv, "limit", lnv);
108 	return (limit);
109 }
110 
111 cap_sysctl_limit_t *
112 cap_sysctl_limit_mib(cap_sysctl_limit_t *limit, const int *mibp, u_int miblen,
113     int flags)
114 {
115 	nvlist_t *lnv;
116 	int error;
117 
118 	lnv = nvlist_create(0);
119 	if (lnv == NULL) {
120 		error = errno;
121 		if (limit->nv != NULL)
122 			nvlist_destroy(limit->nv);
123 		free(limit);
124 		errno = error;
125 		return (NULL);
126 	}
127 	nvlist_add_binary(lnv, "mib", mibp, miblen * sizeof(int));
128 	nvlist_add_number(lnv, "operation", flags);
129 	nvlist_add_nvlist(limit->nv, "limit", lnv);
130 	return (limit);
131 }
132 
133 int
134 cap_sysctl_limit(cap_sysctl_limit_t *limit)
135 {
136 	cap_channel_t *chan;
137 	nvlist_t *lnv;
138 
139 	chan = limit->chan;
140 	lnv = limit->nv;
141 	free(limit);
142 
143 	/* cap_limit_set(3) will always free the nvlist. */
144 	return (cap_limit_set(chan, lnv));
145 }
146 
147 /*
148  * Service interface.
149  */
150 
151 static int
152 do_sysctl(cap_channel_t *chan, nvlist_t *nvl, void *oldp, size_t *oldlenp,
153     const void *newp, size_t newlen)
154 {
155 	const uint8_t *retoldp;
156 	size_t oldlen;
157 	int error;
158 	uint8_t operation;
159 
160 	operation = 0;
161 	if (oldlenp != NULL)
162 		operation |= CAP_SYSCTL_READ;
163 	if (newp != NULL)
164 		operation |= CAP_SYSCTL_WRITE;
165 	nvlist_add_number(nvl, "operation", (uint64_t)operation);
166 	if (oldp == NULL && oldlenp != NULL)
167 		nvlist_add_null(nvl, "justsize");
168 	else if (oldlenp != NULL)
169 		nvlist_add_number(nvl, "oldlen", (uint64_t)*oldlenp);
170 	if (newp != NULL)
171 		nvlist_add_binary(nvl, "newp", newp, newlen);
172 
173 	nvl = cap_xfer_nvlist(chan, nvl);
174 	if (nvl == NULL)
175 		return (-1);
176 	error = (int)dnvlist_get_number(nvl, "error", 0);
177 	if (error != 0) {
178 		nvlist_destroy(nvl);
179 		errno = error;
180 		return (-1);
181 	}
182 
183 	if (oldp == NULL && oldlenp != NULL) {
184 		*oldlenp = (size_t)nvlist_get_number(nvl, "oldlen");
185 	} else if (oldp != NULL) {
186 		retoldp = nvlist_get_binary(nvl, "oldp", &oldlen);
187 		memcpy(oldp, retoldp, oldlen);
188 		if (oldlenp != NULL)
189 			*oldlenp = oldlen;
190 	}
191 
192 	nvlist_destroy(nvl);
193 
194 	return (0);
195 }
196 
197 int
198 cap_sysctl(cap_channel_t *chan, const int *name, u_int namelen, void *oldp,
199     size_t *oldlenp, const void *newp, size_t newlen)
200 {
201 	nvlist_t *req;
202 
203 	req = nvlist_create(0);
204 	nvlist_add_string(req, "cmd", "sysctl");
205 	nvlist_add_binary(req, "mib", name, (size_t)namelen * sizeof(int));
206 	return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen));
207 }
208 
209 int
210 cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp,
211     size_t *oldlenp, const void *newp, size_t newlen)
212 {
213 	nvlist_t *req;
214 
215 	req = nvlist_create(0);
216 	nvlist_add_string(req, "cmd", "sysctlbyname");
217 	nvlist_add_string(req, "name", name);
218 	return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen));
219 }
220 
221 int
222 cap_sysctlnametomib(cap_channel_t *chan, const char *name, int *mibp,
223     size_t *sizep)
224 {
225 	nvlist_t *req;
226 	const void *mib;
227 	size_t mibsz;
228 	int error;
229 
230 	req = nvlist_create(0);
231 	nvlist_add_string(req, "cmd", "sysctlnametomib");
232 	nvlist_add_string(req, "name", name);
233 	nvlist_add_number(req, "operation", 0);
234 	nvlist_add_number(req, "size", (uint64_t)*sizep);
235 
236 	req = cap_xfer_nvlist(chan, req);
237 	if (req == NULL)
238 		return (-1);
239 	error = (int)dnvlist_get_number(req, "error", 0);
240 	if (error != 0) {
241 		nvlist_destroy(req);
242 		errno = error;
243 		return (-1);
244 	}
245 
246 	mib = nvlist_get_binary(req, "mib", &mibsz);
247 	*sizep = mibsz / sizeof(int);
248 
249 	memcpy(mibp, mib, mibsz);
250 
251 	nvlist_destroy(req);
252 
253 	return (0);
254 }
255 
256 /*
257  * Service implementation.
258  */
259 
260 /*
261  * Validate a sysctl description.  This must consist of an nvlist with either a
262  * binary "mib" field or a string "name", and an operation.
263  */
264 static int
265 sysctl_valid(const nvlist_t *nvl, bool limit)
266 {
267 	const char *name;
268 	void *cookie;
269 	int type;
270 	size_t size;
271 	unsigned int field, fields;
272 
273 	/* NULL nvl is of course invalid. */
274 	if (nvl == NULL)
275 		return (EINVAL);
276 	if (nvlist_error(nvl) != 0)
277 		return (nvlist_error(nvl));
278 
279 #define	HAS_NAME	0x01
280 #define	HAS_MIB		0x02
281 #define	HAS_ID		(HAS_NAME | HAS_MIB)
282 #define	HAS_OPERATION	0x04
283 
284 	fields = 0;
285 	cookie = NULL;
286 	while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
287 		if ((strcmp(name, "name") == 0 && type == NV_TYPE_STRING) ||
288 		    (strcmp(name, "mib") == 0 && type == NV_TYPE_BINARY)) {
289 			if (strcmp(name, "mib") == 0) {
290 				/* A MIB must be an array of integers. */
291 				(void)cnvlist_get_binary(cookie, &size);
292 				if (size % sizeof(int) != 0)
293 					return (EINVAL);
294 				field = HAS_MIB;
295 			} else
296 				field = HAS_NAME;
297 
298 			/*
299 			 * A limit may contain both a name and a MIB identifier.
300 			 */
301 			if ((fields & field) != 0 ||
302 			    (!limit && (fields & HAS_ID) != 0))
303 				return (EINVAL);
304 			fields |= field;
305 		} else if (strcmp(name, "operation") == 0) {
306 			uint64_t mask, operation;
307 
308 			if (type != NV_TYPE_NUMBER)
309 				return (EINVAL);
310 
311 			operation = cnvlist_get_number(cookie);
312 
313 			/*
314 			 * Requests can only include the RDWR flags; limits may
315 			 * also include the RECURSIVE flag.
316 			 */
317 			mask = limit ? (CAP_SYSCTL_RDWR |
318 			    CAP_SYSCTL_RECURSIVE) : CAP_SYSCTL_RDWR;
319 			if ((operation & ~mask) != 0 ||
320 			    (operation & CAP_SYSCTL_RDWR) == 0)
321 				return (EINVAL);
322 			/* Only one 'operation' can be present. */
323 			if ((fields & HAS_OPERATION) != 0)
324 				return (EINVAL);
325 			fields |= HAS_OPERATION;
326 		} else if (limit)
327 			return (EINVAL);
328 	}
329 
330 	if ((fields & HAS_OPERATION) == 0 || (fields & HAS_ID) == 0)
331 		return (EINVAL);
332 
333 #undef HAS_OPERATION
334 #undef HAS_ID
335 #undef HAS_MIB
336 #undef HAS_NAME
337 
338 	return (0);
339 }
340 
341 static bool
342 sysctl_allowed(const nvlist_t *limits, const nvlist_t *req)
343 {
344 	const nvlist_t *limit;
345 	uint64_t op, reqop;
346 	const char *lname, *name, *reqname;
347 	void *cookie;
348 	size_t lsize, reqsize;
349 	const int *lmib, *reqmib;
350 	int type;
351 
352 	if (limits == NULL)
353 		return (true);
354 
355 	reqmib = dnvlist_get_binary(req, "mib", &reqsize, NULL, 0);
356 	reqname = dnvlist_get_string(req, "name", NULL);
357 	reqop = nvlist_get_number(req, "operation");
358 
359 	cookie = NULL;
360 	while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
361 		assert(type == NV_TYPE_NVLIST);
362 
363 		limit = cnvlist_get_nvlist(cookie);
364 		op = nvlist_get_number(limit, "operation");
365 		if ((reqop & op) != reqop)
366 			continue;
367 
368 		if (reqname != NULL) {
369 			lname = dnvlist_get_string(limit, "name", NULL);
370 			if (lname == NULL)
371 				continue;
372 			if ((op & CAP_SYSCTL_RECURSIVE) == 0) {
373 				if (strcmp(lname, reqname) != 0)
374 					continue;
375 			} else {
376 				size_t namelen;
377 
378 				namelen = strlen(lname);
379 				if (strncmp(lname, reqname, namelen) != 0)
380 					continue;
381 				if (reqname[namelen] != '.' &&
382 				    reqname[namelen] != '\0')
383 					continue;
384 			}
385 		} else {
386 			lmib = dnvlist_get_binary(limit, "mib", &lsize, NULL, 0);
387 			if (lmib == NULL)
388 				continue;
389 			if (lsize > reqsize || ((op & CAP_SYSCTL_RECURSIVE) == 0 &&
390 			    lsize < reqsize))
391 				continue;
392 			if (memcmp(lmib, reqmib, lsize) != 0)
393 				continue;
394 		}
395 
396 		return (true);
397 	}
398 
399 	return (false);
400 }
401 
402 static int
403 sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
404 {
405 	const nvlist_t *nvl;
406 	const char *name;
407 	void *cookie;
408 	int error, type;
409 
410 	cookie = NULL;
411 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
412 		if (strcmp(name, "limit") != 0 || type != NV_TYPE_NVLIST)
413 			return (EINVAL);
414 		nvl = cnvlist_get_nvlist(cookie);
415 		error = sysctl_valid(nvl, true);
416 		if (error != 0)
417 			return (error);
418 		if (!sysctl_allowed(oldlimits, nvl))
419 			return (ENOTCAPABLE);
420 	}
421 
422 	return (0);
423 }
424 
425 static int
426 nametomib(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
427 {
428 	const char *name;
429 	size_t size;
430 	int error, *mibp;
431 
432 	if (!sysctl_allowed(limits, nvlin))
433 		return (ENOTCAPABLE);
434 
435 	name = nvlist_get_string(nvlin, "name");
436 	size = (size_t)nvlist_get_number(nvlin, "size");
437 
438 	mibp = malloc(size * sizeof(*mibp));
439 	if (mibp == NULL)
440 		return (ENOMEM);
441 
442 	error = sysctlnametomib(name, mibp, &size);
443 	if (error != 0) {
444 		error = errno;
445 		free(mibp);
446 		return (error);
447 	}
448 
449 	nvlist_add_binary(nvlout, "mib", mibp, size * sizeof(*mibp));
450 
451 	return (0);
452 }
453 
454 static int
455 sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
456     nvlist_t *nvlout)
457 {
458 	const char *name;
459 	const void *newp;
460 	const int *mibp;
461 	void *oldp;
462 	uint64_t operation;
463 	size_t oldlen, newlen, size;
464 	size_t *oldlenp;
465 	int error;
466 
467 	if (strcmp(cmd, "sysctlnametomib") == 0)
468 		return (nametomib(limits, nvlin, nvlout));
469 
470 	if (strcmp(cmd, "sysctlbyname") != 0 && strcmp(cmd, "sysctl") != 0)
471 		return (EINVAL);
472 	error = sysctl_valid(nvlin, false);
473 	if (error != 0)
474 		return (error);
475 	if (!sysctl_allowed(limits, nvlin))
476 		return (ENOTCAPABLE);
477 
478 	operation = nvlist_get_number(nvlin, "operation");
479 	if ((operation & CAP_SYSCTL_WRITE) != 0) {
480 		if (!nvlist_exists_binary(nvlin, "newp"))
481 			return (EINVAL);
482 		newp = nvlist_get_binary(nvlin, "newp", &newlen);
483 		assert(newp != NULL && newlen > 0);
484 	} else {
485 		newp = NULL;
486 		newlen = 0;
487 	}
488 
489 	if ((operation & CAP_SYSCTL_READ) != 0) {
490 		if (nvlist_exists_null(nvlin, "justsize")) {
491 			oldp = NULL;
492 			oldlen = 0;
493 			oldlenp = &oldlen;
494 		} else {
495 			if (!nvlist_exists_number(nvlin, "oldlen"))
496 				return (EINVAL);
497 			oldlen = (size_t)nvlist_get_number(nvlin, "oldlen");
498 			if (oldlen == 0)
499 				return (EINVAL);
500 			oldp = calloc(1, oldlen);
501 			if (oldp == NULL)
502 				return (ENOMEM);
503 			oldlenp = &oldlen;
504 		}
505 	} else {
506 		oldp = NULL;
507 		oldlen = 0;
508 		oldlenp = NULL;
509 	}
510 
511 	if (strcmp(cmd, "sysctlbyname") == 0) {
512 		name = nvlist_get_string(nvlin, "name");
513 		error = sysctlbyname(name, oldp, oldlenp, newp, newlen);
514 	} else {
515 		mibp = nvlist_get_binary(nvlin, "mib", &size);
516 		error = sysctl(mibp, size / sizeof(*mibp), oldp, oldlenp, newp,
517 		    newlen);
518 	}
519 	if (error != 0) {
520 		error = errno;
521 		free(oldp);
522 		return (error);
523 	}
524 
525 	if ((operation & CAP_SYSCTL_READ) != 0) {
526 		if (nvlist_exists_null(nvlin, "justsize"))
527 			nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen);
528 		else
529 			nvlist_move_binary(nvlout, "oldp", oldp, oldlen);
530 	}
531 
532 	return (0);
533 }
534 
535 CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0);
536