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 * 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 * 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 * 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 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 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 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 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 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 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 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 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 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 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