aa54f39068
There is no API guarantee that realloc() will not fail when the buffer is shrinking. Handle it by simply returning the untrimmed buffer. While this is unlikely to ever happen in practice, it seems worth handling just to silence static analyzer warnings. PR: 243106 Submitted by: Hans Christian Woithe <chwoithe@yahoo.com> MFC after: 1 week
1119 lines
28 KiB
C
1119 lines
28 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2009 James Gritton.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/jail.h>
|
|
#include <sys/linker.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "jail.h"
|
|
|
|
#define SJPARAM "security.jail.param"
|
|
|
|
#define JPS_IN_ADDR 1
|
|
#define JPS_IN6_ADDR 2
|
|
|
|
#define ARRAY_SANITY 5
|
|
#define ARRAY_SLOP 5
|
|
|
|
|
|
static int jailparam_import_enum(const char **values, int nvalues,
|
|
const char *valstr, size_t valsize, int *value);
|
|
static int jailparam_type(struct jailparam *jp);
|
|
static int kldload_param(const char *name);
|
|
static char *noname(const char *name);
|
|
static char *nononame(const char *name);
|
|
|
|
char jail_errmsg[JAIL_ERRMSGLEN];
|
|
|
|
static const char *bool_values[] = { "false", "true" };
|
|
static const char *jailsys_values[] = { "disable", "new", "inherit" };
|
|
|
|
|
|
/*
|
|
* Import a null-terminated parameter list and set a jail with the flags
|
|
* and parameters.
|
|
*/
|
|
int
|
|
jail_setv(int flags, ...)
|
|
{
|
|
va_list ap, tap;
|
|
struct jailparam *jp;
|
|
const char *name, *value;
|
|
int njp, jid;
|
|
|
|
/* Create the parameter list and import the parameters. */
|
|
va_start(ap, flags);
|
|
va_copy(tap, ap);
|
|
for (njp = 0; va_arg(tap, char *) != NULL; njp++)
|
|
(void)va_arg(tap, char *);
|
|
va_end(tap);
|
|
jp = alloca(njp * sizeof(struct jailparam));
|
|
for (njp = 0; (name = va_arg(ap, char *)) != NULL;) {
|
|
value = va_arg(ap, char *);
|
|
if (jailparam_init(jp + njp, name) < 0)
|
|
goto error;
|
|
if (jailparam_import(jp + njp++, value) < 0)
|
|
goto error;
|
|
}
|
|
va_end(ap);
|
|
jid = jailparam_set(jp, njp, flags);
|
|
jailparam_free(jp, njp);
|
|
return (jid);
|
|
|
|
error:
|
|
jailparam_free(jp, njp);
|
|
va_end(ap);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Read a null-terminated parameter list, get the referenced jail, and export
|
|
* the parameters to the list.
|
|
*/
|
|
int
|
|
jail_getv(int flags, ...)
|
|
{
|
|
va_list ap, tap;
|
|
struct jailparam *jp, *jp_lastjid, *jp_jid, *jp_name, *jp_key;
|
|
char *valarg, *value;
|
|
const char *name, *key_value, *lastjid_value, *jid_value, *name_value;
|
|
int njp, i, jid;
|
|
|
|
/* Create the parameter list and find the key. */
|
|
va_start(ap, flags);
|
|
va_copy(tap, ap);
|
|
for (njp = 0; va_arg(tap, char *) != NULL; njp++)
|
|
(void)va_arg(tap, char *);
|
|
va_end(tap);
|
|
|
|
jp = alloca(njp * sizeof(struct jailparam));
|
|
va_copy(tap, ap);
|
|
jp_lastjid = jp_jid = jp_name = NULL;
|
|
lastjid_value = jid_value = name_value = NULL;
|
|
for (njp = 0; (name = va_arg(tap, char *)) != NULL; njp++) {
|
|
value = va_arg(tap, char *);
|
|
if (jailparam_init(jp + njp, name) < 0) {
|
|
va_end(tap);
|
|
goto error;
|
|
}
|
|
if (!strcmp(jp[njp].jp_name, "lastjid")) {
|
|
jp_lastjid = jp + njp;
|
|
lastjid_value = value;
|
|
} else if (!strcmp(jp[njp].jp_name, "jid")) {
|
|
jp_jid = jp + njp;
|
|
jid_value = value;
|
|
} if (!strcmp(jp[njp].jp_name, "name")) {
|
|
jp_name = jp + njp;
|
|
name_value = value;
|
|
}
|
|
}
|
|
va_end(tap);
|
|
/* Import the key parameter. */
|
|
if (jp_lastjid != NULL) {
|
|
jp_key = jp_lastjid;
|
|
key_value = lastjid_value;
|
|
} else if (jp_jid != NULL && strtol(jid_value, NULL, 10) != 0) {
|
|
jp_key = jp_jid;
|
|
key_value = jid_value;
|
|
} else if (jp_name != NULL) {
|
|
jp_key = jp_name;
|
|
key_value = name_value;
|
|
} else {
|
|
strlcpy(jail_errmsg, "no jail specified", JAIL_ERRMSGLEN);
|
|
errno = ENOENT;
|
|
goto error;
|
|
}
|
|
if (jailparam_import(jp_key, key_value) < 0)
|
|
goto error;
|
|
/* Get the jail and export the parameters. */
|
|
jid = jailparam_get(jp, njp, flags);
|
|
if (jid < 0)
|
|
goto error;
|
|
for (i = 0; i < njp; i++) {
|
|
(void)va_arg(ap, char *);
|
|
valarg = va_arg(ap, char *);
|
|
if (jp + i != jp_key) {
|
|
/* It's up to the caller to ensure there's room. */
|
|
if ((jp[i].jp_ctltype & CTLTYPE) == CTLTYPE_STRING)
|
|
strcpy(valarg, jp[i].jp_value);
|
|
else {
|
|
value = jailparam_export(jp + i);
|
|
if (value == NULL)
|
|
goto error;
|
|
strcpy(valarg, value);
|
|
free(value);
|
|
}
|
|
}
|
|
}
|
|
jailparam_free(jp, njp);
|
|
va_end(ap);
|
|
return (jid);
|
|
|
|
error:
|
|
jailparam_free(jp, njp);
|
|
va_end(ap);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Return a list of all known parameters.
|
|
*/
|
|
int
|
|
jailparam_all(struct jailparam **jpp)
|
|
{
|
|
struct jailparam *jp, *tjp;
|
|
size_t mlen1, mlen2, buflen;
|
|
unsigned njp, nlist;
|
|
int mib1[CTL_MAXNAME], mib2[CTL_MAXNAME - 2];
|
|
char buf[MAXPATHLEN];
|
|
|
|
njp = 0;
|
|
nlist = 32;
|
|
jp = malloc(nlist * sizeof(*jp));
|
|
if (jp == NULL) {
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
mib1[0] = 0;
|
|
mib1[1] = 2;
|
|
mlen1 = CTL_MAXNAME - 2;
|
|
if (sysctlnametomib(SJPARAM, mib1 + 2, &mlen1) < 0) {
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctlnametomib(" SJPARAM "): %s", strerror(errno));
|
|
goto error;
|
|
}
|
|
for (;; njp++) {
|
|
/* Get the next parameter. */
|
|
mlen2 = sizeof(mib2);
|
|
if (sysctl(mib1, mlen1 + 2, mib2, &mlen2, NULL, 0) < 0) {
|
|
if (errno == ENOENT) {
|
|
/* No more entries. */
|
|
break;
|
|
}
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctl(0.2): %s", strerror(errno));
|
|
goto error;
|
|
}
|
|
if (mib2[0] != mib1[2] ||
|
|
mib2[1] != mib1[3] ||
|
|
mib2[2] != mib1[4])
|
|
break;
|
|
/* Convert it to an ascii name. */
|
|
memcpy(mib1 + 2, mib2, mlen2);
|
|
mlen1 = mlen2 / sizeof(int);
|
|
mib1[1] = 1;
|
|
buflen = sizeof(buf);
|
|
if (sysctl(mib1, mlen1 + 2, buf, &buflen, NULL, 0) < 0) {
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctl(0.1): %s", strerror(errno));
|
|
goto error;
|
|
}
|
|
if (buf[buflen - 2] == '.')
|
|
buf[buflen - 2] = '\0';
|
|
/* Add the parameter to the list */
|
|
if (njp >= nlist) {
|
|
nlist *= 2;
|
|
tjp = reallocarray(jp, nlist, sizeof(*jp));
|
|
if (tjp == NULL)
|
|
goto error;
|
|
jp = tjp;
|
|
}
|
|
if (jailparam_init(jp + njp, buf + sizeof(SJPARAM)) < 0)
|
|
goto error;
|
|
mib1[1] = 2;
|
|
}
|
|
/* Just return the untrimmed buffer if reallocarray() somehow fails. */
|
|
tjp = reallocarray(jp, njp, sizeof(*jp));
|
|
if (tjp != NULL)
|
|
jp = tjp;
|
|
*jpp = jp;
|
|
return (njp);
|
|
|
|
error:
|
|
jailparam_free(jp, njp);
|
|
free(jp);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Clear a jail parameter and copy in its name.
|
|
*/
|
|
int
|
|
jailparam_init(struct jailparam *jp, const char *name)
|
|
{
|
|
|
|
memset(jp, 0, sizeof(*jp));
|
|
jp->jp_name = strdup(name);
|
|
if (jp->jp_name == NULL) {
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
if (jailparam_type(jp) < 0) {
|
|
jailparam_free(jp, 1);
|
|
jp->jp_name = NULL;
|
|
jp->jp_value = NULL;
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Put a name and value into a jail parameter element, converting the value
|
|
* to internal form.
|
|
*/
|
|
int
|
|
jailparam_import(struct jailparam *jp, const char *value)
|
|
{
|
|
char *p, *ep, *tvalue;
|
|
const char *avalue;
|
|
int i, nval, fw;
|
|
|
|
if (value == NULL)
|
|
return (0);
|
|
if ((jp->jp_ctltype & CTLTYPE) == CTLTYPE_STRING) {
|
|
jp->jp_value = strdup(value);
|
|
if (jp->jp_value == NULL) {
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
nval = 1;
|
|
if (jp->jp_elemlen) {
|
|
if (value[0] == '\0' || (value[0] == '-' && value[1] == '\0')) {
|
|
jp->jp_value = strdup("");
|
|
if (jp->jp_value == NULL) {
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
jp->jp_valuelen = 0;
|
|
return (0);
|
|
}
|
|
for (p = strchr(value, ','); p; p = strchr(p + 1, ','))
|
|
nval++;
|
|
jp->jp_valuelen = jp->jp_elemlen * nval;
|
|
}
|
|
jp->jp_value = malloc(jp->jp_valuelen);
|
|
if (jp->jp_value == NULL) {
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
avalue = value;
|
|
for (i = 0; i < nval; i++) {
|
|
fw = nval == 1 ? strlen(avalue) : strcspn(avalue, ",");
|
|
switch (jp->jp_ctltype & CTLTYPE) {
|
|
case CTLTYPE_INT:
|
|
if (jp->jp_flags & (JP_BOOL | JP_NOBOOL)) {
|
|
if (!jailparam_import_enum(bool_values, 2,
|
|
avalue, fw, &((int *)jp->jp_value)[i])) {
|
|
snprintf(jail_errmsg,
|
|
JAIL_ERRMSGLEN, "%s: "
|
|
"unknown boolean value \"%.*s\"",
|
|
jp->jp_name, fw, avalue);
|
|
errno = EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
if (jp->jp_flags & JP_JAILSYS) {
|
|
/*
|
|
* Allow setting a jailsys parameter to "new"
|
|
* in a booleanesque fashion.
|
|
*/
|
|
if (value[0] == '\0')
|
|
((int *)jp->jp_value)[i] = JAIL_SYS_NEW;
|
|
else if (!jailparam_import_enum(jailsys_values,
|
|
sizeof(jailsys_values) /
|
|
sizeof(jailsys_values[0]), avalue, fw,
|
|
&((int *)jp->jp_value)[i])) {
|
|
snprintf(jail_errmsg,
|
|
JAIL_ERRMSGLEN, "%s: "
|
|
"unknown jailsys value \"%.*s\"",
|
|
jp->jp_name, fw, avalue);
|
|
errno = EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
((int *)jp->jp_value)[i] = strtol(avalue, &ep, 10);
|
|
integer_test:
|
|
if (ep != avalue + fw) {
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"%s: non-integer value \"%.*s\"",
|
|
jp->jp_name, fw, avalue);
|
|
errno = EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
case CTLTYPE_UINT:
|
|
((unsigned *)jp->jp_value)[i] =
|
|
strtoul(avalue, &ep, 10);
|
|
goto integer_test;
|
|
case CTLTYPE_LONG:
|
|
((long *)jp->jp_value)[i] = strtol(avalue, &ep, 10);
|
|
goto integer_test;
|
|
case CTLTYPE_ULONG:
|
|
((unsigned long *)jp->jp_value)[i] =
|
|
strtoul(avalue, &ep, 10);
|
|
goto integer_test;
|
|
case CTLTYPE_S64:
|
|
((int64_t *)jp->jp_value)[i] =
|
|
strtoimax(avalue, &ep, 10);
|
|
goto integer_test;
|
|
case CTLTYPE_U64:
|
|
((uint64_t *)jp->jp_value)[i] =
|
|
strtoumax(avalue, &ep, 10);
|
|
goto integer_test;
|
|
case CTLTYPE_STRUCT:
|
|
tvalue = alloca(fw + 1);
|
|
strlcpy(tvalue, avalue, fw + 1);
|
|
switch (jp->jp_structtype) {
|
|
case JPS_IN_ADDR:
|
|
if (inet_pton(AF_INET, tvalue,
|
|
&((struct in_addr *)jp->jp_value)[i]) != 1)
|
|
{
|
|
snprintf(jail_errmsg,
|
|
JAIL_ERRMSGLEN,
|
|
"%s: not an IPv4 address: %s",
|
|
jp->jp_name, tvalue);
|
|
errno = EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
case JPS_IN6_ADDR:
|
|
if (inet_pton(AF_INET6, tvalue,
|
|
&((struct in6_addr *)jp->jp_value)[i]) != 1)
|
|
{
|
|
snprintf(jail_errmsg,
|
|
JAIL_ERRMSGLEN,
|
|
"%s: not an IPv6 address: %s",
|
|
jp->jp_name, tvalue);
|
|
errno = EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
default:
|
|
goto unknown_type;
|
|
}
|
|
break;
|
|
default:
|
|
unknown_type:
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"unknown type for %s", jp->jp_name);
|
|
errno = ENOENT;
|
|
goto error;
|
|
}
|
|
avalue += fw + 1;
|
|
}
|
|
return (0);
|
|
|
|
error:
|
|
free(jp->jp_value);
|
|
jp->jp_value = NULL;
|
|
return (-1);
|
|
}
|
|
|
|
static int
|
|
jailparam_import_enum(const char **values, int nvalues, const char *valstr,
|
|
size_t valsize, int *value)
|
|
{
|
|
char *ep;
|
|
int i;
|
|
|
|
for (i = 0; i < nvalues; i++)
|
|
if (valsize == strlen(values[i]) &&
|
|
!strncasecmp(valstr, values[i], valsize)) {
|
|
*value = i;
|
|
return 1;
|
|
}
|
|
*value = strtol(valstr, &ep, 10);
|
|
return (ep == valstr + valsize);
|
|
}
|
|
|
|
/*
|
|
* Put a name and value into a jail parameter element, copying the value
|
|
* but not altering it.
|
|
*/
|
|
int
|
|
jailparam_import_raw(struct jailparam *jp, void *value, size_t valuelen)
|
|
{
|
|
|
|
jp->jp_value = value;
|
|
jp->jp_valuelen = valuelen;
|
|
jp->jp_flags |= JP_RAWVALUE;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Run the jail_set and jail_get system calls on a parameter list.
|
|
*/
|
|
int
|
|
jailparam_set(struct jailparam *jp, unsigned njp, int flags)
|
|
{
|
|
struct iovec *jiov;
|
|
char *nname;
|
|
int i, jid, bool0;
|
|
unsigned j;
|
|
|
|
jiov = alloca(sizeof(struct iovec) * 2 * (njp + 1));
|
|
bool0 = 0;
|
|
for (i = j = 0; j < njp; j++) {
|
|
jiov[i].iov_base = jp[j].jp_name;
|
|
jiov[i].iov_len = strlen(jp[j].jp_name) + 1;
|
|
i++;
|
|
if (jp[j].jp_flags & (JP_BOOL | JP_NOBOOL)) {
|
|
/*
|
|
* Set booleans without values. If one has a value of
|
|
* zero, change it to (or from) its "no" counterpart.
|
|
*/
|
|
jiov[i].iov_base = NULL;
|
|
jiov[i].iov_len = 0;
|
|
if (jp[j].jp_value != NULL &&
|
|
jp[j].jp_valuelen == sizeof(int) &&
|
|
!*(int *)jp[j].jp_value) {
|
|
bool0 = 1;
|
|
nname = jp[j].jp_flags & JP_BOOL
|
|
? noname(jp[j].jp_name)
|
|
: nononame(jp[j].jp_name);
|
|
if (nname == NULL) {
|
|
njp = j;
|
|
jid = -1;
|
|
goto done;
|
|
}
|
|
jiov[i - 1].iov_base = nname;
|
|
jiov[i - 1].iov_len = strlen(nname) + 1;
|
|
|
|
}
|
|
} else {
|
|
/*
|
|
* Try to fill in missing values with an empty string.
|
|
*/
|
|
if (jp[j].jp_value == NULL && jp[j].jp_valuelen > 0 &&
|
|
jailparam_import(jp + j, "") < 0) {
|
|
njp = j;
|
|
jid = -1;
|
|
goto done;
|
|
}
|
|
jiov[i].iov_base = jp[j].jp_value;
|
|
jiov[i].iov_len =
|
|
(jp[j].jp_ctltype & CTLTYPE) == CTLTYPE_STRING
|
|
? strlen(jp[j].jp_value) + 1
|
|
: jp[j].jp_valuelen;
|
|
}
|
|
i++;
|
|
}
|
|
jiov[i].iov_base = __DECONST(char *, "errmsg");
|
|
jiov[i].iov_len = sizeof("errmsg");
|
|
i++;
|
|
jiov[i].iov_base = jail_errmsg;
|
|
jiov[i].iov_len = JAIL_ERRMSGLEN;
|
|
i++;
|
|
jail_errmsg[0] = 0;
|
|
jid = jail_set(jiov, i, flags);
|
|
if (jid < 0 && !jail_errmsg[0])
|
|
snprintf(jail_errmsg, sizeof(jail_errmsg), "jail_set: %s",
|
|
strerror(errno));
|
|
done:
|
|
if (bool0)
|
|
for (j = 0; j < njp; j++)
|
|
if ((jp[j].jp_flags & (JP_BOOL | JP_NOBOOL)) &&
|
|
jp[j].jp_value != NULL &&
|
|
jp[j].jp_valuelen == sizeof(int) &&
|
|
!*(int *)jp[j].jp_value)
|
|
free(jiov[j * 2].iov_base);
|
|
return (jid);
|
|
}
|
|
|
|
int
|
|
jailparam_get(struct jailparam *jp, unsigned njp, int flags)
|
|
{
|
|
struct iovec *jiov;
|
|
struct jailparam *jp_lastjid, *jp_jid, *jp_name, *jp_key;
|
|
int i, ai, ki, jid, arrays, sanity;
|
|
unsigned j;
|
|
|
|
/*
|
|
* Get the types for all parameters.
|
|
* Find the key and any array parameters.
|
|
*/
|
|
jiov = alloca(sizeof(struct iovec) * 2 * (njp + 1));
|
|
jp_lastjid = jp_jid = jp_name = NULL;
|
|
arrays = 0;
|
|
for (ai = j = 0; j < njp; j++) {
|
|
if (!strcmp(jp[j].jp_name, "lastjid"))
|
|
jp_lastjid = jp + j;
|
|
else if (!strcmp(jp[j].jp_name, "jid"))
|
|
jp_jid = jp + j;
|
|
else if (!strcmp(jp[j].jp_name, "name"))
|
|
jp_name = jp + j;
|
|
else if (jp[j].jp_elemlen && !(jp[j].jp_flags & JP_RAWVALUE)) {
|
|
arrays = 1;
|
|
jiov[ai].iov_base = jp[j].jp_name;
|
|
jiov[ai].iov_len = strlen(jp[j].jp_name) + 1;
|
|
ai++;
|
|
jiov[ai].iov_base = NULL;
|
|
jiov[ai].iov_len = 0;
|
|
ai++;
|
|
}
|
|
}
|
|
jp_key = jp_lastjid ? jp_lastjid :
|
|
jp_jid && jp_jid->jp_valuelen == sizeof(int) &&
|
|
jp_jid->jp_value && *(int *)jp_jid->jp_value ? jp_jid : jp_name;
|
|
if (jp_key == NULL || jp_key->jp_value == NULL) {
|
|
strlcpy(jail_errmsg, "no jail specified", JAIL_ERRMSGLEN);
|
|
errno = ENOENT;
|
|
return (-1);
|
|
}
|
|
ki = ai;
|
|
jiov[ki].iov_base = jp_key->jp_name;
|
|
jiov[ki].iov_len = strlen(jp_key->jp_name) + 1;
|
|
ki++;
|
|
jiov[ki].iov_base = jp_key->jp_value;
|
|
jiov[ki].iov_len = (jp_key->jp_ctltype & CTLTYPE) == CTLTYPE_STRING
|
|
? strlen(jp_key->jp_value) + 1 : jp_key->jp_valuelen;
|
|
ki++;
|
|
jiov[ki].iov_base = __DECONST(char *, "errmsg");
|
|
jiov[ki].iov_len = sizeof("errmsg");
|
|
ki++;
|
|
jiov[ki].iov_base = jail_errmsg;
|
|
jiov[ki].iov_len = JAIL_ERRMSGLEN;
|
|
ki++;
|
|
jail_errmsg[0] = 0;
|
|
if (arrays && jail_get(jiov, ki, flags) < 0) {
|
|
if (!jail_errmsg[0])
|
|
snprintf(jail_errmsg, sizeof(jail_errmsg),
|
|
"jail_get: %s", strerror(errno));
|
|
return (-1);
|
|
}
|
|
/* Allocate storage for all parameters. */
|
|
for (ai = j = 0, i = ki; j < njp; j++) {
|
|
if (jp[j].jp_elemlen && !(jp[j].jp_flags & JP_RAWVALUE)) {
|
|
ai++;
|
|
jiov[ai].iov_len += jp[j].jp_elemlen * ARRAY_SLOP;
|
|
if (jp[j].jp_valuelen >= jiov[ai].iov_len)
|
|
jiov[ai].iov_len = jp[j].jp_valuelen;
|
|
else {
|
|
jp[j].jp_valuelen = jiov[ai].iov_len;
|
|
if (jp[j].jp_value != NULL)
|
|
free(jp[j].jp_value);
|
|
jp[j].jp_value = malloc(jp[j].jp_valuelen);
|
|
if (jp[j].jp_value == NULL) {
|
|
strerror_r(errno, jail_errmsg,
|
|
JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
}
|
|
jiov[ai].iov_base = jp[j].jp_value;
|
|
memset(jiov[ai].iov_base, 0, jiov[ai].iov_len);
|
|
ai++;
|
|
} else if (jp + j != jp_key) {
|
|
jiov[i].iov_base = jp[j].jp_name;
|
|
jiov[i].iov_len = strlen(jp[j].jp_name) + 1;
|
|
i++;
|
|
if (jp[j].jp_value == NULL &&
|
|
!(jp[j].jp_flags & JP_RAWVALUE)) {
|
|
jp[j].jp_value = malloc(jp[j].jp_valuelen);
|
|
if (jp[j].jp_value == NULL) {
|
|
strerror_r(errno, jail_errmsg,
|
|
JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
}
|
|
jiov[i].iov_base = jp[j].jp_value;
|
|
jiov[i].iov_len = jp[j].jp_valuelen;
|
|
memset(jiov[i].iov_base, 0, jiov[i].iov_len);
|
|
i++;
|
|
}
|
|
}
|
|
/*
|
|
* Get the prison. If there are array elements, retry a few times
|
|
* in case their sizes changed from under us.
|
|
*/
|
|
for (sanity = 0;; sanity++) {
|
|
jid = jail_get(jiov, i, flags);
|
|
if (jid >= 0 || !arrays || sanity == ARRAY_SANITY ||
|
|
errno != EINVAL || jail_errmsg[0])
|
|
break;
|
|
for (ai = j = 0; j < njp; j++) {
|
|
if (jp[j].jp_elemlen &&
|
|
!(jp[j].jp_flags & JP_RAWVALUE)) {
|
|
ai++;
|
|
jiov[ai].iov_base = NULL;
|
|
jiov[ai].iov_len = 0;
|
|
ai++;
|
|
}
|
|
}
|
|
if (jail_get(jiov, ki, flags) < 0)
|
|
break;
|
|
for (ai = j = 0; j < njp; j++) {
|
|
if (jp[j].jp_elemlen &&
|
|
!(jp[j].jp_flags & JP_RAWVALUE)) {
|
|
ai++;
|
|
jiov[ai].iov_len +=
|
|
jp[j].jp_elemlen * ARRAY_SLOP;
|
|
if (jp[j].jp_valuelen >= jiov[ai].iov_len)
|
|
jiov[ai].iov_len = jp[j].jp_valuelen;
|
|
else {
|
|
jp[j].jp_valuelen = jiov[ai].iov_len;
|
|
if (jp[j].jp_value != NULL)
|
|
free(jp[j].jp_value);
|
|
jp[j].jp_value =
|
|
malloc(jiov[ai].iov_len);
|
|
if (jp[j].jp_value == NULL) {
|
|
strerror_r(errno, jail_errmsg,
|
|
JAIL_ERRMSGLEN);
|
|
return (-1);
|
|
}
|
|
}
|
|
jiov[ai].iov_base = jp[j].jp_value;
|
|
memset(jiov[ai].iov_base, 0, jiov[ai].iov_len);
|
|
ai++;
|
|
}
|
|
}
|
|
}
|
|
if (jid < 0 && !jail_errmsg[0])
|
|
snprintf(jail_errmsg, sizeof(jail_errmsg),
|
|
"jail_get: %s", strerror(errno));
|
|
for (ai = j = 0, i = ki; j < njp; j++) {
|
|
if (jp[j].jp_elemlen && !(jp[j].jp_flags & JP_RAWVALUE)) {
|
|
ai++;
|
|
jp[j].jp_valuelen = jiov[ai].iov_len;
|
|
ai++;
|
|
} else if (jp + j != jp_key) {
|
|
i++;
|
|
jp[j].jp_valuelen = jiov[i].iov_len;
|
|
i++;
|
|
}
|
|
}
|
|
return (jid);
|
|
}
|
|
|
|
/*
|
|
* Convert a jail parameter's value to external form.
|
|
*/
|
|
char *
|
|
jailparam_export(struct jailparam *jp)
|
|
{
|
|
size_t *valuelens;
|
|
char *value, *tvalue, **values;
|
|
size_t valuelen;
|
|
int i, nval, ival;
|
|
char valbuf[INET6_ADDRSTRLEN];
|
|
|
|
if ((jp->jp_ctltype & CTLTYPE) == CTLTYPE_STRING) {
|
|
value = strdup(jp->jp_value);
|
|
if (value == NULL)
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (value);
|
|
}
|
|
nval = jp->jp_elemlen ? jp->jp_valuelen / jp->jp_elemlen : 1;
|
|
if (nval == 0) {
|
|
value = strdup("");
|
|
if (value == NULL)
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (value);
|
|
}
|
|
values = alloca(nval * sizeof(char *));
|
|
valuelens = alloca(nval * sizeof(size_t));
|
|
valuelen = 0;
|
|
for (i = 0; i < nval; i++) {
|
|
switch (jp->jp_ctltype & CTLTYPE) {
|
|
case CTLTYPE_INT:
|
|
ival = ((int *)jp->jp_value)[i];
|
|
if ((jp->jp_flags & (JP_BOOL | JP_NOBOOL)) &&
|
|
(unsigned)ival < 2) {
|
|
strlcpy(valbuf, bool_values[ival],
|
|
sizeof(valbuf));
|
|
break;
|
|
}
|
|
if ((jp->jp_flags & JP_JAILSYS) &&
|
|
(unsigned)ival < sizeof(jailsys_values) /
|
|
sizeof(jailsys_values[0])) {
|
|
strlcpy(valbuf, jailsys_values[ival],
|
|
sizeof(valbuf));
|
|
break;
|
|
}
|
|
snprintf(valbuf, sizeof(valbuf), "%d", ival);
|
|
break;
|
|
case CTLTYPE_UINT:
|
|
snprintf(valbuf, sizeof(valbuf), "%u",
|
|
((unsigned *)jp->jp_value)[i]);
|
|
break;
|
|
case CTLTYPE_LONG:
|
|
snprintf(valbuf, sizeof(valbuf), "%ld",
|
|
((long *)jp->jp_value)[i]);
|
|
break;
|
|
case CTLTYPE_ULONG:
|
|
snprintf(valbuf, sizeof(valbuf), "%lu",
|
|
((unsigned long *)jp->jp_value)[i]);
|
|
break;
|
|
case CTLTYPE_S64:
|
|
snprintf(valbuf, sizeof(valbuf), "%jd",
|
|
(intmax_t)((int64_t *)jp->jp_value)[i]);
|
|
break;
|
|
case CTLTYPE_U64:
|
|
snprintf(valbuf, sizeof(valbuf), "%ju",
|
|
(uintmax_t)((uint64_t *)jp->jp_value)[i]);
|
|
break;
|
|
case CTLTYPE_STRUCT:
|
|
switch (jp->jp_structtype) {
|
|
case JPS_IN_ADDR:
|
|
if (inet_ntop(AF_INET,
|
|
&((struct in_addr *)jp->jp_value)[i],
|
|
valbuf, sizeof(valbuf)) == NULL) {
|
|
strerror_r(errno, jail_errmsg,
|
|
JAIL_ERRMSGLEN);
|
|
return (NULL);
|
|
}
|
|
break;
|
|
case JPS_IN6_ADDR:
|
|
if (inet_ntop(AF_INET6,
|
|
&((struct in6_addr *)jp->jp_value)[i],
|
|
valbuf, sizeof(valbuf)) == NULL) {
|
|
strerror_r(errno, jail_errmsg,
|
|
JAIL_ERRMSGLEN);
|
|
return (NULL);
|
|
}
|
|
break;
|
|
default:
|
|
goto unknown_type;
|
|
}
|
|
break;
|
|
default:
|
|
unknown_type:
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"unknown type for %s", jp->jp_name);
|
|
errno = ENOENT;
|
|
return (NULL);
|
|
}
|
|
valuelens[i] = strlen(valbuf) + 1;
|
|
valuelen += valuelens[i];
|
|
values[i] = alloca(valuelens[i]);
|
|
strcpy(values[i], valbuf);
|
|
}
|
|
value = malloc(valuelen);
|
|
if (value == NULL)
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
else {
|
|
tvalue = value;
|
|
for (i = 0; i < nval; i++) {
|
|
strcpy(tvalue, values[i]);
|
|
if (i < nval - 1) {
|
|
tvalue += valuelens[i];
|
|
tvalue[-1] = ',';
|
|
}
|
|
}
|
|
}
|
|
return (value);
|
|
}
|
|
|
|
/*
|
|
* Free the contents of a jail parameter list (but not the list itself).
|
|
*/
|
|
void
|
|
jailparam_free(struct jailparam *jp, unsigned njp)
|
|
{
|
|
unsigned j;
|
|
|
|
for (j = 0; j < njp; j++) {
|
|
free(jp[j].jp_name);
|
|
if (!(jp[j].jp_flags & JP_RAWVALUE))
|
|
free(jp[j].jp_value);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find a parameter's type and size from its MIB.
|
|
*/
|
|
static int
|
|
jailparam_type(struct jailparam *jp)
|
|
{
|
|
char *p, *name, *nname;
|
|
size_t miblen, desclen;
|
|
int i, isarray;
|
|
struct {
|
|
int i;
|
|
char s[MAXPATHLEN];
|
|
} desc;
|
|
int mib[CTL_MAXNAME];
|
|
|
|
/* The "lastjid" parameter isn't real. */
|
|
name = jp->jp_name;
|
|
if (!strcmp(name, "lastjid")) {
|
|
jp->jp_valuelen = sizeof(int);
|
|
jp->jp_ctltype = CTLTYPE_INT | CTLFLAG_WR;
|
|
return (0);
|
|
}
|
|
|
|
/* Find the sysctl that describes the parameter. */
|
|
mib[0] = 0;
|
|
mib[1] = 3;
|
|
snprintf(desc.s, sizeof(desc.s), SJPARAM ".%s", name);
|
|
miblen = sizeof(mib) - 2 * sizeof(int);
|
|
if (sysctl(mib, 2, mib + 2, &miblen, desc.s, strlen(desc.s)) < 0) {
|
|
if (errno != ENOENT) {
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctl(0.3.%s): %s", name, strerror(errno));
|
|
return (-1);
|
|
}
|
|
if (kldload_param(name) >= 0 && sysctl(mib, 2, mib + 2, &miblen,
|
|
desc.s, strlen(desc.s)) >= 0)
|
|
goto mib_desc;
|
|
/*
|
|
* The parameter probably doesn't exist. But it might be
|
|
* the "no" counterpart to a boolean.
|
|
*/
|
|
nname = nononame(name);
|
|
if (nname == NULL) {
|
|
unknown_parameter:
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"unknown parameter: %s", jp->jp_name);
|
|
errno = ENOENT;
|
|
return (-1);
|
|
}
|
|
name = alloca(strlen(nname) + 1);
|
|
strcpy(name, nname);
|
|
free(nname);
|
|
snprintf(desc.s, sizeof(desc.s), SJPARAM ".%s", name);
|
|
miblen = sizeof(mib) - 2 * sizeof(int);
|
|
if (sysctl(mib, 2, mib + 2, &miblen, desc.s,
|
|
strlen(desc.s)) < 0)
|
|
goto unknown_parameter;
|
|
jp->jp_flags |= JP_NOBOOL;
|
|
}
|
|
mib_desc:
|
|
mib[1] = 4;
|
|
desclen = sizeof(desc);
|
|
if (sysctl(mib, (miblen / sizeof(int)) + 2, &desc, &desclen,
|
|
NULL, 0) < 0) {
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctl(0.4.%s): %s", name, strerror(errno));
|
|
return (-1);
|
|
}
|
|
jp->jp_ctltype = desc.i;
|
|
/* If this came from removing a "no", it better be a boolean. */
|
|
if (jp->jp_flags & JP_NOBOOL) {
|
|
if ((desc.i & CTLTYPE) == CTLTYPE_INT && desc.s[0] == 'B') {
|
|
jp->jp_valuelen = sizeof(int);
|
|
return (0);
|
|
}
|
|
else if ((desc.i & CTLTYPE) != CTLTYPE_NODE)
|
|
goto unknown_parameter;
|
|
}
|
|
/* See if this is an array type. */
|
|
p = strchr(desc.s, '\0');
|
|
isarray = 0;
|
|
if (p - 2 < desc.s || strcmp(p - 2, ",a"))
|
|
isarray = 0;
|
|
else {
|
|
isarray = 1;
|
|
p[-2] = 0;
|
|
}
|
|
/* Look for types we understand. */
|
|
switch (desc.i & CTLTYPE) {
|
|
case CTLTYPE_INT:
|
|
if (desc.s[0] == 'B')
|
|
jp->jp_flags |= JP_BOOL;
|
|
else if (!strcmp(desc.s, "E,jailsys"))
|
|
jp->jp_flags |= JP_JAILSYS;
|
|
case CTLTYPE_UINT:
|
|
jp->jp_valuelen = sizeof(int);
|
|
break;
|
|
case CTLTYPE_LONG:
|
|
case CTLTYPE_ULONG:
|
|
jp->jp_valuelen = sizeof(long);
|
|
break;
|
|
case CTLTYPE_S64:
|
|
case CTLTYPE_U64:
|
|
jp->jp_valuelen = sizeof(int64_t);
|
|
break;
|
|
case CTLTYPE_STRING:
|
|
desc.s[0] = 0;
|
|
desclen = sizeof(desc.s);
|
|
if (sysctl(mib + 2, miblen / sizeof(int), desc.s, &desclen,
|
|
NULL, 0) < 0) {
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctl(" SJPARAM ".%s): %s", name,
|
|
strerror(errno));
|
|
return (-1);
|
|
}
|
|
jp->jp_valuelen = strtoul(desc.s, NULL, 10);
|
|
break;
|
|
case CTLTYPE_STRUCT:
|
|
if (!strcmp(desc.s, "S,in_addr")) {
|
|
jp->jp_structtype = JPS_IN_ADDR;
|
|
jp->jp_valuelen = sizeof(struct in_addr);
|
|
} else if (!strcmp(desc.s, "S,in6_addr")) {
|
|
jp->jp_structtype = JPS_IN6_ADDR;
|
|
jp->jp_valuelen = sizeof(struct in6_addr);
|
|
} else {
|
|
desclen = 0;
|
|
if (sysctl(mib + 2, miblen / sizeof(int),
|
|
NULL, &jp->jp_valuelen, NULL, 0) < 0) {
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctl(" SJPARAM ".%s): %s", name,
|
|
strerror(errno));
|
|
return (-1);
|
|
}
|
|
}
|
|
break;
|
|
case CTLTYPE_NODE:
|
|
/*
|
|
* A node might be described by an empty-named child,
|
|
* which would be immediately before or after the node itself.
|
|
*/
|
|
mib[1] = 1;
|
|
miblen += sizeof(int);
|
|
for (i = -1; i <= 1; i += 2) {
|
|
mib[(miblen / sizeof(int)) + 1] =
|
|
mib[(miblen / sizeof(int))] + i;
|
|
desclen = sizeof(desc.s);
|
|
if (sysctl(mib, (miblen / sizeof(int)) + 2, desc.s,
|
|
&desclen, NULL, 0) < 0) {
|
|
if (errno == ENOENT)
|
|
continue;
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"sysctl(0.1): %s", strerror(errno));
|
|
return (-1);
|
|
}
|
|
if (desclen == sizeof(SJPARAM) + strlen(name) + 2 &&
|
|
memcmp(SJPARAM ".", desc.s, sizeof(SJPARAM)) == 0 &&
|
|
memcmp(name, desc.s + sizeof(SJPARAM),
|
|
desclen - sizeof(SJPARAM) - 2) == 0 &&
|
|
desc.s[desclen - 2] == '.')
|
|
goto mib_desc;
|
|
}
|
|
goto unknown_parameter;
|
|
default:
|
|
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
|
"unknown type for %s", jp->jp_name);
|
|
errno = ENOENT;
|
|
return (-1);
|
|
}
|
|
if (isarray) {
|
|
jp->jp_elemlen = jp->jp_valuelen;
|
|
jp->jp_valuelen = 0;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Attempt to load a kernel module matching an otherwise nonexistent parameter.
|
|
*/
|
|
static int
|
|
kldload_param(const char *name)
|
|
{
|
|
int kl;
|
|
|
|
if (strcmp(name, "linux") == 0 || strncmp(name, "linux.", 6) == 0)
|
|
kl = kldload("linux");
|
|
else if (strcmp(name, "sysvmsg") == 0 || strcmp(name, "sysvsem") == 0 ||
|
|
strcmp(name, "sysvshm") == 0)
|
|
kl = kldload(name);
|
|
else if (strncmp(name, "allow.mount.", 12) == 0) {
|
|
/* Load the matching filesystem */
|
|
const char *modname = name + 12;
|
|
|
|
kl = kldload(modname);
|
|
if (kl < 0 && errno == ENOENT &&
|
|
strncmp(modname, "no", 2) == 0)
|
|
kl = kldload(modname + 2);
|
|
} else {
|
|
errno = ENOENT;
|
|
return (-1);
|
|
}
|
|
if (kl < 0 && errno == EEXIST) {
|
|
/*
|
|
* In the module is already loaded, then it must not contain
|
|
* the parameter.
|
|
*/
|
|
errno = ENOENT;
|
|
}
|
|
return kl;
|
|
}
|
|
|
|
/*
|
|
* Change a boolean parameter name into its "no" counterpart or vice versa.
|
|
*/
|
|
static char *
|
|
noname(const char *name)
|
|
{
|
|
char *nname, *p;
|
|
|
|
nname = malloc(strlen(name) + 3);
|
|
if (nname == NULL) {
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (NULL);
|
|
}
|
|
p = strrchr(name, '.');
|
|
if (p != NULL)
|
|
sprintf(nname, "%.*s.no%s", (int)(p - name), name, p + 1);
|
|
else
|
|
sprintf(nname, "no%s", name);
|
|
return (nname);
|
|
}
|
|
|
|
static char *
|
|
nononame(const char *name)
|
|
{
|
|
char *p, *nname;
|
|
|
|
p = strrchr(name, '.');
|
|
if (strncmp(p ? p + 1 : name, "no", 2)) {
|
|
snprintf(jail_errmsg, sizeof(jail_errmsg),
|
|
"mismatched boolean: %s", name);
|
|
errno = EINVAL;
|
|
return (NULL);
|
|
}
|
|
nname = malloc(strlen(name) - 1);
|
|
if (nname == NULL) {
|
|
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
|
return (NULL);
|
|
}
|
|
if (p != NULL)
|
|
sprintf(nname, "%.*s.%s", (int)(p - name), name, p + 3);
|
|
else
|
|
strcpy(nname, name + 2);
|
|
return (nname);
|
|
}
|