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
52struct fileargs {
53	uint32_t	 fa_magic;
54	nvlist_t	*fa_cache;
55	cap_channel_t	*fa_chann;
56	int		 fa_fdflags;
57};
58
59static int
60fileargs_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
88static int
89fileargs_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
129static void
130fileargs_set_cache(fileargs_t *fa, nvlist_t *nvl)
131{
132
133	nvlist_destroy(fa->fa_cache);
134	fa->fa_cache = nvl;
135}
136
137static nvlist_t*
138fileargs_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
164static nvlist_t *
165fileargs_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
196static fileargs_t *
197fileargs_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
212fileargs_t *
213fileargs_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
230fileargs_t *
231fileargs_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
248fileargs_t *
249fileargs_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
270fileargs_t *
271fileargs_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);
305out:
306	serrno = errno;
307	if (chann != NULL)
308		cap_close(chann);
309	errno = serrno;
310	return (NULL);
311}
312
313int
314fileargs_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
352FILE *
353fileargs_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
364int
365fileargs_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
411char *
412fileargs_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
446void
447fileargs_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
463cap_channel_t *
464fileargs_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
485fileargs_t *
486fileargs_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
500static const char *lastname;
501static void *cacheposition;
502static bool allcached;
503static const cap_rights_t *caprightsp;
504static int capflags;
505static int allowed_operations;
506static mode_t capmode;
507
508static int
509open_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
532static void
533fileargs_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
597static bool
598fileargs_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
619static int
620fileargs_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
638static int
639fileargs_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
669static int
670fileargs_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
691static int
692fileargs_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
721static int
722fileargs_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
736CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command,
737    CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS);
738