On some platforms (seemingly Linux and macOS) it is possible for

repeated calls to getpwuid() can over-write the original struct passwd
strucuture. This can lead to the original user's environment data
being overwritten by the target user's, even when "keepenv" is
specified in the doas.conf file.

We now do a deep copy of the original and target users' struct passwd
information to avoid over-writting the original on platforms where libc
uses a static area for all calls.
This commit is contained in:
Jesse Smith 2019-12-27 22:10:57 -04:00
parent f88bb2e8c4
commit a006f46031
3 changed files with 37 additions and 4 deletions

9
doas.c
View file

@ -279,7 +279,7 @@ main(int argc, char **argv)
const char *cmd;
char cmdline[LINE_MAX];
char myname[_PW_NAME_LEN + 1];
struct passwd *original_pw, *target_pw;
struct passwd *original_pw, *target_pw, *temp_pw;
struct rule *rule;
uid_t uid;
uid_t target = 0;
@ -341,7 +341,8 @@ main(int argc, char **argv)
} else if ((!sflag && !argc) || (sflag && argc))
usage();
original_pw = getpwuid(uid);
temp_pw = getpwuid(uid);
original_pw = copyenvpw(temp_pw);
if (! original_pw)
err(1, "getpwuid failed");
if (strlcpy(myname, original_pw->pw_name, sizeof(myname)) >= sizeof(myname))
@ -505,10 +506,12 @@ main(int argc, char **argv)
if (pledge("stdio rpath getpw exec id", NULL) == -1)
err(1, "pledge");
*/
target_pw = getpwuid(target);
temp_pw = getpwuid(target);
target_pw = copyenvpw(temp_pw);
if (! target_pw)
errx(1, "no passwd entry for target");
#if defined(HAVE_LOGIN_CAP_H)
if (setusercontext(NULL, target_pw, target, LOGIN_SETGROUP |
LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |

1
doas.h
View file

@ -31,6 +31,7 @@ extern int parse_errors;
struct passwd;
char **prepenv(struct rule *, struct passwd *original, struct passwd *target);
struct passwd *copyenvpw(struct passwd *original);
#ifndef GLOBAL_PATH
#define GLOBAL_PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

31
env.c
View file

@ -83,6 +83,32 @@ addnode(struct env *env, const char *key, const char *value)
}
/* Copy an original (possibly static in memory) password structure.
Make a deep copy of it so that our original andtarget do not overlap
on future calls.
*/
struct passwd *
copyenvpw(struct passwd *my_static)
{
struct passwd *new_pw;
if (! my_static)
return NULL;
new_pw = (struct passwd *) calloc(1, sizeof(struct passwd));
if (! new_pw)
return NULL;
new_pw->pw_name = strdup(my_static->pw_name);
new_pw->pw_passwd = strdup(my_static->pw_passwd);
new_pw->pw_uid = my_static->pw_uid;
new_pw->pw_gid = my_static->pw_gid;
new_pw->pw_gecos = strdup(my_static->pw_gecos);
new_pw->pw_dir = strdup(my_static->pw_dir);
new_pw->pw_shell = strdup(my_static->pw_shell);
return new_pw;
}
static struct env *
createenv(struct rule *rule, struct passwd *original, struct passwd *target)
{
@ -96,7 +122,10 @@ createenv(struct rule *rule, struct passwd *original, struct passwd *target)
env->count = 0;
addnode(env, "DOAS_USER", original->pw_name);
addnode(env, "HOME", target->pw_dir);
if (rule->options & KEEPENV)
addnode(env, "HOME", original->pw_dir);
else
addnode(env, "HOME", target->pw_dir);
addnode(env, "LOGNAME", target->pw_name);
addnode(env, "PATH", GLOBAL_PATH);
addnode(env, "SHELL", target->pw_shell);