#include <sys/param.h>
#include <sys/eventhandler.h>
#include <sys/systm.h>
#include <sys/kenv.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/sysent.h>
#include <sys/sysproto.h>
#include <security/mac/mac_framework.h>
#include <vm/uma.h>
static char *_getenv_dynamic_locked(const char *name, int *idx);
static char *_getenv_dynamic(const char *name, int *idx);
static char *kenv_acquire(const char *name);
static void kenv_release(const char *buf);
static MALLOC_DEFINE(M_KENV, "kenv", "kernel environment");
#define KENV_SIZE 512
static uma_zone_t kenv_zone;
static int kenv_mvallen = KENV_MVALLEN;
char *kern_envp;
char *md_envp;
static int md_env_len;
static int md_env_pos;
static char *kernenv_next(char *);
char **kenvp;
struct mtx kenv_lock;
bool dynamic_kenv;
#define KENV_CHECK if (!dynamic_kenv) \
panic("%s: called before SI_SUB_KMEM", __func__)
static int
kenv_dump(struct thread *td, char **envp, int what, char *value, int len)
{
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);
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);
if (what == KENV_DUMP)
mtx_lock(&kenv_lock);
while (*envp != NULL) {
len = strlen(*envp) + 1;
needed += len;
len = min(len, buflen - done);
if (value != NULL && buffer != NULL && len > 0) {
bcopy(*envp, buffer + done, len);
done += len;
}
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;
switch (uap->what) {
case KENV_DUMP:
#ifdef MAC
error = mac_kenv_check_dump(td->td_ucred);
if (error)
return (error);
#endif
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)
return (error);
break;
case KENV_UNSET:
error = priv_check(td, PRIV_KENV_UNSET);
if (error)
return (error);
break;
}
name = malloc(KENV_MNAMELEN + 1, M_TEMP, M_WAITOK);
error = copyinstr(uap->name, name, KENV_MNAMELEN + 1, NULL);
if (error)
goto done;
switch (uap->what) {
case KENV_GET:
#ifdef MAC
error = mac_kenv_check_get(td->td_ucred, name);
if (error)
goto done;
#endif
value = kern_getenv(name);
if (value == NULL) {
error = ENOENT;
goto done;
}
len = strlen(value) + 1;
if (len > uap->len)
len = uap->len;
error = copyout(value, uap->value, len);
freeenv(value);
if (error)
goto done;
td->td_retval[0] = len;
break;
case KENV_SET:
len = uap->len;
if (len < 1) {
error = EINVAL;
goto done;
}
if (len > kenv_mvallen + 1)
len = kenv_mvallen + 1;
value = malloc(len, M_TEMP, M_WAITOK);
error = copyinstr(uap->value, value, len, NULL);
if (error) {
free(value, M_TEMP);
goto done;
}
#ifdef MAC
error = mac_kenv_check_set(td->td_ucred, name, value);
if (error == 0)
#endif
kern_setenv(name, value);
free(value, M_TEMP);
break;
case KENV_UNSET:
#ifdef MAC
error = mac_kenv_check_unset(td->td_ucred, name);
if (error)
goto done;
#endif
error = kern_unsetenv(name);
if (error)
error = ENOENT;
break;
default:
error = EINVAL;
break;
}
done:
free(name, M_TEMP);
return (error);
}
void
init_static_kenv(char *buf, size_t len)
{
KASSERT(!dynamic_kenv, ("kenv: dynamic_kenv already initialized"));
KASSERT(len == 0 || len >= 2,
("kenv: static env must be initialized or suitably sized"));
KASSERT(len == 0 || (*buf == '\0' && *(buf + 1) == '\0'),
("kenv: sized buffer must be initially empty"));
md_envp = NULL;
md_env_len = 0;
md_env_pos = 0;
kern_envp = static_env;
if (!getenv_is_true("loader_env.disabled")) {
md_envp = buf;
md_env_len = len;
md_env_pos = 0;
if (getenv_is_true("static_env.disabled")) {
kern_envp[0] = '\0';
kern_envp[1] = '\0';
}
}
if (getenv_is_true("static_hints.disabled")) {
static_hints[0] = '\0';
static_hints[1] = '\0';
}
}
#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, n;
int i;
if (init_env && *init_env != '\0') {
found = NULL;
i = *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",
cp);
goto sanitize;
}
eqpos = strchr(cp, '=');
if (eqpos == NULL) {
printf(
"WARNING: malformed static env value, ignoring %s\n",
cp);
goto sanitize;
}
*eqpos = 0;
found = _getenv_dynamic_locked(cp, NULL);
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);
}
sanitize:
#ifdef PRESERVE_EARLY_KENV
continue;
#else
explicit_bzero(cp, len - 1);
#endif
}
*curpos = i;
}
}
static void
init_dynamic_kenv(void *data __unused)
{
int dynamic_envpos;
int size;
TUNABLE_INT_FETCH("kenv_mvallen", &kenv_mvallen);
size = KENV_MNAMELEN + 1 + kenv_mvallen + 1;
kenv_zone = uma_zcreate("kenv", size, NULL, NULL, NULL, NULL,
UMA_ALIGN_PTR, 0);
kenvp = malloc((KENV_SIZE + 1) * sizeof(char *), M_KENV,
M_WAITOK | M_ZERO);
dynamic_envpos = 0;
init_dynamic_kenv_from(md_envp, &dynamic_envpos);
init_dynamic_kenv_from(kern_envp, &dynamic_envpos);
kenvp[dynamic_envpos] = NULL;
mtx_init(&kenv_lock, "kernel environment", NULL, MTX_DEF);
dynamic_kenv = true;
}
SYSINIT(kenv, SI_SUB_KMEM + 1, SI_ORDER_FIRST, init_dynamic_kenv, NULL);
void
freeenv(char *env)
{
if (dynamic_kenv && env != NULL) {
explicit_bzero(env, strlen(env));
uma_zfree(kenv_zone, env);
}
}
static char *
_getenv_dynamic_locked(const char *name, int *idx)
{
char *cp;
int len, i;
len = strlen(name);
for (cp = kenvp[0], i = 0; cp != NULL; cp = kenvp[++i]) {
if ((strncmp(cp, name, len) == 0) &&
(cp[len] == '=')) {
if (idx != NULL)
*idx = i;
return (cp + len + 1);
}
}
return (NULL);
}
static char *
_getenv_dynamic(const char *name, int *idx)
{
mtx_assert(&kenv_lock, MA_OWNED);
return (_getenv_dynamic_locked(name, idx));
}
static char *
_getenv_static_from(char *chkenv, const char *name)
{
char *cp, *ep;
int len;
for (cp = chkenv; cp != NULL; cp = kernenv_next(cp)) {
for (ep = cp; (*ep != '=') && (*ep != 0); ep++)
;
if (*ep != '=')
continue;
len = ep - cp;
ep++;
if (!strncmp(name, cp, len) && name[len] == 0)
return (ep);
}
return (NULL);
}
static char *
_getenv_static(const char *name)
{
char *val;
val = _getenv_static_from(md_envp, name);
if (val != NULL)
return (val);
val = _getenv_static_from(kern_envp, name);
if (val != NULL)
return (val);
return (NULL);
}
char *
kern_getenv(const char *name)
{
char *cp, *ret;
int len;
if (dynamic_kenv) {
len = KENV_MNAMELEN + 1 + kenv_mvallen + 1;
ret = uma_zalloc(kenv_zone, M_WAITOK | M_ZERO);
mtx_lock(&kenv_lock);
cp = _getenv_dynamic(name, NULL);
if (cp != NULL)
strlcpy(ret, cp, len);
mtx_unlock(&kenv_lock);
if (cp == NULL) {
uma_zfree(kenv_zone, ret);
ret = NULL;
}
} else
ret = _getenv_static(name);
return (ret);
}
int
testenv(const char *name)
{
char *cp;
cp = kenv_acquire(name);
kenv_release(cp);
if (cp != NULL)
return (1);
return (0);
}
static int
setenv_static(const char *name, const char *value)
{
int len;
if (md_env_pos >= md_env_len)
return (-1);
len = strlen(name) + strlen(value);
if (len + 3 < md_env_len - md_env_pos) {
len = sprintf(&md_envp[md_env_pos], "%s=%s", name, value);
md_env_pos += len+1;
md_envp[md_env_pos] = '\0';
return (0);
} else
return (-1);
}
int
kern_setenv(const char *name, const char *value)
{
char *buf, *cp, *oldenv;
int namelen, vallen, i;
if (!dynamic_kenv && md_env_len > 0)
return (setenv_static(name, value));
KENV_CHECK;
namelen = strlen(name) + 1;
if (namelen > KENV_MNAMELEN + 1)
return (-1);
vallen = strlen(value) + 1;
if (vallen > kenv_mvallen + 1)
return (-1);
buf = malloc(namelen + vallen, M_KENV, M_WAITOK);
sprintf(buf, "%s=%s", name, value);
mtx_lock(&kenv_lock);
cp = _getenv_dynamic(name, &i);
if (cp != NULL) {
oldenv = kenvp[i];
kenvp[i] = buf;
mtx_unlock(&kenv_lock);
free(oldenv, M_KENV);
} else {
for (i = 0; (cp = kenvp[i]) != NULL; i++)
;
if (i < 0 || i >= KENV_SIZE) {
free(buf, M_KENV);
mtx_unlock(&kenv_lock);
return (-1);
}
kenvp[i] = buf;
kenvp[i + 1] = NULL;
mtx_unlock(&kenv_lock);
}
EVENTHANDLER_INVOKE(setenv, name);
return (0);
}
int
kern_unsetenv(const char *name)
{
char *cp, *oldenv;
int i, j;
KENV_CHECK;
mtx_lock(&kenv_lock);
cp = _getenv_dynamic(name, &i);
if (cp != NULL) {
oldenv = kenvp[i];
for (j = i + 1; kenvp[j] != NULL; j++)
kenvp[i++] = kenvp[j];
kenvp[i] = NULL;
mtx_unlock(&kenv_lock);
zfree(oldenv, M_KENV);
EVENTHANDLER_INVOKE(unsetenv, name);
return (0);
}
mtx_unlock(&kenv_lock);
return (-1);
}
static char *
kenv_acquire(const char *name)
{
char *value;
if (dynamic_kenv) {
mtx_lock(&kenv_lock);
value = _getenv_dynamic(name, NULL);
if (value == NULL)
mtx_unlock(&kenv_lock);
return (value);
} else
return (_getenv_static(name));
}
static void
kenv_release(const char *buf)
{
if ((buf != NULL) && dynamic_kenv)
mtx_unlock(&kenv_lock);
}
int
getenv_string(const char *name, char *data, int size)
{
char *cp;
cp = kenv_acquire(name);
if (cp != NULL)
strlcpy(data, cp, size);
kenv_release(cp);
return (cp != NULL);
}
int
getenv_array(const char *name, void *pdata, int size, int *psize,
int type_size, bool allow_signed)
{
uint8_t shift;
int64_t value;
int64_t old;
const char *buf;
char *end;
const char *ptr;
int n;
int rc;
rc = 0;
buf = kenv_acquire(name);
if (buf == NULL)
goto error;
size /= type_size;
n = 0;
for (ptr = buf; *ptr != 0; ) {
value = strtoq(ptr, &end, 0);
if (value < 0 && !allow_signed)
goto error;
if (ptr == end)
goto error;
switch (*end) {
case 't':
case 'T':
shift = 40;
end++;
break;
case 'g':
case 'G':
shift = 30;
end++;
break;
case 'm':
case 'M':
shift = 20;
end++;
break;
case 'k':
case 'K':
shift = 10;
end++;
break;
case ' ':
case '\t':
case ',':
case 0:
shift = 0;
break;
default:
goto error;
}
while (*end == '\t' || *end == ',' || *end == ' ')
end++;
ptr = end;
old = value;
value <<= shift;
if ((value >> shift) != old)
goto error;
if (n >= size)
goto error;
switch (type_size) {
case 1:
if (allow_signed) {
if (value < SCHAR_MIN || value > SCHAR_MAX)
goto error;
} else {
if (value < 0 || value > UCHAR_MAX)
goto error;
}
((uint8_t *)pdata)[n] = (uint8_t)value;
break;
case 2:
if (allow_signed) {
if (value < SHRT_MIN || value > SHRT_MAX)
goto error;
} else {
if (value < 0 || value > USHRT_MAX)
goto error;
}
((uint16_t *)pdata)[n] = (uint16_t)value;
break;
case 4:
if (allow_signed) {
if (value < INT_MIN || value > INT_MAX)
goto error;
} else {
if (value > UINT_MAX)
goto error;
}
((uint32_t *)pdata)[n] = (uint32_t)value;
break;
case 8:
((uint64_t *)pdata)[n] = (uint64_t)value;
break;
default:
goto error;
}
n++;
}
*psize = n * type_size;
if (n != 0)
rc = 1;
error:
kenv_release(buf);
return (rc);
}
int
getenv_int(const char *name, int *data)
{
quad_t tmp;
int rval;
rval = getenv_quad(name, &tmp);
if (rval)
*data = (int) tmp;
return (rval);
}
int
getenv_uint(const char *name, unsigned int *data)
{
quad_t tmp;
int rval;
rval = getenv_quad(name, &tmp);
if (rval)
*data = (unsigned int) tmp;
return (rval);
}
int
getenv_int64(const char *name, int64_t *data)
{
quad_t tmp;
int64_t rval;
rval = getenv_quad(name, &tmp);
if (rval)
*data = (int64_t) tmp;
return (rval);
}
int
getenv_uint64(const char *name, uint64_t *data)
{
quad_t tmp;
uint64_t rval;
rval = getenv_quad(name, &tmp);
if (rval)
*data = (uint64_t) tmp;
return (rval);
}
int
getenv_long(const char *name, long *data)
{
quad_t tmp;
int rval;
rval = getenv_quad(name, &tmp);
if (rval)
*data = (long) tmp;
return (rval);
}
int
getenv_ulong(const char *name, unsigned long *data)
{
quad_t tmp;
int rval;
rval = getenv_quad(name, &tmp);
if (rval)
*data = (unsigned long) tmp;
return (rval);
}
int
getenv_quad(const char *name, quad_t *data)
{
const char *value;
char suffix, *vtp;
quad_t iv;
value = kenv_acquire(name);
if (value == NULL) {
goto error;
}
iv = strtoq(value, &vtp, 0);
if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) {
goto error;
}
suffix = vtp[0];
kenv_release(value);
switch (suffix) {
case 't': case 'T':
iv *= 1024;
case 'g': case 'G':
iv *= 1024;
case 'm': case 'M':
iv *= 1024;
case 'k': case 'K':
iv *= 1024;
case '\0':
break;
default:
return (0);
}
*data = iv;
return (1);
error:
kenv_release(value);
return (0);
}
int
getenv_bool(const char *name, bool *data)
{
char *val;
int ret = 0;
if (name == NULL)
return (0);
val = kern_getenv(name);
if (val == NULL)
return (0);
if ((strcmp(val, "1") == 0) || (strcasecmp(val, "true") == 0)) {
*data = true;
ret = 1;
} else if ((strcmp(val, "0") == 0) || (strcasecmp(val, "false") == 0)) {
*data = false;
ret = 1;
} else {
printf("Environment variable %s has non-boolean value \"%s\"\n",
name, val);
}
freeenv(val);
return (ret);
}
bool
getenv_is_true(const char *name)
{
bool val;
if (getenv_bool(name, &val) != 0)
return (val);
return (false);
}
bool
getenv_is_false(const char *name)
{
bool val;
if (getenv_bool(name, &val) != 0)
return (!val);
return (false);
}
static char *
kernenv_next(char *cp)
{
if (cp != NULL) {
while (*cp != 0)
cp++;
cp++;
if (*cp == 0)
cp = NULL;
}
return (cp);
}
void
tunable_int_init(const void *data)
{
const struct tunable_int *d = data;
TUNABLE_INT_FETCH(d->path, d->var);
}
void
tunable_long_init(const void *data)
{
const struct tunable_long *d = data;
TUNABLE_LONG_FETCH(d->path, d->var);
}
void
tunable_ulong_init(const void *data)
{
const struct tunable_ulong *d = data;
TUNABLE_ULONG_FETCH(d->path, d->var);
}
void
tunable_int64_init(const void *data)
{
const struct tunable_int64 *d = data;
TUNABLE_INT64_FETCH(d->path, d->var);
}
void
tunable_uint64_init(const void *data)
{
const struct tunable_uint64 *d = data;
TUNABLE_UINT64_FETCH(d->path, d->var);
}
void
tunable_quad_init(const void *data)
{
const struct tunable_quad *d = data;
TUNABLE_QUAD_FETCH(d->path, d->var);
}
void
tunable_bool_init(const void *data)
{
const struct tunable_bool *d = data;
TUNABLE_BOOL_FETCH(d->path, d->var);
}
void
tunable_str_init(const void *data)
{
const struct tunable_str *d = data;
TUNABLE_STR_FETCH(d->path, d->var, d->size);
}