309df784b1
Unsign some variables involved in allocation as they will never be negative anyways. Provide some bounds checking through reallocarray(3). This is all very unlikely to have any visible effect. Reviewed by: jamie MFC after: 3 weeks
1075 lines
27 KiB
C
1075 lines
27 KiB
C
/*-
|
|
* 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/types.h>
|
|
#include <sys/jail.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 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;
|
|
}
|
|
jp = reallocarray(jp, njp, sizeof(*jp));
|
|
*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);
|
|
}
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|