1 /*-
2  * Copyright (c) 2012 The FreeBSD Foundation
3  * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
4  * Copyright (c) 2017 Robert N. M. Watson
5  * All rights reserved.
6  *
7  * This software was developed by Pawel Jakub Dawidek under sponsorship from
8  * the FreeBSD Foundation.
9  *
10  * This software was developed by SRI International and the University of
11  * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
12  * ("CTSRD"), as part of the DARPA CRASH research programme.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include <sys/cdefs.h>
37 __FBSDID("$FreeBSD$");
38 
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 
203 	return (0);
204 }
205 
206 static void
207 service_register_core(int fd)
208 {
209 	struct casper_service *casserv;
210 	struct service_connection *sconn;
211 
212 	casserv = service_register(CORE_CASPER_NAME, casper_limit,
213 	    casper_command, 0);
214 	sconn = service_connection_add(casserv->cs_service, fd, NULL);
215 	if (sconn == NULL) {
216 		close(fd);
217 		abort();
218 	}
219 }
220 
221 void
222 casper_main_loop(int fd)
223 {
224 	fd_set fds;
225 	struct casper_service *casserv;
226 	struct service_connection *sconn, *sconntmp;
227 	int sock, maxfd, ret;
228 
229 	if (zygote_init() < 0)
230 		exit(1);
231 
232 	/*
233 	 * Register core services.
234 	 */
235 	service_register_core(fd);
236 
237 	for (;;) {
238 		FD_ZERO(&fds);
239 		FD_SET(fd, &fds);
240 		maxfd = -1;
241 		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
242 			/* We handle only core services. */
243 			if (!CSERVICE_IS_CORE(casserv))
244 				continue;
245 			for (sconn = service_connection_first(casserv->cs_service);
246 			    sconn != NULL;
247 			    sconn = service_connection_next(sconn)) {
248 				sock = service_connection_get_sock(sconn);
249 				FD_SET(sock, &fds);
250 				maxfd = sock > maxfd ? sock : maxfd;
251 			}
252 		}
253 		if (maxfd == -1) {
254 			/* Nothing to do. */
255 			exit(0);
256 		}
257 		maxfd++;
258 
259 
260 		assert(maxfd <= (int)FD_SETSIZE);
261 		ret = select(maxfd, &fds, NULL, NULL, NULL);
262 		assert(ret == -1 || ret > 0);	/* select() cannot timeout */
263 		if (ret == -1) {
264 			if (errno == EINTR)
265 				continue;
266 			exit(1);
267 		}
268 
269 		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
270 			/* We handle only core services. */
271 			if (!CSERVICE_IS_CORE(casserv))
272 				continue;
273 			for (sconn = service_connection_first(casserv->cs_service);
274 			    sconn != NULL; sconn = sconntmp) {
275 				/*
276 				 * Prepare for connection to be removed from
277 				 * the list on failure.
278 				 */
279 				sconntmp = service_connection_next(sconn);
280 				sock = service_connection_get_sock(sconn);
281 				if (FD_ISSET(sock, &fds)) {
282 					service_message(casserv->cs_service,
283 					    sconn);
284 				}
285 			}
286 		}
287 	}
288 }
289