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 <errno.h>
38 #include <stdlib.h>
39 #include <string.h>
40
41 #include <libcapsicum.h>
42 #include <libcapsicum_sysctl.h>
43 #include <libcasper.h>
44 #include <pjdlog.h>
45
46 static int
sysctl_check_one(const nvlist_t * nvl,bool islimit)47 sysctl_check_one(const nvlist_t *nvl, bool islimit)
48 {
49 const char *name;
50 void *cookie;
51 int type;
52 unsigned int fields;
53
54 /* NULL nvl is of course invalid. */
55 if (nvl == NULL)
56 return (EINVAL);
57 if (nvlist_error(nvl) != 0)
58 return (nvlist_error(nvl));
59
60 #define HAS_NAME 0x01
61 #define HAS_OPERATION 0x02
62
63 fields = 0;
64 cookie = NULL;
65 while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
66 /* We accept only one 'name' and one 'operation' in nvl. */
67 if (strcmp(name, "name") == 0) {
68 if (type != NV_TYPE_STRING)
69 return (EINVAL);
70 /* Only one 'name' can be present. */
71 if ((fields & HAS_NAME) != 0)
72 return (EINVAL);
73 fields |= HAS_NAME;
74 } else if (strcmp(name, "operation") == 0) {
75 uint64_t operation;
76
77 if (type != NV_TYPE_NUMBER)
78 return (EINVAL);
79 /*
80 * We accept only CAP_SYSCTL_READ and
81 * CAP_SYSCTL_WRITE flags.
82 */
83 operation = nvlist_get_number(nvl, name);
84 if ((operation & ~(CAP_SYSCTL_RDWR)) != 0)
85 return (EINVAL);
86 /* ...but there has to be at least one of them. */
87 if ((operation & (CAP_SYSCTL_RDWR)) == 0)
88 return (EINVAL);
89 /* Only one 'operation' can be present. */
90 if ((fields & HAS_OPERATION) != 0)
91 return (EINVAL);
92 fields |= HAS_OPERATION;
93 } else if (islimit) {
94 /* If this is limit, there can be no other fields. */
95 return (EINVAL);
96 }
97 }
98
99 /* Both fields has to be there. */
100 if (fields != (HAS_NAME | HAS_OPERATION))
101 return (EINVAL);
102
103 #undef HAS_OPERATION
104 #undef HAS_NAME
105
106 return (0);
107 }
108
109 static bool
sysctl_allowed(const nvlist_t * limits,const char * chname,uint64_t choperation)110 sysctl_allowed(const nvlist_t *limits, const char *chname, uint64_t choperation)
111 {
112 uint64_t operation;
113 const char *name;
114 void *cookie;
115 int type;
116
117 if (limits == NULL)
118 return (true);
119
120 cookie = NULL;
121 while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
122 PJDLOG_ASSERT(type == NV_TYPE_NUMBER);
123
124 operation = nvlist_get_number(limits, name);
125 if ((operation & choperation) != choperation)
126 continue;
127
128 if ((operation & CAP_SYSCTL_RECURSIVE) == 0) {
129 if (strcmp(name, chname) != 0)
130 continue;
131 } else {
132 size_t namelen;
133
134 namelen = strlen(name);
135 if (strncmp(name, chname, namelen) != 0)
136 continue;
137 if (chname[namelen] != '.' && chname[namelen] != '\0')
138 continue;
139 }
140
141 return (true);
142 }
143
144 return (false);
145 }
146
147 static int
sysctl_limit(const nvlist_t * oldlimits,const nvlist_t * newlimits)148 sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
149 {
150 const nvlist_t *nvl;
151 const char *name;
152 void *cookie;
153 uint64_t operation;
154 int error, type;
155
156 cookie = NULL;
157 while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
158 if (type != NV_TYPE_NUMBER)
159 return (EINVAL);
160 operation = nvlist_get_number(newlimits, name);
161 if ((operation & ~(CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) != 0)
162 return (EINVAL);
163 if ((operation & (CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) == 0)
164 return (EINVAL);
165 if (!sysctl_allowed(oldlimits, name, operation))
166 return (ENOTCAPABLE);
167 }
168
169 return (0);
170 }
171
172 static int
sysctl_command(const char * cmd,const nvlist_t * limits,nvlist_t * nvlin,nvlist_t * nvlout)173 sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
174 nvlist_t *nvlout)
175 {
176 const char *name;
177 const void *newp;
178 void *oldp;
179 uint64_t operation;
180 size_t oldlen, newlen;
181 size_t *oldlenp;
182 int error;
183
184 if (strcmp(cmd, "sysctl") != 0)
185 return (EINVAL);
186 error = sysctl_check_one(nvlin, false);
187 if (error != 0)
188 return (error);
189
190 name = nvlist_get_string(nvlin, "name");
191 operation = nvlist_get_number(nvlin, "operation");
192 if (!sysctl_allowed(limits, name, operation))
193 return (ENOTCAPABLE);
194
195 if ((operation & CAP_SYSCTL_WRITE) != 0) {
196 if (!nvlist_exists_binary(nvlin, "newp"))
197 return (EINVAL);
198 newp = nvlist_get_binary(nvlin, "newp", &newlen);
199 PJDLOG_ASSERT(newp != NULL && newlen > 0);
200 } else {
201 newp = NULL;
202 newlen = 0;
203 }
204
205 if ((operation & CAP_SYSCTL_READ) != 0) {
206 if (nvlist_exists_null(nvlin, "justsize")) {
207 oldp = NULL;
208 oldlen = 0;
209 oldlenp = &oldlen;
210 } else {
211 if (!nvlist_exists_number(nvlin, "oldlen"))
212 return (EINVAL);
213 oldlen = (size_t)nvlist_get_number(nvlin, "oldlen");
214 if (oldlen == 0)
215 return (EINVAL);
216 oldp = calloc(1, oldlen);
217 if (oldp == NULL)
218 return (ENOMEM);
219 oldlenp = &oldlen;
220 }
221 } else {
222 oldp = NULL;
223 oldlen = 0;
224 oldlenp = NULL;
225 }
226
227 if (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) {
228 error = errno;
229 free(oldp);
230 return (error);
231 }
232
233 if ((operation & CAP_SYSCTL_READ) != 0) {
234 if (nvlist_exists_null(nvlin, "justsize"))
235 nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen);
236 else
237 nvlist_move_binary(nvlout, "oldp", oldp, oldlen);
238 }
239
240 return (0);
241 }
242
243 int
main(int argc,char * argv[])244 main(int argc, char *argv[])
245 {
246
247 return (service_start("system.sysctl", PARENT_FILENO, sysctl_limit,
248 sysctl_command, argc, argv));
249 }
250