diff --git a/bin/kenv/kenv.1 b/bin/kenv/kenv.1 index 0cadbefb41b3..980bec117515 100644 --- a/bin/kenv/kenv.1 +++ b/bin/kenv/kenv.1 @@ -32,6 +32,7 @@ .Nd list or modify the kernel environment .Sh SYNOPSIS .Nm +.Op Fl l | s .Op Fl hNq .Nm .Op Fl qv @@ -45,6 +46,23 @@ The .Nm utility will list all variables in the kernel environment if invoked without arguments. +.Pp +If the +.Fl l +option is specified, then the static environment provided by +.Xr loader 8 +will be listed instead. +Similarly, the +.Fl s +option will list the static environment defined by the kernel config. +Both of the +.Fl l +and +.Fl s +options are dependent on the kernel being configured to preserve early kernel +environments. +The default kernel configuration does not preserve these environments. +.Pp If the .Fl h option is specified, it will limit the report to kernel probe hints. diff --git a/bin/kenv/kenv.c b/bin/kenv/kenv.c index 77caeaf5bca2..ecf30ee6b617 100644 --- a/bin/kenv/kenv.c +++ b/bin/kenv/kenv.c @@ -29,6 +29,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -36,14 +37,16 @@ __FBSDID("$FreeBSD$"); #include static void usage(void); -static int kdumpenv(void); +static int kdumpenv(int dump_type); static int kgetenv(const char *); static int ksetenv(const char *, char *); static int kunsetenv(const char *); static int hflag = 0; +static int lflag = 0; static int Nflag = 0; static int qflag = 0; +static int sflag = 0; static int uflag = 0; static int vflag = 0; @@ -51,7 +54,7 @@ static void usage(void) { (void)fprintf(stderr, "%s\n%s\n%s\n", - "usage: kenv [-hNq]", + "usage: kenv [-l|-s] [-hNq]", " kenv [-qv] variable[=value]", " kenv [-q] -u variable"); exit(1); @@ -65,17 +68,23 @@ main(int argc, char **argv) val = NULL; env = NULL; - while ((ch = getopt(argc, argv, "hNquv")) != -1) { + while ((ch = getopt(argc, argv, "hlNqsuv")) != -1) { switch (ch) { case 'h': hflag++; break; + case 'l': + lflag++; + break; case 'N': Nflag++; break; case 'q': qflag++; break; + case 's': + sflag++; + break; case 'u': uflag++; break; @@ -100,12 +109,23 @@ main(int argc, char **argv) } if ((hflag || Nflag) && env != NULL) usage(); + if (lflag && sflag) + usage(); if (argc > 0 || ((uflag || vflag) && env == NULL)) usage(); if (env == NULL) { - error = kdumpenv(); - if (error && !qflag) - warn("kdumpenv"); + if (lflag) + error = kdumpenv(KENV_DUMP_LOADER); + else if (sflag) + error = kdumpenv(KENV_DUMP_STATIC); + else + error = kdumpenv(KENV_DUMP); + if (error && !qflag) { + if (errno == ENOENT) + warnx("requested environment is unavailable"); + else + warn("kdumpenv"); + } } else if (val == NULL) { if (uflag) { error = kunsetenv(env); @@ -125,12 +145,12 @@ main(int argc, char **argv) } static int -kdumpenv(void) +kdumpenv(int dump_type) { char *buf, *bp, *cp; int buflen, envlen; - envlen = kenv(KENV_DUMP, NULL, NULL, 0); + envlen = kenv(dump_type, NULL, NULL, 0); if (envlen < 0) return (-1); for (;;) { @@ -138,7 +158,7 @@ kdumpenv(void) buf = calloc(1, buflen + 1); if (buf == NULL) return (-1); - envlen = kenv(KENV_DUMP, NULL, buf, buflen); + envlen = kenv(dump_type, NULL, buf, buflen); if (envlen < 0) { free(buf); return (-1); diff --git a/lib/libc/sys/kenv.2 b/lib/libc/sys/kenv.2 index 06f708170a2f..a1f994569111 100644 --- a/lib/libc/sys/kenv.2 +++ b/lib/libc/sys/kenv.2 @@ -26,7 +26,7 @@ .\" .\" $FreeBSD$ .\" -.Dd February 20, 2017 +.Dd June 20, 2021 .Dt KENV 2 .Os .Sh NAME @@ -49,7 +49,7 @@ the kernel environment. The .Fa action argument can be one of the following: -.Bl -tag -width ".Dv KENV_UNSET" +.Bl -tag -width ".Dv KENV_DUMP_LOADER" .It Dv KENV_GET Get the .Fa value @@ -90,7 +90,7 @@ and arguments are ignored. This option is only available to the superuser. .It Dv KENV_DUMP -Dump as much of the kernel environment as will fit in +Dump as much of the dynamic kernel environment as will fit in .Fa value , whose size is given in .Fa len . @@ -103,6 +103,18 @@ will return the number of bytes required to copy out the entire environment. The .Fa name is ignored. +.It Dv KENV_DUMP_LOADER +Dump the static environment provided by +.Xr loader 8 , +with semantics identical to +.Dv KENV_DUMP . +Duplicate and malformed variables originally present in this environment are +discarded by the kernel and will not appear in the output. +.It Dv KENV_DUMP_STATIC +Dump the static environment defined by the kernel +.Xr config 5 . +The semantics are identical to +.Dv KENV_DUMP_LOADER . .El .Sh RETURN VALUES The @@ -142,6 +154,12 @@ for a .Dv KENV_GET or .Dv KENV_UNSET . +.It Bq Er ENOENT +The requested environment is not available for a +.Dv KENV_DUMP_LOADER +or +.Dv KENV_DUMP_STATIC . +The kernel is configured to destroy these environments by default. .It Bq Er EPERM A user other than the superuser attempted to set or unset a kernel environment variable. diff --git a/sys/kern/kern_environment.c b/sys/kern/kern_environment.c index 8dc345559e95..2a4c62d64a0f 100644 --- a/sys/kern/kern_environment.c +++ b/sys/kern/kern_environment.c @@ -92,60 +92,103 @@ bool dynamic_kenv; #define KENV_CHECK if (!dynamic_kenv) \ panic("%s: called before SI_SUB_KMEM", __func__) -int -sys_kenv(td, uap) - struct thread *td; - struct kenv_args /* { - int what; - const char *name; - char *value; - int len; - } */ *uap; +static int +kenv_dump(struct thread *td, char **envp, int what, char *value, int len) { - char *name, *value, *buffer = NULL; - size_t len, done, needed, buflen; - int error, i; + char *buffer, *senv; + size_t done, needed, buflen; + int error; + + error = 0; + buffer = NULL; + done = needed = 0; + + MPASS(what == KENV_DUMP || what == KENV_DUMP_LOADER || + what == KENV_DUMP_STATIC); + + /* + * For non-dynamic kernel environment, we pass in either md_envp or + * kern_envp and we must traverse with kernenv_next(). This shuffling + * of pointers simplifies the below loop by only differing in how envp + * is modified. + */ + if (what != KENV_DUMP) { + senv = (char *)envp; + envp = &senv; + } + + buflen = len; + if (buflen > KENV_SIZE * (KENV_MNAMELEN + kenv_mvallen + 2)) + buflen = KENV_SIZE * (KENV_MNAMELEN + + kenv_mvallen + 2); + if (len > 0 && value != NULL) + buffer = malloc(buflen, M_TEMP, M_WAITOK|M_ZERO); + + /* Only take the lock for the dynamic kenv. */ + if (what == KENV_DUMP) + mtx_lock(&kenv_lock); + while (*envp != NULL) { + len = strlen(*envp) + 1; + needed += len; + len = min(len, buflen - done); + /* + * If called with a NULL or insufficiently large + * buffer, just keep computing the required size. + */ + if (value != NULL && buffer != NULL && len > 0) { + bcopy(*envp, buffer + done, len); + done += len; + } + + /* Advance the pointer depending on the kenv format. */ + if (what == KENV_DUMP) + envp++; + else + senv = kernenv_next(senv); + } + if (what == KENV_DUMP) + mtx_unlock(&kenv_lock); + if (buffer != NULL) { + error = copyout(buffer, value, done); + free(buffer, M_TEMP); + } + td->td_retval[0] = ((done == needed) ? 0 : needed); + return (error); +} + +int +sys_kenv(struct thread *td, struct kenv_args *uap) +{ + char *name, *value; + size_t len; + int error; KASSERT(dynamic_kenv, ("kenv: dynamic_kenv = false")); error = 0; - if (uap->what == KENV_DUMP) { + + switch (uap->what) { + case KENV_DUMP: #ifdef MAC error = mac_kenv_check_dump(td->td_ucred); if (error) return (error); #endif - done = needed = 0; - buflen = uap->len; - if (buflen > KENV_SIZE * (KENV_MNAMELEN + kenv_mvallen + 2)) - buflen = KENV_SIZE * (KENV_MNAMELEN + - kenv_mvallen + 2); - if (uap->len > 0 && uap->value != NULL) - buffer = malloc(buflen, M_TEMP, M_WAITOK|M_ZERO); - mtx_lock(&kenv_lock); - for (i = 0; kenvp[i] != NULL; i++) { - len = strlen(kenvp[i]) + 1; - needed += len; - len = min(len, buflen - done); - /* - * If called with a NULL or insufficiently large - * buffer, just keep computing the required size. - */ - if (uap->value != NULL && buffer != NULL && len > 0) { - bcopy(kenvp[i], buffer + done, len); - done += len; - } - } - mtx_unlock(&kenv_lock); - if (buffer != NULL) { - error = copyout(buffer, uap->value, done); - free(buffer, M_TEMP); - } - td->td_retval[0] = ((done == needed) ? 0 : needed); - return (error); - } - - switch (uap->what) { + return (kenv_dump(td, kenvp, uap->what, uap->value, uap->len)); + case KENV_DUMP_LOADER: + case KENV_DUMP_STATIC: +#ifdef MAC + error = mac_kenv_check_dump(td->td_ucred); + if (error) + return (error); +#endif +#ifdef PRESERVE_EARLY_KENV + return (kenv_dump(td, + uap->what == KENV_DUMP_LOADER ? (char **)md_envp : + (char **)kern_envp, uap->what, uap->value, uap->len)); +#else + return (ENOENT); +#endif case KENV_SET: error = priv_check(td, PRIV_KENV_SET); if (error) diff --git a/sys/sys/kenv.h b/sys/sys/kenv.h index 4c851631f343..eccdc027adcc 100644 --- a/sys/sys/kenv.h +++ b/sys/sys/kenv.h @@ -34,10 +34,12 @@ /* * Constants for the kenv(2) syscall */ -#define KENV_GET 0 -#define KENV_SET 1 -#define KENV_UNSET 2 -#define KENV_DUMP 3 +#define KENV_GET 0 +#define KENV_SET 1 +#define KENV_UNSET 2 +#define KENV_DUMP 3 +#define KENV_DUMP_LOADER 4 +#define KENV_DUMP_STATIC 5 #define KENV_MNAMELEN 128 /* Maximum name length (for the syscall) */ #define KENV_MVALLEN 128 /* Maximum value length (for the syscall) */