1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/types.h> 33 #include <sys/capsicum.h> 34 #include <sys/sysctl.h> 35 #include <sys/cnv.h> 36 #include <sys/dnv.h> 37 #include <sys/nv.h> 38 #include <sys/stat.h> 39 40 #include <assert.h> 41 #include <errno.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 46 #include <libcasper.h> 47 #include <libcasper_service.h> 48 49 #include "cap_fileargs.h" 50 51 #define CACHE_SIZE 128 52 53 #define FILEARGS_MAGIC 0xFA00FA00 54 55 struct fileargs { 56 uint32_t fa_magic; 57 nvlist_t *fa_cache; 58 cap_channel_t *fa_chann; 59 int fa_fdflags; 60 }; 61 62 static int 63 fileargs_get_lstat_cache(fileargs_t *fa, const char *name, struct stat *sb) 64 { 65 const nvlist_t *nvl; 66 size_t size; 67 const void *buf; 68 69 assert(fa != NULL); 70 assert(fa->fa_magic == FILEARGS_MAGIC); 71 assert(name != NULL); 72 73 if (fa->fa_cache == NULL) 74 return (-1); 75 76 nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL); 77 if (nvl == NULL) 78 return (-1); 79 80 if (!nvlist_exists_binary(nvl, "stat")) { 81 return (-1); 82 } 83 84 buf = nvlist_get_binary(nvl, "stat", &size); 85 assert(size == sizeof(*sb)); 86 memcpy(sb, buf, size); 87 88 return (0); 89 } 90 91 static int 92 fileargs_get_fd_cache(fileargs_t *fa, const char *name) 93 { 94 int fd; 95 const nvlist_t *nvl; 96 nvlist_t *tnvl; 97 98 assert(fa != NULL); 99 assert(fa->fa_magic == FILEARGS_MAGIC); 100 assert(name != NULL); 101 102 if (fa->fa_cache == NULL) 103 return (-1); 104 105 if ((fa->fa_fdflags & O_CREAT) != 0) 106 return (-1); 107 108 nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL); 109 if (nvl == NULL) 110 return (-1); 111 112 tnvl = nvlist_take_nvlist(fa->fa_cache, name); 113 114 if (!nvlist_exists_descriptor(tnvl, "fd")) { 115 nvlist_destroy(tnvl); 116 return (-1); 117 } 118 119 fd = nvlist_take_descriptor(tnvl, "fd"); 120 nvlist_destroy(tnvl); 121 122 if ((fa->fa_fdflags & O_CLOEXEC) != O_CLOEXEC) { 123 if (fcntl(fd, F_SETFD, fa->fa_fdflags) == -1) { 124 close(fd); 125 return (-1); 126 } 127 } 128 129 return (fd); 130 } 131 132 static void 133 fileargs_set_cache(fileargs_t *fa, nvlist_t *nvl) 134 { 135 136 nvlist_destroy(fa->fa_cache); 137 fa->fa_cache = nvl; 138 } 139 140 static nvlist_t* 141 fileargs_fetch(fileargs_t *fa, const char *name, const char *cmd) 142 { 143 nvlist_t *nvl; 144 int serrno; 145 146 assert(fa != NULL); 147 assert(name != NULL); 148 149 nvl = nvlist_create(NV_FLAG_NO_UNIQUE); 150 nvlist_add_string(nvl, "cmd", cmd); 151 nvlist_add_string(nvl, "name", name); 152 153 nvl = cap_xfer_nvlist(fa->fa_chann, nvl); 154 if (nvl == NULL) 155 return (NULL); 156 157 if (nvlist_get_number(nvl, "error") != 0) { 158 serrno = (int)nvlist_get_number(nvl, "error"); 159 nvlist_destroy(nvl); 160 errno = serrno; 161 return (NULL); 162 } 163 164 return (nvl); 165 } 166 167 static nvlist_t * 168 fileargs_create_limit(int argc, const char * const *argv, int flags, 169 mode_t mode, cap_rights_t *rightsp, int operations) 170 { 171 nvlist_t *limits; 172 int i; 173 174 limits = nvlist_create(NV_FLAG_NO_UNIQUE); 175 if (limits == NULL) 176 return (NULL); 177 178 nvlist_add_number(limits, "flags", flags); 179 nvlist_add_number(limits, "operations", operations); 180 if (rightsp != NULL) { 181 nvlist_add_binary(limits, "cap_rights", rightsp, 182 sizeof(*rightsp)); 183 } 184 if ((flags & O_CREAT) != 0) 185 nvlist_add_number(limits, "mode", (uint64_t)mode); 186 187 for (i = 0; i < argc; i++) { 188 if (strlen(argv[i]) >= MAXPATHLEN) { 189 nvlist_destroy(limits); 190 errno = ENAMETOOLONG; 191 return (NULL); 192 } 193 nvlist_add_null(limits, argv[i]); 194 } 195 196 return (limits); 197 } 198 199 static fileargs_t * 200 fileargs_create(cap_channel_t *chan, int fdflags) 201 { 202 fileargs_t *fa; 203 204 fa = malloc(sizeof(*fa)); 205 if (fa != NULL) { 206 fa->fa_cache = NULL; 207 fa->fa_chann = chan; 208 fa->fa_fdflags = fdflags; 209 fa->fa_magic = FILEARGS_MAGIC; 210 } 211 212 return (fa); 213 } 214 215 fileargs_t * 216 fileargs_init(int argc, char *argv[], int flags, mode_t mode, 217 cap_rights_t *rightsp, int operations) 218 { 219 nvlist_t *limits; 220 221 if (argc <= 0 || argv == NULL) { 222 return (fileargs_create(NULL, 0)); 223 } 224 225 limits = fileargs_create_limit(argc, (const char * const *)argv, flags, 226 mode, rightsp, operations); 227 if (limits == NULL) 228 return (NULL); 229 230 return (fileargs_initnv(limits)); 231 } 232 233 fileargs_t * 234 fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], int flags, 235 mode_t mode, cap_rights_t *rightsp, int operations) 236 { 237 nvlist_t *limits; 238 239 if (argc <= 0 || argv == NULL) { 240 return (fileargs_create(NULL, 0)); 241 } 242 243 limits = fileargs_create_limit(argc, (const char * const *)argv, flags, 244 mode, rightsp, operations); 245 if (limits == NULL) 246 return (NULL); 247 248 return (fileargs_cinitnv(cas, limits)); 249 } 250 251 fileargs_t * 252 fileargs_initnv(nvlist_t *limits) 253 { 254 cap_channel_t *cas; 255 fileargs_t *fa; 256 257 if (limits == NULL) { 258 return (fileargs_create(NULL, 0)); 259 } 260 261 cas = cap_init(); 262 if (cas == NULL) { 263 nvlist_destroy(limits); 264 return (NULL); 265 } 266 267 fa = fileargs_cinitnv(cas, limits); 268 cap_close(cas); 269 270 return (fa); 271 } 272 273 fileargs_t * 274 fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits) 275 { 276 cap_channel_t *chann; 277 fileargs_t *fa; 278 int serrno, ret; 279 int flags, operations; 280 281 assert(cas != NULL); 282 283 if (limits == NULL) { 284 return (fileargs_create(NULL, 0)); 285 } 286 287 chann = NULL; 288 fa = NULL; 289 290 chann = cap_service_open(cas, "system.fileargs"); 291 if (chann == NULL) { 292 nvlist_destroy(limits); 293 return (NULL); 294 } 295 296 flags = nvlist_get_number(limits, "flags"); 297 operations = nvlist_get_number(limits, "operations"); 298 299 /* Limits are consumed no need to free them. */ 300 ret = cap_limit_set(chann, limits); 301 if (ret < 0) 302 goto out; 303 304 fa = fileargs_create(chann, flags); 305 if (fa == NULL) 306 goto out; 307 308 return (fa); 309 out: 310 serrno = errno; 311 if (chann != NULL) 312 cap_close(chann); 313 errno = serrno; 314 return (NULL); 315 } 316 317 int 318 fileargs_open(fileargs_t *fa, const char *name) 319 { 320 int fd; 321 nvlist_t *nvl; 322 char *cmd; 323 324 assert(fa != NULL); 325 assert(fa->fa_magic == FILEARGS_MAGIC); 326 327 if (name == NULL) { 328 errno = EINVAL; 329 return (-1); 330 } 331 332 if (fa->fa_chann == NULL) { 333 errno = ENOTCAPABLE; 334 return (-1); 335 } 336 337 fd = fileargs_get_fd_cache(fa, name); 338 if (fd != -1) 339 return (fd); 340 341 nvl = fileargs_fetch(fa, name, "open"); 342 if (nvl == NULL) 343 return (-1); 344 345 fd = nvlist_take_descriptor(nvl, "fd"); 346 cmd = nvlist_take_string(nvl, "cmd"); 347 if (strcmp(cmd, "cache") == 0) 348 fileargs_set_cache(fa, nvl); 349 else 350 nvlist_destroy(nvl); 351 free(cmd); 352 353 return (fd); 354 } 355 356 FILE * 357 fileargs_fopen(fileargs_t *fa, const char *name, const char *mode) 358 { 359 int fd; 360 361 if ((fd = fileargs_open(fa, name)) < 0) { 362 return (NULL); 363 } 364 365 return (fdopen(fd, mode)); 366 } 367 368 int 369 fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb) 370 { 371 nvlist_t *nvl; 372 const void *buf; 373 size_t size; 374 char *cmd; 375 376 assert(fa != NULL); 377 assert(fa->fa_magic == FILEARGS_MAGIC); 378 379 if (name == NULL) { 380 errno = EINVAL; 381 return (-1); 382 } 383 384 if (sb == NULL) { 385 errno = EFAULT; 386 return (-1); 387 } 388 389 if (fa->fa_chann == NULL) { 390 errno = ENOTCAPABLE; 391 return (-1); 392 } 393 394 if (fileargs_get_lstat_cache(fa, name, sb) != -1) 395 return (0); 396 397 nvl = fileargs_fetch(fa, name, "lstat"); 398 if (nvl == NULL) 399 return (-1); 400 401 buf = nvlist_get_binary(nvl, "stat", &size); 402 assert(size == sizeof(*sb)); 403 memcpy(sb, buf, size); 404 405 cmd = nvlist_take_string(nvl, "cmd"); 406 if (strcmp(cmd, "cache") == 0) 407 fileargs_set_cache(fa, nvl); 408 else 409 nvlist_destroy(nvl); 410 free(cmd); 411 412 return (0); 413 } 414 415 void 416 fileargs_free(fileargs_t *fa) 417 { 418 419 if (fa == NULL) 420 return; 421 422 assert(fa->fa_magic == FILEARGS_MAGIC); 423 424 nvlist_destroy(fa->fa_cache); 425 if (fa->fa_chann != NULL) { 426 cap_close(fa->fa_chann); 427 } 428 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 429 free(fa); 430 } 431 432 cap_channel_t * 433 fileargs_unwrap(fileargs_t *fa, int *flags) 434 { 435 cap_channel_t *chan; 436 437 if (fa == NULL) 438 return (NULL); 439 440 assert(fa->fa_magic == FILEARGS_MAGIC); 441 442 chan = fa->fa_chann; 443 if (flags != NULL) { 444 *flags = fa->fa_fdflags; 445 } 446 447 nvlist_destroy(fa->fa_cache); 448 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 449 free(fa); 450 451 return (chan); 452 } 453 454 fileargs_t * 455 fileargs_wrap(cap_channel_t *chan, int fdflags) 456 { 457 458 if (chan == NULL) { 459 return (NULL); 460 } 461 462 return (fileargs_create(chan, fdflags)); 463 } 464 465 /* 466 * Service functions. 467 */ 468 469 static const char *lastname; 470 static void *cacheposition; 471 static bool allcached; 472 static const cap_rights_t *caprightsp; 473 static int capflags; 474 static int allowed_operations; 475 static mode_t capmode; 476 477 static int 478 open_file(const char *name) 479 { 480 int fd, serrno; 481 482 if ((capflags & O_CREAT) == 0) 483 fd = open(name, capflags); 484 else 485 fd = open(name, capflags, capmode); 486 if (fd < 0) 487 return (-1); 488 489 if (caprightsp != NULL) { 490 if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) { 491 serrno = errno; 492 close(fd); 493 errno = serrno; 494 return (-1); 495 } 496 } 497 498 return (fd); 499 } 500 501 static void 502 fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits, 503 const char *current_name) 504 { 505 int type, i, fd; 506 void *cookie; 507 nvlist_t *new; 508 const char *fname; 509 struct stat sb; 510 511 if ((capflags & O_CREAT) != 0) { 512 allcached = true; 513 return; 514 } 515 516 cookie = cacheposition; 517 for (i = 0; i < CACHE_SIZE + 1; i++) { 518 fname = nvlist_next(limits, &type, &cookie); 519 if (fname == NULL) { 520 cacheposition = NULL; 521 lastname = NULL; 522 allcached = true; 523 return; 524 } 525 /* We doing that to catch next element name. */ 526 if (i == CACHE_SIZE) { 527 break; 528 } 529 530 if (type != NV_TYPE_NULL || (current_name != NULL && 531 strcmp(fname, current_name) == 0)) { 532 current_name = NULL; 533 i--; 534 continue; 535 } 536 537 new = nvlist_create(NV_FLAG_NO_UNIQUE); 538 if ((allowed_operations & FA_OPEN) != 0) { 539 fd = open_file(fname); 540 if (fd < 0) { 541 i--; 542 nvlist_destroy(new); 543 continue; 544 } 545 nvlist_move_descriptor(new, "fd", fd); 546 } 547 if ((allowed_operations & FA_LSTAT) != 0) { 548 if (lstat(fname, &sb) < 0) { 549 i--; 550 nvlist_destroy(new); 551 continue; 552 } 553 nvlist_add_binary(new, "stat", &sb, sizeof(sb)); 554 } 555 556 nvlist_move_nvlist(nvlout, fname, new); 557 } 558 cacheposition = cookie; 559 lastname = fname; 560 } 561 562 static bool 563 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation) 564 { 565 const char *name; 566 567 if ((allowed_operations & operation) == 0) 568 return (false); 569 570 name = dnvlist_get_string(request, "name", NULL); 571 if (name == NULL) 572 return (false); 573 574 /* Fast path. */ 575 if (lastname != NULL && strcmp(name, lastname) == 0) 576 return (true); 577 578 if (!nvlist_exists_null(limits, name)) 579 return (false); 580 581 return (true); 582 } 583 584 static int 585 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 586 { 587 588 if (oldlimits != NULL) 589 return (ENOTCAPABLE); 590 591 capflags = (int)dnvlist_get_number(newlimits, "flags", 0); 592 allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0); 593 if ((capflags & O_CREAT) != 0) 594 capmode = (mode_t)nvlist_get_number(newlimits, "mode"); 595 else 596 capmode = 0; 597 598 caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0); 599 600 return (0); 601 } 602 603 static int 604 fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin, 605 nvlist_t *nvlout) 606 { 607 int error; 608 const char *name; 609 struct stat sb; 610 611 if (limits == NULL) 612 return (ENOTCAPABLE); 613 614 if (!fileargs_allowed(limits, nvlin, FA_LSTAT)) 615 return (ENOTCAPABLE); 616 617 name = nvlist_get_string(nvlin, "name"); 618 619 error = lstat(name, &sb); 620 if (error < 0) 621 return (errno); 622 623 if (!allcached && (lastname == NULL || 624 strcmp(name, lastname) == 0)) { 625 nvlist_add_string(nvlout, "cmd", "cache"); 626 fileargs_add_cache(nvlout, limits, name); 627 } else { 628 nvlist_add_string(nvlout, "cmd", "lstat"); 629 } 630 nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb)); 631 return (0); 632 } 633 634 static int 635 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, 636 nvlist_t *nvlout) 637 { 638 int fd; 639 const char *name; 640 641 if (limits == NULL) 642 return (ENOTCAPABLE); 643 644 if (!fileargs_allowed(limits, nvlin, FA_OPEN)) 645 return (ENOTCAPABLE); 646 647 name = nvlist_get_string(nvlin, "name"); 648 649 fd = open_file(name); 650 if (fd < 0) 651 return (errno); 652 653 if (!allcached && (lastname == NULL || 654 strcmp(name, lastname) == 0)) { 655 nvlist_add_string(nvlout, "cmd", "cache"); 656 fileargs_add_cache(nvlout, limits, name); 657 } else { 658 nvlist_add_string(nvlout, "cmd", "open"); 659 } 660 nvlist_move_descriptor(nvlout, "fd", fd); 661 return (0); 662 } 663 664 static int 665 fileargs_command(const char *cmd, const nvlist_t *limits, 666 nvlist_t *nvlin, nvlist_t *nvlout) 667 { 668 669 if (strcmp(cmd, "open") == 0) 670 return (fileargs_command_open(limits, nvlin, nvlout)); 671 672 if (strcmp(cmd, "lstat") == 0) 673 return (fileargs_command_lstat(limits, nvlin, nvlout)); 674 675 return (EINVAL); 676 } 677 678 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command, 679 CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS); 680