Skip to content

Commit

Permalink
Removed the transfer of most environment variables, such as USER, HOME,
Browse files Browse the repository at this point in the history
and PATH from the original user to the target user. This could cause
files in the wrogn path or home directory to be read (or written to),
which resulted in potential security problems.

This has been changed so that only DISPLAY and TERM are passed to the
new environment. This is fine for running command line programs. When
GUI programs need to be run, "keepenv" can be added to the user's
doas.conf entry. This results in variables like HOME being copied
to the target user, allowing GUI programs to run.

Many thanks to Sander Bos for reporting this issue and explaining
how it can be exploited.

This commit also adds the ability to pass a customized PATH to
target users. The new PATH can be set at compile time in the
Makefile. The default path is provided in the Makefile and commented
out.
  • Loading branch information
Jesse Smith committed Aug 3, 2019
1 parent a8cd6a4 commit 8e9c2bd
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 29 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ YACC?=yacc
BIN=doas
PREFIX?=/usr/local
OBJECTS=doas.o env.o execvpe.o reallocarray.o y.tab.o
CFLAGS+=-DUSE_PAM -DDOAS_CONF=\"${PREFIX}/etc/doas.conf\"
# Can set GLOBAL_PATH here to set PATH for target user.
# TARGETPATH=-DGLOBAL_PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:\"
CFLAGS+=-DUSE_PAM -DDOAS_CONF=\"${PREFIX}/etc/doas.conf\" $(TARGETPATH)
LDFLAGS+=-lpam
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
Expand Down Expand Up @@ -31,7 +33,7 @@ y.tab.o: parse.y
$(YACC) parse.y
$(CC) $(CFLAGS) -c y.tab.c

install: all
install: $(BIN)
mkdir -p $(PREFIX)/bin
cp $(BIN) $(PREFIX)/bin/
chmod 4755 $(PREFIX)/bin/$(BIN)
Expand Down
20 changes: 10 additions & 10 deletions doas.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ main(int argc, char **argv)
const char *cmd;
char cmdline[LINE_MAX];
char myname[_PW_NAME_LEN + 1];
struct passwd *pw;
struct passwd *original_pw, *target_pw;
struct rule *rule;
uid_t uid;
uid_t target = 0;
Expand Down Expand Up @@ -376,10 +376,10 @@ main(int argc, char **argv)
} else if ((!sflag && !argc) || (sflag && argc))
usage();

pw = getpwuid(uid);
if (!pw)
original_pw = getpwuid(uid);
if (! original_pw)
err(1, "getpwuid failed");
if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname))
if (strlcpy(myname, original_pw->pw_name, sizeof(myname)) >= sizeof(myname))
errx(1, "pw_name too long");

ngroups = getgroups(NGROUPS_MAX, groups);
Expand All @@ -390,7 +390,7 @@ main(int argc, char **argv)
if (sflag) {
sh = getenv("SHELL");
if (sh == NULL || *sh == '\0') {
shargv[0] = strdup(pw->pw_shell);
shargv[0] = strdup(original_pw->pw_shell);
if (shargv[0] == NULL)
err(1, NULL);
} else
Expand Down Expand Up @@ -540,12 +540,12 @@ main(int argc, char **argv)
if (pledge("stdio rpath getpw exec id", NULL) == -1)
err(1, "pledge");
*/
pw = getpwuid(target);
if (!pw)
target_pw = getpwuid(target);
if (! target_pw)
errx(1, "no passwd entry for target");

#if defined(HAVE_LOGIN_CAP_H)
if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
if (setusercontext(NULL, target_pw, target, LOGIN_SETGROUP |
LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
LOGIN_SETUSER) != 0)
errx(1, "failed to set user context for target");
Expand Down Expand Up @@ -574,9 +574,9 @@ main(int argc, char **argv)
#endif

syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command %s as %s from %s",
myname, cmdline, pw->pw_name, cwd);
myname, cmdline, target_pw->pw_name, cwd);

envp = prepenv(rule);
envp = prepenv(rule, original_pw, target_pw);

if (rule->cmd) {
if (setenv("PATH", safepath, 1) == -1)
Expand Down
21 changes: 13 additions & 8 deletions doas.conf.5
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,20 @@ again for some time. Works on OpenBSD only, persist is not available on Linux or
.It Ic keepenv
The user's environment is maintained.
The default is to reset the environment, except for the variables
.Ev DISPLAY ,
.Ev HOME ,
.Ev LOGNAME ,
.Ev MAIL ,
.Ev PATH ,
.Ev TERM ,
.Ev USER
.Ev DISPLAY
and
.Ev USERNAME .
.Ev TERM .

Note: In order to be able to run most desktop (GUI) applications, the user needs to
have the keepenv keyword specified. If keepenv is not specified then key elements, like
the user's $HOME variable, will be reset and cause the GUI application to crash.
Users who only need to run command line applications can usually get away without
keepenv. When in doubt, try to avoid using keepenv as it is less secure to have
environment variables passed to privileged users.

Note: The target user's PATH variable can be set at compile time by adjusting the
GLOBAL_PATH variable in doas's Makefile. By default, the target user's path will
be set to "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:"
.It Ic setenv { Oo Ar variable ... Oc Oo Ar variable=value ... Oc Ic }
In addition to the variables mentioned above, keep the space-separated
specified variables.
Expand Down
7 changes: 6 additions & 1 deletion doas.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ extern struct rule **rules;
extern int nrules;
extern int parse_errors;

char **prepenv(struct rule *);
struct passwd;
char **prepenv(struct rule *, struct passwd *original, struct passwd *target);

#ifndef GLOBAL_PATH
#define GLOBAL_PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
#endif

#define PERMIT 1
#define DENY 2
Expand Down
34 changes: 26 additions & 8 deletions env.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <err.h>
#include <unistd.h>
#include <errno.h>
Expand Down Expand Up @@ -77,8 +78,19 @@ freenode(struct envnode *node)
free(node);
}

static void
addnode(struct env *env, const char *key, const char *value)
{
struct envnode *node;

node = createnode(key, value);
RB_INSERT(envtree, &env->root, node);
env->count++;
}


static struct env *
createenv(struct rule *rule)
createenv(struct rule *rule, struct passwd *original, struct passwd *target)
{
struct env *env;
u_int i;
Expand All @@ -89,6 +101,13 @@ createenv(struct rule *rule)
RB_INIT(&env->root);
env->count = 0;

addnode(env, "DOAS_USER", original->pw_name);
addnode(env, "HOME", target->pw_dir);
addnode(env, "LOGNAME", target->pw_name);
addnode(env, "PATH", GLOBAL_PATH);
addnode(env, "SHELL", target->pw_shell);
addnode(env, "USER", target->pw_name);

if (rule->options & KEEPENV) {
#ifndef linux
extern const char **environ;
Expand Down Expand Up @@ -197,16 +216,15 @@ fillenv(struct env *env, const char **envlist)
}

char **
prepenv(struct rule *rule)
prepenv(struct rule *rule, struct passwd *original, struct passwd *target)
{
static const char *safeset[] = {
"DISPLAY", "HOME", "LOGNAME", "MAIL",
"PATH", "TERM", "USER", "USERNAME",
NULL
};
static const char *safeset[] = {
"DISPLAY", "TERM", NULL
};

struct env *env;

env = createenv(rule);
env = createenv(rule, original, target);

/* if we started with blank, fill some defaults then apply rules */
if (!(rule->options & KEEPENV))
Expand Down

0 comments on commit 8e9c2bd

Please sign in to comment.