git: d1a465f02b14 - main - jailmeta: reject empty keys in metadata lookups

From: Igor Ostapenko <igoro_at_FreeBSD.org>
Date: Fri, 30 Oct 2026 21:53:24 UTC
The branch main has been updated by igoro:

URL: https://cgit.FreeBSD.org/src/commit/?id=d1a465f02b14c9eed8a87c31092bb9251dc053c1

commit d1a465f02b14c9eed8a87c31092bb9251dc053c1
Author:     Yura Tkachenko <yura.tkachenko@gmail.com>
AuthorDate: 2026-10-30 20:20:24 +0000
Commit:     Igor Ostapenko <igoro@FreeBSD.org>
CommitDate: 2026-10-30 21:45:43 +0000

    jailmeta: reject empty keys in metadata lookups
    
    Reject empty keys in metadata lookups with EINVAL to prevent an infinite
    loop while holding allprison_lock and pr_mtx. Add tests for empty-key
    reads and writes with empty and populated meta/env buffers, trailing
    newlines, and empty final values.
    
    Reviewed by:    igoro, jamie
    MFC after:      3 days
    Differential Revision:  https://reviews.freebsd.org/D61025
---
 sys/kern/kern_jailmeta.c   |  4 ++++
 tests/sys/kern/jailmeta.sh | 28 ++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/sys/kern/kern_jailmeta.c b/sys/kern/kern_jailmeta.c
index 91bb7155820d..83d773ca6caa 100644
--- a/sys/kern/kern_jailmeta.c
+++ b/sys/kern/kern_jailmeta.c
@@ -436,6 +436,10 @@ jm_osd_method_get(void *obj, void *data, const struct meta *meta)
 		p = osd;
 		key = opt->name + strlen(meta->name) + 1;
 		keylen = strlen(key);
+		if (keylen < 1) {
+			error = EINVAL;
+			break;
+		}
 		while ((p = strstr(p, key)) != NULL) {
 			if ((p == osd || *(p - 1) == '\n')
 			    && p[keylen] == '=') {
diff --git a/tests/sys/kern/jailmeta.sh b/tests/sys/kern/jailmeta.sh
index f030e44b14c3..236690ffddcb 100644
--- a/tests/sys/kern/jailmeta.sh
+++ b/tests/sys/kern/jailmeta.sh
@@ -397,12 +397,28 @@ keyvalue_generic()
 
 	atf_check -sexit:0 -oinline:'""\n'		jls -jj $meta
 
+	# Reject empty keys even when the metadata buffer is empty.
+	atf_check -s exit:1 -o empty -e match:'Invalid argument' \
+	    jls -jj "${meta}."
+	atf_check -s exit:1 -o empty -e match:'Invalid argument' \
+	    jail -m name=j "${meta}.=value"
+	atf_check -s exit:0 -o inline:'""\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
+
+	# Reject empty keys with populated metadata; failed writes preserve it.
+	atf_check -s exit:1 -o empty -e match:'Invalid argument' \
+	    jls -jj "${meta}."
+	atf_check -s exit:1 -o empty -e match:'Invalid argument' \
+	    jail -m name=j "${meta}.=value"
+	atf_check -s exit:0 -o inline:'a=1\n' jls -jj "$meta"
+	atf_check -s exit:0 -o inline:'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
@@ -492,6 +508,18 @@ keyvalue_generic()
 
 	# Should respectively support "jls -s" for a missing key
 	atf_check -sexit:0 -oinline:''$meta'.missing\n'	jls -jj -s $meta.missing
+
+	# Preserve the trailing newline despite command substitution.
+	local value
+	value=$(printf 'a=1\nb=2\n.')
+	value=${value%.}
+	atf_check -s exit:0 jail -m name=j "${meta}=$value"
+	atf_check -s exit:0 -o inline:'1\n' jls -jj "${meta}.a"
+	atf_check -s exit:0 -o inline:'2\n' jls -jj "${meta}.b"
+
+	# An empty value on the final line needs only its terminating NUL.
+	atf_check -s exit:0 jail -m name=j "${meta}=a="
+	atf_check -s exit:0 -o inline:'""\n' jls -jj "${meta}.a"
 }
 keyvalue_body()
 {