1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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 flags, ret, serrno; 279 280 assert(cas != NULL); 281 282 if (limits == NULL) { 283 return (fileargs_create(NULL, 0)); 284 } 285 286 chann = NULL; 287 fa = NULL; 288 289 chann = cap_service_open(cas, "system.fileargs"); 290 if (chann == NULL) { 291 nvlist_destroy(limits); 292 return (NULL); 293 } 294 295 flags = nvlist_get_number(limits, "flags"); 296 (void)nvlist_get_number(limits, "operations"); 297 298 /* Limits are consumed no need to free them. */ 299 ret = cap_limit_set(chann, limits); 300 if (ret < 0) 301 goto out; 302 303 fa = fileargs_create(chann, flags); 304 if (fa == NULL) 305 goto out; 306 307 return (fa); 308 out: 309 serrno = errno; 310 if (chann != NULL) 311 cap_close(chann); 312 errno = serrno; 313 return (NULL); 314 } 315 316 int 317 fileargs_open(fileargs_t *fa, const char *name) 318 { 319 int fd; 320 nvlist_t *nvl; 321 char *cmd; 322 323 assert(fa != NULL); 324 assert(fa->fa_magic == FILEARGS_MAGIC); 325 326 if (name == NULL) { 327 errno = EINVAL; 328 return (-1); 329 } 330 331 if (fa->fa_chann == NULL) { 332 errno = ENOTCAPABLE; 333 return (-1); 334 } 335 336 fd = fileargs_get_fd_cache(fa, name); 337 if (fd != -1) 338 return (fd); 339 340 nvl = fileargs_fetch(fa, name, "open"); 341 if (nvl == NULL) 342 return (-1); 343 344 fd = nvlist_take_descriptor(nvl, "fd"); 345 cmd = nvlist_take_string(nvl, "cmd"); 346 if (strcmp(cmd, "cache") == 0) 347 fileargs_set_cache(fa, nvl); 348 else 349 nvlist_destroy(nvl); 350 free(cmd); 351 352 return (fd); 353 } 354 355 FILE * 356 fileargs_fopen(fileargs_t *fa, const char *name, const char *mode) 357 { 358 int fd; 359 360 if ((fd = fileargs_open(fa, name)) < 0) { 361 return (NULL); 362 } 363 364 return (fdopen(fd, mode)); 365 } 366 367 int 368 fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb) 369 { 370 nvlist_t *nvl; 371 const void *buf; 372 size_t size; 373 char *cmd; 374 375 assert(fa != NULL); 376 assert(fa->fa_magic == FILEARGS_MAGIC); 377 378 if (name == NULL) { 379 errno = EINVAL; 380 return (-1); 381 } 382 383 if (sb == NULL) { 384 errno = EFAULT; 385 return (-1); 386 } 387 388 if (fa->fa_chann == NULL) { 389 errno = ENOTCAPABLE; 390 return (-1); 391 } 392 393 if (fileargs_get_lstat_cache(fa, name, sb) != -1) 394 return (0); 395 396 nvl = fileargs_fetch(fa, name, "lstat"); 397 if (nvl == NULL) 398 return (-1); 399 400 buf = nvlist_get_binary(nvl, "stat", &size); 401 assert(size == sizeof(*sb)); 402 memcpy(sb, buf, size); 403 404 cmd = nvlist_take_string(nvl, "cmd"); 405 if (strcmp(cmd, "cache") == 0) 406 fileargs_set_cache(fa, nvl); 407 else 408 nvlist_destroy(nvl); 409 free(cmd); 410 411 return (0); 412 } 413 414 char * 415 fileargs_realpath(fileargs_t *fa, const char *pathname, char *reserved_path) 416 { 417 nvlist_t *nvl; 418 char *ret; 419 420 assert(fa != NULL); 421 assert(fa->fa_magic == FILEARGS_MAGIC); 422 423 if (pathname == NULL) { 424 errno = EINVAL; 425 return (NULL); 426 } 427 428 if (fa->fa_chann == NULL) { 429 errno = ENOTCAPABLE; 430 return (NULL); 431 } 432 433 nvl = fileargs_fetch(fa, pathname, "realpath"); 434 if (nvl == NULL) 435 return (NULL); 436 437 if (reserved_path != NULL) { 438 ret = reserved_path; 439 strcpy(reserved_path, 440 nvlist_get_string(nvl, "realpath")); 441 } else { 442 ret = nvlist_take_string(nvl, "realpath"); 443 } 444 nvlist_destroy(nvl); 445 446 return (ret); 447 } 448 449 void 450 fileargs_free(fileargs_t *fa) 451 { 452 453 if (fa == NULL) 454 return; 455 456 assert(fa->fa_magic == FILEARGS_MAGIC); 457 458 nvlist_destroy(fa->fa_cache); 459 if (fa->fa_chann != NULL) { 460 cap_close(fa->fa_chann); 461 } 462 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 463 free(fa); 464 } 465 466 cap_channel_t * 467 fileargs_unwrap(fileargs_t *fa, int *flags) 468 { 469 cap_channel_t *chan; 470 471 if (fa == NULL) 472 return (NULL); 473 474 assert(fa->fa_magic == FILEARGS_MAGIC); 475 476 chan = fa->fa_chann; 477 if (flags != NULL) { 478 *flags = fa->fa_fdflags; 479 } 480 481 nvlist_destroy(fa->fa_cache); 482 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 483 free(fa); 484 485 return (chan); 486 } 487 488 fileargs_t * 489 fileargs_wrap(cap_channel_t *chan, int fdflags) 490 { 491 492 if (chan == NULL) { 493 return (NULL); 494 } 495 496 return (fileargs_create(chan, fdflags)); 497 } 498 499 /* 500 * Service functions. 501 */ 502 503 static const char *lastname; 504 static void *cacheposition; 505 static bool allcached; 506 static const cap_rights_t *caprightsp; 507 static int capflags; 508 static int allowed_operations; 509 static mode_t capmode; 510 511 static int 512 open_file(const char *name) 513 { 514 int fd, serrno; 515 516 if ((capflags & O_CREAT) == 0) 517 fd = open(name, capflags); 518 else 519 fd = open(name, capflags, capmode); 520 if (fd < 0) 521 return (-1); 522 523 if (caprightsp != NULL) { 524 if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) { 525 serrno = errno; 526 close(fd); 527 errno = serrno; 528 return (-1); 529 } 530 } 531 532 return (fd); 533 } 534 535 static void 536 fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits, 537 const char *current_name) 538 { 539 int type, i, fd; 540 void *cookie; 541 nvlist_t *new; 542 const char *fname; 543 struct stat sb; 544 545 if ((capflags & O_CREAT) != 0) { 546 allcached = true; 547 return; 548 } 549 550 cookie = cacheposition; 551 for (i = 0; i < CACHE_SIZE + 1; i++) { 552 fname = nvlist_next(limits, &type, &cookie); 553 if (fname == NULL) { 554 cacheposition = NULL; 555 lastname = NULL; 556 allcached = true; 557 return; 558 } 559 /* We doing that to catch next element name. */ 560 if (i == CACHE_SIZE) { 561 break; 562 } 563 564 if (type != NV_TYPE_NULL) { 565 i--; 566 continue; 567 } 568 if (current_name != NULL && 569 strcmp(fname, current_name) == 0) { 570 current_name = NULL; 571 i--; 572 continue; 573 } 574 575 new = nvlist_create(NV_FLAG_NO_UNIQUE); 576 if ((allowed_operations & FA_OPEN) != 0) { 577 fd = open_file(fname); 578 if (fd < 0) { 579 i--; 580 nvlist_destroy(new); 581 continue; 582 } 583 nvlist_move_descriptor(new, "fd", fd); 584 } 585 if ((allowed_operations & FA_LSTAT) != 0) { 586 if (lstat(fname, &sb) < 0) { 587 i--; 588 nvlist_destroy(new); 589 continue; 590 } 591 nvlist_add_binary(new, "stat", &sb, sizeof(sb)); 592 } 593 594 nvlist_move_nvlist(nvlout, fname, new); 595 } 596 cacheposition = cookie; 597 lastname = fname; 598 } 599 600 static bool 601 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation) 602 { 603 const char *name; 604 605 if ((allowed_operations & operation) == 0) 606 return (false); 607 608 name = dnvlist_get_string(request, "name", NULL); 609 if (name == NULL) 610 return (false); 611 612 /* Fast path. */ 613 if (lastname != NULL && strcmp(name, lastname) == 0) 614 return (true); 615 616 if (!nvlist_exists_null(limits, name)) 617 return (false); 618 619 return (true); 620 } 621 622 static int 623 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 624 { 625 626 if (oldlimits != NULL) 627 return (ENOTCAPABLE); 628 629 capflags = (int)dnvlist_get_number(newlimits, "flags", 0); 630 allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0); 631 if ((capflags & O_CREAT) != 0) 632 capmode = (mode_t)nvlist_get_number(newlimits, "mode"); 633 else 634 capmode = 0; 635 636 caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0); 637 638 return (0); 639 } 640 641 static int 642 fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin, 643 nvlist_t *nvlout) 644 { 645 int error; 646 const char *name; 647 struct stat sb; 648 649 if (limits == NULL) 650 return (ENOTCAPABLE); 651 652 if (!fileargs_allowed(limits, nvlin, FA_LSTAT)) 653 return (ENOTCAPABLE); 654 655 name = nvlist_get_string(nvlin, "name"); 656 657 error = lstat(name, &sb); 658 if (error < 0) 659 return (errno); 660 661 if (!allcached && (lastname == NULL || 662 strcmp(name, lastname) == 0)) { 663 nvlist_add_string(nvlout, "cmd", "cache"); 664 fileargs_add_cache(nvlout, limits, name); 665 } else { 666 nvlist_add_string(nvlout, "cmd", "lstat"); 667 } 668 nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb)); 669 return (0); 670 } 671 672 static int 673 fileargs_command_realpath(const nvlist_t *limits, nvlist_t *nvlin, 674 nvlist_t *nvlout) 675 { 676 const char *pathname; 677 char *resolvedpath; 678 679 if (limits == NULL) 680 return (ENOTCAPABLE); 681 682 if (!fileargs_allowed(limits, nvlin, FA_REALPATH)) 683 return (ENOTCAPABLE); 684 685 pathname = nvlist_get_string(nvlin, "name"); 686 resolvedpath = realpath(pathname, NULL); 687 if (resolvedpath == NULL) 688 return (errno); 689 690 nvlist_move_string(nvlout, "realpath", resolvedpath); 691 return (0); 692 } 693 694 static int 695 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, 696 nvlist_t *nvlout) 697 { 698 int fd; 699 const char *name; 700 701 if (limits == NULL) 702 return (ENOTCAPABLE); 703 704 if (!fileargs_allowed(limits, nvlin, FA_OPEN)) 705 return (ENOTCAPABLE); 706 707 name = nvlist_get_string(nvlin, "name"); 708 709 fd = open_file(name); 710 if (fd < 0) 711 return (errno); 712 713 if (!allcached && (lastname == NULL || 714 strcmp(name, lastname) == 0)) { 715 nvlist_add_string(nvlout, "cmd", "cache"); 716 fileargs_add_cache(nvlout, limits, name); 717 } else { 718 nvlist_add_string(nvlout, "cmd", "open"); 719 } 720 nvlist_move_descriptor(nvlout, "fd", fd); 721 return (0); 722 } 723 724 static int 725 fileargs_command(const char *cmd, const nvlist_t *limits, 726 nvlist_t *nvlin, nvlist_t *nvlout) 727 { 728 729 if (strcmp(cmd, "open") == 0) 730 return (fileargs_command_open(limits, nvlin, nvlout)); 731 if (strcmp(cmd, "lstat") == 0) 732 return (fileargs_command_lstat(limits, nvlin, nvlout)); 733 if (strcmp(cmd, "realpath") == 0) 734 return (fileargs_command_realpath(limits, nvlin, nvlout)); 735 736 return (EINVAL); 737 } 738 739 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command, 740 CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS); 741