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