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 || (current_name != NULL && 566 strcmp(fname, current_name) == 0)) { 567 current_name = NULL; 568 i--; 569 continue; 570 } 571 572 new = nvlist_create(NV_FLAG_NO_UNIQUE); 573 if ((allowed_operations & FA_OPEN) != 0) { 574 fd = open_file(fname); 575 if (fd < 0) { 576 i--; 577 nvlist_destroy(new); 578 continue; 579 } 580 nvlist_move_descriptor(new, "fd", fd); 581 } 582 if ((allowed_operations & FA_LSTAT) != 0) { 583 if (lstat(fname, &sb) < 0) { 584 i--; 585 nvlist_destroy(new); 586 continue; 587 } 588 nvlist_add_binary(new, "stat", &sb, sizeof(sb)); 589 } 590 591 nvlist_move_nvlist(nvlout, fname, new); 592 } 593 cacheposition = cookie; 594 lastname = fname; 595 } 596 597 static bool 598 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation) 599 { 600 const char *name; 601 602 if ((allowed_operations & operation) == 0) 603 return (false); 604 605 name = dnvlist_get_string(request, "name", NULL); 606 if (name == NULL) 607 return (false); 608 609 /* Fast path. */ 610 if (lastname != NULL && strcmp(name, lastname) == 0) 611 return (true); 612 613 if (!nvlist_exists_null(limits, name)) 614 return (false); 615 616 return (true); 617 } 618 619 static int 620 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 621 { 622 623 if (oldlimits != NULL) 624 return (ENOTCAPABLE); 625 626 capflags = (int)dnvlist_get_number(newlimits, "flags", 0); 627 allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0); 628 if ((capflags & O_CREAT) != 0) 629 capmode = (mode_t)nvlist_get_number(newlimits, "mode"); 630 else 631 capmode = 0; 632 633 caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0); 634 635 return (0); 636 } 637 638 static int 639 fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin, 640 nvlist_t *nvlout) 641 { 642 int error; 643 const char *name; 644 struct stat sb; 645 646 if (limits == NULL) 647 return (ENOTCAPABLE); 648 649 if (!fileargs_allowed(limits, nvlin, FA_LSTAT)) 650 return (ENOTCAPABLE); 651 652 name = nvlist_get_string(nvlin, "name"); 653 654 error = lstat(name, &sb); 655 if (error < 0) 656 return (errno); 657 658 if (!allcached && (lastname == NULL || 659 strcmp(name, lastname) == 0)) { 660 nvlist_add_string(nvlout, "cmd", "cache"); 661 fileargs_add_cache(nvlout, limits, name); 662 } else { 663 nvlist_add_string(nvlout, "cmd", "lstat"); 664 } 665 nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb)); 666 return (0); 667 } 668 669 static int 670 fileargs_command_realpath(const nvlist_t *limits, nvlist_t *nvlin, 671 nvlist_t *nvlout) 672 { 673 const char *pathname; 674 char *resolvedpath; 675 676 if (limits == NULL) 677 return (ENOTCAPABLE); 678 679 if (!fileargs_allowed(limits, nvlin, FA_REALPATH)) 680 return (ENOTCAPABLE); 681 682 pathname = nvlist_get_string(nvlin, "name"); 683 resolvedpath = realpath(pathname, NULL); 684 if (resolvedpath == NULL) 685 return (errno); 686 687 nvlist_move_string(nvlout, "realpath", resolvedpath); 688 return (0); 689 } 690 691 static int 692 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, 693 nvlist_t *nvlout) 694 { 695 int fd; 696 const char *name; 697 698 if (limits == NULL) 699 return (ENOTCAPABLE); 700 701 if (!fileargs_allowed(limits, nvlin, FA_OPEN)) 702 return (ENOTCAPABLE); 703 704 name = nvlist_get_string(nvlin, "name"); 705 706 fd = open_file(name); 707 if (fd < 0) 708 return (errno); 709 710 if (!allcached && (lastname == NULL || 711 strcmp(name, lastname) == 0)) { 712 nvlist_add_string(nvlout, "cmd", "cache"); 713 fileargs_add_cache(nvlout, limits, name); 714 } else { 715 nvlist_add_string(nvlout, "cmd", "open"); 716 } 717 nvlist_move_descriptor(nvlout, "fd", fd); 718 return (0); 719 } 720 721 static int 722 fileargs_command(const char *cmd, const nvlist_t *limits, 723 nvlist_t *nvlin, nvlist_t *nvlout) 724 { 725 726 if (strcmp(cmd, "open") == 0) 727 return (fileargs_command_open(limits, nvlin, nvlout)); 728 if (strcmp(cmd, "lstat") == 0) 729 return (fileargs_command_lstat(limits, nvlin, nvlout)); 730 if (strcmp(cmd, "realpath") == 0) 731 return (fileargs_command_realpath(limits, nvlin, nvlout)); 732 733 return (EINVAL); 734 } 735 736 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command, 737 CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS); 738