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