xref: /freebsd/lib/libcasper/libcasper/service.c (revision 42249ef2)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <sys/types.h>
37 #include <sys/queue.h>
38 #include <sys/socket.h>
39 #include <sys/nv.h>
40 
41 #include <assert.h>
42 #include <dirent.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <paths.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <strings.h>
52 #include <unistd.h>
53 
54 #include "libcasper.h"
55 #include "libcasper_impl.h"
56 
57 /*
58  * Currently there is only one service_connection per service.
59  * In the future we may want multiple connections from multiple clients
60  * per one service instance, but it has to be carefully designed.
61  * The problem is that we may restrict/sandbox service instance according
62  * to the limits provided. When new connection comes in with different
63  * limits we won't be able to access requested resources.
64  * Not to mention one process will serve to mutiple mutually untrusted
65  * clients and compromise of this service instance by one of its clients
66  * can lead to compromise of the other clients.
67  */
68 
69 /*
70  * Client connections to the given service.
71  */
72 #define	SERVICE_CONNECTION_MAGIC	0x5e91c0ec
73 struct service_connection {
74 	int		 sc_magic;
75 	cap_channel_t	*sc_chan;
76 	nvlist_t	*sc_limits;
77 	TAILQ_ENTRY(service_connection) sc_next;
78 };
79 
80 #define	SERVICE_MAGIC	0x5e91ce
81 struct service {
82 	int			 s_magic;
83 	char			*s_name;
84 	uint64_t		 s_flags;
85 	service_limit_func_t	*s_limit;
86 	service_command_func_t	*s_command;
87 	TAILQ_HEAD(, service_connection) s_connections;
88 };
89 
90 struct service *
91 service_alloc(const char *name, service_limit_func_t *limitfunc,
92     service_command_func_t *commandfunc, uint64_t flags)
93 {
94 	struct service *service;
95 
96 	service = malloc(sizeof(*service));
97 	if (service == NULL)
98 		return (NULL);
99 	service->s_name = strdup(name);
100 	if (service->s_name == NULL) {
101 		free(service);
102 		return (NULL);
103 	}
104 	service->s_limit = limitfunc;
105 	service->s_command = commandfunc;
106 	service->s_flags = flags;
107 	TAILQ_INIT(&service->s_connections);
108 	service->s_magic = SERVICE_MAGIC;
109 
110 	return (service);
111 }
112 
113 void
114 service_free(struct service *service)
115 {
116 	struct service_connection *sconn;
117 
118 	assert(service->s_magic == SERVICE_MAGIC);
119 
120 	service->s_magic = 0;
121 	while ((sconn = service_connection_first(service)) != NULL)
122 		service_connection_remove(service, sconn);
123 	free(service->s_name);
124 	free(service);
125 }
126 
127 struct service_connection *
128 service_connection_add(struct service *service, int sock,
129     const nvlist_t *limits)
130 {
131 	struct service_connection *sconn;
132 	int serrno;
133 
134 	assert(service->s_magic == SERVICE_MAGIC);
135 
136 	sconn = malloc(sizeof(*sconn));
137 	if (sconn == NULL)
138 		return (NULL);
139 	sconn->sc_chan = cap_wrap(sock,
140 	    service_get_channel_flags(service));
141 	if (sconn->sc_chan == NULL) {
142 		serrno = errno;
143 		free(sconn);
144 		errno = serrno;
145 		return (NULL);
146 	}
147 	if (limits == NULL) {
148 		sconn->sc_limits = NULL;
149 	} else {
150 		sconn->sc_limits = nvlist_clone(limits);
151 		if (sconn->sc_limits == NULL) {
152 			serrno = errno;
153 			(void)cap_unwrap(sconn->sc_chan, NULL);
154 			free(sconn);
155 			errno = serrno;
156 			return (NULL);
157 		}
158 	}
159 	sconn->sc_magic = SERVICE_CONNECTION_MAGIC;
160 	TAILQ_INSERT_TAIL(&service->s_connections, sconn, sc_next);
161 	return (sconn);
162 }
163 
164 void
165 service_connection_remove(struct service *service,
166     struct service_connection *sconn)
167 {
168 
169 	assert(service->s_magic == SERVICE_MAGIC);
170 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
171 
172 	TAILQ_REMOVE(&service->s_connections, sconn, sc_next);
173 	sconn->sc_magic = 0;
174 	nvlist_destroy(sconn->sc_limits);
175 	cap_close(sconn->sc_chan);
176 	free(sconn);
177 }
178 
179 int
180 service_connection_clone(struct service *service,
181     struct service_connection *sconn)
182 {
183 	struct service_connection *newsconn;
184 	int serrno, sock[2];
185 
186 	if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sock) < 0)
187 		return (-1);
188 
189 	newsconn = service_connection_add(service, sock[0],
190 	    service_connection_get_limits(sconn));
191 	if (newsconn == NULL) {
192 		serrno = errno;
193 		close(sock[0]);
194 		close(sock[1]);
195 		errno = serrno;
196 		return (-1);
197 	}
198 
199 	return (sock[1]);
200 }
201 
202 struct service_connection *
203 service_connection_first(struct service *service)
204 {
205 	struct service_connection *sconn;
206 
207 	assert(service->s_magic == SERVICE_MAGIC);
208 
209 	sconn = TAILQ_FIRST(&service->s_connections);
210 	assert(sconn == NULL ||
211 	    sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
212 	return (sconn);
213 }
214 
215 struct service_connection *
216 service_connection_next(struct service_connection *sconn)
217 {
218 
219 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
220 
221 	sconn = TAILQ_NEXT(sconn, sc_next);
222 	assert(sconn == NULL ||
223 	    sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
224 	return (sconn);
225 }
226 
227 cap_channel_t *
228 service_connection_get_chan(const struct service_connection *sconn)
229 {
230 
231 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
232 
233 	return (sconn->sc_chan);
234 }
235 
236 int
237 service_connection_get_sock(const struct service_connection *sconn)
238 {
239 
240 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
241 
242 	return (cap_sock(sconn->sc_chan));
243 }
244 
245 const nvlist_t *
246 service_connection_get_limits(const struct service_connection *sconn)
247 {
248 
249 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
250 
251 	return (sconn->sc_limits);
252 }
253 
254 void
255 service_connection_set_limits(struct service_connection *sconn,
256     nvlist_t *limits)
257 {
258 
259 	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
260 
261 	nvlist_destroy(sconn->sc_limits);
262 	sconn->sc_limits = limits;
263 }
264 
265 void
266 service_message(struct service *service, struct service_connection *sconn)
267 {
268 	nvlist_t *nvlin, *nvlout;
269 	const char *cmd;
270 	int error, flags;
271 
272 	flags = 0;
273 	if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
274 		flags = NV_FLAG_NO_UNIQUE;
275 
276 	nvlin = cap_recv_nvlist(service_connection_get_chan(sconn));
277 	if (nvlin == NULL) {
278 		service_connection_remove(service, sconn);
279 		return;
280 	}
281 
282 	error = EDOOFUS;
283 	nvlout = nvlist_create(flags);
284 
285 	cmd = nvlist_get_string(nvlin, "cmd");
286 	if (strcmp(cmd, "limit_set") == 0) {
287 		nvlist_t *nvllim;
288 
289 		nvllim = nvlist_take_nvlist(nvlin, "limits");
290 		if (service->s_limit == NULL) {
291 			error = EOPNOTSUPP;
292 		} else {
293 			error = service->s_limit(
294 			    service_connection_get_limits(sconn), nvllim);
295 		}
296 		if (error == 0) {
297 			service_connection_set_limits(sconn, nvllim);
298 			/* Function consumes nvllim. */
299 		} else {
300 			nvlist_destroy(nvllim);
301 		}
302 	} else if (strcmp(cmd, "limit_get") == 0) {
303 		const nvlist_t *nvllim;
304 
305 		nvllim = service_connection_get_limits(sconn);
306 		if (nvllim != NULL)
307 			nvlist_add_nvlist(nvlout, "limits", nvllim);
308 		else
309 			nvlist_add_null(nvlout, "limits");
310 		error = 0;
311 	} else if (strcmp(cmd, "clone") == 0) {
312 		int sock;
313 
314 		sock = service_connection_clone(service, sconn);
315 		if (sock == -1) {
316 			error = errno;
317 		} else {
318 			nvlist_move_descriptor(nvlout, "sock", sock);
319 			error = 0;
320 		}
321 	} else {
322 		error = service->s_command(cmd,
323 		    service_connection_get_limits(sconn), nvlin, nvlout);
324 	}
325 
326 	nvlist_destroy(nvlin);
327 	nvlist_add_number(nvlout, "error", (uint64_t)error);
328 
329 	if (cap_send_nvlist(service_connection_get_chan(sconn), nvlout) == -1)
330 		service_connection_remove(service, sconn);
331 
332 	nvlist_destroy(nvlout);
333 }
334 
335 static int
336 fd_add(fd_set *fdsp, int maxfd, int fd)
337 {
338 
339 	FD_SET(fd, fdsp);
340 	return (fd > maxfd ? fd : maxfd);
341 }
342 
343 const char *
344 service_name(struct service *service)
345 {
346 
347 	assert(service->s_magic == SERVICE_MAGIC);
348 	return (service->s_name);
349 }
350 
351 int
352 service_get_channel_flags(struct service *service)
353 {
354 	int flags;
355 
356 	assert(service->s_magic == SERVICE_MAGIC);
357 	flags = 0;
358 
359 	if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
360 		flags |= CASPER_NO_UNIQ;
361 
362 	return (flags);
363 }
364 
365 static void
366 stdnull(void)
367 {
368 	int fd;
369 
370 	fd = open(_PATH_DEVNULL, O_RDWR);
371 	if (fd == -1)
372 		errx(1, "Unable to open %s", _PATH_DEVNULL);
373 
374 	if (setsid() == -1)
375 		errx(1, "Unable to detach from session");
376 
377 	if (dup2(fd, STDIN_FILENO) == -1)
378 		errx(1, "Unable to cover stdin");
379 	if (dup2(fd, STDOUT_FILENO) == -1)
380 		errx(1, "Unable to cover stdout");
381 	if (dup2(fd, STDERR_FILENO) == -1)
382 		errx(1, "Unable to cover stderr");
383 
384 	if (fd > STDERR_FILENO)
385 		close(fd);
386 }
387 
388 static void
389 service_clean(int sock, int procfd, uint64_t flags)
390 {
391 	int fd, maxfd, minfd;
392 
393 	assert(sock > STDERR_FILENO);
394 	assert(procfd > STDERR_FILENO);
395 	assert(sock != procfd);
396 
397 	if ((flags & CASPER_SERVICE_STDIO) == 0)
398 		stdnull();
399 
400 	if ((flags & CASPER_SERVICE_FD) == 0) {
401 		if (procfd > sock) {
402 			maxfd = procfd;
403 			minfd = sock;
404 		} else {
405 			maxfd = sock;
406 			minfd = procfd;
407 		}
408 
409 		for (fd = STDERR_FILENO + 1; fd < maxfd; fd++) {
410 			if (fd != minfd)
411 				close(fd);
412 		}
413 		closefrom(maxfd + 1);
414 	}
415 }
416 
417 void
418 service_start(struct service *service, int sock, int procfd)
419 {
420 	struct service_connection *sconn, *sconntmp;
421 	fd_set fds;
422 	int maxfd, nfds;
423 
424 	assert(service != NULL);
425 	assert(service->s_magic == SERVICE_MAGIC);
426 	setproctitle("%s", service->s_name);
427 	service_clean(sock, procfd, service->s_flags);
428 
429 	if (service_connection_add(service, sock, NULL) == NULL)
430 		_exit(1);
431 
432 	for (;;) {
433 		FD_ZERO(&fds);
434 		maxfd = -1;
435 		for (sconn = service_connection_first(service); sconn != NULL;
436 		    sconn = service_connection_next(sconn)) {
437 			maxfd = fd_add(&fds, maxfd,
438 			    service_connection_get_sock(sconn));
439 		}
440 
441 		assert(maxfd >= 0);
442 		assert(maxfd + 1 <= (int)FD_SETSIZE);
443 		nfds = select(maxfd + 1, &fds, NULL, NULL, NULL);
444 		if (nfds < 0) {
445 			if (errno != EINTR)
446 				_exit(1);
447 			continue;
448 		} else if (nfds == 0) {
449 			/* Timeout. */
450 			abort();
451 		}
452 
453 		for (sconn = service_connection_first(service); sconn != NULL;
454 		    sconn = sconntmp) {
455 			/*
456 			 * Prepare for connection to be removed from the list
457 			 * on failure.
458 			 */
459 			sconntmp = service_connection_next(sconn);
460 			if (FD_ISSET(service_connection_get_sock(sconn), &fds))
461 				service_message(service, sconn);
462 		}
463 		if (service_connection_first(service) == NULL) {
464 			/*
465 			 * No connections left, exiting.
466 			 */
467 			break;
468 		}
469 	}
470 
471 	_exit(0);
472 }
473