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