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
fileargs_get_lstat_cache(fileargs_t * fa,const char * name,struct stat * sb)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
fileargs_get_fd_cache(fileargs_t * fa,const char * name)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
fileargs_set_cache(fileargs_t * fa,nvlist_t * nvl)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*
fileargs_fetch(fileargs_t * fa,const char * name,const char * cmd)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 *
fileargs_create_limit(int argc,const char * const * argv,int flags,mode_t mode,cap_rights_t * rightsp,int operations)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 *
fileargs_create(cap_channel_t * chan,int fdflags)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 *
fileargs_init(int argc,char * argv[],int flags,mode_t mode,cap_rights_t * rightsp,int operations)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 *
fileargs_cinit(cap_channel_t * cas,int argc,char * argv[],int flags,mode_t mode,cap_rights_t * rightsp,int operations)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 *
fileargs_initnv(nvlist_t * limits)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 *
fileargs_cinitnv(cap_channel_t * cas,nvlist_t * limits)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
fileargs_open(fileargs_t * fa,const char * name)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 *
fileargs_fopen(fileargs_t * fa,const char * name,const char * mode)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
fileargs_lstat(fileargs_t * fa,const char * name,struct stat * sb)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 *
fileargs_realpath(fileargs_t * fa,const char * pathname,char * reserved_path)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
fileargs_free(fileargs_t * fa)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 *
fileargs_unwrap(fileargs_t * fa,int * flags)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 *
fileargs_wrap(cap_channel_t * chan,int fdflags)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
open_file(const char * name)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
fileargs_add_cache(nvlist_t * nvlout,const nvlist_t * limits,const char * current_name)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
fileargs_allowed(const nvlist_t * limits,const nvlist_t * request,int operation)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
fileargs_limit(const nvlist_t * oldlimits,const nvlist_t * newlimits)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
fileargs_command_lstat(const nvlist_t * limits,nvlist_t * nvlin,nvlist_t * nvlout)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
fileargs_command_realpath(const nvlist_t * limits,nvlist_t * nvlin,nvlist_t * nvlout)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
fileargs_command_open(const nvlist_t * limits,nvlist_t * nvlin,nvlist_t * nvlout)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
fileargs_command(const char * cmd,const nvlist_t * limits,nvlist_t * nvlin,nvlist_t * nvlout)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