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
.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.

View File

@ -29,6 +29,7 @@ __FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/sysctl.h>
#include <err.h>
#include <errno.h>
#include <kenv.h>
#include <stdio.h>
#include <stdlib.h>
@ -36,14 +37,16 @@ __FBSDID("$FreeBSD$");
#include <unistd.h>
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);

View File

@ -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.

View File

@ -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)

View File

@ -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) */