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
49int
50cap_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 */
99static int
100sysctl_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
162static bool
163sysctl_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
200static int
201sysctl_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
224static int
225sysctl_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
295CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0);
296