xref: /freebsd/lib/libcasper/services/cap_fileargs/cap_fileargs.c (revision b9f654b163bce26de79705e77b872427c9f2afa1)
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 #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 		nvlist_add_null(limits, argv[i]);
189 	}
190 
191 	return (limits);
192 }
193 
194 static fileargs_t *
195 fileargs_create(cap_channel_t *chan, int fdflags)
196 {
197 	fileargs_t *fa;
198 
199 	fa = malloc(sizeof(*fa));
200 	if (fa != NULL) {
201 		fa->fa_cache = NULL;
202 		fa->fa_chann = chan;
203 		fa->fa_fdflags = fdflags;
204 		fa->fa_magic = FILEARGS_MAGIC;
205 	}
206 
207 	return (fa);
208 }
209 
210 fileargs_t *
211 fileargs_init(int argc, char *argv[], int flags, mode_t mode,
212     cap_rights_t *rightsp, int operations)
213 {
214 	nvlist_t *limits;
215 
216 	if (argc <= 0 || argv == NULL) {
217 		return (fileargs_create(NULL, 0));
218 	}
219 
220 	limits = fileargs_create_limit(argc, (const char * const *)argv, flags,
221 	   mode, rightsp, operations);
222 	if (limits == NULL)
223 		return (NULL);
224 
225 	return (fileargs_initnv(limits));
226 }
227 
228 fileargs_t *
229 fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], int flags,
230      mode_t mode, cap_rights_t *rightsp, int operations)
231 {
232 	nvlist_t *limits;
233 
234 	if (argc <= 0 || argv == NULL) {
235 		return (fileargs_create(NULL, 0));
236 	}
237 
238 	limits = fileargs_create_limit(argc, (const char * const *)argv, flags,
239 	   mode, rightsp, operations);
240 	if (limits == NULL)
241 		return (NULL);
242 
243 	return (fileargs_cinitnv(cas, limits));
244 }
245 
246 fileargs_t *
247 fileargs_initnv(nvlist_t *limits)
248 {
249         cap_channel_t *cas;
250 	fileargs_t *fa;
251 
252 	if (limits == NULL) {
253 		return (fileargs_create(NULL, 0));
254 	}
255 
256         cas = cap_init();
257         if (cas == NULL) {
258 		nvlist_destroy(limits);
259                 return (NULL);
260 	}
261 
262         fa = fileargs_cinitnv(cas, limits);
263         cap_close(cas);
264 
265 	return (fa);
266 }
267 
268 fileargs_t *
269 fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits)
270 {
271 	cap_channel_t *chann;
272 	fileargs_t *fa;
273 	int serrno, ret;
274 	int flags, operations;
275 
276 	assert(cas != NULL);
277 
278 	if (limits == NULL) {
279 		return (fileargs_create(NULL, 0));
280 	}
281 
282 	chann = NULL;
283 	fa = NULL;
284 
285 	chann = cap_service_open(cas, "system.fileargs");
286 	if (chann == NULL) {
287 		nvlist_destroy(limits);
288 		return (NULL);
289 	}
290 
291 	flags = nvlist_get_number(limits, "flags");
292 	operations = nvlist_get_number(limits, "operations");
293 
294 	/* Limits are consumed no need to free them. */
295 	ret = cap_limit_set(chann, limits);
296 	if (ret < 0)
297 		goto out;
298 
299 	fa = fileargs_create(chann, flags);
300 	if (fa == NULL)
301 		goto out;
302 
303 	return (fa);
304 out:
305 	serrno = errno;
306 	if (chann != NULL)
307 		cap_close(chann);
308 	errno = serrno;
309 	return (NULL);
310 }
311 
312 int
313 fileargs_open(fileargs_t *fa, const char *name)
314 {
315 	int fd;
316 	nvlist_t *nvl;
317 	char *cmd;
318 
319 	assert(fa != NULL);
320 	assert(fa->fa_magic == FILEARGS_MAGIC);
321 
322 	if (name == NULL) {
323 		errno = EINVAL;
324 		return (-1);
325 	}
326 
327 	if (fa->fa_chann == NULL) {
328 		errno = ENOTCAPABLE;
329 		return (-1);
330 	}
331 
332 	fd = fileargs_get_fd_cache(fa, name);
333 	if (fd != -1)
334 		return (fd);
335 
336 	nvl = fileargs_fetch(fa, name, "open");
337 	if (nvl == NULL)
338 		return (-1);
339 
340 	fd = nvlist_take_descriptor(nvl, "fd");
341 	cmd = nvlist_take_string(nvl, "cmd");
342 	if (strcmp(cmd, "cache") == 0)
343 		fileargs_set_cache(fa, nvl);
344 	else
345 		nvlist_destroy(nvl);
346 	free(cmd);
347 
348 	return (fd);
349 }
350 
351 FILE *
352 fileargs_fopen(fileargs_t *fa, const char *name, const char *mode)
353 {
354 	int fd;
355 
356 	if ((fd = fileargs_open(fa, name)) < 0) {
357 		return (NULL);
358 	}
359 
360 	return (fdopen(fd, mode));
361 }
362 
363 int
364 fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb)
365 {
366 	nvlist_t *nvl;
367 	const void *buf;
368 	size_t size;
369 	char *cmd;
370 
371 	assert(fa != NULL);
372 	assert(fa->fa_magic == FILEARGS_MAGIC);
373 
374 	if (name == NULL) {
375 		errno = EINVAL;
376 		return (-1);
377 	}
378 
379 	if (sb == NULL) {
380 		errno = EFAULT;
381 		return (-1);
382 	}
383 
384 	if (fa->fa_chann == NULL) {
385 		errno = ENOTCAPABLE;
386 		return (-1);
387 	}
388 
389 	if (fileargs_get_lstat_cache(fa, name, sb) != -1)
390 		return (0);
391 
392 	nvl = fileargs_fetch(fa, name, "lstat");
393 	if (nvl == NULL)
394 		return (-1);
395 
396 	buf = nvlist_get_binary(nvl, "stat", &size);
397 	assert(size == sizeof(*sb));
398 	memcpy(sb, buf, size);
399 
400 	cmd = nvlist_take_string(nvl, "cmd");
401 	if (strcmp(cmd, "cache") == 0)
402 		fileargs_set_cache(fa, nvl);
403 	else
404 		nvlist_destroy(nvl);
405 	free(cmd);
406 
407 	return (0);
408 }
409 
410 void
411 fileargs_free(fileargs_t *fa)
412 {
413 
414 	if (fa == NULL)
415 		return;
416 
417 	assert(fa->fa_magic == FILEARGS_MAGIC);
418 
419 	nvlist_destroy(fa->fa_cache);
420 	if (fa->fa_chann != NULL) {
421 		cap_close(fa->fa_chann);
422 	}
423 	explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic));
424 	free(fa);
425 }
426 
427 cap_channel_t *
428 fileargs_unwrap(fileargs_t *fa, int *flags)
429 {
430 	cap_channel_t *chan;
431 
432 	if (fa == NULL)
433 		return (NULL);
434 
435 	assert(fa->fa_magic == FILEARGS_MAGIC);
436 
437 	chan = fa->fa_chann;
438 	if (flags != NULL) {
439 		*flags = fa->fa_fdflags;
440 	}
441 
442 	nvlist_destroy(fa->fa_cache);
443 	explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic));
444 	free(fa);
445 
446 	return (chan);
447 }
448 
449 fileargs_t *
450 fileargs_wrap(cap_channel_t *chan, int fdflags)
451 {
452 
453 	if (chan == NULL) {
454 		return (NULL);
455 	}
456 
457 	return (fileargs_create(chan, fdflags));
458 }
459 
460 /*
461  * Service functions.
462  */
463 
464 static const char *lastname;
465 static void *cacheposition;
466 static bool allcached;
467 static const cap_rights_t *caprightsp;
468 static int capflags;
469 static int allowed_operations;
470 static mode_t capmode;
471 
472 static int
473 open_file(const char *name)
474 {
475 	int fd, serrno;
476 
477 	if ((capflags & O_CREAT) == 0)
478 		fd = open(name, capflags);
479 	else
480 		fd = open(name, capflags, capmode);
481 	if (fd < 0)
482 		return (-1);
483 
484 	if (caprightsp != NULL) {
485 		if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) {
486 			serrno = errno;
487 			close(fd);
488 			errno = serrno;
489 			return (-1);
490 		}
491 	}
492 
493 	return (fd);
494 }
495 
496 static void
497 fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits,
498     const char *curent_name)
499 {
500 	int type, i, fd;
501 	void *cookie;
502 	nvlist_t *new;
503 	const char *fname;
504 	struct stat sb;
505 
506 	if ((capflags & O_CREAT) != 0) {
507 		allcached = true;
508 		return;
509 	}
510 
511 	cookie = cacheposition;
512 	for (i = 0; i < CACHE_SIZE + 1; i++) {
513 		fname = nvlist_next(limits, &type, &cookie);
514 		if (fname == NULL) {
515 			cacheposition = NULL;
516 			lastname = NULL;
517 			allcached = true;
518 			return;
519 		}
520 		/* We doing that to catch next element name. */
521 		if (i == CACHE_SIZE) {
522 			break;
523 		}
524 
525 		if (type != NV_TYPE_NULL ||
526 		    (curent_name != NULL && strcmp(fname, curent_name) == 0)) {
527 			curent_name = NULL;
528 			i--;
529 			continue;
530 		}
531 
532 		new = nvlist_create(NV_FLAG_NO_UNIQUE);
533 		if ((allowed_operations & FA_OPEN) != 0) {
534 			fd = open_file(fname);
535 			if (fd < 0) {
536 				i--;
537 				nvlist_destroy(new);
538 				continue;
539 			}
540 			nvlist_move_descriptor(new, "fd", fd);
541 		}
542 		if ((allowed_operations & FA_LSTAT) != 0) {
543 			if (lstat(fname, &sb) < 0) {
544 				i--;
545 				nvlist_destroy(new);
546 				continue;
547 			}
548 			nvlist_add_binary(new, "stat", &sb, sizeof(sb));
549 		}
550 
551 		nvlist_add_nvlist(nvlout, fname, new);
552 	}
553 	cacheposition = cookie;
554 	lastname = fname;
555 }
556 
557 static bool
558 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation)
559 {
560 	const char *name;
561 
562 	if ((allowed_operations & operation) == 0)
563 		return (false);
564 
565 	name = dnvlist_get_string(request, "name", NULL);
566 	if (name == NULL)
567 		return (false);
568 
569 	/* Fast path. */
570 	if (lastname != NULL && strcmp(name, lastname) == 0)
571 		return (true);
572 
573 	if (!nvlist_exists_null(limits, name))
574 		return (false);
575 
576 	return (true);
577 }
578 
579 static int
580 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
581 {
582 
583 	if (oldlimits != NULL)
584 		return (ENOTCAPABLE);
585 
586 	capflags = (int)dnvlist_get_number(newlimits, "flags", 0);
587 	allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0);
588 	if ((capflags & O_CREAT) != 0)
589 		capmode = (mode_t)nvlist_get_number(newlimits, "mode");
590 	else
591 		capmode = 0;
592 
593 	caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0);
594 
595 	return (0);
596 }
597 
598 static int
599 fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin,
600     nvlist_t *nvlout)
601 {
602 	int error;
603 	const char *name;
604 	struct stat sb;
605 
606 	if (limits == NULL)
607 		return (ENOTCAPABLE);
608 
609 	if (!fileargs_allowed(limits, nvlin, FA_LSTAT))
610 		return (ENOTCAPABLE);
611 
612 	name = nvlist_get_string(nvlin, "name");
613 
614 	error = lstat(name, &sb);
615 	if (error < 0)
616 		return (errno);
617 
618 	if (!allcached && (lastname == NULL ||
619 	    strcmp(name, lastname) == 0)) {
620 		nvlist_add_string(nvlout, "cmd", "cache");
621 		fileargs_add_cache(nvlout, limits, name);
622 	} else {
623 		nvlist_add_string(nvlout, "cmd", "lstat");
624 	}
625 	nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb));
626 	return (0);
627 }
628 
629 static int
630 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin,
631     nvlist_t *nvlout)
632 {
633 	int fd;
634 	const char *name;
635 
636 	if (limits == NULL)
637 		return (ENOTCAPABLE);
638 
639 	if (!fileargs_allowed(limits, nvlin, FA_OPEN))
640 		return (ENOTCAPABLE);
641 
642 	name = nvlist_get_string(nvlin, "name");
643 
644 	fd = open_file(name);
645 	if (fd < 0)
646 		return (errno);
647 
648 	if (!allcached && (lastname == NULL ||
649 	    strcmp(name, lastname) == 0)) {
650 		nvlist_add_string(nvlout, "cmd", "cache");
651 		fileargs_add_cache(nvlout, limits, name);
652 	} else {
653 		nvlist_add_string(nvlout, "cmd", "open");
654 	}
655 	nvlist_move_descriptor(nvlout, "fd", fd);
656 	return (0);
657 }
658 
659 static int
660 fileargs_command(const char *cmd, const nvlist_t *limits,
661     nvlist_t *nvlin, nvlist_t *nvlout)
662 {
663 
664 	if (strcmp(cmd, "open") == 0)
665 		return (fileargs_command_open(limits, nvlin, nvlout));
666 
667 	if (strcmp(cmd, "lstat") == 0)
668 		return (fileargs_command_lstat(limits, nvlin, nvlout));
669 
670 	return (EINVAL);
671 }
672 
673 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command,
674     CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS);
675