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 39 #include <assert.h> 40 #include <errno.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 45 #include <libcasper.h> 46 #include <libcasper_service.h> 47 48 #include "cap_fileargs.h" 49 50 #define CACHE_SIZE 128 51 52 #define FILEARGS_MAGIC 0xFA00FA00 53 54 struct fileargs { 55 uint32_t fa_magic; 56 nvlist_t *fa_cache; 57 cap_channel_t *fa_chann; 58 int fa_fdflags; 59 }; 60 61 static int 62 fileargs_get_cache(fileargs_t *fa, const char *name) 63 { 64 int fd; 65 const nvlist_t *nvl; 66 nvlist_t *tnvl; 67 68 assert(fa != NULL); 69 assert(fa->fa_magic == FILEARGS_MAGIC); 70 assert(name != NULL); 71 72 if (fa->fa_cache == NULL) 73 return (-1); 74 75 if ((fa->fa_fdflags & O_CREAT) != 0) 76 return (-1); 77 78 nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL); 79 if (nvl == NULL) 80 return (-1); 81 82 tnvl = nvlist_take_nvlist(fa->fa_cache, name); 83 fd = nvlist_take_descriptor(tnvl, "fd"); 84 nvlist_destroy(tnvl); 85 86 if ((fa->fa_fdflags & O_CLOEXEC) != O_CLOEXEC) { 87 if (fcntl(fd, F_SETFD, fa->fa_fdflags) == -1) { 88 close(fd); 89 return (-1); 90 } 91 } 92 93 return (fd); 94 } 95 96 static void 97 fileargs_set_cache(fileargs_t *fa, nvlist_t *nvl) 98 { 99 100 nvlist_destroy(fa->fa_cache); 101 fa->fa_cache = nvl; 102 } 103 104 static nvlist_t* 105 fileargs_fetch(fileargs_t *fa, const char *name) 106 { 107 nvlist_t *nvl; 108 int serrno; 109 110 assert(fa != NULL); 111 assert(name != NULL); 112 113 nvl = nvlist_create(NV_FLAG_NO_UNIQUE); 114 nvlist_add_string(nvl, "cmd", "open"); 115 nvlist_add_string(nvl, "name", name); 116 117 nvl = cap_xfer_nvlist(fa->fa_chann, nvl); 118 if (nvl == NULL) 119 return (NULL); 120 121 if (nvlist_get_number(nvl, "error") != 0) { 122 serrno = (int)nvlist_get_number(nvl, "error"); 123 nvlist_destroy(nvl); 124 errno = serrno; 125 return (NULL); 126 } 127 128 return (nvl); 129 } 130 131 static nvlist_t * 132 fileargs_create_limit(int argc, const char * const *argv, int flags, 133 mode_t mode, cap_rights_t *rightsp) 134 { 135 nvlist_t *limits; 136 int i; 137 138 limits = nvlist_create(NV_FLAG_NO_UNIQUE); 139 if (limits == NULL) 140 return (NULL); 141 142 nvlist_add_number(limits, "flags", flags); 143 if (rightsp != NULL) { 144 nvlist_add_binary(limits, "cap_rights", rightsp, 145 sizeof(*rightsp)); 146 } 147 if ((flags & O_CREAT) != 0) 148 nvlist_add_number(limits, "mode", (uint64_t)mode); 149 150 for (i = 0; i < argc; i++) { 151 nvlist_add_null(limits, argv[i]); 152 } 153 154 return (limits); 155 } 156 157 static fileargs_t * 158 fileargs_create(cap_channel_t *chan, int fdflags) 159 { 160 fileargs_t *fa; 161 162 fa = malloc(sizeof(*fa)); 163 if (fa != NULL) { 164 fa->fa_cache = NULL; 165 fa->fa_chann = chan; 166 fa->fa_fdflags = fdflags; 167 fa->fa_magic = FILEARGS_MAGIC; 168 } 169 170 return (fa); 171 } 172 173 fileargs_t * 174 fileargs_init(int argc, char *argv[], int flags, mode_t mode, 175 cap_rights_t *rightsp) 176 { 177 nvlist_t *limits; 178 179 if (argc <= 0 || argv == NULL) { 180 return (fileargs_create(NULL, 0)); 181 } 182 183 limits = fileargs_create_limit(argc, (const char * const *)argv, flags, 184 mode, rightsp); 185 if (limits == NULL) 186 return (NULL); 187 188 return (fileargs_initnv(limits)); 189 } 190 191 fileargs_t * 192 fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], int flags, 193 mode_t mode, cap_rights_t *rightsp) 194 { 195 nvlist_t *limits; 196 197 if (argc <= 0 || argv == NULL) { 198 return (fileargs_create(NULL, 0)); 199 } 200 201 limits = fileargs_create_limit(argc, (const char * const *)argv, flags, 202 mode, rightsp); 203 if (limits == NULL) 204 return (NULL); 205 206 return (fileargs_cinitnv(cas, limits)); 207 } 208 209 fileargs_t * 210 fileargs_initnv(nvlist_t *limits) 211 { 212 cap_channel_t *cas; 213 fileargs_t *fa; 214 215 if (limits == NULL) { 216 return (fileargs_create(NULL, 0)); 217 } 218 219 cas = cap_init(); 220 if (cas == NULL) { 221 nvlist_destroy(limits); 222 return (NULL); 223 } 224 225 fa = fileargs_cinitnv(cas, limits); 226 cap_close(cas); 227 228 return (fa); 229 } 230 231 fileargs_t * 232 fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits) 233 { 234 cap_channel_t *chann; 235 fileargs_t *fa; 236 int serrno, ret; 237 int flags; 238 239 assert(cas != NULL); 240 241 if (limits == NULL) { 242 return (fileargs_create(NULL, 0)); 243 } 244 245 chann = NULL; 246 fa = NULL; 247 248 chann = cap_service_open(cas, "system.fileargs"); 249 if (chann == NULL) { 250 nvlist_destroy(limits); 251 return (NULL); 252 } 253 254 flags = nvlist_get_number(limits, "flags"); 255 256 /* Limits are consumed no need to free them. */ 257 ret = cap_limit_set(chann, limits); 258 if (ret < 0) 259 goto out; 260 261 fa = fileargs_create(chann, flags); 262 if (fa == NULL) 263 goto out; 264 265 return (fa); 266 out: 267 serrno = errno; 268 if (chann != NULL) 269 cap_close(chann); 270 errno = serrno; 271 return (NULL); 272 } 273 274 int 275 fileargs_open(fileargs_t *fa, const char *name) 276 { 277 int fd; 278 nvlist_t *nvl; 279 char *cmd; 280 281 assert(fa != NULL); 282 assert(fa->fa_magic == FILEARGS_MAGIC); 283 284 if (name == NULL) { 285 errno = EINVAL; 286 return (-1); 287 } 288 289 if (fa->fa_chann == NULL) { 290 errno = ENOTCAPABLE; 291 return (-1); 292 } 293 294 fd = fileargs_get_cache(fa, name); 295 if (fd != -1) 296 return (fd); 297 298 nvl = fileargs_fetch(fa, name); 299 if (nvl == NULL) 300 return (-1); 301 302 fd = nvlist_take_descriptor(nvl, "fd"); 303 cmd = nvlist_take_string(nvl, "cmd"); 304 if (strcmp(cmd, "cache") == 0) 305 fileargs_set_cache(fa, nvl); 306 else 307 nvlist_destroy(nvl); 308 free(cmd); 309 310 return (fd); 311 } 312 313 FILE * 314 fileargs_fopen(fileargs_t *fa, const char *name, const char *mode) 315 { 316 int fd; 317 318 if ((fd = fileargs_open(fa, name)) < 0) { 319 return (NULL); 320 } 321 322 return (fdopen(fd, mode)); 323 } 324 325 void 326 fileargs_free(fileargs_t *fa) 327 { 328 329 if (fa == NULL) 330 return; 331 332 assert(fa->fa_magic == FILEARGS_MAGIC); 333 334 nvlist_destroy(fa->fa_cache); 335 if (fa->fa_chann != NULL) { 336 cap_close(fa->fa_chann); 337 } 338 explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); 339 free(fa); 340 } 341 342 /* 343 * Service functions. 344 */ 345 346 static const char *lastname; 347 static void *cacheposition; 348 static bool allcached; 349 static const cap_rights_t *caprightsp; 350 static int capflags; 351 static mode_t capmode; 352 353 static int 354 open_file(const char *name) 355 { 356 int fd, serrno; 357 358 if ((capflags & O_CREAT) == 0) 359 fd = open(name, capflags); 360 else 361 fd = open(name, capflags, capmode); 362 if (fd < 0) 363 return (-1); 364 365 if (caprightsp != NULL) { 366 if (cap_rights_limit(fd, caprightsp) < 0) { 367 serrno = errno; 368 close(fd); 369 errno = serrno; 370 return (-1); 371 } 372 } 373 374 return (fd); 375 } 376 377 static void 378 fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits, 379 const char *curent_name) 380 { 381 int type, i, fd; 382 void *cookie; 383 nvlist_t *new; 384 const char *fname; 385 386 if ((capflags & O_CREAT) != 0) { 387 allcached = true; 388 return; 389 } 390 391 cookie = cacheposition; 392 for (i = 0; i < CACHE_SIZE + 1; i++) { 393 fname = nvlist_next(limits, &type, &cookie); 394 if (fname == NULL) { 395 cacheposition = NULL; 396 lastname = NULL; 397 allcached = true; 398 return; 399 } 400 /* We doing that to catch next element name. */ 401 if (i == CACHE_SIZE) { 402 break; 403 } 404 405 if (type != NV_TYPE_NULL || 406 (curent_name != NULL && strcmp(fname, curent_name) == 0)) { 407 curent_name = NULL; 408 i--; 409 continue; 410 } 411 412 fd = open_file(fname); 413 if (fd < 0) { 414 i--; 415 continue; 416 } 417 418 new = nvlist_create(NV_FLAG_NO_UNIQUE); 419 nvlist_move_descriptor(new, "fd", fd); 420 nvlist_add_nvlist(nvlout, fname, new); 421 } 422 cacheposition = cookie; 423 lastname = fname; 424 } 425 426 static bool 427 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request) 428 { 429 const char *name; 430 431 name = dnvlist_get_string(request, "name", NULL); 432 if (name == NULL) 433 return (false); 434 435 /* Fast path. */ 436 if (lastname != NULL && strcmp(name, lastname) == 0) 437 return (true); 438 439 if (!nvlist_exists_null(limits, name)) 440 return (false); 441 442 return (true); 443 } 444 445 static int 446 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 447 { 448 449 if (oldlimits != NULL) 450 return (ENOTCAPABLE); 451 452 capflags = (int)dnvlist_get_number(newlimits, "flags", 0); 453 if ((capflags & O_CREAT) != 0) 454 capmode = (mode_t)nvlist_get_number(newlimits, "mode"); 455 else 456 capmode = 0; 457 458 caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0); 459 460 return (0); 461 } 462 463 static int 464 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, 465 nvlist_t *nvlout) 466 { 467 int fd; 468 const char *name; 469 470 if (limits == NULL) 471 return (ENOTCAPABLE); 472 473 if (!fileargs_allowed(limits, nvlin)) 474 return (ENOTCAPABLE); 475 476 name = nvlist_get_string(nvlin, "name"); 477 478 fd = open_file(name); 479 if (fd < 0) 480 return (errno); 481 482 if (!allcached && (lastname == NULL || 483 strcmp(name, lastname) == 0)) { 484 nvlist_add_string(nvlout, "cmd", "cache"); 485 fileargs_add_cache(nvlout, limits, name); 486 } else { 487 nvlist_add_string(nvlout, "cmd", "open"); 488 } 489 nvlist_move_descriptor(nvlout, "fd", fd); 490 return (0); 491 } 492 493 static int 494 fileargs_command(const char *cmd, const nvlist_t *limits, 495 nvlist_t *nvlin, nvlist_t *nvlout) 496 { 497 498 if (strcmp(cmd, "open") == 0) 499 return (fileargs_command_open(limits, nvlin, nvlout)); 500 501 return (EINVAL); 502 } 503 504 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command, 505 CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS); 506