xref: /freebsd/usr.sbin/sysconf/sysconf_query_sysctl.c (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
1 /*
2  * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
3  * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
4  *
5  * SPDX-License-Identifier: BSD-2-Clause
6  */
7 
8 /*
9  * Running-kernel sysctl OID descriptions and writability checks.
10  */
11 
12 #include "sysconf_priv.h"
13 
14 #if defined(__FreeBSD__)
15 
16 /*
17  * Fetch the running kernel's description of the sysctl OID `name' into
18  * `buf' via CTL_SYSCTL_OIDDESCR (the same information sysctl(8) prints
19  * with its own -d; an OID with no description yields the empty string).
20  * Returns zero on success; -1 when the OID does not resolve.
21  */
22 int
sysctl_descr(const char * name,char * buf,size_t bufsize)23 sysctl_descr(const char *name, char *buf, size_t bufsize)
24 {
25 	int mib[CTL_MAXNAME];
26 	int qoid[CTL_MAXNAME + 2];
27 	size_t len;
28 	size_t size;
29 
30 	buf[0] = '\0';
31 
32 	len = CTL_MAXNAME;
33 	if (sysctlnametomib(name, mib, &len) != 0)
34 		return (-1);
35 
36 	qoid[0] = CTL_SYSCTL;
37 	qoid[1] = CTL_SYSCTL_OIDDESCR;
38 	memcpy(qoid + 2, mib, len * sizeof(int));
39 	size = bufsize - 1;
40 	if (sysctl(qoid, (u_int)len + 2, buf, &size, 0, 0) != 0)
41 		buf[0] = '\0';
42 	buf[bufsize - 1] = '\0';
43 
44 	return (0);
45 }
46 
47 /*
48  * Process `-d' with explicit names for the sysctl target: report each
49  * requested OID's description from the running kernel. Returns
50  * EXIT_SUCCESS or EXIT_FAILURE.
51  */
52 int
describe_sysctl(void)53 describe_sysctl(void)
54 {
55 	int rv = EXIT_SUCCESS;
56 	unsigned int n;
57 	char buf[BUFSIZ];
58 
59 	for (n = 0; n < nreqs; n++) {
60 		if (sysctl_descr(reqs[n].name, buf, sizeof(buf)) != 0) {
61 			if (ignore_unknown)
62 				continue;
63 			if (!quiet)
64 				warnx("unknown oid '%s'", reqs[n].name);
65 			rv = EXIT_FAILURE;
66 			continue;
67 		}
68 		if (name_only)
69 			puts(reqs[n].name);
70 		else if (value_only)
71 			puts(buf);
72 		else
73 			printf("%s: %s\n", reqs[n].name, buf);
74 	}
75 
76 	return (rv);
77 }
78 
79 /*
80  * Return a short name for a CTLTYPE_* value (for warnings).
81  */
82 static const char *
sysctl_typename(u_int kind)83 sysctl_typename(u_int kind)
84 {
85 
86 	switch (kind & CTLTYPE) {
87 	case CTLTYPE_INT:	return ("integer");
88 	case CTLTYPE_UINT:	return ("unsigned integer");
89 	case CTLTYPE_LONG:	return ("long integer");
90 	case CTLTYPE_ULONG:	return ("unsigned long");
91 	case CTLTYPE_S8:	return ("int8_t");
92 	case CTLTYPE_S16:	return ("int16_t");
93 	case CTLTYPE_S32:	return ("int32_t");
94 	case CTLTYPE_S64:	return ("int64_t");
95 	case CTLTYPE_U8:	return ("uint8_t");
96 	case CTLTYPE_U16:	return ("uint16_t");
97 	case CTLTYPE_U32:	return ("uint32_t");
98 	case CTLTYPE_U64:	return ("uint64_t");
99 	case CTLTYPE_STRING:	return ("string");
100 	case CTLTYPE_OPAQUE:	return ("opaque");
101 	default:		return ("unknown");
102 	}
103 }
104 
105 /*
106  * True if `value' can be represented as a signed integer of the given
107  * inclusive [min, max] range. Requires a full-string numeric parse.
108  */
109 static int
sysctl_signed_ok(const char * value,intmax_t min,intmax_t max)110 sysctl_signed_ok(const char *value, intmax_t min, intmax_t max)
111 {
112 	char *end;
113 	intmax_t v;
114 
115 	errno = 0;
116 	v = strtoimax(value, &end, 0);
117 	if (errno != 0 || end == value || *end != '\0')
118 		return (0);
119 	return (v >= min && v <= max);
120 }
121 
122 /*
123  * True if `value' can be represented as an unsigned integer <= max.
124  * Leading whitespace is not accepted (sysctl.conf values are trimmed by
125  * the caller before we see them); a leading minus is rejected so that
126  * strtoumax() cannot wrap a negative into a large positive.
127  */
128 static int
sysctl_unsigned_ok(const char * value,uintmax_t max)129 sysctl_unsigned_ok(const char *value, uintmax_t max)
130 {
131 	char *end;
132 	uintmax_t v;
133 
134 	if (value[0] == '\0' || value[0] == '-')
135 		return (0);
136 	errno = 0;
137 	v = strtoumax(value, &end, 0);
138 	if (errno != 0 || end == value || *end != '\0')
139 		return (0);
140 	return (v <= max);
141 }
142 
143 /*
144  * Validate that `value' fits the numeric CTLTYPE described by `kind'.
145  * `fmt' is the OIDFMT format string (may start with "IK" for Kelvin
146  * temperatures); those are left to sysctl(8) at apply time. String and
147  * opaque OIDs are accepted as-is. Returns zero if acceptable; -1 if not.
148  */
149 static int
sysctl_value_ok(u_int kind,const char * fmt,const char * value)150 sysctl_value_ok(u_int kind, const char *fmt, const char *value)
151 {
152 
153 	if (value == NULL)
154 		return (0);
155 
156 	switch (kind & CTLTYPE) {
157 	case CTLTYPE_STRING:
158 	case CTLTYPE_OPAQUE:
159 		return (0);
160 	case CTLTYPE_INT:
161 		/*
162 		 * Temperature OIDs use an "IK" / "IK<digit>" display format
163 		 * with a specialized parser in sysctl(8); do not apply a
164 		 * plain integer range here.
165 		 */
166 		if (fmt != NULL && strncmp(fmt, "IK", 2) == 0)
167 			return (0);
168 		if (sysctl_signed_ok(value, INT_MIN, INT_MAX))
169 			return (0);
170 		break;
171 	case CTLTYPE_UINT:
172 		if (sysctl_unsigned_ok(value, UINT_MAX))
173 			return (0);
174 		break;
175 	case CTLTYPE_LONG:
176 		if (sysctl_signed_ok(value, LONG_MIN, LONG_MAX))
177 			return (0);
178 		break;
179 	case CTLTYPE_ULONG:
180 		if (sysctl_unsigned_ok(value, ULONG_MAX))
181 			return (0);
182 		break;
183 	case CTLTYPE_S8:
184 		if (sysctl_signed_ok(value, INT8_MIN, INT8_MAX))
185 			return (0);
186 		break;
187 	case CTLTYPE_S16:
188 		if (sysctl_signed_ok(value, INT16_MIN, INT16_MAX))
189 			return (0);
190 		break;
191 	case CTLTYPE_S32:
192 		if (sysctl_signed_ok(value, INT32_MIN, INT32_MAX))
193 			return (0);
194 		break;
195 	case CTLTYPE_S64:
196 		if (sysctl_signed_ok(value, INT64_MIN, INT64_MAX))
197 			return (0);
198 		break;
199 	case CTLTYPE_U8:
200 		if (sysctl_unsigned_ok(value, UINT8_MAX))
201 			return (0);
202 		break;
203 	case CTLTYPE_U16:
204 		if (sysctl_unsigned_ok(value, UINT16_MAX))
205 			return (0);
206 		break;
207 	case CTLTYPE_U32:
208 		if (sysctl_unsigned_ok(value, UINT32_MAX))
209 			return (0);
210 		break;
211 	case CTLTYPE_U64:
212 		if (sysctl_unsigned_ok(value, UINT64_MAX))
213 			return (0);
214 		break;
215 	default:
216 		/* NODE already rejected; unknown types: do not block */
217 		return (0);
218 	}
219 	return (-1);
220 }
221 
222 /*
223  * Determine whether the sysctl OID `name' can take effect when set from
224  * sysctl.conf(5) to `value': if the OID resolves it must be a leaf and
225  * writable at run time, and `value' must fit the OID's CTLTYPE (so an
226  * overflowing assignment cannot land in the conf file and surprise
227  * sysctl(8) at boot). An OID that is only tunable from the boot loader is
228  * reported as such (pointing at the loader target). An OID that does not
229  * resolve is still written -- sysctl.conf(5) commonly names OIDs from
230  * modules that are not yet loaded, and init(8) only warns -- so we warn
231  * (unless `quiet_unknown') and allow the assignment through; flag and
232  * range checks are only possible against a resolvable OID. Returns zero
233  * when the value should be written; -1 (after warning) otherwise.
234  */
235 int
sysctl_writable(const char * name,const char * value,int quiet_unknown)236 sysctl_writable(const char *name, const char *value, int quiet_unknown)
237 {
238 	int mib[CTL_MAXNAME];
239 	int qoid[CTL_MAXNAME + 2];
240 	size_t len;
241 	size_t size;
242 	u_int kind;
243 	u_char buf[BUFSIZ];
244 	const char *fmt;
245 
246 	len = CTL_MAXNAME;
247 	if (sysctlnametomib(name, mib, &len) != 0) {
248 		if (!quiet_unknown)
249 			warnx("WARNING: unknown oid '%s'", name);
250 		return (0);
251 	}
252 
253 	qoid[0] = CTL_SYSCTL;
254 	qoid[1] = CTL_SYSCTL_OIDFMT;
255 	memcpy(qoid + 2, mib, len * sizeof(int));
256 	size = sizeof(buf);
257 	if (sysctl(qoid, (u_int)len + 2, buf, &size, 0, 0) != 0) {
258 		warnx("couldn't find format of oid '%s'", name);
259 		return (-1);
260 	}
261 	kind = *(u_int *)(void *)buf;
262 	fmt = (const char *)(buf + sizeof(u_int));
263 
264 	if ((kind & CTLTYPE) == CTLTYPE_NODE) {
265 		warnx("oid '%s' isn't a leaf node", name);
266 		return (-1);
267 	}
268 	if ((kind & CTLFLAG_WR) == 0) {
269 		if ((kind & CTLFLAG_TUN) != 0) {
270 			warnx("oid '%s' is a read only tunable", name);
271 			warnx("use: %s loader %s=<value>", pgm, name);
272 		} else
273 			warnx("oid '%s' is read only", name);
274 		return (-1);
275 	}
276 	if (sysctl_value_ok(kind, fmt, value) != 0) {
277 		warnx("oid '%s' value '%s' is invalid or out of range for %s",
278 		    name, value, sysctl_typename(kind));
279 		return (-1);
280 	}
281 
282 	return (0);
283 }
284 
285 #endif /* __FreeBSD__ */
286