xref: /freebsd/lib/libcasper/libcasper/service.c (revision a10bc81d333e04664c1a1d6024c580794b079eca)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2013 The FreeBSD Foundation
5  * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
6  * All rights reserved.
7  *
8  * This software was developed by Pawel Jakub Dawidek under sponsorship from
9  * the FreeBSD Foundation.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/param.h>
34 #include <sys/queue.h>
35 #include <sys/socket.h>
36 #include <sys/nv.h>
37 
38 #include <assert.h>
39 #include <dirent.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <paths.h>
44 #include <poll.h>
45 #include <stdbool.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <strings.h>
50 #include <unistd.h>
51 
52 #include "libcasper.h"
53 #include "libcasper_impl.h"
54 
55 /*
56  * Currently there is only one service_connection per service.
57  * In the future we may want multiple connections from multiple clients
58  * per one service instance, but it has to be carefully designed.
59  * The problem is that we may restrict/sandbox service instance according
60  * to the limits provided. When new connection comes in with different
61  * limits we won't be able to access requested resources.
62  * Not to mention one process will serve to multiple mutually untrusted
63  * clients and compromise of this service instance by one of its clients
64  * can lead to compromise of the other clients.
65  */
66 
67 /*
68  * Client connections to the given service.
69  */
70 #define	SERVICE_CONNECTION_MAGIC	0x5e91c0ec
71 struct service_connection {
72 	int		 sc_magic;
73 	cap_channel_t	*sc_chan;
74 	nvlist_t	*sc_limits;
75 	struct service	*sc_service;
76 	size_t		 sc_pollidx;
77 };
78 
79 #define	SERVICE_MAGIC	0x5e91ce
80 struct service {
81 	int			 s_magic;
82 	char			*s_name;
83 	uint64_t		 s_flags;
84 	service_limit_func_t	*s_limit;
85 	service_command_func_t	*s_command;
86 };
87 
88 #define	POLLSET_CHUNK	8
89 static struct pollfd		*pollset_pfds;
90 static struct service_connection **pollset_conns;
91 static size_t			 pollset_cap;
92 static size_t			 pollset_size;
93 
94 static int
pollset_add(struct service_connection * sconn,int sock)95 pollset_add(struct service_connection *sconn, int sock)
96 {
97 	size_t i, newcap;
98 	void *p;
99 
100 	for (i = 0; i < pollset_size; i++) {
101 		if (pollset_pfds[i].fd < 0)
102 			break;
103 	}
104 	if (i == pollset_size) {
105 		newcap = roundup2(pollset_size + 1, POLLSET_CHUNK);
106 		if (newcap > pollset_cap) {
107 			p = reallocarray(pollset_pfds, newcap,
108 			    sizeof(*pollset_pfds));
109 			if (p == NULL)
110 				return (-1);
111 			pollset_pfds = p;
112 			p = reallocarray(pollset_conns, newcap,
113 			    sizeof(*pollset_conns));
114 			if (p == NULL)
115 				return (-1);
116 			pollset_conns = p;
117 			pollset_cap = newcap;
118 		}
119 		pollset_size++;
120 	}
121 	pollset_pfds[i].fd = sock;
122 	pollset_pfds[i].events = POLLIN;
123 	pollset_pfds[i].revents = 0;
124 	pollset_conns[i] = sconn;
125 	sconn->sc_pollidx = i;
126 	return (0);
127 }
128 
129 static void
pollset_remove(struct service_connection * sconn)130 pollset_remove(struct service_connection *sconn)
131 {
132 
133 	pollset_pfds[sconn->sc_pollidx].fd = -1;
134 	pollset_conns[sconn->sc_pollidx] = NULL;
135 }
136 
137 bool
service_have_connections(void)138 service_have_connections(void)
139 {
140 	size_t i;
141 
142 	for (i = 0; i < pollset_size; i++) {
143 		if (pollset_pfds[i].fd >= 0)
144 			return (true);
145 	}
146 	return (false);
147 }
148 
149 bool
service_poll_dispatch(void)150 service_poll_dispatch(void)
151 {
152 	size_t i;
153 	int ret;
154 
155 	do {
156 		ret = poll(pollset_pfds, pollset_size, -1);
157 	} while (ret == -1 && errno == EINTR);
158 	if (ret == -1)
159 		return (false);
160 
161 	for (i = 0; i < pollset_size; i++) {
162 		if (pollset_pfds[i].revents == 0)
163 			continue;
164 		service_message(pollset_conns[i]->sc_service,
165 		    pollset_conns[i]);
166 	}
167 	return (true);
168 }
169 
170 struct service *
service_alloc(const char * name,service_limit_func_t * limitfunc,service_command_func_t * commandfunc,uint64_t flags)171 service_alloc(const char *name, service_limit_func_t *limitfunc,
172     service_command_func_t *commandfunc, uint64_t flags)
173 {
174 	struct service *service;
175 
176 	service = malloc(sizeof(*service));
177 	if (service == NULL)
178 		return (NULL);
179 	service->s_name = strdup(name);
180 	if (service->s_name == NULL) {
181 		free(service);
182 		return (NULL);
183 	}
184 	service->s_limit = limitfunc;
185 	service->s_command = commandfunc;
186 	service->s_flags = flags;
187 	service->s_magic = SERVICE_MAGIC;
188 
189 	return (service);
190 }
191 
192 void
service_free(struct service * service)193 service_free(struct service *service)
194 {
195 	size_t i;
196 
197 	assert(service->s_magic == SERVICE_MAGIC);
198 
199 	service->s_magic = 0;
200 	for (i = 0; i < pollset_size; i++) {
201 		if (pollset_conns[i] != NULL &&
202 		    pollset_conns[i]->sc_service == service)
203 			service_connection_remove(service, pollset_conns[i]);
204 	}
205 	free(service->s_name);
206 	free(service);
207 }
208 
209 struct service_connection *
service_connection_add(struct service * service,int sock,const nvlist_t * limits)210 service_connection_add(struct service *service, int sock,
211     const nvlist_t *limits)
212 {
213 	struct service_connection *sconn;
214 	int serrno;
215 
216 	assert(service->s_magic == SERVICE_MAGIC);
217 
218 	sconn = malloc(sizeof(*sconn));
219 	if (sconn == NULL)
220 		return (NULL);
221 	sconn->sc_chan = cap_wrap(sock,
222 	    service_get_channel_flags(service));
223 	if (sconn->sc_chan == NULL) {
224 		serrno = errno;
225 		free(sconn);
226 		errno = serrno;
227 		return (NULL);
228 	}
229 	if (limits == NULL) {
230 		sconn->sc_limits = NULL;
231 	} else {
232 		sconn->sc_limits = nvlist_clone(limits);
233 		if (sconn->sc_limits == NULL) {
234 			serrno = errno;
235 			(void)cap_unwrap(sconn->sc_chan, NULL);
236 			free(sconn);
237 			errno = serrno;
238 			return (NULL);
239 		}
240 	}
241 	sconn->sc_service = service;
242 	if (pollset_add(sconn, sock) == -1) {
243 		serrno = errno;
244 		nvlist_destroy(sconn->sc_limits);
245 		(void)cap_unwrap(sconn->sc_chan, NULL);
246 		free(sconn);
247 		errno = serrno;
248 		return (NULL);
249 	}
250 	sconn->sc_magic = SERVICE_CONNECTION_MAGIC;
251 	return (sconn);
252 }
253 
254 void
service_connection_remove(struct service * service,struct service_connection * sconn)255 service_connection_remove(struct service *service,
256     struct service_connection *sconn)
257 {
258 
259 	assert(service->s_magic == SERVICE_MAGIC);
260 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
261 
262 	pollset_remove(sconn);
263 	sconn->sc_magic = 0;
264 	nvlist_destroy(sconn->sc_limits);
265 	cap_close(sconn->sc_chan);
266 	free(sconn);
267 }
268 
269 int
service_connection_clone(struct service * service,struct service_connection * sconn)270 service_connection_clone(struct service *service,
271     struct service_connection *sconn)
272 {
273 	struct service_connection *newsconn;
274 	int serrno, sock[2];
275 
276 	if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sock) < 0)
277 		return (-1);
278 
279 	newsconn = service_connection_add(service, sock[0],
280 	    service_connection_get_limits(sconn));
281 	if (newsconn == NULL) {
282 		serrno = errno;
283 		close(sock[0]);
284 		close(sock[1]);
285 		errno = serrno;
286 		return (-1);
287 	}
288 
289 	return (sock[1]);
290 }
291 
292 cap_channel_t *
service_connection_get_chan(const struct service_connection * sconn)293 service_connection_get_chan(const struct service_connection *sconn)
294 {
295 
296 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
297 
298 	return (sconn->sc_chan);
299 }
300 
301 int
service_connection_get_sock(const struct service_connection * sconn)302 service_connection_get_sock(const struct service_connection *sconn)
303 {
304 
305 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
306 
307 	return (cap_sock(sconn->sc_chan));
308 }
309 
310 const nvlist_t *
service_connection_get_limits(const struct service_connection * sconn)311 service_connection_get_limits(const struct service_connection *sconn)
312 {
313 
314 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
315 
316 	return (sconn->sc_limits);
317 }
318 
319 void
service_connection_set_limits(struct service_connection * sconn,nvlist_t * limits)320 service_connection_set_limits(struct service_connection *sconn,
321     nvlist_t *limits)
322 {
323 
324 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
325 
326 	nvlist_destroy(sconn->sc_limits);
327 	sconn->sc_limits = limits;
328 }
329 
330 void
service_message(struct service * service,struct service_connection * sconn)331 service_message(struct service *service, struct service_connection *sconn)
332 {
333 	nvlist_t *nvlin, *nvlout;
334 	const char *cmd;
335 	int error, flags;
336 
337 	flags = 0;
338 	if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
339 		flags = NV_FLAG_NO_UNIQUE;
340 
341 	nvlin = cap_recv_nvlist(service_connection_get_chan(sconn));
342 	if (nvlin == NULL) {
343 		service_connection_remove(service, sconn);
344 		return;
345 	}
346 
347 	error = EDOOFUS;
348 	nvlout = nvlist_create(flags);
349 
350 	cmd = nvlist_get_string(nvlin, "cmd");
351 	if (strcmp(cmd, "limit_set") == 0) {
352 		nvlist_t *nvllim;
353 
354 		nvllim = nvlist_take_nvlist(nvlin, "limits");
355 		if (service->s_limit == NULL) {
356 			error = EOPNOTSUPP;
357 		} else {
358 			error = service->s_limit(
359 			    service_connection_get_limits(sconn), nvllim);
360 		}
361 		if (error == 0) {
362 			service_connection_set_limits(sconn, nvllim);
363 			/* Function consumes nvllim. */
364 		} else {
365 			nvlist_destroy(nvllim);
366 		}
367 	} else if (strcmp(cmd, "limit_get") == 0) {
368 		const nvlist_t *nvllim;
369 
370 		nvllim = service_connection_get_limits(sconn);
371 		if (nvllim != NULL)
372 			nvlist_add_nvlist(nvlout, "limits", nvllim);
373 		else
374 			nvlist_add_null(nvlout, "limits");
375 		error = 0;
376 	} else if (strcmp(cmd, "clone") == 0) {
377 		int sock;
378 
379 		sock = service_connection_clone(service, sconn);
380 		if (sock == -1) {
381 			error = errno;
382 		} else {
383 			nvlist_move_descriptor(nvlout, "sock", sock);
384 			error = 0;
385 		}
386 	} else {
387 		error = service->s_command(cmd,
388 		    service_connection_get_limits(sconn), nvlin, nvlout);
389 	}
390 
391 	nvlist_destroy(nvlin);
392 	nvlist_add_number(nvlout, "error", (uint64_t)error);
393 
394 	if (cap_send_nvlist(service_connection_get_chan(sconn), nvlout) == -1)
395 		service_connection_remove(service, sconn);
396 
397 	nvlist_destroy(nvlout);
398 }
399 
400 const char *
service_name(struct service * service)401 service_name(struct service *service)
402 {
403 
404 	assert(service->s_magic == SERVICE_MAGIC);
405 	return (service->s_name);
406 }
407 
408 int
service_get_channel_flags(struct service * service)409 service_get_channel_flags(struct service *service)
410 {
411 	int flags;
412 
413 	assert(service->s_magic == SERVICE_MAGIC);
414 	flags = 0;
415 
416 	if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
417 		flags |= CASPER_NO_UNIQ;
418 
419 	return (flags);
420 }
421 
422 static void
stdnull(void)423 stdnull(void)
424 {
425 	int fd;
426 
427 	fd = open(_PATH_DEVNULL, O_RDWR);
428 	if (fd == -1)
429 		errx(1, "Unable to open %s", _PATH_DEVNULL);
430 
431 	if (setsid() == -1)
432 		errx(1, "Unable to detach from session");
433 
434 	if (dup2(fd, STDIN_FILENO) == -1)
435 		errx(1, "Unable to cover stdin");
436 	if (dup2(fd, STDOUT_FILENO) == -1)
437 		errx(1, "Unable to cover stdout");
438 	if (dup2(fd, STDERR_FILENO) == -1)
439 		errx(1, "Unable to cover stderr");
440 
441 	if (fd > STDERR_FILENO)
442 		close(fd);
443 }
444 
445 static void
service_clean(int * sockp,int * procfdp,uint64_t flags)446 service_clean(int *sockp, int *procfdp, uint64_t flags)
447 {
448 	int fd, maxfd, minfd;
449 
450 	fd_fix_environment(sockp);
451 	fd_fix_environment(procfdp);
452 
453 	assert(*sockp > STDERR_FILENO);
454 	assert(*procfdp > STDERR_FILENO);
455 	assert(*sockp != *procfdp);
456 
457 	if ((flags & CASPER_SERVICE_STDIO) == 0)
458 		stdnull();
459 
460 	if ((flags & CASPER_SERVICE_FD) == 0) {
461 		if (*procfdp > *sockp) {
462 			maxfd = *procfdp;
463 			minfd = *sockp;
464 		} else {
465 			maxfd = *sockp;
466 			minfd = *procfdp;
467 		}
468 
469 		for (fd = STDERR_FILENO + 1; fd < maxfd; fd++) {
470 			if (fd != minfd)
471 				close(fd);
472 		}
473 		closefrom(maxfd + 1);
474 	}
475 }
476 
477 void
service_start(struct service * service,int sock,int procfd)478 service_start(struct service *service, int sock, int procfd)
479 {
480 
481 	assert(service != NULL);
482 	assert(service->s_magic == SERVICE_MAGIC);
483 	setproctitle("%s", service->s_name);
484 	service_clean(&sock, &procfd, service->s_flags);
485 
486 	if (service_connection_add(service, sock, NULL) == NULL)
487 		_exit(1);
488 
489 	while (service_have_connections()) {
490 		if (!service_poll_dispatch())
491 			_exit(1);
492 	}
493 
494 	_exit(0);
495 }
496