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 <assert.h> 38 #include <errno.h> 39 #include <stdlib.h> 40 #include <string.h> 41 42 #include <libcasper.h> 43 #include <libcasper_service.h> 44 45 #include "cap_sysctl.h" 46 47 int 48 cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp, 49 size_t *oldlenp, const void *newp, size_t newlen) 50 { 51 nvlist_t *nvl; 52 const uint8_t *retoldp; 53 uint8_t operation; 54 size_t oldlen; 55 56 operation = 0; 57 if (oldp != NULL) 58 operation |= CAP_SYSCTL_READ; 59 if (newp != NULL) 60 operation |= CAP_SYSCTL_WRITE; 61 62 nvl = nvlist_create(0); 63 nvlist_add_string(nvl, "cmd", "sysctl"); 64 nvlist_add_string(nvl, "name", name); 65 nvlist_add_number(nvl, "operation", (uint64_t)operation); 66 if (oldp == NULL && oldlenp != NULL) 67 nvlist_add_null(nvl, "justsize"); 68 else if (oldlenp != NULL) 69 nvlist_add_number(nvl, "oldlen", (uint64_t)*oldlenp); 70 if (newp != NULL) 71 nvlist_add_binary(nvl, "newp", newp, newlen); 72 nvl = cap_xfer_nvlist(chan, nvl, 0); 73 if (nvl == NULL) 74 return (-1); 75 if (nvlist_get_number(nvl, "error") != 0) { 76 errno = (int)nvlist_get_number(nvl, "error"); 77 nvlist_destroy(nvl); 78 return (-1); 79 } 80 81 if (oldp == NULL && oldlenp != NULL) { 82 *oldlenp = (size_t)nvlist_get_number(nvl, "oldlen"); 83 } else if (oldp != NULL) { 84 retoldp = nvlist_get_binary(nvl, "oldp", &oldlen); 85 memcpy(oldp, retoldp, oldlen); 86 if (oldlenp != NULL) 87 *oldlenp = oldlen; 88 } 89 nvlist_destroy(nvl); 90 91 return (0); 92 } 93 94 /* 95 * Service functions. 96 */ 97 static int 98 sysctl_check_one(const nvlist_t *nvl, bool islimit) 99 { 100 const char *name; 101 void *cookie; 102 int type; 103 unsigned int fields; 104 105 /* NULL nvl is of course invalid. */ 106 if (nvl == NULL) 107 return (EINVAL); 108 if (nvlist_error(nvl) != 0) 109 return (nvlist_error(nvl)); 110 111 #define HAS_NAME 0x01 112 #define HAS_OPERATION 0x02 113 114 fields = 0; 115 cookie = NULL; 116 while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) { 117 /* We accept only one 'name' and one 'operation' in nvl. */ 118 if (strcmp(name, "name") == 0) { 119 if (type != NV_TYPE_STRING) 120 return (EINVAL); 121 /* Only one 'name' can be present. */ 122 if ((fields & HAS_NAME) != 0) 123 return (EINVAL); 124 fields |= HAS_NAME; 125 } else if (strcmp(name, "operation") == 0) { 126 uint64_t operation; 127 128 if (type != NV_TYPE_NUMBER) 129 return (EINVAL); 130 /* 131 * We accept only CAP_SYSCTL_READ and 132 * CAP_SYSCTL_WRITE flags. 133 */ 134 operation = nvlist_get_number(nvl, name); 135 if ((operation & ~(CAP_SYSCTL_RDWR)) != 0) 136 return (EINVAL); 137 /* ...but there has to be at least one of them. */ 138 if ((operation & (CAP_SYSCTL_RDWR)) == 0) 139 return (EINVAL); 140 /* Only one 'operation' can be present. */ 141 if ((fields & HAS_OPERATION) != 0) 142 return (EINVAL); 143 fields |= HAS_OPERATION; 144 } else if (islimit) { 145 /* If this is limit, there can be no other fields. */ 146 return (EINVAL); 147 } 148 } 149 150 /* Both fields has to be there. */ 151 if (fields != (HAS_NAME | HAS_OPERATION)) 152 return (EINVAL); 153 154 #undef HAS_OPERATION 155 #undef HAS_NAME 156 157 return (0); 158 } 159 160 static bool 161 sysctl_allowed(const nvlist_t *limits, const char *chname, uint64_t choperation) 162 { 163 uint64_t operation; 164 const char *name; 165 void *cookie; 166 int type; 167 168 if (limits == NULL) 169 return (true); 170 171 cookie = NULL; 172 while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { 173 assert(type == NV_TYPE_NUMBER); 174 175 operation = nvlist_get_number(limits, name); 176 if ((operation & choperation) != choperation) 177 continue; 178 179 if ((operation & CAP_SYSCTL_RECURSIVE) == 0) { 180 if (strcmp(name, chname) != 0) 181 continue; 182 } else { 183 size_t namelen; 184 185 namelen = strlen(name); 186 if (strncmp(name, chname, namelen) != 0) 187 continue; 188 if (chname[namelen] != '.' && chname[namelen] != '\0') 189 continue; 190 } 191 192 return (true); 193 } 194 195 return (false); 196 } 197 198 static int 199 sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 200 { 201 const char *name; 202 void *cookie; 203 uint64_t operation; 204 int type; 205 206 cookie = NULL; 207 while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { 208 if (type != NV_TYPE_NUMBER) 209 return (EINVAL); 210 operation = nvlist_get_number(newlimits, name); 211 if ((operation & ~(CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) != 0) 212 return (EINVAL); 213 if ((operation & (CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) == 0) 214 return (EINVAL); 215 if (!sysctl_allowed(oldlimits, name, operation)) 216 return (ENOTCAPABLE); 217 } 218 219 return (0); 220 } 221 222 static int 223 sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, 224 nvlist_t *nvlout) 225 { 226 const char *name; 227 const void *newp; 228 void *oldp; 229 uint64_t operation; 230 size_t oldlen, newlen; 231 size_t *oldlenp; 232 int error; 233 234 if (strcmp(cmd, "sysctl") != 0) 235 return (EINVAL); 236 error = sysctl_check_one(nvlin, false); 237 if (error != 0) 238 return (error); 239 240 name = nvlist_get_string(nvlin, "name"); 241 operation = nvlist_get_number(nvlin, "operation"); 242 if (!sysctl_allowed(limits, name, operation)) 243 return (ENOTCAPABLE); 244 245 if ((operation & CAP_SYSCTL_WRITE) != 0) { 246 if (!nvlist_exists_binary(nvlin, "newp")) 247 return (EINVAL); 248 newp = nvlist_get_binary(nvlin, "newp", &newlen); 249 assert(newp != NULL && newlen > 0); 250 } else { 251 newp = NULL; 252 newlen = 0; 253 } 254 255 if ((operation & CAP_SYSCTL_READ) != 0) { 256 if (nvlist_exists_null(nvlin, "justsize")) { 257 oldp = NULL; 258 oldlen = 0; 259 oldlenp = &oldlen; 260 } else { 261 if (!nvlist_exists_number(nvlin, "oldlen")) 262 return (EINVAL); 263 oldlen = (size_t)nvlist_get_number(nvlin, "oldlen"); 264 if (oldlen == 0) 265 return (EINVAL); 266 oldp = calloc(1, oldlen); 267 if (oldp == NULL) 268 return (ENOMEM); 269 oldlenp = &oldlen; 270 } 271 } else { 272 oldp = NULL; 273 oldlen = 0; 274 oldlenp = NULL; 275 } 276 277 if (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) { 278 error = errno; 279 free(oldp); 280 return (error); 281 } 282 283 if ((operation & CAP_SYSCTL_READ) != 0) { 284 if (nvlist_exists_null(nvlin, "justsize")) 285 nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen); 286 else 287 nvlist_move_binary(nvlout, "oldp", oldp, oldlen); 288 } 289 290 return (0); 291 } 292 293 CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command); 294