jail: Add meta and env parameters
Each one is an arbitrary string associated with a jail. It can be set
upon jail creation or added/modified later:
> jail -cm ... meta="tag1=value1 tag2=value2" env="configuration"
The values are not inherited from the parent jail.
A parent jail can read both metadata parameters, while a child jail can
read only env via security.jail.env sysctl.
The maximum size of meta or env per jail is controlled by the
global security.jail.meta_maxbufsize sysctl. Decreasing it does not
alter the existing meta information.
Each metadata buffer can be handled as a set of key=value\n strings:
> jail -cm ... meta="$(echo k1=v1; echo k2=v2)" env.1=one
> jls meta.k2 env.1 meta.k1
While meta.k1= resets the value to an empty string, the meta.k1 without
the equal sign removes the given key.
Relnotes: yes
Reviewed by: jamie
Tested by: dch
Sponsored by: SkunkWerks GmbH
Differential Revision: https://reviews.freebsd.org/D47668
This commit is contained in:
+69
-15
@@ -59,6 +59,7 @@ 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);
|
||||
static char *kvname(const char *name);
|
||||
|
||||
char jail_errmsg[JAIL_ERRMSGLEN];
|
||||
|
||||
@@ -521,6 +522,11 @@ jailparam_set(struct jailparam *jp, unsigned njp, int flags)
|
||||
jiov[i - 1].iov_len = strlen(nname) + 1;
|
||||
|
||||
}
|
||||
} else if (jp[j].jp_flags & JP_KEYVALUE &&
|
||||
jp[j].jp_value == NULL) {
|
||||
/* No value means key removal. */
|
||||
jiov[i].iov_base = NULL;
|
||||
jiov[i].iov_len = 0;
|
||||
} else {
|
||||
/*
|
||||
* Try to fill in missing values with an empty string.
|
||||
@@ -907,22 +913,41 @@ jailparam_type(struct jailparam *jp)
|
||||
* 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);
|
||||
if (nname != NULL) {
|
||||
snprintf(desc.s, sizeof(desc.s), SJPARAM ".%s", nname);
|
||||
miblen = sizeof(mib) - 2 * sizeof(int);
|
||||
if (sysctl(mib, 2, mib + 2, &miblen, desc.s,
|
||||
strlen(desc.s)) >= 0) {
|
||||
name = alloca(strlen(nname) + 1);
|
||||
strcpy(name, nname);
|
||||
free(nname);
|
||||
jp->jp_flags |= JP_NOBOOL;
|
||||
goto mib_desc;
|
||||
}
|
||||
free(nname);
|
||||
}
|
||||
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;
|
||||
/*
|
||||
* It might be an assumed sub-node of a fmt='A,keyvalue' sysctl.
|
||||
*/
|
||||
nname = kvname(name);
|
||||
if (nname != NULL) {
|
||||
snprintf(desc.s, sizeof(desc.s), SJPARAM ".%s", nname);
|
||||
miblen = sizeof(mib) - 2 * sizeof(int);
|
||||
if (sysctl(mib, 2, mib + 2, &miblen, desc.s,
|
||||
strlen(desc.s)) >= 0) {
|
||||
name = alloca(strlen(nname) + 1);
|
||||
strcpy(name, nname);
|
||||
free(nname);
|
||||
jp->jp_flags |= JP_KEYVALUE;
|
||||
goto mib_desc;
|
||||
}
|
||||
free(nname);
|
||||
}
|
||||
unknown_parameter:
|
||||
snprintf(jail_errmsg, JAIL_ERRMSGLEN,
|
||||
"unknown parameter: %s", jp->jp_name);
|
||||
errno = ENOENT;
|
||||
return (-1);
|
||||
}
|
||||
mib_desc:
|
||||
mib[1] = 4;
|
||||
@@ -943,6 +968,12 @@ jailparam_type(struct jailparam *jp)
|
||||
else if ((desc.i & CTLTYPE) != CTLTYPE_NODE)
|
||||
goto unknown_parameter;
|
||||
}
|
||||
/* Make sure it is a valid keyvalue param. */
|
||||
if (jp->jp_flags & JP_KEYVALUE) {
|
||||
if ((desc.i & CTLTYPE) != CTLTYPE_STRING ||
|
||||
strcmp(desc.s, "A,keyvalue") != 0)
|
||||
goto unknown_parameter;
|
||||
}
|
||||
/* See if this is an array type. */
|
||||
p = strchr(desc.s, '\0');
|
||||
isarray = 0;
|
||||
@@ -1119,3 +1150,26 @@ nononame(const char *name)
|
||||
strcpy(nname, name + 2);
|
||||
return (nname);
|
||||
}
|
||||
|
||||
static char *
|
||||
kvname(const char *name)
|
||||
{
|
||||
const char *p;
|
||||
char *kvname;
|
||||
size_t len;
|
||||
|
||||
p = strchr(name, '.');
|
||||
if (p == NULL)
|
||||
return (NULL);
|
||||
|
||||
len = p - name;
|
||||
kvname = malloc(len + 1);
|
||||
if (kvname == NULL) {
|
||||
strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN);
|
||||
return (NULL);
|
||||
}
|
||||
strncpy(kvname, name, len);
|
||||
kvname[len] = '\0';
|
||||
|
||||
return (kvname);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#define JP_BOOL 0x02
|
||||
#define JP_NOBOOL 0x04
|
||||
#define JP_JAILSYS 0x08
|
||||
#define JP_KEYVALUE 0x10
|
||||
|
||||
#define JAIL_ERRMSGLEN 1024
|
||||
|
||||
|
||||
@@ -445,9 +445,16 @@ l_getparams(lua_State *L)
|
||||
for (size_t i = 0; i < params_count; ++i) {
|
||||
char *value;
|
||||
|
||||
value = jailparam_export(¶ms[i]);
|
||||
lua_pushstring(L, value);
|
||||
free(value);
|
||||
if (params[i].jp_flags & JP_KEYVALUE &&
|
||||
params[i].jp_valuelen == 0) {
|
||||
/* Communicate back a missing key. */
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
value = jailparam_export(¶ms[i]);
|
||||
lua_pushstring(L, value);
|
||||
free(value);
|
||||
}
|
||||
|
||||
lua_setfield(L, -2, params[i].jp_name);
|
||||
}
|
||||
|
||||
@@ -535,7 +542,8 @@ l_setparams(lua_State *L)
|
||||
}
|
||||
|
||||
value = lua_tostring(L, -1);
|
||||
if (value == NULL) {
|
||||
/* Allow passing NULL for key removal. */
|
||||
if (value == NULL && !(params[i].jp_flags & JP_KEYVALUE)) {
|
||||
jailparam_free(params, i + 1);
|
||||
free(params);
|
||||
return (luaL_argerror(L, 2,
|
||||
|
||||
@@ -3792,6 +3792,7 @@ kern/kern_hhook.c standard
|
||||
kern/kern_idle.c standard
|
||||
kern/kern_intr.c standard
|
||||
kern/kern_jail.c standard
|
||||
kern/kern_jailmeta.c standard
|
||||
kern/kern_kcov.c optional kcov \
|
||||
compile-with "${NOSAN_C} ${MSAN_CFLAGS}"
|
||||
kern/kern_khelp.c standard
|
||||
|
||||
+10
-1
@@ -2552,6 +2552,15 @@ kern_jail_get(struct thread *td, struct uio *optuio, int flags)
|
||||
|
||||
/* By now, all parameters should have been noted. */
|
||||
TAILQ_FOREACH(opt, opts, link) {
|
||||
if (!opt->seen &&
|
||||
(strstr(opt->name, JAIL_META_PRIVATE ".") == opt->name ||
|
||||
strstr(opt->name, JAIL_META_SHARED ".") == opt->name)) {
|
||||
/* Communicate back a missing key. */
|
||||
free(opt->value, M_MOUNT);
|
||||
opt->value = NULL;
|
||||
opt->len = 0;
|
||||
continue;
|
||||
}
|
||||
if (!opt->seen && strcmp(opt->name, "errmsg")) {
|
||||
error = EINVAL;
|
||||
vfs_opterror(opts, "unknown parameter: %s", opt->name);
|
||||
@@ -4272,7 +4281,7 @@ prison_path(struct prison *pr1, struct prison *pr2)
|
||||
/*
|
||||
* Jail-related sysctls.
|
||||
*/
|
||||
static SYSCTL_NODE(_security, OID_AUTO, jail, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
|
||||
SYSCTL_NODE(_security, OID_AUTO, jail, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
|
||||
"Jails");
|
||||
|
||||
#if defined(INET) || defined(INET6)
|
||||
|
||||
@@ -0,0 +1,621 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2024 SkunkWerks GmbH
|
||||
*
|
||||
* This software was developed by Igor Ostapenko <igoro@FreeBSD.org>
|
||||
* under sponsorship from SkunkWerks GmbH.
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/_bitset.h>
|
||||
#include <sys/bitset.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/sx.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/jail.h>
|
||||
#include <sys/osd.h>
|
||||
#include <sys/proc.h>
|
||||
|
||||
/*
|
||||
* Buffer limit.
|
||||
*
|
||||
* The hard limit is the actual value used during setting or modification. The
|
||||
* soft limit is used solely by the security.jail.param.meta and .env sysctl. If
|
||||
* the hard limit is decreased, the soft limit may remain higher to ensure that
|
||||
* previously set meta strings can still be correctly interpreted by end-user
|
||||
* interfaces, such as jls(8).
|
||||
*/
|
||||
|
||||
static uint32_t jm_maxbufsize_hard = 4096;
|
||||
static uint32_t jm_maxbufsize_soft = 4096;
|
||||
|
||||
static int
|
||||
jm_sysctl_meta_maxbufsize(SYSCTL_HANDLER_ARGS)
|
||||
{
|
||||
int error;
|
||||
uint32_t newmax = 0;
|
||||
|
||||
/* Reading only. */
|
||||
|
||||
if (req->newptr == NULL) {
|
||||
sx_slock(&allprison_lock);
|
||||
error = SYSCTL_OUT(req, &jm_maxbufsize_hard,
|
||||
sizeof(jm_maxbufsize_hard));
|
||||
sx_sunlock(&allprison_lock);
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
/* Reading and writing. */
|
||||
|
||||
sx_xlock(&allprison_lock);
|
||||
|
||||
error = SYSCTL_OUT(req, &jm_maxbufsize_hard,
|
||||
sizeof(jm_maxbufsize_hard));
|
||||
if (error != 0)
|
||||
goto end;
|
||||
|
||||
error = SYSCTL_IN(req, &newmax, sizeof(newmax));
|
||||
if (error != 0)
|
||||
goto end;
|
||||
|
||||
jm_maxbufsize_hard = newmax;
|
||||
if (jm_maxbufsize_hard >= jm_maxbufsize_soft) {
|
||||
jm_maxbufsize_soft = jm_maxbufsize_hard;
|
||||
} else if (TAILQ_EMPTY(&allprison)) {
|
||||
/*
|
||||
* For now, this is the simplest way to
|
||||
* avoid O(n) iteration over all prisons in
|
||||
* case of a large n.
|
||||
*/
|
||||
jm_maxbufsize_soft = jm_maxbufsize_hard;
|
||||
}
|
||||
|
||||
end:
|
||||
sx_xunlock(&allprison_lock);
|
||||
return (error);
|
||||
}
|
||||
SYSCTL_PROC(_security_jail, OID_AUTO, meta_maxbufsize,
|
||||
CTLTYPE_U32 | CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, 0,
|
||||
jm_sysctl_meta_maxbufsize, "IU",
|
||||
"Maximum buffer size of each meta and env");
|
||||
|
||||
|
||||
/* Jail parameter announcement. */
|
||||
|
||||
static int
|
||||
jm_sysctl_param_meta(SYSCTL_HANDLER_ARGS)
|
||||
{
|
||||
uint32_t soft;
|
||||
|
||||
sx_slock(&allprison_lock);
|
||||
soft = jm_maxbufsize_soft;
|
||||
sx_sunlock(&allprison_lock);
|
||||
|
||||
return (sysctl_jail_param(oidp, arg1, soft, req));
|
||||
}
|
||||
SYSCTL_PROC(_security_jail_param, OID_AUTO, meta,
|
||||
CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, 0,
|
||||
jm_sysctl_param_meta, "A,keyvalue",
|
||||
"Jail meta information hidden from the jail");
|
||||
SYSCTL_PROC(_security_jail_param, OID_AUTO, env,
|
||||
CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, 0,
|
||||
jm_sysctl_param_meta, "A,keyvalue",
|
||||
"Jail meta information readable by the jail");
|
||||
|
||||
|
||||
/* Generic OSD-based logic for any metadata buffer. */
|
||||
|
||||
struct meta {
|
||||
char *name;
|
||||
u_int osd_slot;
|
||||
osd_method_t methods[PR_MAXMETHOD];
|
||||
};
|
||||
|
||||
/* A chain of hunks representing the final buffer after all manipulations. */
|
||||
struct hunk {
|
||||
char *p; /* a buf reference */
|
||||
size_t len; /* number of bytes referred */
|
||||
char *owned; /* must be freed */
|
||||
struct hunk *next;
|
||||
};
|
||||
|
||||
static inline struct hunk *
|
||||
jm_h_alloc(void)
|
||||
{
|
||||
/* All fields are zeroed. */
|
||||
return (malloc(sizeof(struct hunk), M_PRISON, M_WAITOK | M_ZERO));
|
||||
}
|
||||
|
||||
static inline struct hunk *
|
||||
jm_h_prepend(struct hunk *h, char *p, size_t len)
|
||||
{
|
||||
struct hunk *n;
|
||||
|
||||
n = jm_h_alloc();
|
||||
n->p = p;
|
||||
n->len = len;
|
||||
n->next = h;
|
||||
return (n);
|
||||
}
|
||||
|
||||
static inline void
|
||||
jm_h_cut_line(struct hunk *h, char *begin)
|
||||
{
|
||||
struct hunk *rem;
|
||||
char *end;
|
||||
|
||||
/* Find the end of key=value. */
|
||||
for (end = begin; (end + 1) < (h->p + h->len); end++)
|
||||
if (*end == '\0' || *end == '\n')
|
||||
break;
|
||||
|
||||
/* Pick up a non-empty remainder. */
|
||||
if ((end + 1) < (h->p + h->len) && *(end + 1) != '\0') {
|
||||
rem = jm_h_alloc();
|
||||
rem->p = end + 1;
|
||||
rem->len = h->p + h->len - rem->p;
|
||||
|
||||
/* insert */
|
||||
rem->next = h->next;
|
||||
h->next = rem;
|
||||
}
|
||||
|
||||
/* Shorten this hunk. */
|
||||
h->len = begin - h->p;
|
||||
}
|
||||
|
||||
static inline void
|
||||
jm_h_cut_occurrences(struct hunk *h, const char *key, size_t keylen)
|
||||
{
|
||||
char *p = h->p;
|
||||
|
||||
#define nexthunk() \
|
||||
do { \
|
||||
h = h->next; \
|
||||
p = (h == NULL) ? NULL : h->p; \
|
||||
} while (0)
|
||||
|
||||
while (p != NULL) {
|
||||
p = strnstr(p, key, h->len - (p - h->p));
|
||||
if (p == NULL) {
|
||||
nexthunk();
|
||||
continue;
|
||||
}
|
||||
if ((p == h->p || *(p - 1) == '\n') && p[keylen] == '=') {
|
||||
jm_h_cut_line(h, p);
|
||||
nexthunk();
|
||||
continue;
|
||||
}
|
||||
/* Continue with this hunk. */
|
||||
p += keylen;
|
||||
/* Empty? The next hunk then. */
|
||||
if ((p - h->p) >= h->len)
|
||||
nexthunk();
|
||||
}
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
jm_h_len(struct hunk *h)
|
||||
{
|
||||
size_t len = 0;
|
||||
while (h != NULL) {
|
||||
len += h->len;
|
||||
h = h->next;
|
||||
}
|
||||
return (len);
|
||||
}
|
||||
|
||||
static inline void
|
||||
jm_h_assemble(char *dst, struct hunk *h)
|
||||
{
|
||||
while (h != NULL) {
|
||||
if (h->len > 0) {
|
||||
memcpy(dst, h->p, h->len);
|
||||
dst += h->len;
|
||||
/* If not the last hunk then concatenate with \n. */
|
||||
if (h->next != NULL && *(dst - 1) == '\0')
|
||||
*(dst - 1) = '\n';
|
||||
}
|
||||
h = h->next;
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct hunk *
|
||||
jm_h_freechain(struct hunk *h)
|
||||
{
|
||||
struct hunk *n = h;
|
||||
while (n != NULL) {
|
||||
h = n;
|
||||
n = h->next;
|
||||
free(h->owned, M_PRISON);
|
||||
free(h, M_PRISON);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static int
|
||||
jm_osd_method_set(void *obj, void *data, const struct meta *meta)
|
||||
{
|
||||
struct prison *pr = obj;
|
||||
struct vfsoptlist *opts = data;
|
||||
struct vfsopt *opt;
|
||||
|
||||
char *origosd;
|
||||
char *origosd_copy;
|
||||
char *oldosd;
|
||||
char *osd;
|
||||
size_t osdlen;
|
||||
struct hunk *h;
|
||||
char *key;
|
||||
size_t keylen;
|
||||
int error;
|
||||
int repeats = 0;
|
||||
bool repeat;
|
||||
|
||||
sx_assert(&allprison_lock, SA_XLOCKED);
|
||||
|
||||
again:
|
||||
origosd = NULL;
|
||||
origosd_copy = NULL;
|
||||
osd = NULL;
|
||||
h = NULL;
|
||||
error = 0;
|
||||
repeat = false;
|
||||
TAILQ_FOREACH(opt, opts, link) {
|
||||
/* Look for options with <metaname> prefix. */
|
||||
if (strstr(opt->name, meta->name) != opt->name)
|
||||
continue;
|
||||
/* Consider only full <metaname> or <metaname>.* ones. */
|
||||
if (opt->name[strlen(meta->name)] != '.' &&
|
||||
opt->name[strlen(meta->name)] != '\0')
|
||||
continue;
|
||||
opt->seen = 1;
|
||||
|
||||
/* The very first preconditions. */
|
||||
if (opt->len < 0)
|
||||
continue;
|
||||
if (opt->len > jm_maxbufsize_hard) {
|
||||
error = EFBIG;
|
||||
break;
|
||||
}
|
||||
/* NULL-terminated strings are expected from vfsopt. */
|
||||
if (opt->value != NULL &&
|
||||
((char *)opt->value)[opt->len - 1] != '\0') {
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Work with our own copy of existing metadata. */
|
||||
if (h == NULL) {
|
||||
h = jm_h_alloc(); /* zeroed */
|
||||
mtx_lock(&pr->pr_mtx);
|
||||
origosd = osd_jail_get(pr, meta->osd_slot);
|
||||
if (origosd != NULL) {
|
||||
origosd_copy = malloc(strlen(origosd) + 1,
|
||||
M_PRISON, M_NOWAIT);
|
||||
if (origosd_copy == NULL)
|
||||
error = ENOMEM;
|
||||
else {
|
||||
h->p = origosd_copy;
|
||||
h->len = strlen(origosd) + 1;
|
||||
memcpy(h->p, origosd, h->len);
|
||||
}
|
||||
}
|
||||
mtx_unlock(&pr->pr_mtx);
|
||||
if (error != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* 1) Change the whole metadata. */
|
||||
if (strcmp(opt->name, meta->name) == 0) {
|
||||
if (opt->len > jm_maxbufsize_hard) {
|
||||
error = EFBIG;
|
||||
break;
|
||||
}
|
||||
h = jm_h_freechain(h);
|
||||
h = jm_h_prepend(h,
|
||||
(opt->value != NULL) ? opt->value : "",
|
||||
/* avoid empty NULL-terminated string */
|
||||
(opt->len > 1) ? opt->len : 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 2) Or add/replace/remove a specific key=value. */
|
||||
key = opt->name + strlen(meta->name) + 1;
|
||||
keylen = strlen(key);
|
||||
if (keylen < 1) {
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
jm_h_cut_occurrences(h, key, keylen);
|
||||
if (opt->value == NULL)
|
||||
continue; /* key removal */
|
||||
h = jm_h_prepend(h, NULL, 0);
|
||||
h->len = keylen + 1 + opt->len; /* key=value\0 */
|
||||
h->owned = malloc(h->len, M_PRISON, M_WAITOK | M_ZERO);
|
||||
h->p = h->owned;
|
||||
memcpy(h->p, key, keylen);
|
||||
h->p[keylen] = '=';
|
||||
memcpy(h->p + keylen + 1, opt->value, opt->len);
|
||||
}
|
||||
|
||||
if (h == NULL || error != 0)
|
||||
goto end;
|
||||
|
||||
/* Assemble the final contiguous buffer. */
|
||||
osdlen = jm_h_len(h);
|
||||
if (osdlen > jm_maxbufsize_hard) {
|
||||
error = EFBIG;
|
||||
goto end;
|
||||
}
|
||||
if (osdlen > 1) {
|
||||
osd = malloc(osdlen, M_PRISON, M_WAITOK);
|
||||
jm_h_assemble(osd, h);
|
||||
osd[osdlen - 1] = '\0'; /* sealed */
|
||||
}
|
||||
|
||||
/* Compare and swap the buffers. */
|
||||
mtx_lock(&pr->pr_mtx);
|
||||
oldosd = osd_jail_get(pr, meta->osd_slot);
|
||||
if (oldosd == origosd) {
|
||||
error = osd_jail_set(pr, meta->osd_slot, osd);
|
||||
} else {
|
||||
/*
|
||||
* The osd(9) framework requires protection only for pr_osd,
|
||||
* which is covered by pr_mtx. Therefore, other code might
|
||||
* legally alter jail metadata without allprison_lock. It
|
||||
* means that here we could override data just added by other
|
||||
* thread. This extra caution with retry mechanism aims to
|
||||
* prevent user data loss in such potential cases.
|
||||
*/
|
||||
error = EAGAIN;
|
||||
repeat = true;
|
||||
}
|
||||
mtx_unlock(&pr->pr_mtx);
|
||||
if (error == 0)
|
||||
osd = oldosd;
|
||||
|
||||
end:
|
||||
jm_h_freechain(h);
|
||||
free(osd, M_PRISON);
|
||||
free(origosd_copy, M_PRISON);
|
||||
|
||||
if (repeat && ++repeats < 3)
|
||||
goto again;
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
static int
|
||||
jm_osd_method_get(void *obj, void *data, const struct meta *meta)
|
||||
{
|
||||
struct prison *pr = obj;
|
||||
struct vfsoptlist *opts = data;
|
||||
struct vfsopt *opt;
|
||||
char *osd = NULL;
|
||||
char empty = '\0';
|
||||
int error = 0;
|
||||
bool locked = false;
|
||||
const char *key;
|
||||
size_t keylen;
|
||||
const char *p;
|
||||
|
||||
sx_assert(&allprison_lock, SA_SLOCKED);
|
||||
|
||||
TAILQ_FOREACH(opt, opts, link) {
|
||||
if (strstr(opt->name, meta->name) != opt->name)
|
||||
continue;
|
||||
if (opt->name[strlen(meta->name)] != '.' &&
|
||||
opt->name[strlen(meta->name)] != '\0')
|
||||
continue;
|
||||
|
||||
if (!locked) {
|
||||
mtx_lock(&pr->pr_mtx);
|
||||
locked = true;
|
||||
osd = osd_jail_get(pr, meta->osd_slot);
|
||||
if (osd == NULL)
|
||||
osd = ∅
|
||||
}
|
||||
|
||||
/* Provide full metadata. */
|
||||
if (strcmp(opt->name, meta->name) == 0) {
|
||||
if (strlcpy(opt->value, osd, opt->len) >= opt->len) {
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
opt->seen = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Extract a specific key=value. */
|
||||
p = osd;
|
||||
key = opt->name + strlen(meta->name) + 1;
|
||||
keylen = strlen(key);
|
||||
while ((p = strstr(p, key)) != NULL) {
|
||||
if ((p == osd || *(p - 1) == '\n')
|
||||
&& p[keylen] == '=') {
|
||||
if (strlcpy(opt->value, p + keylen + 1,
|
||||
MIN(opt->len, strchr(p + keylen + 1, '\n') -
|
||||
(p + keylen + 1) + 1)) >= opt->len) {
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
opt->seen = 1;
|
||||
}
|
||||
p += keylen;
|
||||
}
|
||||
if (error != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (locked)
|
||||
mtx_unlock(&pr->pr_mtx);
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
static int
|
||||
jm_osd_method_check(void *obj __unused, void *data, const struct meta *meta)
|
||||
{
|
||||
struct vfsoptlist *opts = data;
|
||||
struct vfsopt *opt;
|
||||
|
||||
TAILQ_FOREACH(opt, opts, link) {
|
||||
if (strstr(opt->name, meta->name) != opt->name)
|
||||
continue;
|
||||
if (opt->name[strlen(meta->name)] != '.' &&
|
||||
opt->name[strlen(meta->name)] != '\0')
|
||||
continue;
|
||||
opt->seen = 1;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
jm_osd_destructor(void *osd)
|
||||
{
|
||||
free(osd, M_PRISON);
|
||||
}
|
||||
|
||||
|
||||
/* OSD for "meta" param */
|
||||
|
||||
static struct meta meta;
|
||||
|
||||
static inline int
|
||||
jm_osd_method_set_meta(void *obj, void *data)
|
||||
{
|
||||
return (jm_osd_method_set(obj, data, &meta));
|
||||
}
|
||||
|
||||
static inline int
|
||||
jm_osd_method_get_meta(void *obj, void *data)
|
||||
{
|
||||
return (jm_osd_method_get(obj, data, &meta));
|
||||
}
|
||||
|
||||
static inline int
|
||||
jm_osd_method_check_meta(void *obj, void *data)
|
||||
{
|
||||
return (jm_osd_method_check(obj, data, &meta));
|
||||
}
|
||||
|
||||
static struct meta meta = {
|
||||
.name = JAIL_META_PRIVATE,
|
||||
.osd_slot = 0,
|
||||
.methods = {
|
||||
[PR_METHOD_SET] = jm_osd_method_set_meta,
|
||||
[PR_METHOD_GET] = jm_osd_method_get_meta,
|
||||
[PR_METHOD_CHECK] = jm_osd_method_check_meta,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* OSD for "env" param */
|
||||
|
||||
static struct meta env;
|
||||
|
||||
static inline int
|
||||
jm_osd_method_set_env(void *obj, void *data)
|
||||
{
|
||||
return (jm_osd_method_set(obj, data, &env));
|
||||
}
|
||||
|
||||
static inline int
|
||||
jm_osd_method_get_env(void *obj, void *data)
|
||||
{
|
||||
return (jm_osd_method_get(obj, data, &env));
|
||||
}
|
||||
|
||||
static inline int
|
||||
jm_osd_method_check_env(void *obj, void *data)
|
||||
{
|
||||
return (jm_osd_method_check(obj, data, &env));
|
||||
}
|
||||
|
||||
static struct meta env = {
|
||||
.name = JAIL_META_SHARED,
|
||||
.osd_slot = 0,
|
||||
.methods = {
|
||||
[PR_METHOD_SET] = jm_osd_method_set_env,
|
||||
[PR_METHOD_GET] = jm_osd_method_get_env,
|
||||
[PR_METHOD_CHECK] = jm_osd_method_check_env,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* A jail can read its "env". */
|
||||
|
||||
static int
|
||||
jm_sysctl_env(SYSCTL_HANDLER_ARGS)
|
||||
{
|
||||
struct prison *pr;
|
||||
char empty = '\0';
|
||||
char *tmpbuf;
|
||||
size_t outlen;
|
||||
int error = 0;
|
||||
|
||||
pr = req->td->td_ucred->cr_prison;
|
||||
|
||||
mtx_lock(&pr->pr_mtx);
|
||||
arg1 = osd_jail_get(pr, env.osd_slot);
|
||||
if (arg1 == NULL) {
|
||||
tmpbuf = ∅
|
||||
outlen = 1;
|
||||
} else {
|
||||
outlen = strlen(arg1) + 1;
|
||||
if (req->oldptr != NULL) {
|
||||
tmpbuf = malloc(outlen, M_PRISON, M_NOWAIT);
|
||||
error = (tmpbuf == NULL) ? ENOMEM : 0;
|
||||
if (error == 0)
|
||||
memcpy(tmpbuf, arg1, outlen);
|
||||
}
|
||||
}
|
||||
mtx_unlock(&pr->pr_mtx);
|
||||
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
if (req->oldptr == NULL)
|
||||
SYSCTL_OUT(req, NULL, outlen);
|
||||
else {
|
||||
SYSCTL_OUT(req, tmpbuf, outlen);
|
||||
if (tmpbuf != &empty)
|
||||
free(tmpbuf, M_PRISON);
|
||||
}
|
||||
|
||||
return (error);
|
||||
}
|
||||
SYSCTL_PROC(_security_jail, OID_AUTO, env,
|
||||
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
|
||||
0, 0, jm_sysctl_env, "A", "Meta information provided by parent jail");
|
||||
|
||||
|
||||
/* Setup and tear down. */
|
||||
|
||||
static int
|
||||
jm_sysinit(void *arg __unused)
|
||||
{
|
||||
meta.osd_slot = osd_jail_register(jm_osd_destructor, meta.methods);
|
||||
env.osd_slot = osd_jail_register(jm_osd_destructor, env.methods);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
jm_sysuninit(void *arg __unused)
|
||||
{
|
||||
osd_jail_deregister(meta.osd_slot);
|
||||
osd_jail_deregister(env.osd_slot);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
SYSINIT(jailmeta, SI_SUB_DRIVERS, SI_ORDER_ANY, jm_sysinit, NULL);
|
||||
SYSUNINIT(jailmeta, SI_SUB_DRIVERS, SI_ORDER_ANY, jm_sysuninit, NULL);
|
||||
@@ -141,6 +141,9 @@ MALLOC_DECLARE(M_PRISON);
|
||||
#define DEFAULT_HOSTUUID "00000000-0000-0000-0000-000000000000"
|
||||
#define OSRELEASELEN 32
|
||||
|
||||
#define JAIL_META_PRIVATE "meta"
|
||||
#define JAIL_META_SHARED "env"
|
||||
|
||||
struct racct;
|
||||
struct prison_racct;
|
||||
|
||||
@@ -376,6 +379,7 @@ extern struct sx allprison_lock;
|
||||
/*
|
||||
* Sysctls to describe jail parameters.
|
||||
*/
|
||||
SYSCTL_DECL(_security_jail);
|
||||
SYSCTL_DECL(_security_jail_param);
|
||||
|
||||
#define SYSCTL_JAIL_PARAM_DECL(name) \
|
||||
|
||||
@@ -59,6 +59,7 @@ TEST_METADATA.sigsys+= is_exclusive="true"
|
||||
|
||||
ATF_TESTS_SH+= coredump_phnum_test
|
||||
ATF_TESTS_SH+= logsigexit_test
|
||||
ATF_TESTS_SH+= jailmeta
|
||||
ATF_TESTS_SH+= sonewconn_overflow
|
||||
TEST_METADATA.sonewconn_overflow+= required_programs="python"
|
||||
TEST_METADATA.sonewconn_overflow+= required_user="root"
|
||||
|
||||
@@ -0,0 +1,588 @@
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# Copyright (c) 2024 SkunkWerks GmbH
|
||||
#
|
||||
# This software was developed by Igor Ostapenko <igoro@FreeBSD.org>
|
||||
# under sponsorship from SkunkWerks GmbH.
|
||||
#
|
||||
|
||||
setup()
|
||||
{
|
||||
# Check if we have enough buffer space for testing
|
||||
if [ $(sysctl -n security.jail.meta_maxbufsize) -lt 128 ]; then
|
||||
atf_skip "sysctl security.jail.meta_maxbufsize must be 128+ for testing."
|
||||
fi
|
||||
}
|
||||
|
||||
atf_test_case "jail_create" "cleanup"
|
||||
jail_create_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be set upon jail creation with jail(8)'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
jail_create_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist meta="a b c" env="C B A"
|
||||
|
||||
atf_check -s exit:0 -o inline:"a b c\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"C B A\n" \
|
||||
jls -jj env
|
||||
}
|
||||
jail_create_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "jail_modify" "cleanup"
|
||||
jail_modify_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be modified after jail creation with jail(8)'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
jail_modify_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist meta="a b c" env="CAB"
|
||||
|
||||
atf_check -s exit:0 -o inline:"a b c\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"CAB\n" \
|
||||
jls -jj env
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -m name=j meta="t1=A t2=B" env="CAB2"
|
||||
|
||||
atf_check -s exit:0 -o inline:"t1=A t2=B\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"CAB2\n" \
|
||||
jls -jj env
|
||||
}
|
||||
jail_modify_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "jail_add" "cleanup"
|
||||
jail_add_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be added to an existing jail with jail(8)'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
jail_add_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist host.hostname=jail1
|
||||
|
||||
atf_check -s exit:0 -o inline:'""\n' \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:'""\n' \
|
||||
jls -jj env
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -m name=j meta="$(jot 3 1 3)" env="$(jot 2 11 12)"
|
||||
|
||||
atf_check -s exit:0 -o inline:"1\n2\n3\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"11\n12\n" \
|
||||
jls -jj env
|
||||
}
|
||||
jail_add_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "jail_reset" "cleanup"
|
||||
jail_reset_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be reset to an empty string with jail(8)'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
jail_reset_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist meta="123" env="456"
|
||||
|
||||
atf_check -s exit:0 -o inline:"123\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"456\n" \
|
||||
jls -jj env
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -m name=j meta= env=
|
||||
|
||||
atf_check -s exit:0 -o inline:'""\n' \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:'""\n' \
|
||||
jls -jj env
|
||||
}
|
||||
jail_reset_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "jls_libxo_json" "cleanup"
|
||||
jls_libxo_json_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be read with jls(8) using libxo JSON'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
jls_libxo_json_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist meta="a b c" env="1 2 3"
|
||||
|
||||
atf_check -s exit:0 -o inline:'{"__version": "2", "jail-information": {"jail": [{"name":"j","meta":"a b c"}]}}\n' \
|
||||
jls -jj --libxo json name meta
|
||||
atf_check -s exit:0 -o inline:'{"__version": "2", "jail-information": {"jail": [{"env":"1 2 3"}]}}\n' \
|
||||
jls -jj --libxo json env
|
||||
}
|
||||
jls_libxo_json_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "flua_create" "cleanup"
|
||||
flua_create_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be set upon jail creation with flua'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
flua_create_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
/usr/libexec/flua -ljail -e 'jail.setparams("j", {["meta"]="t1 t2=v2", ["env"]="BAC", ["persist"]="true"}, jail.CREATE)'
|
||||
|
||||
atf_check -s exit:0 -o inline:"t1 t2=v2\n" \
|
||||
/usr/libexec/flua -ljail -e 'jid, res = jail.getparams("j", {"meta"}); print(res["meta"])'
|
||||
atf_check -s exit:0 -o inline:"BAC\n" \
|
||||
/usr/libexec/flua -ljail -e 'jid, res = jail.getparams("j", {"env"}); print(res["env"])'
|
||||
}
|
||||
flua_create_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "flua_modify" "cleanup"
|
||||
flua_modify_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be changed with flua after jail creation'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
flua_modify_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist meta="ABC" env="123"
|
||||
|
||||
atf_check -s exit:0 -o inline:"ABC\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"123\n" \
|
||||
jls -jj env
|
||||
|
||||
atf_check -s exit:0 \
|
||||
/usr/libexec/flua -ljail -e 'jail.setparams("j", {["meta"]="t1 t2=v", ["env"]="4"}, jail.UPDATE)'
|
||||
|
||||
atf_check -s exit:0 -o inline:"t1 t2=v\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"4\n" \
|
||||
jls -jj env
|
||||
}
|
||||
flua_modify_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "env_readable_by_jail" "cleanup"
|
||||
env_readable_by_jail_head()
|
||||
{
|
||||
atf_set descr 'Test that a jail can read its own env parameter via sysctl(8)'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
env_readable_by_jail_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -jj
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist meta="a b c" env="CBA"
|
||||
|
||||
atf_check -s exit:0 -o inline:"a b c\n" \
|
||||
jls -jj meta
|
||||
atf_check -s exit:0 -o inline:"CBA\n" \
|
||||
jls -jj env
|
||||
|
||||
atf_check -s exit:0 -o inline:"CBA\n" \
|
||||
jexec j sysctl -n security.jail.env
|
||||
}
|
||||
env_readable_by_jail_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "not_inheritable" "cleanup"
|
||||
not_inheritable_head()
|
||||
{
|
||||
atf_set descr 'Test that a jail does not inherit metadata from its parent jail'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
not_inheritable_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -j parent
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=parent children.max=1 persist meta="abc" env="cba"
|
||||
|
||||
jexec parent jail -c name=child persist
|
||||
|
||||
atf_check -s exit:0 -o inline:"abc\n" \
|
||||
jls -j parent meta
|
||||
atf_check -s exit:0 -o inline:'""\n' \
|
||||
jls -j parent.child meta
|
||||
|
||||
atf_check -s exit:0 -o inline:"cba\n" \
|
||||
jexec parent sysctl -n security.jail.env
|
||||
atf_check -s exit:0 -o inline:"\n" \
|
||||
jexec parent.child sysctl -n security.jail.env
|
||||
}
|
||||
not_inheritable_cleanup()
|
||||
{
|
||||
jail -r parent.child
|
||||
jail -r parent
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "maxbufsize" "cleanup"
|
||||
maxbufsize_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata buffer maximum size can be changed'
|
||||
atf_set require.user root
|
||||
atf_set is.exclusive true
|
||||
}
|
||||
maxbufsize_body()
|
||||
{
|
||||
setup
|
||||
|
||||
jn=jailmeta_maxbufsize
|
||||
|
||||
atf_check -s not-exit:0 -e match:"not found" -o ignore \
|
||||
jls -j $jn
|
||||
|
||||
# the size counts string length and the trailing \0 char
|
||||
origmax=$(sysctl -n security.jail.meta_maxbufsize)
|
||||
|
||||
# must be fine with current max
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=$jn persist meta="$(printf %$((origmax-1))s)"
|
||||
atf_check -s exit:0 -o inline:"${origmax}\n" \
|
||||
jls -j $jn meta | wc -c
|
||||
#
|
||||
atf_check -s exit:0 \
|
||||
jail -m name=$jn env="$(printf %$((origmax-1))s)"
|
||||
atf_check -s exit:0 -o inline:"${origmax}\n" \
|
||||
jls -j $jn env | wc -c
|
||||
|
||||
# should not allow exceeding current max
|
||||
atf_check -s not-exit:0 -e match:"too large" \
|
||||
jail -m name=$jn meta="$(printf %${origmax}s)"
|
||||
#
|
||||
atf_check -s not-exit:0 -e match:"too large" \
|
||||
jail -m name=$jn env="$(printf %${origmax}s)"
|
||||
|
||||
# should allow the same size with increased max
|
||||
newmax=$((origmax + 1))
|
||||
sysctl security.jail.meta_maxbufsize=$newmax
|
||||
atf_check -s exit:0 \
|
||||
jail -m name=$jn meta="$(printf %${origmax}s)"
|
||||
atf_check -s exit:0 -o inline:"${origmax}\n" \
|
||||
jls -j $jn meta | wc -c
|
||||
#
|
||||
atf_check -s exit:0 \
|
||||
jail -m name=$jn env="$(printf %${origmax}s)"
|
||||
atf_check -s exit:0 -o inline:"${origmax}\n" \
|
||||
jls -j $jn env | wc -c
|
||||
|
||||
# decrease back to the original max
|
||||
sysctl security.jail.meta_maxbufsize=$origmax
|
||||
atf_check -s not-exit:0 -e match:"too large" \
|
||||
jail -m name=$jn meta="$(printf %${origmax}s)"
|
||||
#
|
||||
atf_check -s not-exit:0 -e match:"too large" \
|
||||
jail -m name=$jn env="$(printf %${origmax}s)"
|
||||
|
||||
# the previously set long meta is still readable as is
|
||||
# due to the soft limit remains higher than the hard limit
|
||||
atf_check_equal '${newmax}' '$(sysctl -n security.jail.param.meta)'
|
||||
atf_check_equal '${newmax}' '$(sysctl -n security.jail.param.env)'
|
||||
atf_check -s exit:0 -o inline:"${origmax}\n" \
|
||||
jls -j $jn meta | wc -c
|
||||
#
|
||||
atf_check -s exit:0 -o inline:"${origmax}\n" \
|
||||
jls -j $jn env | wc -c
|
||||
}
|
||||
maxbufsize_cleanup()
|
||||
{
|
||||
jail -r jailmeta_maxbufsize
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "keyvalue" "cleanup"
|
||||
keyvalue_head()
|
||||
{
|
||||
atf_set descr 'Test that metadata can be handled as a set of key=value\n strings using jail(8), jls(8), and flua'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
}
|
||||
keyvalue_generic()
|
||||
{
|
||||
local meta=$1
|
||||
|
||||
atf_check -sexit:0 -oinline:'""\n' jls -jj $meta
|
||||
|
||||
# Note: each sub-case depends on the results of the previous ones
|
||||
|
||||
# Should be able to extract a key added manually
|
||||
atf_check -sexit:0 jail -m name=j $meta="a=1"
|
||||
atf_check -sexit:0 -oinline:'a=1\n' jls -jj $meta
|
||||
atf_check -sexit:0 -oinline:'1\n' jls -jj $meta.a
|
||||
atf_check -sexit:0 jail -m name=j $meta="$(printf 'a=2\nb=3')"
|
||||
atf_check -sexit:0 -oinline:'a=2\nb=3\n' jls -jj $meta
|
||||
atf_check -sexit:0 -oinline:'2\n' jls -jj $meta.a
|
||||
atf_check -sexit:0 -oinline:'3\n' jls -jj $meta.b
|
||||
|
||||
# Should provide nothing for a non-found key
|
||||
atf_check -sexit:0 -oinline:'\n' jls -jj $meta.c
|
||||
|
||||
# Should be able to lookup multiple keys at once
|
||||
atf_check -sexit:0 -oinline:'3 2\n' jls -jj $meta.b $meta.a
|
||||
|
||||
# Should be able to lookup keys and the whole buffer at once
|
||||
atf_check -sexit:0 -oinline:'3 a=2\nb=3 2\n' jls -jj $meta.b $meta $meta.a
|
||||
|
||||
# Should be able to lookup a key using libxo-based JSON output
|
||||
s='{"__version": "2", "jail-information": {"jail": [{"'$meta'.b":"3"}]}}\n'
|
||||
atf_check -s exit:0 -o inline:"$s" jls -jj --libxo json $meta.b
|
||||
|
||||
# Should provide nothing for a non-found key using libxo-based JSON output
|
||||
s='{"__version": "2", "jail-information": {"jail": [{}]}}\n'
|
||||
atf_check -s exit:0 -o inline:"$s" jls -jj --libxo json $meta.c $meta.d
|
||||
|
||||
# Should be able to lookup a key using flua
|
||||
atf_check -s exit:0 -o inline:"2\n" \
|
||||
/usr/libexec/flua -ljail -e 'jid, res = jail.getparams("j", {"'$meta'.a"}); print(res["'$meta'.a"])'
|
||||
|
||||
# Should provide nil for a non-found key using flua
|
||||
atf_check -s exit:0 -o inline:"true\n" \
|
||||
/usr/libexec/flua -ljail -e 'jid, res = jail.getparams("j", {"'$meta'.meta"}); print(res["'$meta'.meta"] == nil)'
|
||||
|
||||
# Should allow resetting a buffer
|
||||
atf_check -sexit:0 jail -m name=j $meta=
|
||||
atf_check -sexit:0 -oinline:' "" \n' jls -jj $meta.c $meta $meta.a
|
||||
|
||||
# Should allow adding a new key
|
||||
atf_check -sexit:0 jail -m name=j $meta.a=1
|
||||
atf_check -sexit:0 -oinline:'1\n' jls -jj $meta.a
|
||||
atf_check -sexit:0 -oinline:'a=1\n' jls -jj $meta
|
||||
|
||||
# Should allow adding multiple new keys at once
|
||||
atf_check -sexit:0 jail -m name=j $meta.c=3 $meta.b=2
|
||||
atf_check -sexit:0 -oinline:'3\n' jls -jj $meta.c
|
||||
atf_check -sexit:0 -oinline:'2\n' jls -jj $meta.b
|
||||
atf_check -sexit:0 -oinline:'b=2\nc=3\na=1\n' jls -jj $meta
|
||||
|
||||
# Should replace existing keys
|
||||
atf_check -sexit:0 jail -m name=j $meta.a=A $meta.c=C
|
||||
atf_check -sexit:0 -oinline:'A\n' jls -jj $meta.a
|
||||
atf_check -sexit:0 -oinline:'C\n' jls -jj $meta.c
|
||||
atf_check -sexit:0 -oinline:'c=C\na=A\nb=2\n' jls -jj $meta
|
||||
|
||||
# Should treat empty value correctly
|
||||
atf_check -sexit:0 jail -m name=j $meta.a=
|
||||
atf_check -sexit:0 -oinline:'""\n' jls -jj $meta.a
|
||||
atf_check -sexit:0 -oinline:'a=\nc=C\nb=2\n' jls -jj $meta
|
||||
|
||||
# Should treat NULL value as a key removal
|
||||
atf_check -sexit:0 -oinline:'2\n' jls -jj $meta.b
|
||||
atf_check -sexit:0 jail -m name=j $meta.b
|
||||
atf_check -sexit:0 -oinline:'\n' jls -jj $meta.b
|
||||
atf_check -sexit:0 -oinline:'a=\nc=C\n' jls -jj $meta
|
||||
|
||||
# Should allow changing the whole buffer and per key at once (order matters)
|
||||
atf_check -sexit:0 jail -m name=j $meta.a=1 $meta=ttt $meta.b=2
|
||||
atf_check -sexit:0 -oinline:'\n' jls -jj $meta.a
|
||||
atf_check -sexit:0 -oinline:'2\n' jls -jj $meta.b
|
||||
atf_check -sexit:0 -oinline:'b=2\nttt\n' jls -jj $meta
|
||||
|
||||
# Should treat only the first equal sign as syntax
|
||||
atf_check -sexit:0 jail -m name=j $meta.b==
|
||||
atf_check -sexit:0 -oinline:'=\n' jls -jj $meta.b
|
||||
atf_check -sexit:0 -oinline:'b==\nttt\n' jls -jj $meta
|
||||
|
||||
# Should allow adding or modifying keys using flua
|
||||
atf_check -s exit:0 \
|
||||
/usr/libexec/flua -ljail -e 'jail.setparams("j", {["'$meta.b'"]="ttt", ["'$meta'.c"]="C"}, jail.UPDATE)'
|
||||
atf_check -sexit:0 -oinline:'ttt\n' jls -jj $meta.b
|
||||
atf_check -sexit:0 -oinline:'C\n' jls -jj $meta.c
|
||||
|
||||
# Should allow key removal using flua
|
||||
atf_check -s exit:0 \
|
||||
/usr/libexec/flua -ljail -e 'jail.setparams("j", {["'$meta.c'"] = {}}, jail.UPDATE)'
|
||||
atf_check -sexit:0 -oinline:'\n' jls -jj $meta.c
|
||||
atf_check -s exit:0 \
|
||||
/usr/libexec/flua -ljail -e 'jail.setparams("j", {["'$meta.b'"] = false}, jail.UPDATE)'
|
||||
atf_check -sexit:0 -oinline:'\n' jls -jj $meta.b
|
||||
|
||||
# Should respectively support "jls -s" for a missing key
|
||||
atf_check -sexit:0 -oinline:''$meta'.missing\n' jls -jj -s $meta.missing
|
||||
}
|
||||
keyvalue_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s exit:0 \
|
||||
jail -c name=j persist meta env
|
||||
|
||||
keyvalue_generic "meta"
|
||||
keyvalue_generic "env"
|
||||
}
|
||||
keyvalue_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_test_case "keyvalue_contention" "cleanup"
|
||||
keyvalue_contention_head()
|
||||
{
|
||||
atf_set descr 'Try to stress metadata read/write mechanism with some contention'
|
||||
atf_set require.user root
|
||||
atf_set execenv jail
|
||||
atf_set timeout 30
|
||||
}
|
||||
keyvalue_stresser()
|
||||
{
|
||||
local jailname=$1
|
||||
local modifier=$2
|
||||
|
||||
while true
|
||||
do
|
||||
jail -m name=$jailname $modifier
|
||||
done
|
||||
}
|
||||
keyvalue_contention_body()
|
||||
{
|
||||
setup
|
||||
|
||||
atf_check -s exit:0 jail -c name=j persist meta env
|
||||
|
||||
keyvalue_stresser "j" "meta.a=1" &
|
||||
apid=$!
|
||||
keyvalue_stresser "j" "meta.b=2" &
|
||||
bpid=$!
|
||||
keyvalue_stresser "j" "env.c=3" &
|
||||
cpid=$!
|
||||
keyvalue_stresser "j" "env.d=4" &
|
||||
dpid=$!
|
||||
|
||||
for it in $(jot 8)
|
||||
do
|
||||
jail -m name=j meta='meta=META' env='env=ENV'
|
||||
sleep 1
|
||||
atf_check -sexit:0 -oinline:'1\n' jls -jj meta.a
|
||||
atf_check -sexit:0 -oinline:'2\n' jls -jj meta.b
|
||||
atf_check -sexit:0 -oinline:'3\n' jls -jj env.c
|
||||
atf_check -sexit:0 -oinline:'4\n' jls -jj env.d
|
||||
atf_check -sexit:0 -oinline:'META\n' jls -jj meta.meta
|
||||
atf_check -sexit:0 -oinline:'ENV\n' jls -jj env.env
|
||||
done
|
||||
|
||||
# TODO: Think of adding a stresser on the kernel side which does
|
||||
# osd_set() w/o allprison lock. It could test the compare
|
||||
# and swap mechanism in jm_osd_method_set().
|
||||
|
||||
kill -9 $apid $bpid $cpid $dpid
|
||||
}
|
||||
keyvalue_contention_cleanup()
|
||||
{
|
||||
jail -r j
|
||||
return 0
|
||||
}
|
||||
|
||||
atf_init_test_cases()
|
||||
{
|
||||
atf_add_test_case "jail_create"
|
||||
atf_add_test_case "jail_modify"
|
||||
atf_add_test_case "jail_add"
|
||||
atf_add_test_case "jail_reset"
|
||||
|
||||
atf_add_test_case "jls_libxo_json"
|
||||
|
||||
atf_add_test_case "flua_create"
|
||||
atf_add_test_case "flua_modify"
|
||||
|
||||
atf_add_test_case "env_readable_by_jail"
|
||||
atf_add_test_case "not_inheritable"
|
||||
|
||||
atf_add_test_case "maxbufsize"
|
||||
|
||||
atf_add_test_case "keyvalue"
|
||||
atf_add_test_case "keyvalue_contention"
|
||||
}
|
||||
@@ -513,6 +513,42 @@ sysctl and uname -r.
|
||||
The number for the jail's
|
||||
.Va kern.osreldate
|
||||
and uname -K.
|
||||
.It Va meta , Va env
|
||||
An arbitrary string associated with the jail.
|
||||
Its maximum buffer size is controlled by the global
|
||||
.Va security.jail.meta_maxbufsize
|
||||
sysctl, which can only be adjusted by the non-jailed root user.
|
||||
While the
|
||||
.Va meta
|
||||
is hidden from the jail, the
|
||||
.Va env
|
||||
is readable through the
|
||||
.Va security.jail.env
|
||||
sysctl.
|
||||
.Pp
|
||||
Each buffer can be treated as a set of key=value\\n strings.
|
||||
In order to add or replace a specific key the
|
||||
.Va meta.keyname=value
|
||||
or
|
||||
.Va env.keyname=value
|
||||
parameter notations must be used.
|
||||
While
|
||||
.Va meta.keyname=
|
||||
or
|
||||
.Va env.keyname=
|
||||
reset the value to an empty string, the
|
||||
.Va meta.keyname
|
||||
or
|
||||
.Va env.keyname
|
||||
notations, without the equal sign, remove the given key.
|
||||
Respectively, the same
|
||||
.Va meta.keyname
|
||||
or
|
||||
.Va env.keyname
|
||||
notations are used to query a specific key while reading jail parameters
|
||||
using such commands as
|
||||
.Xr jls 8 .
|
||||
Multiple keys can be queried or modified with a single command.
|
||||
.It Va allow.*
|
||||
Some restrictions of the jail environment may be set on a per-jail
|
||||
basis.
|
||||
|
||||
@@ -448,6 +448,7 @@ print_jail(int pflags, int jflags)
|
||||
if (!(params[i].jp_flags & JP_USER))
|
||||
continue;
|
||||
if ((pflags & PRINT_SKIP) &&
|
||||
!(params[i].jp_flags & JP_KEYVALUE) &&
|
||||
((!(params[i].jp_ctltype &
|
||||
(CTLFLAG_WR | CTLFLAG_TUN))) ||
|
||||
(param_parent[i] >= 0 &&
|
||||
@@ -458,6 +459,13 @@ print_jail(int pflags, int jflags)
|
||||
xo_emit("{P: }");
|
||||
else
|
||||
spc = 1;
|
||||
if ((params[i].jp_flags & JP_KEYVALUE) &&
|
||||
params[i].jp_valuelen == 0) {
|
||||
/* Communicate back a missing key. */
|
||||
if (pflags & PRINT_NAMEVAL)
|
||||
xo_emit("{d:%s}", params[i].jp_name);
|
||||
continue;
|
||||
}
|
||||
if (pflags & PRINT_NAMEVAL) {
|
||||
/*
|
||||
* Generally "name=value", but for booleans
|
||||
|
||||
Reference in New Issue
Block a user