1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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