1 /*-
2  * Copyright (c) 2013 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Pawel Jakub Dawidek under sponsorship from
6  * the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/types.h>
34 #include <sys/sysctl.h>
35 #include <sys/nv.h>
36 
37 #include <assert.h>
38 #include <errno.h>
39 #include <stdlib.h>
40 #include <string.h>
41 
42 #include <libcasper.h>
43 #include <libcasper_service.h>
44 
45 #include "cap_sysctl.h"
46 
47 int
48 cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp,
49     size_t *oldlenp, const void *newp, size_t newlen)
50 {
51 	nvlist_t *nvl;
52 	const uint8_t *retoldp;
53 	uint8_t operation;
54 	size_t oldlen;
55 
56 	operation = 0;
57 	if (oldp != NULL)
58 		operation |= CAP_SYSCTL_READ;
59 	if (newp != NULL)
60 		operation |= CAP_SYSCTL_WRITE;
61 
62 	nvl = nvlist_create(0);
63 	nvlist_add_string(nvl, "cmd", "sysctl");
64 	nvlist_add_string(nvl, "name", name);
65 	nvlist_add_number(nvl, "operation", (uint64_t)operation);
66 	if (oldp == NULL && oldlenp != NULL)
67 		nvlist_add_null(nvl, "justsize");
68 	else if (oldlenp != NULL)
69 		nvlist_add_number(nvl, "oldlen", (uint64_t)*oldlenp);
70 	if (newp != NULL)
71 		nvlist_add_binary(nvl, "newp", newp, newlen);
72 	nvl = cap_xfer_nvlist(chan, nvl, 0);
73 	if (nvl == NULL)
74 		return (-1);
75 	if (nvlist_get_number(nvl, "error") != 0) {
76 		errno = (int)nvlist_get_number(nvl, "error");
77 		nvlist_destroy(nvl);
78 		return (-1);
79 	}
80 
81 	if (oldp == NULL && oldlenp != NULL) {
82 		*oldlenp = (size_t)nvlist_get_number(nvl, "oldlen");
83 	} else if (oldp != NULL) {
84 		retoldp = nvlist_get_binary(nvl, "oldp", &oldlen);
85 		memcpy(oldp, retoldp, oldlen);
86 		if (oldlenp != NULL)
87 			*oldlenp = oldlen;
88 	}
89 	nvlist_destroy(nvl);
90 
91 	return (0);
92 }
93 
94 /*
95  * Service functions.
96  */
97 static int
98 sysctl_check_one(const nvlist_t *nvl, bool islimit)
99 {
100 	const char *name;
101 	void *cookie;
102 	int type;
103 	unsigned int fields;
104 
105 	/* NULL nvl is of course invalid. */
106 	if (nvl == NULL)
107 		return (EINVAL);
108 	if (nvlist_error(nvl) != 0)
109 		return (nvlist_error(nvl));
110 
111 #define	HAS_NAME	0x01
112 #define	HAS_OPERATION	0x02
113 
114 	fields = 0;
115 	cookie = NULL;
116 	while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
117 		/* We accept only one 'name' and one 'operation' in nvl. */
118 		if (strcmp(name, "name") == 0) {
119 			if (type != NV_TYPE_STRING)
120 				return (EINVAL);
121 			/* Only one 'name' can be present. */
122 			if ((fields & HAS_NAME) != 0)
123 				return (EINVAL);
124 			fields |= HAS_NAME;
125 		} else if (strcmp(name, "operation") == 0) {
126 			uint64_t operation;
127 
128 			if (type != NV_TYPE_NUMBER)
129 				return (EINVAL);
130 			/*
131 			 * We accept only CAP_SYSCTL_READ and
132 			 * CAP_SYSCTL_WRITE flags.
133 			 */
134 			operation = nvlist_get_number(nvl, name);
135 			if ((operation & ~(CAP_SYSCTL_RDWR)) != 0)
136 				return (EINVAL);
137 			/* ...but there has to be at least one of them. */
138 			if ((operation & (CAP_SYSCTL_RDWR)) == 0)
139 				return (EINVAL);
140 			/* Only one 'operation' can be present. */
141 			if ((fields & HAS_OPERATION) != 0)
142 				return (EINVAL);
143 			fields |= HAS_OPERATION;
144 		} else if (islimit) {
145 			/* If this is limit, there can be no other fields. */
146 			return (EINVAL);
147 		}
148 	}
149 
150 	/* Both fields has to be there. */
151 	if (fields != (HAS_NAME | HAS_OPERATION))
152 		return (EINVAL);
153 
154 #undef	HAS_OPERATION
155 #undef	HAS_NAME
156 
157 	return (0);
158 }
159 
160 static bool
161 sysctl_allowed(const nvlist_t *limits, const char *chname, uint64_t choperation)
162 {
163 	uint64_t operation;
164 	const char *name;
165 	void *cookie;
166 	int type;
167 
168 	if (limits == NULL)
169 		return (true);
170 
171 	cookie = NULL;
172 	while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
173 		assert(type == NV_TYPE_NUMBER);
174 
175 		operation = nvlist_get_number(limits, name);
176 		if ((operation & choperation) != choperation)
177 			continue;
178 
179 		if ((operation & CAP_SYSCTL_RECURSIVE) == 0) {
180 			if (strcmp(name, chname) != 0)
181 				continue;
182 		} else {
183 			size_t namelen;
184 
185 			namelen = strlen(name);
186 			if (strncmp(name, chname, namelen) != 0)
187 				continue;
188 			if (chname[namelen] != '.' && chname[namelen] != '\0')
189 				continue;
190 		}
191 
192 		return (true);
193 	}
194 
195 	return (false);
196 }
197 
198 static int
199 sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
200 {
201 	const char *name;
202 	void *cookie;
203 	uint64_t operation;
204 	int type;
205 
206 	cookie = NULL;
207 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
208 		if (type != NV_TYPE_NUMBER)
209 			return (EINVAL);
210 		operation = nvlist_get_number(newlimits, name);
211 		if ((operation & ~(CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) != 0)
212 			return (EINVAL);
213 		if ((operation & (CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) == 0)
214 			return (EINVAL);
215 		if (!sysctl_allowed(oldlimits, name, operation))
216 			return (ENOTCAPABLE);
217 	}
218 
219 	return (0);
220 }
221 
222 static int
223 sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
224     nvlist_t *nvlout)
225 {
226 	const char *name;
227 	const void *newp;
228 	void *oldp;
229 	uint64_t operation;
230 	size_t oldlen, newlen;
231 	size_t *oldlenp;
232 	int error;
233 
234 	if (strcmp(cmd, "sysctl") != 0)
235 		return (EINVAL);
236 	error = sysctl_check_one(nvlin, false);
237 	if (error != 0)
238 		return (error);
239 
240 	name = nvlist_get_string(nvlin, "name");
241 	operation = nvlist_get_number(nvlin, "operation");
242 	if (!sysctl_allowed(limits, name, operation))
243 		return (ENOTCAPABLE);
244 
245 	if ((operation & CAP_SYSCTL_WRITE) != 0) {
246 		if (!nvlist_exists_binary(nvlin, "newp"))
247 			return (EINVAL);
248 		newp = nvlist_get_binary(nvlin, "newp", &newlen);
249 		assert(newp != NULL && newlen > 0);
250 	} else {
251 		newp = NULL;
252 		newlen = 0;
253 	}
254 
255 	if ((operation & CAP_SYSCTL_READ) != 0) {
256 		if (nvlist_exists_null(nvlin, "justsize")) {
257 			oldp = NULL;
258 			oldlen = 0;
259 			oldlenp = &oldlen;
260 		} else {
261 			if (!nvlist_exists_number(nvlin, "oldlen"))
262 				return (EINVAL);
263 			oldlen = (size_t)nvlist_get_number(nvlin, "oldlen");
264 			if (oldlen == 0)
265 				return (EINVAL);
266 			oldp = calloc(1, oldlen);
267 			if (oldp == NULL)
268 				return (ENOMEM);
269 			oldlenp = &oldlen;
270 		}
271 	} else {
272 		oldp = NULL;
273 		oldlen = 0;
274 		oldlenp = NULL;
275 	}
276 
277 	if (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) {
278 		error = errno;
279 		free(oldp);
280 		return (error);
281 	}
282 
283 	if ((operation & CAP_SYSCTL_READ) != 0) {
284 		if (nvlist_exists_null(nvlin, "justsize"))
285 			nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen);
286 		else
287 			nvlist_move_binary(nvlout, "oldp", oldp, oldlen);
288 	}
289 
290 	return (0);
291 }
292 
293 CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0);
294