1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2012 The FreeBSD Foundation
5  * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
6  * Copyright (c) 2017 Robert N. M. Watson
7  * All rights reserved.
8  *
9  * This software was developed by Pawel Jakub Dawidek under sponsorship from
10  * the FreeBSD Foundation.
11  *
12  * This software was developed by SRI International and the University of
13  * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
14  * ("CTSRD"), as part of the DARPA CRASH research programme.
15  *
16  * Redistribution and use in source and binary forms, with or without
17  * modification, are permitted provided that the following conditions
18  * are met:
19  * 1. Redistributions of source code must retain the above copyright
20  *    notice, this list of conditions and the following disclaimer.
21  * 2. Redistributions in binary form must reproduce the above copyright
22  *    notice, this list of conditions and the following disclaimer in the
23  *    documentation and/or other materials provided with the distribution.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #include <sys/cdefs.h>
39 __FBSDID("$FreeBSD$");
40 
41 #include <sys/types.h>
42 #include <sys/queue.h>
43 #include <sys/socket.h>
44 #include <sys/nv.h>
45 
46 #include <assert.h>
47 #include <errno.h>
48 #include <stdbool.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 
54 #include "libcasper_impl.h"
55 #include "zygote.h"
56 
57 struct casper_service {
58 	struct service			*cs_service;
59 	TAILQ_ENTRY(casper_service)	 cs_next;
60 };
61 
62 static TAILQ_HEAD(, casper_service) casper_services =
63     TAILQ_HEAD_INITIALIZER(casper_services);
64 
65 #define	CORE_CASPER_NAME		"core.casper"
66 #define	CSERVICE_IS_CORE(service)	\
67 	(strcmp(service_name(service->cs_service), CORE_CASPER_NAME) == 0)
68 
69 static struct casper_service *
70 service_find(const char *name)
71 {
72 	struct casper_service *casserv;
73 
74 	TAILQ_FOREACH(casserv, &casper_services, cs_next) {
75 		if (strcmp(service_name(casserv->cs_service), name) == 0)
76 			break;
77 	}
78 	return (casserv);
79 }
80 
81 struct casper_service *
82 service_register(const char *name, service_limit_func_t *limitfunc,
83    service_command_func_t *commandfunc, uint64_t flags)
84 {
85 	struct casper_service *casserv;
86 
87 	if (commandfunc == NULL)
88 		return (NULL);
89 	if (name == NULL || name[0] == '\0')
90 		return (NULL);
91 	if (service_find(name) != NULL)
92 		return (NULL);
93 
94 	casserv = malloc(sizeof(*casserv));
95 	if (casserv == NULL)
96 		return (NULL);
97 
98 	casserv->cs_service = service_alloc(name, limitfunc, commandfunc,
99 	    flags);
100 	if (casserv->cs_service == NULL) {
101 		free(casserv);
102 		return (NULL);
103 	}
104 	TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next);
105 
106 	return (casserv);
107 }
108 
109 static bool
110 casper_allowed_service(const nvlist_t *limits, const char *service)
111 {
112 
113 	if (limits == NULL)
114 		return (true);
115 
116 	if (nvlist_exists_null(limits, service))
117 		return (true);
118 
119 	return (false);
120 }
121 
122 static int
123 casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
124 {
125 	const char *name;
126 	int type;
127 	void *cookie;
128 
129 	cookie = NULL;
130 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
131 		if (type != NV_TYPE_NULL)
132 			return (EINVAL);
133 		if (!casper_allowed_service(oldlimits, name))
134 			return (ENOTCAPABLE);
135 	}
136 
137 	return (0);
138 }
139 
140 void
141 service_execute(int chanfd)
142 {
143 	struct casper_service *casserv;
144 	struct service *service;
145 	const char *servname;
146 	nvlist_t *nvl;
147 	int procfd;
148 
149 	nvl = nvlist_recv(chanfd, 0);
150 	if (nvl == NULL)
151 		_exit(1);
152 	if (!nvlist_exists_string(nvl, "service"))
153 		_exit(1);
154 	servname = nvlist_get_string(nvl, "service");
155 	casserv = service_find(servname);
156 	if (casserv == NULL)
157 		_exit(1);
158 	service = casserv->cs_service;
159 	procfd = nvlist_take_descriptor(nvl, "procfd");
160 	nvlist_destroy(nvl);
161 
162 	service_start(service, chanfd, procfd);
163 	/* Not reached. */
164 	_exit(1);
165 }
166 
167 static int
168 casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
169     nvlist_t *nvlout)
170 {
171 	struct casper_service *casserv;
172 	const char *servname;
173 	nvlist_t *nvl;
174 	int chanfd, procfd, error;
175 
176 	if (strcmp(cmd, "open") != 0)
177 		return (EINVAL);
178 	if (!nvlist_exists_string(nvlin, "service"))
179 		return (EINVAL);
180 
181 	servname = nvlist_get_string(nvlin, "service");
182 	casserv = service_find(servname);
183 	if (casserv == NULL)
184 		return (ENOENT);
185 
186 	if (!casper_allowed_service(limits, servname))
187 		return (ENOTCAPABLE);
188 
189 	if (zygote_clone_service_execute(&chanfd, &procfd) == -1)
190 		return (errno);
191 
192 	nvl = nvlist_create(0);
193 	nvlist_add_string(nvl, "service", servname);
194 	nvlist_move_descriptor(nvl, "procfd", procfd);
195 	if (nvlist_send(chanfd, nvl) == -1) {
196 		error = errno;
197 		nvlist_destroy(nvl);
198 		close(chanfd);
199 		return (error);
200 	}
201 	nvlist_destroy(nvl);
202 
203 	nvlist_move_descriptor(nvlout, "chanfd", chanfd);
204 	nvlist_add_number(nvlout, "chanflags",
205 	    service_get_channel_flags(casserv->cs_service));
206 
207 	return (0);
208 }
209 
210 static void
211 service_register_core(int fd)
212 {
213 	struct casper_service *casserv;
214 	struct service_connection *sconn;
215 
216 	casserv = service_register(CORE_CASPER_NAME, casper_limit,
217 	    casper_command, 0);
218 	sconn = service_connection_add(casserv->cs_service, fd, NULL);
219 	if (sconn == NULL) {
220 		close(fd);
221 		abort();
222 	}
223 }
224 
225 void
226 casper_main_loop(int fd)
227 {
228 	fd_set fds;
229 	struct casper_service *casserv;
230 	struct service_connection *sconn, *sconntmp;
231 	int sock, maxfd, ret;
232 
233 	if (zygote_init() < 0)
234 		_exit(1);
235 
236 	/*
237 	 * Register core services.
238 	 */
239 	service_register_core(fd);
240 
241 	for (;;) {
242 		FD_ZERO(&fds);
243 		FD_SET(fd, &fds);
244 		maxfd = -1;
245 		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
246 			/* We handle only core services. */
247 			if (!CSERVICE_IS_CORE(casserv))
248 				continue;
249 			for (sconn = service_connection_first(casserv->cs_service);
250 			    sconn != NULL;
251 			    sconn = service_connection_next(sconn)) {
252 				sock = service_connection_get_sock(sconn);
253 				FD_SET(sock, &fds);
254 				maxfd = sock > maxfd ? sock : maxfd;
255 			}
256 		}
257 		if (maxfd == -1) {
258 			/* Nothing to do. */
259 			_exit(0);
260 		}
261 		maxfd++;
262 
263 
264 		assert(maxfd <= (int)FD_SETSIZE);
265 		ret = select(maxfd, &fds, NULL, NULL, NULL);
266 		assert(ret == -1 || ret > 0);	/* select() cannot timeout */
267 		if (ret == -1) {
268 			if (errno == EINTR)
269 				continue;
270 			_exit(1);
271 		}
272 
273 		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
274 			/* We handle only core services. */
275 			if (!CSERVICE_IS_CORE(casserv))
276 				continue;
277 			for (sconn = service_connection_first(casserv->cs_service);
278 			    sconn != NULL; sconn = sconntmp) {
279 				/*
280 				 * Prepare for connection to be removed from
281 				 * the list on failure.
282 				 */
283 				sconntmp = service_connection_next(sconn);
284 				sock = service_connection_get_sock(sconn);
285 				if (FD_ISSET(sock, &fds)) {
286 					service_message(casserv->cs_service,
287 					    sconn);
288 				}
289 			}
290 		}
291 	}
292 }
293