xref: /NextBSD/libexec/casper/sysctl/sysctl.c (revision 287e3b14e9552995def1802ec9c5034f4adf28ec)
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