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 *
cap_sysctl_limit_init(cap_channel_t * chan)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 *
cap_sysctl_limit_name(cap_sysctl_limit_t * limit,const char * name,int flags)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 *
cap_sysctl_limit_mib(cap_sysctl_limit_t * limit,const int * mibp,u_int miblen,int flags)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
cap_sysctl_limit(cap_sysctl_limit_t * limit)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
do_sysctl(cap_channel_t * chan,nvlist_t * nvl,void * oldp,size_t * oldlenp,const void * newp,size_t newlen)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
cap_sysctl(cap_channel_t * chan,const int * name,u_int namelen,void * oldp,size_t * oldlenp,const void * newp,size_t newlen)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
cap_sysctlbyname(cap_channel_t * chan,const char * name,void * oldp,size_t * oldlenp,const void * newp,size_t newlen)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
cap_sysctlnametomib(cap_channel_t * chan,const char * name,int * mibp,size_t * sizep)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
sysctl_valid(const nvlist_t * nvl,bool limit)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
sysctl_allowed(const nvlist_t * limits,const nvlist_t * req)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
sysctl_limit(const nvlist_t * oldlimits,const nvlist_t * newlimits)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
nametomib(const nvlist_t * limits,const nvlist_t * nvlin,nvlist_t * nvlout)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
sysctl_command(const char * cmd,const nvlist_t * limits,nvlist_t * nvlin,nvlist_t * nvlout)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