1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2018-2021 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 char * 416 fileargs_realpath(fileargs_t *fa, const char *pathname, char *reserved_path) 417 { 418 nvlist_t *nvl; 419 char *ret; 420 421 assert(fa != NULL); 422 assert(fa->fa_magic == FILEARGS_MAGIC); 423 424 if (pathname == NULL) { 425 errno = EINVAL; 426 return (NULL); 427 } 428 429 if (fa->fa_chann == NULL) { 430 errno = ENOTCAPABLE; 431 return (NULL); 432 } 433 434 nvl = fileargs_fetch(fa, pathname, "realpath"); 435 if (nvl == NULL) 436 return (NULL); 437 438 if (reserved_path != NULL) { 439 ret = reserved_path; 440 strcpy(reserved_path, 441 nvlist_get_string(nvl, "realpath")); 442 } else { 443 ret = nvlist_take_string(nvl, "realpath"); 444 } 445 nvlist_destroy(nvl); 446 447 return (ret); 448 } 449 450 void 451 fileargs_free(fileargs_t *fa) 452 { 453 454 if (fa == NULL) 455 return; 456 457 assert(fa->fa_magic == FILEARGS_MAGIC); 458 459 nvlist_destroy(fa->fa_cache); 460 if (fa->fa_chann != NULL) { 461 cap_close(fa->fa_chann); 462 } 463 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 464 free(fa); 465 } 466 467 cap_channel_t * 468 fileargs_unwrap(fileargs_t *fa, int *flags) 469 { 470 cap_channel_t *chan; 471 472 if (fa == NULL) 473 return (NULL); 474 475 assert(fa->fa_magic == FILEARGS_MAGIC); 476 477 chan = fa->fa_chann; 478 if (flags != NULL) { 479 *flags = fa->fa_fdflags; 480 } 481 482 nvlist_destroy(fa->fa_cache); 483 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 484 free(fa); 485 486 return (chan); 487 } 488 489 fileargs_t * 490 fileargs_wrap(cap_channel_t *chan, int fdflags) 491 { 492 493 if (chan == NULL) { 494 return (NULL); 495 } 496 497 return (fileargs_create(chan, fdflags)); 498 } 499 500 /* 501 * Service functions. 502 */ 503 504 static const char *lastname; 505 static void *cacheposition; 506 static bool allcached; 507 static const cap_rights_t *caprightsp; 508 static int capflags; 509 static int allowed_operations; 510 static mode_t capmode; 511 512 static int 513 open_file(const char *name) 514 { 515 int fd, serrno; 516 517 if ((capflags & O_CREAT) == 0) 518 fd = open(name, capflags); 519 else 520 fd = open(name, capflags, capmode); 521 if (fd < 0) 522 return (-1); 523 524 if (caprightsp != NULL) { 525 if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) { 526 serrno = errno; 527 close(fd); 528 errno = serrno; 529 return (-1); 530 } 531 } 532 533 return (fd); 534 } 535 536 static void 537 fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits, 538 const char *current_name) 539 { 540 int type, i, fd; 541 void *cookie; 542 nvlist_t *new; 543 const char *fname; 544 struct stat sb; 545 546 if ((capflags & O_CREAT) != 0) { 547 allcached = true; 548 return; 549 } 550 551 cookie = cacheposition; 552 for (i = 0; i < CACHE_SIZE + 1; i++) { 553 fname = nvlist_next(limits, &type, &cookie); 554 if (fname == NULL) { 555 cacheposition = NULL; 556 lastname = NULL; 557 allcached = true; 558 return; 559 } 560 /* We doing that to catch next element name. */ 561 if (i == CACHE_SIZE) { 562 break; 563 } 564 565 if (type != NV_TYPE_NULL) { 566 i--; 567 continue; 568 } 569 if (current_name != NULL && 570 strcmp(fname, current_name) == 0) { 571 current_name = NULL; 572 i--; 573 continue; 574 } 575 576 new = nvlist_create(NV_FLAG_NO_UNIQUE); 577 if ((allowed_operations & FA_OPEN) != 0) { 578 fd = open_file(fname); 579 if (fd < 0) { 580 i--; 581 nvlist_destroy(new); 582 continue; 583 } 584 nvlist_move_descriptor(new, "fd", fd); 585 } 586 if ((allowed_operations & FA_LSTAT) != 0) { 587 if (lstat(fname, &sb) < 0) { 588 i--; 589 nvlist_destroy(new); 590 continue; 591 } 592 nvlist_add_binary(new, "stat", &sb, sizeof(sb)); 593 } 594 595 nvlist_move_nvlist(nvlout, fname, new); 596 } 597 cacheposition = cookie; 598 lastname = fname; 599 } 600 601 static bool 602 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation) 603 { 604 const char *name; 605 606 if ((allowed_operations & operation) == 0) 607 return (false); 608 609 name = dnvlist_get_string(request, "name", NULL); 610 if (name == NULL) 611 return (false); 612 613 /* Fast path. */ 614 if (lastname != NULL && strcmp(name, lastname) == 0) 615 return (true); 616 617 if (!nvlist_exists_null(limits, name)) 618 return (false); 619 620 return (true); 621 } 622 623 static int 624 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 625 { 626 627 if (oldlimits != NULL) 628 return (ENOTCAPABLE); 629 630 capflags = (int)dnvlist_get_number(newlimits, "flags", 0); 631 allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0); 632 if ((capflags & O_CREAT) != 0) 633 capmode = (mode_t)nvlist_get_number(newlimits, "mode"); 634 else 635 capmode = 0; 636 637 caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0); 638 639 return (0); 640 } 641 642 static int 643 fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin, 644 nvlist_t *nvlout) 645 { 646 int error; 647 const char *name; 648 struct stat sb; 649 650 if (limits == NULL) 651 return (ENOTCAPABLE); 652 653 if (!fileargs_allowed(limits, nvlin, FA_LSTAT)) 654 return (ENOTCAPABLE); 655 656 name = nvlist_get_string(nvlin, "name"); 657 658 error = lstat(name, &sb); 659 if (error < 0) 660 return (errno); 661 662 if (!allcached && (lastname == NULL || 663 strcmp(name, lastname) == 0)) { 664 nvlist_add_string(nvlout, "cmd", "cache"); 665 fileargs_add_cache(nvlout, limits, name); 666 } else { 667 nvlist_add_string(nvlout, "cmd", "lstat"); 668 } 669 nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb)); 670 return (0); 671 } 672 673 static int 674 fileargs_command_realpath(const nvlist_t *limits, nvlist_t *nvlin, 675 nvlist_t *nvlout) 676 { 677 const char *pathname; 678 char *resolvedpath; 679 680 if (limits == NULL) 681 return (ENOTCAPABLE); 682 683 if (!fileargs_allowed(limits, nvlin, FA_REALPATH)) 684 return (ENOTCAPABLE); 685 686 pathname = nvlist_get_string(nvlin, "name"); 687 resolvedpath = realpath(pathname, NULL); 688 if (resolvedpath == NULL) 689 return (errno); 690 691 nvlist_move_string(nvlout, "realpath", resolvedpath); 692 return (0); 693 } 694 695 static int 696 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, 697 nvlist_t *nvlout) 698 { 699 int fd; 700 const char *name; 701 702 if (limits == NULL) 703 return (ENOTCAPABLE); 704 705 if (!fileargs_allowed(limits, nvlin, FA_OPEN)) 706 return (ENOTCAPABLE); 707 708 name = nvlist_get_string(nvlin, "name"); 709 710 fd = open_file(name); 711 if (fd < 0) 712 return (errno); 713 714 if (!allcached && (lastname == NULL || 715 strcmp(name, lastname) == 0)) { 716 nvlist_add_string(nvlout, "cmd", "cache"); 717 fileargs_add_cache(nvlout, limits, name); 718 } else { 719 nvlist_add_string(nvlout, "cmd", "open"); 720 } 721 nvlist_move_descriptor(nvlout, "fd", fd); 722 return (0); 723 } 724 725 static int 726 fileargs_command(const char *cmd, const nvlist_t *limits, 727 nvlist_t *nvlin, nvlist_t *nvlout) 728 { 729 730 if (strcmp(cmd, "open") == 0) 731 return (fileargs_command_open(limits, nvlin, nvlout)); 732 if (strcmp(cmd, "lstat") == 0) 733 return (fileargs_command_lstat(limits, nvlin, nvlout)); 734 if (strcmp(cmd, "realpath") == 0) 735 return (fileargs_command_realpath(limits, nvlin, nvlout)); 736 737 return (EINVAL); 738 } 739 740 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command, 741 CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS); 742