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
cap_sysctlbyname(cap_channel_t * chan,const char * name,void * oldp,size_t * oldlenp,const void * newp,size_t newlen)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
sysctl_check_one(const nvlist_t * nvl,bool islimit)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
sysctl_allowed(const nvlist_t * limits,const char * chname,uint64_t choperation)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
sysctl_limit(const nvlist_t * oldlimits,const nvlist_t * newlimits)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
sysctl_command(const char * cmd,const nvlist_t * limits,nvlist_t * nvlin,nvlist_t * nvlout)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