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 #include <sys/types.h>
40 #include <sys/queue.h>
41 #include <sys/socket.h>
42 #include <sys/nv.h>
43 
44 #include <assert.h>
45 #include <errno.h>
46 #include <stdbool.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 #include "libcasper_impl.h"
53 #include "zygote.h"
54 
55 struct casper_service {
56 	struct service			*cs_service;
57 	TAILQ_ENTRY(casper_service)	 cs_next;
58 };
59 
60 static TAILQ_HEAD(, casper_service) casper_services =
61     TAILQ_HEAD_INITIALIZER(casper_services);
62 
63 #define	CORE_CASPER_NAME		"core.casper"
64 #define	CSERVICE_IS_CORE(service)	\
65 	(strcmp(service_name(service->cs_service), CORE_CASPER_NAME) == 0)
66 
67 static struct casper_service *
68 service_find(const char *name)
69 {
70 	struct casper_service *casserv;
71 
72 	TAILQ_FOREACH(casserv, &casper_services, cs_next) {
73 		if (strcmp(service_name(casserv->cs_service), name) == 0)
74 			break;
75 	}
76 	return (casserv);
77 }
78 
79 struct casper_service *
80 service_register(const char *name, service_limit_func_t *limitfunc,
81    service_command_func_t *commandfunc, uint64_t flags)
82 {
83 	struct casper_service *casserv;
84 
85 	if (commandfunc == NULL)
86 		return (NULL);
87 	if (name == NULL || name[0] == '\0')
88 		return (NULL);
89 	if (service_find(name) != NULL)
90 		return (NULL);
91 
92 	casserv = malloc(sizeof(*casserv));
93 	if (casserv == NULL)
94 		return (NULL);
95 
96 	casserv->cs_service = service_alloc(name, limitfunc, commandfunc,
97 	    flags);
98 	if (casserv->cs_service == NULL) {
99 		free(casserv);
100 		return (NULL);
101 	}
102 	TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next);
103 
104 	return (casserv);
105 }
106 
107 static bool
108 casper_allowed_service(const nvlist_t *limits, const char *service)
109 {
110 
111 	if (limits == NULL)
112 		return (true);
113 
114 	if (nvlist_exists_null(limits, service))
115 		return (true);
116 
117 	return (false);
118 }
119 
120 static int
121 casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
122 {
123 	const char *name;
124 	int type;
125 	void *cookie;
126 
127 	cookie = NULL;
128 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
129 		if (type != NV_TYPE_NULL)
130 			return (EINVAL);
131 		if (!casper_allowed_service(oldlimits, name))
132 			return (ENOTCAPABLE);
133 	}
134 
135 	return (0);
136 }
137 
138 void
139 service_execute(int chanfd)
140 {
141 	struct casper_service *casserv;
142 	struct service *service;
143 	const char *servname;
144 	nvlist_t *nvl;
145 	int procfd;
146 
147 	nvl = nvlist_recv(chanfd, 0);
148 	if (nvl == NULL)
149 		_exit(1);
150 	if (!nvlist_exists_string(nvl, "service"))
151 		_exit(1);
152 	servname = nvlist_get_string(nvl, "service");
153 	casserv = service_find(servname);
154 	if (casserv == NULL)
155 		_exit(1);
156 	service = casserv->cs_service;
157 	procfd = nvlist_take_descriptor(nvl, "procfd");
158 	nvlist_destroy(nvl);
159 
160 	service_start(service, chanfd, procfd);
161 	/* Not reached. */
162 	_exit(1);
163 }
164 
165 static int
166 casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
167     nvlist_t *nvlout)
168 {
169 	struct casper_service *casserv;
170 	const char *servname;
171 	nvlist_t *nvl;
172 	int chanfd, procfd, error;
173 
174 	if (strcmp(cmd, "open") != 0)
175 		return (EINVAL);
176 	if (!nvlist_exists_string(nvlin, "service"))
177 		return (EINVAL);
178 
179 	servname = nvlist_get_string(nvlin, "service");
180 	casserv = service_find(servname);
181 	if (casserv == NULL)
182 		return (ENOENT);
183 
184 	if (!casper_allowed_service(limits, servname))
185 		return (ENOTCAPABLE);
186 
187 	if (zygote_clone_service_execute(&chanfd, &procfd) == -1)
188 		return (errno);
189 
190 	nvl = nvlist_create(0);
191 	nvlist_add_string(nvl, "service", servname);
192 	nvlist_move_descriptor(nvl, "procfd", procfd);
193 	if (nvlist_send(chanfd, nvl) == -1) {
194 		error = errno;
195 		nvlist_destroy(nvl);
196 		close(chanfd);
197 		return (error);
198 	}
199 	nvlist_destroy(nvl);
200 
201 	nvlist_move_descriptor(nvlout, "chanfd", chanfd);
202 	nvlist_add_number(nvlout, "chanflags",
203 	    service_get_channel_flags(casserv->cs_service));
204 
205 	return (0);
206 }
207 
208 static void
209 service_register_core(int fd)
210 {
211 	struct casper_service *casserv;
212 	struct service_connection *sconn;
213 
214 	casserv = service_register(CORE_CASPER_NAME, casper_limit,
215 	    casper_command, 0);
216 	sconn = service_connection_add(casserv->cs_service, fd, NULL);
217 	if (sconn == NULL) {
218 		close(fd);
219 		abort();
220 	}
221 }
222 
223 void
224 casper_main_loop(int fd)
225 {
226 	fd_set fds;
227 	struct casper_service *casserv;
228 	struct service_connection *sconn, *sconntmp;
229 	int sock, maxfd, ret;
230 
231 	if (zygote_init() < 0)
232 		_exit(1);
233 
234 	/*
235 	 * Register core services.
236 	 */
237 	service_register_core(fd);
238 
239 	for (;;) {
240 		FD_ZERO(&fds);
241 		FD_SET(fd, &fds);
242 		maxfd = -1;
243 		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
244 			/* We handle only core services. */
245 			if (!CSERVICE_IS_CORE(casserv))
246 				continue;
247 			for (sconn = service_connection_first(casserv->cs_service);
248 			    sconn != NULL;
249 			    sconn = service_connection_next(sconn)) {
250 				sock = service_connection_get_sock(sconn);
251 				FD_SET(sock, &fds);
252 				maxfd = sock > maxfd ? sock : maxfd;
253 			}
254 		}
255 		if (maxfd == -1) {
256 			/* Nothing to do. */
257 			_exit(0);
258 		}
259 		maxfd++;
260 
261 
262 		assert(maxfd <= (int)FD_SETSIZE);
263 		ret = select(maxfd, &fds, NULL, NULL, NULL);
264 		assert(ret == -1 || ret > 0);	/* select() cannot timeout */
265 		if (ret == -1) {
266 			if (errno == EINTR)
267 				continue;
268 			_exit(1);
269 		}
270 
271 		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
272 			/* We handle only core services. */
273 			if (!CSERVICE_IS_CORE(casserv))
274 				continue;
275 			for (sconn = service_connection_first(casserv->cs_service);
276 			    sconn != NULL; sconn = sconntmp) {
277 				/*
278 				 * Prepare for connection to be removed from
279 				 * the list on failure.
280 				 */
281 				sconntmp = service_connection_next(sconn);
282 				sock = service_connection_get_sock(sconn);
283 				if (FD_ISSET(sock, &fds)) {
284 					service_message(casserv->cs_service,
285 					    sconn);
286 				}
287 			}
288 		}
289 	}
290 }
291