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:
Igor Ostapenko
2025-03-31 09:08:43 +00:00
parent 197997a4c3
commit 30e6e008bc
11 changed files with 1351 additions and 20 deletions
+69 -15
View File
@@ -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);
}
+1
View File
@@ -33,6 +33,7 @@
#define JP_BOOL 0x02
#define JP_NOBOOL 0x04
#define JP_JAILSYS 0x08
#define JP_KEYVALUE 0x10
#define JAIL_ERRMSGLEN 1024
+12 -4
View File
@@ -445,9 +445,16 @@ l_getparams(lua_State *L)
for (size_t i = 0; i < params_count; ++i) {
char *value;
value = jailparam_export(&params[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(&params[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,
+1
View File
@@ -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
View File
@@ -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)
+621
View File
@@ -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 = &empty;
}
/* 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 = &empty;
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);
+4
View File
@@ -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) \
+1
View File
@@ -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"
+588
View File
@@ -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"
}
+36
View File
@@ -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.
+8
View File
@@ -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