kenv: allow listing of static kernel environments

The early environment is typically cleared, so these new options
need the PRESERVE_EARLY_KENV kernel config(8) option. These environments
are reported as missing by kenv(1) if the option is not present in the
running kernel.

Reviewed by:	imp
Differential Revision:	https://reviews.freebsd.org/D30835
This commit is contained in:
Kyle Evans 2021-06-20 14:36:10 -05:00
parent 7a129c973b
commit db0f264393
5 changed files with 161 additions and 60 deletions

View File

@ -32,6 +32,7 @@
.Nd list or modify the kernel environment .Nd list or modify the kernel environment
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl l | s
.Op Fl hNq .Op Fl hNq
.Nm .Nm
.Op Fl qv .Op Fl qv
@ -45,6 +46,23 @@ The
.Nm .Nm
utility will list all variables in the kernel environment if utility will list all variables in the kernel environment if
invoked without arguments. 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 If the
.Fl h .Fl h
option is specified, it will limit the report to kernel probe hints. option is specified, it will limit the report to kernel probe hints.

View File

@ -29,6 +29,7 @@ __FBSDID("$FreeBSD$");
#include <sys/types.h> #include <sys/types.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <err.h> #include <err.h>
#include <errno.h>
#include <kenv.h> #include <kenv.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -36,14 +37,16 @@ __FBSDID("$FreeBSD$");
#include <unistd.h> #include <unistd.h>
static void usage(void); static void usage(void);
static int kdumpenv(void); static int kdumpenv(int dump_type);
static int kgetenv(const char *); static int kgetenv(const char *);
static int ksetenv(const char *, char *); static int ksetenv(const char *, char *);
static int kunsetenv(const char *); static int kunsetenv(const char *);
static int hflag = 0; static int hflag = 0;
static int lflag = 0;
static int Nflag = 0; static int Nflag = 0;
static int qflag = 0; static int qflag = 0;
static int sflag = 0;
static int uflag = 0; static int uflag = 0;
static int vflag = 0; static int vflag = 0;
@ -51,7 +54,7 @@ static void
usage(void) usage(void)
{ {
(void)fprintf(stderr, "%s\n%s\n%s\n", (void)fprintf(stderr, "%s\n%s\n%s\n",
"usage: kenv [-hNq]", "usage: kenv [-l|-s] [-hNq]",
" kenv [-qv] variable[=value]", " kenv [-qv] variable[=value]",
" kenv [-q] -u variable"); " kenv [-q] -u variable");
exit(1); exit(1);
@ -65,17 +68,23 @@ main(int argc, char **argv)
val = NULL; val = NULL;
env = NULL; env = NULL;
while ((ch = getopt(argc, argv, "hNquv")) != -1) { while ((ch = getopt(argc, argv, "hlNqsuv")) != -1) {
switch (ch) { switch (ch) {
case 'h': case 'h':
hflag++; hflag++;
break; break;
case 'l':
lflag++;
break;
case 'N': case 'N':
Nflag++; Nflag++;
break; break;
case 'q': case 'q':
qflag++; qflag++;
break; break;
case 's':
sflag++;
break;
case 'u': case 'u':
uflag++; uflag++;
break; break;
@ -100,12 +109,23 @@ main(int argc, char **argv)
} }
if ((hflag || Nflag) && env != NULL) if ((hflag || Nflag) && env != NULL)
usage(); usage();
if (lflag && sflag)
usage();
if (argc > 0 || ((uflag || vflag) && env == NULL)) if (argc > 0 || ((uflag || vflag) && env == NULL))
usage(); usage();
if (env == NULL) { if (env == NULL) {
error = kdumpenv(); if (lflag)
if (error && !qflag) error = kdumpenv(KENV_DUMP_LOADER);
warn("kdumpenv"); 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) { } else if (val == NULL) {
if (uflag) { if (uflag) {
error = kunsetenv(env); error = kunsetenv(env);
@ -125,12 +145,12 @@ main(int argc, char **argv)
} }
static int static int
kdumpenv(void) kdumpenv(int dump_type)
{ {
char *buf, *bp, *cp; char *buf, *bp, *cp;
int buflen, envlen; int buflen, envlen;
envlen = kenv(KENV_DUMP, NULL, NULL, 0); envlen = kenv(dump_type, NULL, NULL, 0);
if (envlen < 0) if (envlen < 0)
return (-1); return (-1);
for (;;) { for (;;) {
@ -138,7 +158,7 @@ kdumpenv(void)
buf = calloc(1, buflen + 1); buf = calloc(1, buflen + 1);
if (buf == NULL) if (buf == NULL)
return (-1); return (-1);
envlen = kenv(KENV_DUMP, NULL, buf, buflen); envlen = kenv(dump_type, NULL, buf, buflen);
if (envlen < 0) { if (envlen < 0) {
free(buf); free(buf);
return (-1); return (-1);

View File

@ -26,7 +26,7 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd February 20, 2017 .Dd June 20, 2021
.Dt KENV 2 .Dt KENV 2
.Os .Os
.Sh NAME .Sh NAME
@ -49,7 +49,7 @@ the kernel environment.
The The
.Fa action .Fa action
argument can be one of the following: argument can be one of the following:
.Bl -tag -width ".Dv KENV_UNSET" .Bl -tag -width ".Dv KENV_DUMP_LOADER"
.It Dv KENV_GET .It Dv KENV_GET
Get the Get the
.Fa value .Fa value
@ -90,7 +90,7 @@ and
arguments are ignored. arguments are ignored.
This option is only available to the superuser. This option is only available to the superuser.
.It Dv KENV_DUMP .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 , .Fa value ,
whose size is given in whose size is given in
.Fa len . .Fa len .
@ -103,6 +103,18 @@ will return the number of bytes required to copy out the entire environment.
The The
.Fa name .Fa name
is ignored. 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 .El
.Sh RETURN VALUES .Sh RETURN VALUES
The The
@ -142,6 +154,12 @@ for a
.Dv KENV_GET .Dv KENV_GET
or or
.Dv KENV_UNSET . .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 .It Bq Er EPERM
A user other than the superuser attempted to set or unset a kernel A user other than the superuser attempted to set or unset a kernel
environment variable. environment variable.

View File

@ -92,60 +92,103 @@ bool dynamic_kenv;
#define KENV_CHECK if (!dynamic_kenv) \ #define KENV_CHECK if (!dynamic_kenv) \
panic("%s: called before SI_SUB_KMEM", __func__) panic("%s: called before SI_SUB_KMEM", __func__)
int static int
sys_kenv(td, uap) kenv_dump(struct thread *td, char **envp, int what, char *value, int len)
struct thread *td;
struct kenv_args /* {
int what;
const char *name;
char *value;
int len;
} */ *uap;
{ {
char *name, *value, *buffer = NULL; char *buffer, *senv;
size_t len, done, needed, buflen; size_t done, needed, buflen;
int error, i; 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")); KASSERT(dynamic_kenv, ("kenv: dynamic_kenv = false"));
error = 0; error = 0;
if (uap->what == KENV_DUMP) {
switch (uap->what) {
case KENV_DUMP:
#ifdef MAC #ifdef MAC
error = mac_kenv_check_dump(td->td_ucred); error = mac_kenv_check_dump(td->td_ucred);
if (error) if (error)
return (error); return (error);
#endif #endif
done = needed = 0; return (kenv_dump(td, kenvp, uap->what, uap->value, uap->len));
buflen = uap->len; case KENV_DUMP_LOADER:
if (buflen > KENV_SIZE * (KENV_MNAMELEN + kenv_mvallen + 2)) case KENV_DUMP_STATIC:
buflen = KENV_SIZE * (KENV_MNAMELEN + #ifdef MAC
kenv_mvallen + 2); error = mac_kenv_check_dump(td->td_ucred);
if (uap->len > 0 && uap->value != NULL) if (error)
buffer = malloc(buflen, M_TEMP, M_WAITOK|M_ZERO); return (error);
mtx_lock(&kenv_lock); #endif
for (i = 0; kenvp[i] != NULL; i++) { #ifdef PRESERVE_EARLY_KENV
len = strlen(kenvp[i]) + 1; return (kenv_dump(td,
needed += len; uap->what == KENV_DUMP_LOADER ? (char **)md_envp :
len = min(len, buflen - done); (char **)kern_envp, uap->what, uap->value, uap->len));
/* #else
* If called with a NULL or insufficiently large return (ENOENT);
* buffer, just keep computing the required size. #endif
*/
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) {
case KENV_SET: case KENV_SET:
error = priv_check(td, PRIV_KENV_SET); error = priv_check(td, PRIV_KENV_SET);
if (error) if (error)

View File

@ -34,10 +34,12 @@
/* /*
* Constants for the kenv(2) syscall * Constants for the kenv(2) syscall
*/ */
#define KENV_GET 0 #define KENV_GET 0
#define KENV_SET 1 #define KENV_SET 1
#define KENV_UNSET 2 #define KENV_UNSET 2
#define KENV_DUMP 3 #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_MNAMELEN 128 /* Maximum name length (for the syscall) */
#define KENV_MVALLEN 128 /* Maximum value length (for the syscall) */ #define KENV_MVALLEN 128 /* Maximum value length (for the syscall) */