xref: /freebsd/lib/libcasper/services/cap_fileargs/cap_fileargs.c (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
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