Page MenuHomeFreeBSD

D47668.id148298.diff
No OneTemporary

D47668.id148298.diff

diff --git a/lib/libjail/jail.h b/lib/libjail/jail.h
--- a/lib/libjail/jail.h
+++ b/lib/libjail/jail.h
@@ -33,6 +33,7 @@
#define JP_BOOL 0x02
#define JP_NOBOOL 0x04
#define JP_JAILSYS 0x08
+#define JP_KEYVALUE 0x10
#define JAIL_ERRMSGLEN 1024
diff --git a/lib/libjail/jail.c b/lib/libjail/jail.c
--- a/lib/libjail/jail.c
+++ b/lib/libjail/jail.c
@@ -59,6 +59,7 @@
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];
@@ -907,22 +908,41 @@
* 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 +963,12 @@
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 +1145,26 @@
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);
+}
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3805,6 +3805,7 @@
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
diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c
--- a/sys/kern/kern_jail.c
+++ b/sys/kern/kern_jail.c
@@ -4254,7 +4254,7 @@
/*
* 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)
diff --git a/sys/kern/kern_jailmeta.c b/sys/kern/kern_jailmeta.c
new file mode 100644
--- /dev/null
+++ b/sys/kern/kern_jailmeta.c
@@ -0,0 +1,702 @@
+/*-
+ * 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;
+
+ /* only reading */
+
+ 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");
+
+
+/* Allowed chars */
+
+#define NCHARS 256
+BITSET_DEFINE(charbitset, NCHARS);
+static struct charbitset allowedchars;
+
+static int
+jm_sysctl_meta_allowedchars(SYSCTL_HANDLER_ARGS)
+{
+ int error;
+ unsigned char chars[NCHARS];
+ int len = 0;
+ const bool readonly = req->newptr == NULL;
+
+ readonly ? sx_slock(&allprison_lock) : sx_xlock(&allprison_lock);
+
+ if (!BIT_ISFULLSET(NCHARS, &allowedchars))
+ for (size_t i = 1; i < NCHARS; i++) {
+ if (!BIT_ISSET(NCHARS, i, &allowedchars))
+ continue;
+ chars[len] = i;
+ len++;
+ }
+ chars[len] = 0;
+
+ error = sysctl_handle_string(oidp, chars, arg2, req);
+
+ if (!readonly && error == 0) {
+ if (chars[0] == 0) {
+ BIT_FILL(NCHARS, &allowedchars);
+ } else {
+ BIT_ZERO(NCHARS, &allowedchars);
+ for (size_t i = 0; i < NCHARS; i++) {
+ if (chars[i] == 0)
+ break;
+ BIT_SET(NCHARS, chars[i], &allowedchars);
+ }
+ }
+ }
+
+ readonly ? sx_sunlock(&allprison_lock) : sx_xunlock(&allprison_lock);
+
+ return (error);
+}
+SYSCTL_PROC(_security_jail, OID_AUTO, meta_allowedchars,
+ CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, NCHARS,
+ jm_sysctl_meta_allowedchars, "A",
+ "The single-byte chars allowed to be used for meta and env"
+ " (empty string means all chars are allowed)");
+
+
+/* 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");
+
+
+/* OSD -- generic logic for any metadata buffer */
+
+struct meta {
+ char *name;
+ u_int osd_slot;
+ osd_method_t methods[PR_MAXMETHOD];
+};
+
+/*
+ * A chain of hunks depicts the final buffer after all manipulations.
+ */
+struct hunk {
+ char *p;
+ 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\n */
+ for (end = begin; (end + 1) - h->p < h->len; end++)
+ if (*end == '\0' || *end == '\n')
+ break;
+
+ /* Pick up 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;
+ }
+ /* vfsopt is expected to provide NULL-terminated strings */
+ if (((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;
+ }
+
+ /* 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,
+ /* avoid empty NULL-terminated string */
+ (opt->len > 1) ? opt->len : 0);
+ continue;
+ }
+
+ /* Add or replace existing 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);
+ 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 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 */
+ /* Check allowed chars */
+ for (size_t i = 0; i < osdlen; i++) {
+ if (osd[i] == 0)
+ continue;
+ if (!BIT_ISSET(NCHARS, osd[i], &allowedchars)) {
+ error = EINVAL;
+ goto end;
+ }
+ }
+ }
+
+ /* Compare and swap 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 {
+ /* someone else was quicker - re-apply opts then */
+ 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++;
+ if (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;
+
+ opt->seen = 1;
+
+ 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;
+ }
+ continue;
+ }
+
+ /* Extract a specific key=value\n */
+ 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;
+ }
+ }
+ p += keylen;
+ }
+ }
+
+ 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 -- meta */
+
+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 = "meta",
+ .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 -- env */
+
+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 = "env",
+ .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)
+{
+ /* Default set of allowed chars */
+
+ BIT_ZERO(NCHARS, &allowedchars);
+
+ /* Base64 */
+ for (size_t i = 0x41; i <= 0x5A; i++) /* A-Z */
+ BIT_SET(NCHARS, i, &allowedchars);
+ for (size_t i = 0x61; i <= 0x7A; i++) /* a-z */
+ BIT_SET(NCHARS, i, &allowedchars);
+ for (size_t i = 0x30; i <= 0x39; i++) /* 0-9 */
+ BIT_SET(NCHARS, i, &allowedchars);
+ BIT_SET(NCHARS, 0x2B, &allowedchars); /* + */
+ BIT_SET(NCHARS, 0x2F, &allowedchars); /* / */
+ BIT_SET(NCHARS, 0x3D, &allowedchars); /* = */
+
+ /* key=value\n format */
+ BIT_SET(NCHARS, 0x0A, &allowedchars); /* LF */
+ BIT_SET(NCHARS, 0x0D, &allowedchars); /* CR */
+
+ /* Extra */
+ BIT_SET(NCHARS, 0x09, &allowedchars); /* HT */
+ BIT_SET(NCHARS, 0x20, &allowedchars); /* SP */
+ BIT_SET(NCHARS, 0x2C, &allowedchars); /* , */
+ BIT_SET(NCHARS, 0x2D, &allowedchars); /* - */
+ BIT_SET(NCHARS, 0x2E, &allowedchars); /* . */
+ BIT_SET(NCHARS, 0x3A, &allowedchars); /* : */
+ BIT_SET(NCHARS, 0x40, &allowedchars); /* @ */
+ BIT_SET(NCHARS, 0x5F, &allowedchars); /* _ */
+
+
+ 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);
diff --git a/sys/sys/jail.h b/sys/sys/jail.h
--- a/sys/sys/jail.h
+++ b/sys/sys/jail.h
@@ -376,6 +376,7 @@
/*
* Sysctls to describe jail parameters.
*/
+SYSCTL_DECL(_security_jail);
SYSCTL_DECL(_security_jail_param);
#define SYSCTL_JAIL_PARAM_DECL(name) \
diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -59,6 +59,8 @@
ATF_TESTS_SH+= coredump_phnum_test
ATF_TESTS_SH+= logsigexit_test
+ATF_TESTS_SH+= jailmeta
+TEST_METADATA.jailmeta+= is_exclusive="true"
ATF_TESTS_SH+= sonewconn_overflow
TEST_METADATA.sonewconn_overflow+= required_programs="python"
TEST_METADATA.sonewconn_overflow+= required_user="root"
diff --git a/tests/sys/kern/jailmeta.sh b/tests/sys/kern/jailmeta.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/kern/jailmeta.sh
@@ -0,0 +1,631 @@
+#
+# 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
+
+ # Check if chars required for testing are allowed
+ allowed="$(sysctl -b security.jail.meta_allowedchars | hexdump -e '1/1 "%02x\n"')"
+ # ABCabctv =0-9\t\n
+ for b in 41 42 43 61 62 63 74 76 20 30 31 32 33 34 35 36 37 38 39 09 0a
+ do
+ if ! echo $allowed | grep -q $b; then
+ atf_skip "sysctl security.jail.meta_allowedchars is not wide enough for testing"
+ fi
+ done
+}
+
+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" "cleanup"
+jls_libxo_head()
+{
+ atf_set descr 'Test that metadata can be read with jls(8) using libxo'
+ atf_set require.user root
+ atf_set execenv jail
+}
+jls_libxo_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_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
+}
+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 "allowedchars" "cleanup"
+allowedchars_head()
+{
+ atf_set descr 'Test that the set of allowed chars can be changed'
+ atf_set require.user root
+}
+allowedchars_body()
+{
+ setup
+
+ jn=jailmeta_allowedchars
+ atf_check -s not-exit:0 -e match:"not found" -o ignore \
+ jls -j $jn
+ atf_check -s exit:0 \
+ jail -c name=$jn persist
+
+ # Save the original value
+ sysctl -b security.jail.meta_allowedchars > meta_allowedchars.bin
+
+ # All chars
+ sysctl security.jail.meta_allowedchars=
+ printf $(jot -w '\%o' -s '' -n 127 1 127) > 7bit.bin
+ atf_check -s exit:0 \
+ jail -m name=$jn meta="$(cat 7bit.bin)" env="$(cat 7bit.bin)"
+ jls -j $jn meta > meta.bin
+ jls -j $jn env > env.bin
+ printf '\n' >> 7bit.bin # jls adds a newline
+ atf_check -s exit:0 diff 7bit.bin meta.bin
+ atf_check -s exit:0 diff 7bit.bin env.bin
+
+ # Limited set
+ sysctl security.jail.meta_allowedchars="$(printf 'AB\1\2_\3\11C')"
+ # should be okay if within the limits
+ atf_check -s exit:0 \
+ jail -m name=$jn meta="$(printf 'C\11A\3')" env="$(printf '\1A\2B\3')"
+ # should error and not change env
+ atf_check -s not-exit:0 -o ignore -e ignore \
+ jail -m name=$jn meta="$(printf 'XC\11A\3')" env="$(printf '_\1A\2B\3')"
+ # should error and not change meta
+ atf_check -s not-exit:0 -o ignore -e ignore \
+ jail -m name=$jn meta="$(printf '_C\11A\3')" env="$(printf '\1A\2B\3x')"
+ # should stay intact after errors
+ atf_check -s exit:0 -o inline:"43094103" \
+ jls -j $jn meta | hexdump -e '1/1 "%02x"'
+ atf_check -s exit:0 -o inline:"0141024303" \
+ jls -j $jn env | hexdump -e '1/1 "%02x"'
+
+}
+allowedchars_cleanup()
+{
+ # Restore the original value
+ test -f meta_allowedchars.bin \
+ && sysctl security.jail.meta_allowedchars="'$(cat meta_allowedchars.bin)'"
+ rm *.bin
+
+ jail -r jailmeta_allowedchars
+ 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) and jls(8)'
+ atf_set require.user root
+ atf_set execenv jail
+}
+keyvalue_generic()
+{
+ local meta=$1
+
+ atf_check -sexit:0 -oinline:'""\n' jls -jj $meta
+
+ # 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 an empty string 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 with libxo-based output
+ s='{"__version": "2", "jail-information": {"jail": [{"'$meta'.b":"3","'$meta'.c":""}]}}\n'
+ atf_check -s exit:0 -o inline:"$s" jls -jj --libxo json $meta.b $meta.c
+
+ # Should be able to lookup a key with 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 be fine if a buffer is empty
+ 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.b $meta.a=
+ atf_check -sexit:0 -oinline:'""\n' jls -jj $meta.a
+ atf_check -sexit:0 -oinline:'""\n' jls -jj $meta.b
+ atf_check -sexit:0 -oinline:'a=\nb=\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 with 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
+}
+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
+ sleep 1
+ jail -m name=j meta='meta=META' env='env=ENV'
+ atf_check -sexit:0 -oinline:'META\n' jls -jj meta.meta
+ atf_check -sexit:0 -oinline:'ENV\n' jls -jj env.env
+ 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
+ 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"
+
+ 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 "allowedchars"
+
+ atf_add_test_case "keyvalue"
+ atf_add_test_case "keyvalue_contention"
+}
diff --git a/usr.sbin/jail/jail.8 b/usr.sbin/jail/jail.8
--- a/usr.sbin/jail/jail.8
+++ b/usr.sbin/jail/jail.8
@@ -513,6 +513,36 @@
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.
+The set of allowed single-byte characters for both buffers is limited by the
+global
+.Va security.jail.meta_allowedchars
+sysctl, which is also tunable by the non-jailed root user.
+All characters are allowed if it is set to an empty string.
+.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.
+The
+.Va meta.keyname
+or
+.Va env.keyname
+notations are used for reading.
+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.

File Metadata

Mime Type
text/plain
Expires
Tue, Jan 28, 2:03 AM (10 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16237513
Default Alt Text
D47668.id148298.diff (38 KB)

Event Timeline