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