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