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