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 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 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 * 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 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 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 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 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