Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F108540376
D47668.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
38 KB
Referenced Files
None
Subscribers
None
D47668.diff
View Options
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
@@ -4272,7 +4272,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,709 @@
+/*-
+ * 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 {
+ /*
+ * 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++;
+ 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 = ∅
+ }
+
+ /* 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 = ∅
+ 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,7 @@
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"
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,633 @@
+#
+# 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
+ 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 "allowedchars" "cleanup"
+allowedchars_head()
+{
+ atf_set descr 'Test that the set of allowed chars can be changed'
+ atf_set require.user root
+ atf_set is.exclusive true
+}
+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
+ 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"
+
+ 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
Details
Attached
Mime Type
text/plain
Expires
Mon, Jan 27, 2:42 AM (45 m, 52 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16185508
Default Alt Text
D47668.diff (38 KB)
Attached To
Mode
D47668: jail: Add meta and env parameters
Attached
Detach File
Event Timeline
Log In to Comment