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 55 struct cap_sysctl_limit { 56 cap_channel_t *chan; 57 nvlist_t *nv; 58 }; 59 60 cap_sysctl_limit_t * 61 cap_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 80 cap_sysctl_limit_t * 81 cap_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 108 cap_sysctl_limit_t * 109 cap_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 130 int 131 cap_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 148 static int 149 do_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 194 int 195 cap_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 206 int 207 cap_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 218 int 219 cap_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 */ 261 static int 262 sysctl_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 338 static bool 339 sysctl_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 399 static int 400 sysctl_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 422 static int 423 nametomib(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 451 static int 452 sysctl_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 532 CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0); 533