Path: blob/main/lib/libcasper/services/cap_sysctl/cap_sysctl.c
48261 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2013, 2018 The FreeBSD Foundation4*5* This software was developed by Pawel Jakub Dawidek under sponsorship from6* the FreeBSD Foundation.7*8* Portions of this software were developed by Mark Johnston9* under sponsorship from the FreeBSD Foundation.10*11* Redistribution and use in source and binary forms, with or without12* modification, are permitted provided that the following conditions13* are met:14* 1. Redistributions of source code must retain the above copyright15* notice, this list of conditions and the following disclaimer.16* 2. Redistributions in binary form must reproduce the above copyright17* notice, this list of conditions and the following disclaimer in the18* documentation and/or other materials provided with the distribution.19*20* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND21* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE22* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE23* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE24* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL25* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS26* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)27* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT28* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY29* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF30* SUCH DAMAGE.31*/3233#include <sys/param.h>34#include <sys/cnv.h>35#include <sys/dnv.h>36#include <sys/nv.h>37#include <sys/sysctl.h>3839#include <assert.h>40#include <errno.h>41#include <stdlib.h>42#include <string.h>4344#include <libcasper.h>45#include <libcasper_service.h>4647#include "cap_sysctl.h"4849/*50* Limit interface.51*/5253struct cap_sysctl_limit {54cap_channel_t *chan;55nvlist_t *nv;56};5758cap_sysctl_limit_t *59cap_sysctl_limit_init(cap_channel_t *chan)60{61cap_sysctl_limit_t *limit;62int error;6364limit = malloc(sizeof(*limit));65if (limit != NULL) {66limit->chan = chan;67limit->nv = nvlist_create(NV_FLAG_NO_UNIQUE);68if (limit->nv == NULL) {69error = errno;70free(limit);71limit = NULL;72errno = error;73}74}75return (limit);76}7778cap_sysctl_limit_t *79cap_sysctl_limit_name(cap_sysctl_limit_t *limit, const char *name, int flags)80{81nvlist_t *lnv;82size_t mibsz;83int error, mib[CTL_MAXNAME];8485lnv = nvlist_create(0);86if (lnv == NULL) {87error = errno;88if (limit->nv != NULL)89nvlist_destroy(limit->nv);90free(limit);91errno = error;92return (NULL);93}94nvlist_add_string(lnv, "name", name);95nvlist_add_number(lnv, "operation", flags);9697mibsz = nitems(mib);98error = cap_sysctlnametomib(limit->chan, name, mib, &mibsz);99if (error == 0)100nvlist_add_binary(lnv, "mib", mib, mibsz * sizeof(int));101102nvlist_move_nvlist(limit->nv, "limit", lnv);103return (limit);104}105106cap_sysctl_limit_t *107cap_sysctl_limit_mib(cap_sysctl_limit_t *limit, const int *mibp, u_int miblen,108int flags)109{110nvlist_t *lnv;111int error;112113lnv = nvlist_create(0);114if (lnv == NULL) {115error = errno;116if (limit->nv != NULL)117nvlist_destroy(limit->nv);118free(limit);119errno = error;120return (NULL);121}122nvlist_add_binary(lnv, "mib", mibp, miblen * sizeof(int));123nvlist_add_number(lnv, "operation", flags);124nvlist_add_nvlist(limit->nv, "limit", lnv);125return (limit);126}127128int129cap_sysctl_limit(cap_sysctl_limit_t *limit)130{131cap_channel_t *chan;132nvlist_t *lnv;133134chan = limit->chan;135lnv = limit->nv;136free(limit);137138/* cap_limit_set(3) will always free the nvlist. */139return (cap_limit_set(chan, lnv));140}141142/*143* Service interface.144*/145146static int147do_sysctl(cap_channel_t *chan, nvlist_t *nvl, void *oldp, size_t *oldlenp,148const void *newp, size_t newlen)149{150const uint8_t *retoldp;151size_t oldlen;152int error;153uint8_t operation;154155operation = 0;156if (oldlenp != NULL)157operation |= CAP_SYSCTL_READ;158if (newp != NULL)159operation |= CAP_SYSCTL_WRITE;160nvlist_add_number(nvl, "operation", (uint64_t)operation);161if (oldp == NULL && oldlenp != NULL)162nvlist_add_null(nvl, "justsize");163else if (oldlenp != NULL)164nvlist_add_number(nvl, "oldlen", (uint64_t)*oldlenp);165if (newp != NULL)166nvlist_add_binary(nvl, "newp", newp, newlen);167168nvl = cap_xfer_nvlist(chan, nvl);169if (nvl == NULL)170return (-1);171error = (int)dnvlist_get_number(nvl, "error", 0);172if (error != 0) {173nvlist_destroy(nvl);174errno = error;175return (-1);176}177178if (oldp == NULL && oldlenp != NULL) {179*oldlenp = (size_t)nvlist_get_number(nvl, "oldlen");180} else if (oldp != NULL) {181retoldp = nvlist_get_binary(nvl, "oldp", &oldlen);182memcpy(oldp, retoldp, oldlen);183if (oldlenp != NULL)184*oldlenp = oldlen;185}186187nvlist_destroy(nvl);188189return (0);190}191192int193cap_sysctl(cap_channel_t *chan, const int *name, u_int namelen, void *oldp,194size_t *oldlenp, const void *newp, size_t newlen)195{196nvlist_t *req;197198req = nvlist_create(0);199nvlist_add_string(req, "cmd", "sysctl");200nvlist_add_binary(req, "mib", name, (size_t)namelen * sizeof(int));201return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen));202}203204int205cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp,206size_t *oldlenp, const void *newp, size_t newlen)207{208nvlist_t *req;209210req = nvlist_create(0);211nvlist_add_string(req, "cmd", "sysctlbyname");212nvlist_add_string(req, "name", name);213return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen));214}215216int217cap_sysctlnametomib(cap_channel_t *chan, const char *name, int *mibp,218size_t *sizep)219{220nvlist_t *req;221const void *mib;222size_t mibsz;223int error;224225req = nvlist_create(0);226nvlist_add_string(req, "cmd", "sysctlnametomib");227nvlist_add_string(req, "name", name);228nvlist_add_number(req, "operation", 0);229nvlist_add_number(req, "size", (uint64_t)*sizep);230231req = cap_xfer_nvlist(chan, req);232if (req == NULL)233return (-1);234error = (int)dnvlist_get_number(req, "error", 0);235if (error != 0) {236nvlist_destroy(req);237errno = error;238return (-1);239}240241mib = nvlist_get_binary(req, "mib", &mibsz);242*sizep = mibsz / sizeof(int);243244memcpy(mibp, mib, mibsz);245246nvlist_destroy(req);247248return (0);249}250251/*252* Service implementation.253*/254255/*256* Validate a sysctl description. This must consist of an nvlist with either a257* binary "mib" field or a string "name", and an operation.258*/259static int260sysctl_valid(const nvlist_t *nvl, bool limit)261{262const char *name;263void *cookie;264int type;265size_t size;266unsigned int field, fields;267268/* NULL nvl is of course invalid. */269if (nvl == NULL)270return (EINVAL);271if (nvlist_error(nvl) != 0)272return (nvlist_error(nvl));273274#define HAS_NAME 0x01275#define HAS_MIB 0x02276#define HAS_ID (HAS_NAME | HAS_MIB)277#define HAS_OPERATION 0x04278279fields = 0;280cookie = NULL;281while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {282if ((strcmp(name, "name") == 0 && type == NV_TYPE_STRING) ||283(strcmp(name, "mib") == 0 && type == NV_TYPE_BINARY)) {284if (strcmp(name, "mib") == 0) {285/* A MIB must be an array of integers. */286(void)cnvlist_get_binary(cookie, &size);287if (size % sizeof(int) != 0)288return (EINVAL);289field = HAS_MIB;290} else291field = HAS_NAME;292293/*294* A limit may contain both a name and a MIB identifier.295*/296if ((fields & field) != 0 ||297(!limit && (fields & HAS_ID) != 0))298return (EINVAL);299fields |= field;300} else if (strcmp(name, "operation") == 0) {301uint64_t mask, operation;302303if (type != NV_TYPE_NUMBER)304return (EINVAL);305306operation = cnvlist_get_number(cookie);307308/*309* Requests can only include the RDWR flags; limits may310* also include the RECURSIVE flag.311*/312mask = limit ? (CAP_SYSCTL_RDWR |313CAP_SYSCTL_RECURSIVE) : CAP_SYSCTL_RDWR;314if ((operation & ~mask) != 0 ||315(operation & CAP_SYSCTL_RDWR) == 0)316return (EINVAL);317/* Only one 'operation' can be present. */318if ((fields & HAS_OPERATION) != 0)319return (EINVAL);320fields |= HAS_OPERATION;321} else if (limit)322return (EINVAL);323}324325if ((fields & HAS_OPERATION) == 0 || (fields & HAS_ID) == 0)326return (EINVAL);327328#undef HAS_OPERATION329#undef HAS_ID330#undef HAS_MIB331#undef HAS_NAME332333return (0);334}335336static bool337sysctl_allowed(const nvlist_t *limits, const nvlist_t *req)338{339const nvlist_t *limit;340uint64_t op, reqop;341const char *lname, *name, *reqname;342void *cookie;343size_t lsize, reqsize;344const int *lmib, *reqmib;345int type;346347if (limits == NULL)348return (true);349350reqmib = dnvlist_get_binary(req, "mib", &reqsize, NULL, 0);351reqname = dnvlist_get_string(req, "name", NULL);352reqop = nvlist_get_number(req, "operation");353354cookie = NULL;355while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {356assert(type == NV_TYPE_NVLIST);357358limit = cnvlist_get_nvlist(cookie);359op = nvlist_get_number(limit, "operation");360if ((reqop & op) != reqop)361continue;362363if (reqname != NULL) {364lname = dnvlist_get_string(limit, "name", NULL);365if (lname == NULL)366continue;367if ((op & CAP_SYSCTL_RECURSIVE) == 0) {368if (strcmp(lname, reqname) != 0)369continue;370} else {371size_t namelen;372373namelen = strlen(lname);374if (strncmp(lname, reqname, namelen) != 0)375continue;376if (reqname[namelen] != '.' &&377reqname[namelen] != '\0')378continue;379}380} else {381lmib = dnvlist_get_binary(limit, "mib", &lsize, NULL, 0);382if (lmib == NULL)383continue;384if (lsize > reqsize || ((op & CAP_SYSCTL_RECURSIVE) == 0 &&385lsize < reqsize))386continue;387if (memcmp(lmib, reqmib, lsize) != 0)388continue;389}390391return (true);392}393394return (false);395}396397static int398sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)399{400const nvlist_t *nvl;401const char *name;402void *cookie;403int error, type;404405cookie = NULL;406while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {407if (strcmp(name, "limit") != 0 || type != NV_TYPE_NVLIST)408return (EINVAL);409nvl = cnvlist_get_nvlist(cookie);410error = sysctl_valid(nvl, true);411if (error != 0)412return (error);413if (!sysctl_allowed(oldlimits, nvl))414return (ENOTCAPABLE);415}416417return (0);418}419420static int421nametomib(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)422{423const char *name;424size_t size;425int error, *mibp;426427if (!sysctl_allowed(limits, nvlin))428return (ENOTCAPABLE);429430name = nvlist_get_string(nvlin, "name");431size = (size_t)nvlist_get_number(nvlin, "size");432433mibp = malloc(size * sizeof(*mibp));434if (mibp == NULL)435return (ENOMEM);436437error = sysctlnametomib(name, mibp, &size);438if (error != 0) {439error = errno;440free(mibp);441return (error);442}443444nvlist_add_binary(nvlout, "mib", mibp, size * sizeof(*mibp));445446return (0);447}448449static int450sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,451nvlist_t *nvlout)452{453const char *name;454const void *newp;455const int *mibp;456void *oldp;457uint64_t operation;458size_t oldlen, newlen, size;459size_t *oldlenp;460int error;461462if (strcmp(cmd, "sysctlnametomib") == 0)463return (nametomib(limits, nvlin, nvlout));464465if (strcmp(cmd, "sysctlbyname") != 0 && strcmp(cmd, "sysctl") != 0)466return (EINVAL);467error = sysctl_valid(nvlin, false);468if (error != 0)469return (error);470if (!sysctl_allowed(limits, nvlin))471return (ENOTCAPABLE);472473operation = nvlist_get_number(nvlin, "operation");474if ((operation & CAP_SYSCTL_WRITE) != 0) {475if (!nvlist_exists_binary(nvlin, "newp"))476return (EINVAL);477newp = nvlist_get_binary(nvlin, "newp", &newlen);478assert(newp != NULL && newlen > 0);479} else {480newp = NULL;481newlen = 0;482}483484if ((operation & CAP_SYSCTL_READ) != 0) {485if (nvlist_exists_null(nvlin, "justsize")) {486oldp = NULL;487oldlen = 0;488oldlenp = &oldlen;489} else {490if (!nvlist_exists_number(nvlin, "oldlen"))491return (EINVAL);492oldlen = (size_t)nvlist_get_number(nvlin, "oldlen");493if (oldlen == 0)494return (EINVAL);495oldp = calloc(1, oldlen);496if (oldp == NULL)497return (ENOMEM);498oldlenp = &oldlen;499}500} else {501oldp = NULL;502oldlen = 0;503oldlenp = NULL;504}505506if (strcmp(cmd, "sysctlbyname") == 0) {507name = nvlist_get_string(nvlin, "name");508error = sysctlbyname(name, oldp, oldlenp, newp, newlen);509} else {510mibp = nvlist_get_binary(nvlin, "mib", &size);511error = sysctl(mibp, size / sizeof(*mibp), oldp, oldlenp, newp,512newlen);513}514if (error != 0) {515error = errno;516free(oldp);517return (error);518}519520if ((operation & CAP_SYSCTL_READ) != 0) {521if (nvlist_exists_null(nvlin, "justsize"))522nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen);523else524nvlist_move_binary(nvlout, "oldp", oldp, oldlen);525}526527return (0);528}529530CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0);531532533