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