git: c32bd97641da - main - kern: Support duplicate variables in early kenv

From: Colin Percival <cperciva_at_FreeBSD.org>
Date: Tue, 18 Oct 2022 06:03:04 UTC
The branch main has been updated by cperciva:

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

commit c32bd97641da2e49188e68fb83452605f6ace0ef
Author:     Colin Percival <cperciva@FreeBSD.org>
AuthorDate: 2022-08-13 00:14:47 +0000
Commit:     Colin Percival <cperciva@FreeBSD.org>
CommitDate: 2022-10-18 06:02:20 +0000

    kern: Support duplicate variables in early kenv
    
    Some virtual machines pass virtio MMIO device parameters via the kernel
    command line as a series of virtio_mmio.device=<parameters> options.
    These get translated into FreeBSD kernel environment variables; but
    unfortunately they all use the same variable name, which resulted in
    all but the first such parameter being ignored when the dynamic kernel
    environment is set up from the initial environment buffers.
    
    With this commit, duplicate environment settings will instead be stored
    as ${name}_1, ${name}_2... ${name}_9999.  In the unlikely event that
    the same variable is set over 10000 times before the dynamic kernel
    environment is set up, we panic.
    
    Variable settings after the dynamic environment is initialized continue
    to override the previously-set value; the change is limited to the very
    early kernel boot (prior to SI_SUB_KMEM + 1) and changes behaviour from
    "ignore" to "store with a different name" only.
    
    Reviewed by:    imp
    Feedback from:  kevans
    Sponsored by:   https://patreon.com/cperciva
    Differential Revision:  https://reviews.freebsd.org/D36187
---
 sys/kern/kern_environment.c | 68 +++++++++++++++++++++++++++++++++------------
 1 file changed, 51 insertions(+), 17 deletions(-)

diff --git a/sys/kern/kern_environment.c b/sys/kern/kern_environment.c
index 77fdf833c530..f4c720aaaeb4 100644
--- a/sys/kern/kern_environment.c
+++ b/sys/kern/kern_environment.c
@@ -359,11 +359,33 @@ init_static_kenv(char *buf, size_t len)
 	}
 }
 
+/* Maximum suffix number appended for duplicate environment variable names. */
+#define MAXSUFFIX 9999
+#define SUFFIXLEN strlen("_" __XSTRING(MAXSUFFIX))
+
+static void
+getfreesuffix(char *cp, size_t *n)
+{
+	size_t len = strlen(cp);
+	char * ncp;
+
+	ncp = malloc(len + SUFFIXLEN + 1, M_KENV, M_WAITOK);
+	memcpy(ncp, cp, len);
+	for (*n = 1; *n <= MAXSUFFIX; (*n)++) {
+		sprintf(&ncp[len], "_%zu", *n);
+		if (!_getenv_dynamic_locked(ncp, NULL))
+			break;
+	}
+	free(ncp, M_KENV);
+	if (*n > MAXSUFFIX)
+		panic("Too many duplicate kernel environment values: %s", cp);
+}
+
 static void
 init_dynamic_kenv_from(char *init_env, int *curpos)
 {
 	char *cp, *cpnext, *eqpos, *found;
-	size_t len;
+	size_t len, n;
 	int i;
 
 	if (init_env && *init_env != '\0') {
@@ -372,6 +394,12 @@ init_dynamic_kenv_from(char *init_env, int *curpos)
 		for (cp = init_env; cp != NULL; cp = cpnext) {
 			cpnext = kernenv_next(cp);
 			len = strlen(cp) + 1;
+			if (i > KENV_SIZE) {
+				printf(
+				"WARNING: too many kenv strings, ignoring %s\n",
+				    cp);
+				goto sanitize;
+			}
 			if (len > KENV_MNAMELEN + 1 + kenv_mvallen + 1) {
 				printf(
 				"WARNING: too long kenv string, ignoring %s\n",
@@ -387,25 +415,31 @@ init_dynamic_kenv_from(char *init_env, int *curpos)
 			}
 			*eqpos = 0;
 			/*
-			 * De-dupe the environment as we go.  We don't add the
-			 * duplicated assignments because config(8) will flip
-			 * the order of the static environment around to make
-			 * kernel processing match the order of specification
-			 * in the kernel config.
+			 * Handle duplicates in the environment as we go; we
+			 * add the duplicated assignments with _N suffixes.
+			 * This ensures that (a) if a variable is set in the
+			 * static environment and in the "loader" environment
+			 * provided by MD code, the value from the loader will
+			 * have the expected variable name and the value from
+			 * the static environment will have the suffix; and (b)
+			 * if the "loader" environment has the same variable
+			 * set multiple times (as is possible with values being
+			 * passed via the kernel "command line") the extra
+			 * values are visible to code which knows where to look
+			 * for them.
 			 */
 			found = _getenv_dynamic_locked(cp, NULL);
-			*eqpos = '=';
-			if (found != NULL)
-				goto sanitize;
-			if (i > KENV_SIZE) {
-				printf(
-				"WARNING: too many kenv strings, ignoring %s\n",
-				    cp);
-				goto sanitize;
+			if (found != NULL) {
+				getfreesuffix(cp, &n);
+				kenvp[i] = malloc(len + SUFFIXLEN,
+				    M_KENV, M_WAITOK);
+				sprintf(kenvp[i++], "%s_%zu=%s", cp, n,
+				    &eqpos[1]);
+			} else {
+				kenvp[i] = malloc(len, M_KENV, M_WAITOK);
+				*eqpos = '=';
+				strcpy(kenvp[i++], cp);
 			}
-
-			kenvp[i] = malloc(len, M_KENV, M_WAITOK);
-			strcpy(kenvp[i++], cp);
 sanitize:
 #ifdef PRESERVE_EARLY_KENV
 			continue;