Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F133182892
D47668.id151752.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
42 KB
Referenced Files
None
Subscribers
None
D47668.id151752.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];
@@ -521,6 +522,11 @@
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 @@
* 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 @@
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 @@
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/libexec/flua/libjail/lua_jail.c b/libexec/flua/libjail/lua_jail.c
--- a/libexec/flua/libjail/lua_jail.c
+++ b/libexec/flua/libjail/lua_jail.c
@@ -445,9 +445,16 @@
for (size_t i = 0; i < params_count; ++i) {
char *value;
- value = jailparam_export(¶ms[i]);
- lua_pushstring(L, value);
- free(value);
+ if (params[i].jp_flags & JP_KEYVALUE &&
+ params[i].jp_valuelen == 0) {
+ /* Communicate back a missing key. */
+ lua_pushnil(L);
+ } else {
+ value = jailparam_export(¶ms[i]);
+ lua_pushstring(L, value);
+ free(value);
+ }
+
lua_setfield(L, -2, params[i].jp_name);
}
@@ -535,7 +542,8 @@
}
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,
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3792,6 +3792,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
@@ -2552,6 +2552,15 @@
/* 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 @@
/*
* 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,712 @@
+/*-
+ * 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");
+
+
+/* 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");
+
+
+/* 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 */
+ /* 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 the buffers. */
+ mtx_lock(&pr->pr_mtx);
+ oldosd = osd_jail_get(pr, meta->osd_slot);
+ if (oldosd == origosd) {
+ error = osd_jail_set(pr, meta->osd_slot, osd);
+ } else {
+ /*
+ * The osd(9) framework requires protection only for pr_osd,
+ * which is covered by pr_mtx. Therefore, other code might
+ * legally alter jail metadata without allprison_lock. It
+ * means that here we could override data just added by other
+ * thread. This extra caution with retry mechanism aims to
+ * prevent user data loss in such potential cases.
+ */
+ error = EAGAIN;
+ repeat = true;
+ }
+ mtx_unlock(&pr->pr_mtx);
+ if (error == 0)
+ osd = oldosd;
+
+end:
+ jm_h_freechain(h);
+ free(osd, M_PRISON);
+ free(origosd_copy, M_PRISON);
+
+ if (repeat && ++repeats < 3)
+ goto again;
+
+ return (error);
+}
+
+static int
+jm_osd_method_get(void *obj, void *data, const struct meta *meta)
+{
+ struct prison *pr = obj;
+ struct vfsoptlist *opts = data;
+ struct vfsopt *opt;
+ char *osd = NULL;
+ char empty = '\0';
+ int error = 0;
+ bool locked = false;
+ const char *key;
+ size_t keylen;
+ const char *p;
+
+ sx_assert(&allprison_lock, SA_SLOCKED);
+
+ TAILQ_FOREACH(opt, opts, link) {
+ if (strstr(opt->name, meta->name) != opt->name)
+ continue;
+ if (opt->name[strlen(meta->name)] != '.' &&
+ opt->name[strlen(meta->name)] != '\0')
+ continue;
+
+ if (!locked) {
+ mtx_lock(&pr->pr_mtx);
+ locked = true;
+ osd = osd_jail_get(pr, meta->osd_slot);
+ if (osd == NULL)
+ osd = ∅
+ }
+
+ /* Provide full metadata. */
+ if (strcmp(opt->name, meta->name) == 0) {
+ if (strlcpy(opt->value, osd, opt->len) >= opt->len) {
+ error = EINVAL;
+ break;
+ }
+ opt->seen = 1;
+ continue;
+ }
+
+ /* Extract a specific key=value. */
+ p = osd;
+ key = opt->name + strlen(meta->name) + 1;
+ keylen = strlen(key);
+ while ((p = strstr(p, key)) != NULL) {
+ if ((p == osd || *(p - 1) == '\n')
+ && p[keylen] == '=') {
+ if (strlcpy(opt->value, p + keylen + 1,
+ MIN(opt->len, strchr(p + keylen + 1, '\n') -
+ (p + keylen + 1) + 1)) >= opt->len) {
+ error = EINVAL;
+ break;
+ }
+ opt->seen = 1;
+ }
+ p += keylen;
+ }
+ if (error != 0)
+ break;
+ }
+
+ if (locked)
+ mtx_unlock(&pr->pr_mtx);
+
+ return (error);
+}
+
+static int
+jm_osd_method_check(void *obj __unused, void *data, const struct meta *meta)
+{
+ struct vfsoptlist *opts = data;
+ struct vfsopt *opt;
+
+ TAILQ_FOREACH(opt, opts, link) {
+ if (strstr(opt->name, meta->name) != opt->name)
+ continue;
+ if (opt->name[strlen(meta->name)] != '.' &&
+ opt->name[strlen(meta->name)] != '\0')
+ continue;
+ opt->seen = 1;
+ }
+
+ return (0);
+}
+
+static void
+jm_osd_destructor(void *osd)
+{
+ free(osd, M_PRISON);
+}
+
+
+/* OSD for "meta" param */
+
+static struct meta meta;
+
+static inline int
+jm_osd_method_set_meta(void *obj, void *data)
+{
+ return (jm_osd_method_set(obj, data, &meta));
+}
+
+static inline int
+jm_osd_method_get_meta(void *obj, void *data)
+{
+ return (jm_osd_method_get(obj, data, &meta));
+}
+
+static inline int
+jm_osd_method_check_meta(void *obj, void *data)
+{
+ return (jm_osd_method_check(obj, data, &meta));
+}
+
+static struct meta meta = {
+ .name = JAIL_META_PRIVATE,
+ .osd_slot = 0,
+ .methods = {
+ [PR_METHOD_SET] = jm_osd_method_set_meta,
+ [PR_METHOD_GET] = jm_osd_method_get_meta,
+ [PR_METHOD_CHECK] = jm_osd_method_check_meta,
+ }
+};
+
+
+/* OSD for "env" param */
+
+static struct meta env;
+
+static inline int
+jm_osd_method_set_env(void *obj, void *data)
+{
+ return (jm_osd_method_set(obj, data, &env));
+}
+
+static inline int
+jm_osd_method_get_env(void *obj, void *data)
+{
+ return (jm_osd_method_get(obj, data, &env));
+}
+
+static inline int
+jm_osd_method_check_env(void *obj, void *data)
+{
+ return (jm_osd_method_check(obj, data, &env));
+}
+
+static struct meta env = {
+ .name = JAIL_META_SHARED,
+ .osd_slot = 0,
+ .methods = {
+ [PR_METHOD_SET] = jm_osd_method_set_env,
+ [PR_METHOD_GET] = jm_osd_method_get_env,
+ [PR_METHOD_CHECK] = jm_osd_method_check_env,
+ }
+};
+
+
+/* A jail can read its "env". */
+
+static int
+jm_sysctl_env(SYSCTL_HANDLER_ARGS)
+{
+ struct prison *pr;
+ char empty = '\0';
+ char *tmpbuf;
+ size_t outlen;
+ int error = 0;
+
+ pr = req->td->td_ucred->cr_prison;
+
+ mtx_lock(&pr->pr_mtx);
+ arg1 = osd_jail_get(pr, env.osd_slot);
+ if (arg1 == NULL) {
+ tmpbuf = ∅
+ outlen = 1;
+ } else {
+ outlen = strlen(arg1) + 1;
+ if (req->oldptr != NULL) {
+ tmpbuf = malloc(outlen, M_PRISON, M_NOWAIT);
+ error = (tmpbuf == NULL) ? ENOMEM : 0;
+ if (error == 0)
+ memcpy(tmpbuf, arg1, outlen);
+ }
+ }
+ mtx_unlock(&pr->pr_mtx);
+
+ if (error != 0)
+ return (error);
+
+ if (req->oldptr == NULL)
+ SYSCTL_OUT(req, NULL, outlen);
+ else {
+ SYSCTL_OUT(req, tmpbuf, outlen);
+ if (tmpbuf != &empty)
+ free(tmpbuf, M_PRISON);
+ }
+
+ return (error);
+}
+SYSCTL_PROC(_security_jail, OID_AUTO, env,
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ 0, 0, jm_sysctl_env, "A", "Meta information provided by parent jail");
+
+
+/* Setup and tear down. */
+
+static int
+jm_sysinit(void *arg __unused)
+{
+ /* 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[\r]\n format */
+ BIT_SET(NCHARS, 0x0A, &allowedchars); /* LF */
+ BIT_SET(NCHARS, 0x0D, &allowedchars); /* CR */
+
+ /* other useful chars */
+ 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); /* _ */
+
+
+ /* OSD registration. */
+
+ 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
@@ -141,6 +141,9 @@
#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 @@
/*
* 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,659 @@
+#
+# 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_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 "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
+
+ # 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 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.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 "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,47 @@
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.
+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.
diff --git a/usr.sbin/jls/jls.c b/usr.sbin/jls/jls.c
--- a/usr.sbin/jls/jls.c
+++ b/usr.sbin/jls/jls.c
@@ -448,6 +448,7 @@
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 @@
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
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Oct 24, 6:22 PM (8 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
24140054
Default Alt Text
D47668.id151752.diff (42 KB)
Attached To
Mode
D47668: jail: Add meta and env parameters
Attached
Detach File
Event Timeline
Log In to Comment