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