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 #include <sys/types.h> 31 #include <sys/capsicum.h> 32 #include <sys/sysctl.h> 33 #include <sys/cnv.h> 34 #include <sys/dnv.h> 35 #include <sys/nv.h> 36 #include <sys/stat.h> 37 38 #include <assert.h> 39 #include <errno.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 44 #include <libcasper.h> 45 #include <libcasper_service.h> 46 47 #include "cap_fileargs.h" 48 49 #define CACHE_SIZE 128 50 51 #define FILEARGS_MAGIC 0xFA00FA00 52 53 struct fileargs { 54 uint32_t fa_magic; 55 nvlist_t *fa_cache; 56 cap_channel_t *fa_chann; 57 int fa_fdflags; 58 }; 59 60 static int 61 fileargs_get_lstat_cache(fileargs_t *fa, const char *name, struct stat *sb) 62 { 63 const nvlist_t *nvl; 64 size_t size; 65 const void *buf; 66 67 assert(fa != NULL); 68 assert(fa->fa_magic == FILEARGS_MAGIC); 69 assert(name != NULL); 70 71 if (fa->fa_cache == NULL) 72 return (-1); 73 74 nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL); 75 if (nvl == NULL) 76 return (-1); 77 78 if (!nvlist_exists_binary(nvl, "stat")) { 79 return (-1); 80 } 81 82 buf = nvlist_get_binary(nvl, "stat", &size); 83 assert(size == sizeof(*sb)); 84 memcpy(sb, buf, size); 85 86 return (0); 87 } 88 89 static int 90 fileargs_get_fd_cache(fileargs_t *fa, const char *name) 91 { 92 int fd; 93 const nvlist_t *nvl; 94 nvlist_t *tnvl; 95 96 assert(fa != NULL); 97 assert(fa->fa_magic == FILEARGS_MAGIC); 98 assert(name != NULL); 99 100 if (fa->fa_cache == NULL) 101 return (-1); 102 103 if ((fa->fa_fdflags & O_CREAT) != 0) 104 return (-1); 105 106 nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL); 107 if (nvl == NULL) 108 return (-1); 109 110 tnvl = nvlist_take_nvlist(fa->fa_cache, name); 111 112 if (!nvlist_exists_descriptor(tnvl, "fd")) { 113 nvlist_destroy(tnvl); 114 return (-1); 115 } 116 117 fd = nvlist_take_descriptor(tnvl, "fd"); 118 nvlist_destroy(tnvl); 119 120 if ((fa->fa_fdflags & O_CLOEXEC) != O_CLOEXEC) { 121 if (fcntl(fd, F_SETFD, fa->fa_fdflags) == -1) { 122 close(fd); 123 return (-1); 124 } 125 } 126 127 return (fd); 128 } 129 130 static void 131 fileargs_set_cache(fileargs_t *fa, nvlist_t *nvl) 132 { 133 134 nvlist_destroy(fa->fa_cache); 135 fa->fa_cache = nvl; 136 } 137 138 static nvlist_t* 139 fileargs_fetch(fileargs_t *fa, const char *name, const char *cmd) 140 { 141 nvlist_t *nvl; 142 int serrno; 143 144 assert(fa != NULL); 145 assert(name != NULL); 146 147 nvl = nvlist_create(NV_FLAG_NO_UNIQUE); 148 nvlist_add_string(nvl, "cmd", cmd); 149 nvlist_add_string(nvl, "name", name); 150 151 nvl = cap_xfer_nvlist(fa->fa_chann, nvl); 152 if (nvl == NULL) 153 return (NULL); 154 155 if (nvlist_get_number(nvl, "error") != 0) { 156 serrno = (int)nvlist_get_number(nvl, "error"); 157 nvlist_destroy(nvl); 158 errno = serrno; 159 return (NULL); 160 } 161 162 return (nvl); 163 } 164 165 static nvlist_t * 166 fileargs_create_limit(int argc, const char * const *argv, int flags, 167 mode_t mode, cap_rights_t *rightsp, int operations) 168 { 169 nvlist_t *limits; 170 int i; 171 172 limits = nvlist_create(NV_FLAG_NO_UNIQUE); 173 if (limits == NULL) 174 return (NULL); 175 176 nvlist_add_number(limits, "flags", flags); 177 nvlist_add_number(limits, "operations", operations); 178 if (rightsp != NULL) { 179 nvlist_add_binary(limits, "cap_rights", rightsp, 180 sizeof(*rightsp)); 181 } 182 if ((flags & O_CREAT) != 0) 183 nvlist_add_number(limits, "mode", (uint64_t)mode); 184 185 for (i = 0; i < argc; i++) { 186 if (strlen(argv[i]) >= MAXPATHLEN) { 187 nvlist_destroy(limits); 188 errno = ENAMETOOLONG; 189 return (NULL); 190 } 191 nvlist_add_null(limits, argv[i]); 192 } 193 194 return (limits); 195 } 196 197 static fileargs_t * 198 fileargs_create(cap_channel_t *chan, int fdflags) 199 { 200 fileargs_t *fa; 201 202 fa = malloc(sizeof(*fa)); 203 if (fa != NULL) { 204 fa->fa_cache = NULL; 205 fa->fa_chann = chan; 206 fa->fa_fdflags = fdflags; 207 fa->fa_magic = FILEARGS_MAGIC; 208 } 209 210 return (fa); 211 } 212 213 fileargs_t * 214 fileargs_init(int argc, char *argv[], int flags, mode_t mode, 215 cap_rights_t *rightsp, int operations) 216 { 217 nvlist_t *limits; 218 219 if (argc <= 0 || argv == NULL) { 220 return (fileargs_create(NULL, 0)); 221 } 222 223 limits = fileargs_create_limit(argc, (const char * const *)argv, flags, 224 mode, rightsp, operations); 225 if (limits == NULL) 226 return (NULL); 227 228 return (fileargs_initnv(limits)); 229 } 230 231 fileargs_t * 232 fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], int flags, 233 mode_t mode, cap_rights_t *rightsp, int operations) 234 { 235 nvlist_t *limits; 236 237 if (argc <= 0 || argv == NULL) { 238 return (fileargs_create(NULL, 0)); 239 } 240 241 limits = fileargs_create_limit(argc, (const char * const *)argv, flags, 242 mode, rightsp, operations); 243 if (limits == NULL) 244 return (NULL); 245 246 return (fileargs_cinitnv(cas, limits)); 247 } 248 249 fileargs_t * 250 fileargs_initnv(nvlist_t *limits) 251 { 252 cap_channel_t *cas; 253 fileargs_t *fa; 254 255 if (limits == NULL) { 256 return (fileargs_create(NULL, 0)); 257 } 258 259 cas = cap_init(); 260 if (cas == NULL) { 261 nvlist_destroy(limits); 262 return (NULL); 263 } 264 265 fa = fileargs_cinitnv(cas, limits); 266 cap_close(cas); 267 268 return (fa); 269 } 270 271 fileargs_t * 272 fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits) 273 { 274 cap_channel_t *chann; 275 fileargs_t *fa; 276 int flags, ret, serrno; 277 278 assert(cas != NULL); 279 280 if (limits == NULL) { 281 return (fileargs_create(NULL, 0)); 282 } 283 284 chann = NULL; 285 fa = NULL; 286 287 chann = cap_service_open(cas, "system.fileargs"); 288 if (chann == NULL) { 289 nvlist_destroy(limits); 290 return (NULL); 291 } 292 293 flags = nvlist_get_number(limits, "flags"); 294 (void)nvlist_get_number(limits, "operations"); 295 296 /* Limits are consumed no need to free them. */ 297 ret = cap_limit_set(chann, limits); 298 if (ret < 0) 299 goto out; 300 301 fa = fileargs_create(chann, flags); 302 if (fa == NULL) 303 goto out; 304 305 return (fa); 306 out: 307 serrno = errno; 308 if (chann != NULL) 309 cap_close(chann); 310 errno = serrno; 311 return (NULL); 312 } 313 314 int 315 fileargs_open(fileargs_t *fa, const char *name) 316 { 317 int fd; 318 nvlist_t *nvl; 319 char *cmd; 320 321 assert(fa != NULL); 322 assert(fa->fa_magic == FILEARGS_MAGIC); 323 324 if (name == NULL) { 325 errno = EINVAL; 326 return (-1); 327 } 328 329 if (fa->fa_chann == NULL) { 330 errno = ENOTCAPABLE; 331 return (-1); 332 } 333 334 fd = fileargs_get_fd_cache(fa, name); 335 if (fd != -1) 336 return (fd); 337 338 nvl = fileargs_fetch(fa, name, "open"); 339 if (nvl == NULL) 340 return (-1); 341 342 fd = nvlist_take_descriptor(nvl, "fd"); 343 cmd = nvlist_take_string(nvl, "cmd"); 344 if (strcmp(cmd, "cache") == 0) 345 fileargs_set_cache(fa, nvl); 346 else 347 nvlist_destroy(nvl); 348 free(cmd); 349 350 return (fd); 351 } 352 353 FILE * 354 fileargs_fopen(fileargs_t *fa, const char *name, const char *mode) 355 { 356 int fd; 357 358 if ((fd = fileargs_open(fa, name)) < 0) { 359 return (NULL); 360 } 361 362 return (fdopen(fd, mode)); 363 } 364 365 int 366 fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb) 367 { 368 nvlist_t *nvl; 369 const void *buf; 370 size_t size; 371 char *cmd; 372 373 assert(fa != NULL); 374 assert(fa->fa_magic == FILEARGS_MAGIC); 375 376 if (name == NULL) { 377 errno = EINVAL; 378 return (-1); 379 } 380 381 if (sb == NULL) { 382 errno = EFAULT; 383 return (-1); 384 } 385 386 if (fa->fa_chann == NULL) { 387 errno = ENOTCAPABLE; 388 return (-1); 389 } 390 391 if (fileargs_get_lstat_cache(fa, name, sb) != -1) 392 return (0); 393 394 nvl = fileargs_fetch(fa, name, "lstat"); 395 if (nvl == NULL) 396 return (-1); 397 398 buf = nvlist_get_binary(nvl, "stat", &size); 399 assert(size == sizeof(*sb)); 400 memcpy(sb, buf, size); 401 402 cmd = nvlist_take_string(nvl, "cmd"); 403 if (strcmp(cmd, "cache") == 0) 404 fileargs_set_cache(fa, nvl); 405 else 406 nvlist_destroy(nvl); 407 free(cmd); 408 409 return (0); 410 } 411 412 char * 413 fileargs_realpath(fileargs_t *fa, const char *pathname, char *reserved_path) 414 { 415 nvlist_t *nvl; 416 char *ret; 417 418 assert(fa != NULL); 419 assert(fa->fa_magic == FILEARGS_MAGIC); 420 421 if (pathname == NULL) { 422 errno = EINVAL; 423 return (NULL); 424 } 425 426 if (fa->fa_chann == NULL) { 427 errno = ENOTCAPABLE; 428 return (NULL); 429 } 430 431 nvl = fileargs_fetch(fa, pathname, "realpath"); 432 if (nvl == NULL) 433 return (NULL); 434 435 if (reserved_path != NULL) { 436 ret = reserved_path; 437 strcpy(reserved_path, 438 nvlist_get_string(nvl, "realpath")); 439 } else { 440 ret = nvlist_take_string(nvl, "realpath"); 441 } 442 nvlist_destroy(nvl); 443 444 return (ret); 445 } 446 447 void 448 fileargs_free(fileargs_t *fa) 449 { 450 451 if (fa == NULL) 452 return; 453 454 assert(fa->fa_magic == FILEARGS_MAGIC); 455 456 nvlist_destroy(fa->fa_cache); 457 if (fa->fa_chann != NULL) { 458 cap_close(fa->fa_chann); 459 } 460 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 461 free(fa); 462 } 463 464 cap_channel_t * 465 fileargs_unwrap(fileargs_t *fa, int *flags) 466 { 467 cap_channel_t *chan; 468 469 if (fa == NULL) 470 return (NULL); 471 472 assert(fa->fa_magic == FILEARGS_MAGIC); 473 474 chan = fa->fa_chann; 475 if (flags != NULL) { 476 *flags = fa->fa_fdflags; 477 } 478 479 nvlist_destroy(fa->fa_cache); 480 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 481 free(fa); 482 483 return (chan); 484 } 485 486 fileargs_t * 487 fileargs_wrap(cap_channel_t *chan, int fdflags) 488 { 489 490 if (chan == NULL) { 491 return (NULL); 492 } 493 494 return (fileargs_create(chan, fdflags)); 495 } 496 497 /* 498 * Service functions. 499 */ 500 501 static const char *lastname; 502 static void *cacheposition; 503 static bool allcached; 504 static const cap_rights_t *caprightsp; 505 static int capflags; 506 static int allowed_operations; 507 static mode_t capmode; 508 509 static int 510 open_file(const char *name) 511 { 512 int fd, serrno; 513 514 if ((capflags & O_CREAT) == 0) 515 fd = open(name, capflags); 516 else 517 fd = open(name, capflags, capmode); 518 if (fd < 0) 519 return (-1); 520 521 if (caprightsp != NULL) { 522 if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) { 523 serrno = errno; 524 close(fd); 525 errno = serrno; 526 return (-1); 527 } 528 } 529 530 return (fd); 531 } 532 533 static void 534 fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits, 535 const char *current_name) 536 { 537 int type, i, fd; 538 void *cookie; 539 nvlist_t *new; 540 const char *fname; 541 struct stat sb; 542 543 if ((capflags & O_CREAT) != 0) { 544 allcached = true; 545 return; 546 } 547 548 cookie = cacheposition; 549 for (i = 0; i < CACHE_SIZE + 1; i++) { 550 fname = nvlist_next(limits, &type, &cookie); 551 if (fname == NULL) { 552 cacheposition = NULL; 553 lastname = NULL; 554 allcached = true; 555 return; 556 } 557 /* We doing that to catch next element name. */ 558 if (i == CACHE_SIZE) { 559 break; 560 } 561 562 if (type != NV_TYPE_NULL) { 563 i--; 564 continue; 565 } 566 if (current_name != NULL && 567 strcmp(fname, current_name) == 0) { 568 current_name = NULL; 569 i--; 570 continue; 571 } 572 573 new = nvlist_create(NV_FLAG_NO_UNIQUE); 574 if ((allowed_operations & FA_OPEN) != 0) { 575 fd = open_file(fname); 576 if (fd < 0) { 577 i--; 578 nvlist_destroy(new); 579 continue; 580 } 581 nvlist_move_descriptor(new, "fd", fd); 582 } 583 if ((allowed_operations & FA_LSTAT) != 0) { 584 if (lstat(fname, &sb) < 0) { 585 i--; 586 nvlist_destroy(new); 587 continue; 588 } 589 nvlist_add_binary(new, "stat", &sb, sizeof(sb)); 590 } 591 592 nvlist_move_nvlist(nvlout, fname, new); 593 } 594 cacheposition = cookie; 595 lastname = fname; 596 } 597 598 static bool 599 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation) 600 { 601 const char *name; 602 603 if ((allowed_operations & operation) == 0) 604 return (false); 605 606 name = dnvlist_get_string(request, "name", NULL); 607 if (name == NULL) 608 return (false); 609 610 /* Fast path. */ 611 if (lastname != NULL && strcmp(name, lastname) == 0) 612 return (true); 613 614 if (!nvlist_exists_null(limits, name)) 615 return (false); 616 617 return (true); 618 } 619 620 static int 621 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 622 { 623 624 if (oldlimits != NULL) 625 return (ENOTCAPABLE); 626 627 capflags = (int)dnvlist_get_number(newlimits, "flags", 0); 628 allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0); 629 if ((capflags & O_CREAT) != 0) 630 capmode = (mode_t)nvlist_get_number(newlimits, "mode"); 631 else 632 capmode = 0; 633 634 caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0); 635 636 return (0); 637 } 638 639 static int 640 fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin, 641 nvlist_t *nvlout) 642 { 643 int error; 644 const char *name; 645 struct stat sb; 646 647 if (limits == NULL) 648 return (ENOTCAPABLE); 649 650 if (!fileargs_allowed(limits, nvlin, FA_LSTAT)) 651 return (ENOTCAPABLE); 652 653 name = nvlist_get_string(nvlin, "name"); 654 655 error = lstat(name, &sb); 656 if (error < 0) 657 return (errno); 658 659 if (!allcached && (lastname == NULL || 660 strcmp(name, lastname) == 0)) { 661 nvlist_add_string(nvlout, "cmd", "cache"); 662 fileargs_add_cache(nvlout, limits, name); 663 } else { 664 nvlist_add_string(nvlout, "cmd", "lstat"); 665 } 666 nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb)); 667 return (0); 668 } 669 670 static int 671 fileargs_command_realpath(const nvlist_t *limits, nvlist_t *nvlin, 672 nvlist_t *nvlout) 673 { 674 const char *pathname; 675 char *resolvedpath; 676 677 if (limits == NULL) 678 return (ENOTCAPABLE); 679 680 if (!fileargs_allowed(limits, nvlin, FA_REALPATH)) 681 return (ENOTCAPABLE); 682 683 pathname = nvlist_get_string(nvlin, "name"); 684 resolvedpath = realpath(pathname, NULL); 685 if (resolvedpath == NULL) 686 return (errno); 687 688 nvlist_move_string(nvlout, "realpath", resolvedpath); 689 return (0); 690 } 691 692 static int 693 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, 694 nvlist_t *nvlout) 695 { 696 int fd; 697 const char *name; 698 699 if (limits == NULL) 700 return (ENOTCAPABLE); 701 702 if (!fileargs_allowed(limits, nvlin, FA_OPEN)) 703 return (ENOTCAPABLE); 704 705 name = nvlist_get_string(nvlin, "name"); 706 707 fd = open_file(name); 708 if (fd < 0) 709 return (errno); 710 711 if (!allcached && (lastname == NULL || 712 strcmp(name, lastname) == 0)) { 713 nvlist_add_string(nvlout, "cmd", "cache"); 714 fileargs_add_cache(nvlout, limits, name); 715 } else { 716 nvlist_add_string(nvlout, "cmd", "open"); 717 } 718 nvlist_move_descriptor(nvlout, "fd", fd); 719 return (0); 720 } 721 722 static int 723 fileargs_command(const char *cmd, const nvlist_t *limits, 724 nvlist_t *nvlin, nvlist_t *nvlout) 725 { 726 727 if (strcmp(cmd, "open") == 0) 728 return (fileargs_command_open(limits, nvlin, nvlout)); 729 if (strcmp(cmd, "lstat") == 0) 730 return (fileargs_command_lstat(limits, nvlin, nvlout)); 731 if (strcmp(cmd, "realpath") == 0) 732 return (fileargs_command_realpath(limits, nvlin, nvlout)); 733 734 return (EINVAL); 735 } 736 737 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command, 738 CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS); 739