Fix potential TOCTOU when creating time stamp directory and file.

This commit is contained in:
Todd C. Miller
2022-09-21 19:08:09 -06:00
parent 8c482bfeb2
commit cbd52e705c
5 changed files with 223 additions and 118 deletions

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 2013-2018 Todd C. Miller <Todd.Miller@sudo.ws> * Copyright (c) 2013-2022 Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@@ -247,6 +247,8 @@ sudo_dso_public const char *sudo_logpri2str_v1(int num);
/* mkdir_parents.c */ /* mkdir_parents.c */
sudo_dso_public bool sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet); sudo_dso_public bool sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet);
#define sudo_mkdir_parents(_a, _b, _c, _d, _e) sudo_mkdir_parents_v1((_a), (_b), (_c), (_d), (_e)) #define sudo_mkdir_parents(_a, _b, _c, _d, _e) sudo_mkdir_parents_v1((_a), (_b), (_c), (_d), (_e))
sudo_dso_public int sudo_open_parent_dir_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet);
#define sudo_open_parent_dir(_a, _b, _c, _d, _e) sudo_open_parent_dir_v1((_a), (_b), (_c), (_d), (_e))
/* mmap_alloc.c */ /* mmap_alloc.c */
sudo_dso_public void *sudo_mmap_alloc_v1(size_t size) sudo_malloclike; sudo_dso_public void *sudo_mmap_alloc_v1(size_t size) sudo_malloclike;
@@ -295,6 +297,10 @@ sudo_dso_public int sudo_secure_dir_v1(const char *path, uid_t uid, gid_t gid, s
#define sudo_secure_dir(_a, _b, _c, _d) sudo_secure_dir_v1((_a), (_b), (_c), (_d)) #define sudo_secure_dir(_a, _b, _c, _d) sudo_secure_dir_v1((_a), (_b), (_c), (_d))
sudo_dso_public int sudo_secure_file_v1(const char *path, uid_t uid, gid_t gid, struct stat *sbp); sudo_dso_public int sudo_secure_file_v1(const char *path, uid_t uid, gid_t gid, struct stat *sbp);
#define sudo_secure_file(_a, _b, _c, _d) sudo_secure_file_v1((_a), (_b), (_c), (_d)) #define sudo_secure_file(_a, _b, _c, _d) sudo_secure_file_v1((_a), (_b), (_c), (_d))
sudo_dso_public int sudo_secure_open_file_v1(const char *path, uid_t uid, gid_t gid, int *error);
#define sudo_secure_open_file(_a, _b, _c, _d) sudo_secure_open_file_v1((_a), (_b), (_c), (_d))
sudo_dso_public int sudo_secure_open_dir_v1(const char *path, uid_t uid, gid_t gid, int *error);
#define sudo_secure_open_dir(_a, _b, _c, _d) sudo_secure_open_dir_v1((_a), (_b), (_c), (_d))
/* setgroups.c */ /* setgroups.c */
sudo_dso_public int sudo_setgroups_v1(int ngids, const GETGROUPS_T *gids); sudo_dso_public int sudo_setgroups_v1(int ngids, const GETGROUPS_T *gids);

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws> * Copyright (c) 2009-2022 Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@@ -76,16 +76,17 @@ is_dir(int dfd, const char *name, int namelen, bool quiet)
} }
/* /*
* Create any parent directories needed by path (but not path itself). * Create any parent directories needed by path (but not path itself)
* and return an open fd for the parent directory or -1 on error.
*/ */
bool int
sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet) sudo_open_parent_dir_v1(const char *path, uid_t uid, gid_t gid, mode_t mode,
bool quiet)
{ {
const char *cp, *ep, *pathend; const char *cp, *ep, *pathend;
char name[PATH_MAX]; char name[PATH_MAX];
bool ret = false;
int parentfd; int parentfd;
debug_decl(sudo_mkdir_parents, SUDO_DEBUG_UTIL); debug_decl(sudo_open_parent_dir, SUDO_DEBUG_UTIL);
/* Starting parent dir is either root or cwd. */ /* Starting parent dir is either root or cwd. */
cp = path; cp = path;
@@ -100,7 +101,7 @@ sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool
if (parentfd == -1) { if (parentfd == -1) {
if (!quiet) if (!quiet)
sudo_warn(U_("unable to open %s"), *path == '/' ? "/" : "."); sudo_warn(U_("unable to open %s"), *path == '/' ? "/" : ".");
debug_return_bool(false); debug_return_int(-1);
} }
/* Iterate over path components, skipping the last one. */ /* Iterate over path components, skipping the last one. */
@@ -129,7 +130,7 @@ reopen:
sudo_warn(U_("unable to open %.*s"), sudo_warn(U_("unable to open %.*s"),
(int)(ep - path), path); (int)(ep - path), path);
} }
goto done; goto bad;
} }
if (mkdirat(parentfd, name, mode) == 0) { if (mkdirat(parentfd, name, mode) == 0) {
dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0); dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0);
@@ -138,12 +139,12 @@ reopen:
sudo_warn(U_("unable to open %.*s"), sudo_warn(U_("unable to open %.*s"),
(int)(ep - path), path); (int)(ep - path), path);
} }
goto done; goto bad;
} }
/* Make sure the path we created is still a directory. */ /* Make sure the path we created is still a directory. */
if (!is_dir(dfd, path, ep - path, quiet)) { if (!is_dir(dfd, path, ep - path, quiet)) {
close(dfd); close(dfd);
goto done; goto bad;
} }
if (uid != (uid_t)-1 && gid != (gid_t)-1) { if (uid != (uid_t)-1 && gid != (gid_t)-1) {
if (fchown(dfd, uid, gid) != 0) { if (fchown(dfd, uid, gid) != 0) {
@@ -159,22 +160,39 @@ reopen:
sudo_warn(U_("unable to mkdir %.*s"), sudo_warn(U_("unable to mkdir %.*s"),
(int)(ep - path), path); (int)(ep - path), path);
} }
goto done; goto bad;
} }
} else { } else {
/* Already exists, make sure it is a directory. */ /* Already exists, make sure it is a directory. */
if (!is_dir(dfd, path, ep - path, quiet)) { if (!is_dir(dfd, path, ep - path, quiet)) {
close(dfd); close(dfd);
goto done; goto bad;
} }
} }
close(parentfd); close(parentfd);
parentfd = dfd; parentfd = dfd;
} }
ret = true;
done: debug_return_int(parentfd);
bad:
if (parentfd != -1) if (parentfd != -1)
close(parentfd); close(parentfd);
debug_return_bool(ret); debug_return_int(-1);
}
/*
* Create any parent directories needed by path (but not path itself).
*/
bool
sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode,
bool quiet)
{
int fd;
debug_decl(sudo_mkdir_parents, SUDO_DEBUG_UTIL);
fd = sudo_open_parent_dir(path, uid, gid, mode, quiet);
if (fd == -1)
debug_return_bool(false);
close(fd);
debug_return_bool(true);
} }

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 2012, 2014-2016 Todd C. Miller <Todd.Miller@sudo.ws> * Copyright (c) 2012, 2014-2022 Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@@ -24,7 +24,9 @@
#include <config.h> #include <config.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include "sudo_compat.h" #include "sudo_compat.h"
#include "sudo_util.h" #include "sudo_util.h"
@@ -34,29 +36,41 @@
* Verify that path is the right type and not writable by other users. * Verify that path is the right type and not writable by other users.
*/ */
static int static int
sudo_secure_path(const char *path, unsigned int type, uid_t uid, gid_t gid, struct stat *sb) sudo_check_secure(struct stat *sb, unsigned int type, uid_t uid, gid_t gid)
{
int ret = SUDO_PATH_SECURE;
debug_decl(sudo_check_secure, SUDO_DEBUG_UTIL);
if ((sb->st_mode & S_IFMT) != type) {
ret = SUDO_PATH_BAD_TYPE;
} else if (uid != (uid_t)-1 && sb->st_uid != uid) {
ret = SUDO_PATH_WRONG_OWNER;
} else if (sb->st_mode & S_IWOTH) {
ret = SUDO_PATH_WORLD_WRITABLE;
} else if (ISSET(sb->st_mode, S_IWGRP) &&
(gid == (gid_t)-1 || sb->st_gid != gid)) {
ret = SUDO_PATH_GROUP_WRITABLE;
}
debug_return_int(ret);
}
/*
* Verify that path is the right type and not writable by other users.
*/
static int
sudo_secure_path(const char *path, unsigned int type, uid_t uid, gid_t gid,
struct stat *sb)
{ {
struct stat stat_buf;
int ret = SUDO_PATH_MISSING; int ret = SUDO_PATH_MISSING;
struct stat stat_buf;
debug_decl(sudo_secure_path, SUDO_DEBUG_UTIL); debug_decl(sudo_secure_path, SUDO_DEBUG_UTIL);
if (sb == NULL) if (sb == NULL)
sb = &stat_buf; sb = &stat_buf;
if (path != NULL && stat(path, sb) == 0) { if (path != NULL && stat(path, sb) == 0)
if ((sb->st_mode & S_IFMT) != type) { ret = sudo_check_secure(sb, type, uid, gid);
ret = SUDO_PATH_BAD_TYPE;
} else if (uid != (uid_t)-1 && sb->st_uid != uid) {
ret = SUDO_PATH_WRONG_OWNER;
} else if (sb->st_mode & S_IWOTH) {
ret = SUDO_PATH_WORLD_WRITABLE;
} else if (ISSET(sb->st_mode, S_IWGRP) &&
(gid == (gid_t)-1 || sb->st_gid != gid)) {
ret = SUDO_PATH_GROUP_WRITABLE;
} else {
ret = SUDO_PATH_SECURE;
}
}
debug_return_int(ret); debug_return_int(ret);
} }
@@ -65,16 +79,60 @@ sudo_secure_path(const char *path, unsigned int type, uid_t uid, gid_t gid, stru
* Verify that path is a regular file and not writable by other users. * Verify that path is a regular file and not writable by other users.
*/ */
int int
sudo_secure_file_v1(const char *path, uid_t uid, gid_t gid, struct stat *st) sudo_secure_file_v1(const char *path, uid_t uid, gid_t gid, struct stat *sb)
{ {
return sudo_secure_path(path, S_IFREG, uid, gid, st); return sudo_secure_path(path, S_IFREG, uid, gid, sb);
} }
/* /*
* Verify that path is a directory and not writable by other users. * Verify that path is a directory and not writable by other users.
*/ */
int int
sudo_secure_dir_v1(const char *path, uid_t uid, gid_t gid, struct stat *st) sudo_secure_dir_v1(const char *path, uid_t uid, gid_t gid, struct stat *sb)
{ {
return sudo_secure_path(path, S_IFDIR, uid, gid, st); return sudo_secure_path(path, S_IFDIR, uid, gid, sb);
}
/*
* Open path read-only as long as it is not writable by other users.
* Returns an open file descriptor on success, else -1.
* Sets error to SUDO_PATH_SECURE on success, and a value < 0 on failure.
*/
static int
sudo_secure_open(const char *path, int type, uid_t uid, gid_t gid, int *error)
{
struct stat sb;
int fd;
debug_decl(sudo_secure_open, SUDO_DEBUG_UTIL);
fd = open(path, O_RDONLY|O_NONBLOCK);
if (fd == -1 || fstat(fd, &sb) != 0) {
if (fd != -1)
close(fd);
*error = SUDO_PATH_MISSING;
debug_return_int(-1);
}
*error = sudo_check_secure(&sb, type, uid, gid);
if (*error == SUDO_PATH_SECURE) {
(void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
} else {
/* Not secure, caller can check error flag. */
close(fd);
fd = -1;
}
debug_return_int(fd);
}
int
sudo_secure_open_file_v1(const char *path, uid_t uid, gid_t gid, int *error)
{
return sudo_secure_open(path, S_IFREG, uid, gid, error);
}
int
sudo_secure_open_dir_v1(const char *path, uid_t uid, gid_t gid, int *error)
{
return sudo_secure_open(path, S_IFDIR, uid, gid, error);
} }

View File

@@ -116,6 +116,7 @@ sudo_mmap_free_v1
sudo_mmap_protect_v1 sudo_mmap_protect_v1
sudo_mmap_strdup_v1 sudo_mmap_strdup_v1
sudo_new_key_val_v1 sudo_new_key_val_v1
sudo_open_parent_dir_v1
sudo_parse_gids_v1 sudo_parse_gids_v1
sudo_parseln_v1 sudo_parseln_v1
sudo_parseln_v2 sudo_parseln_v2
@@ -127,6 +128,8 @@ sudo_rcstr_dup
sudo_regex_compile_v1 sudo_regex_compile_v1
sudo_secure_dir_v1 sudo_secure_dir_v1
sudo_secure_file_v1 sudo_secure_file_v1
sudo_secure_open_dir_v1
sudo_secure_open_file_v1
sudo_setgroups_v1 sudo_setgroups_v1
sudo_str2logfac_v1 sudo_str2logfac_v1
sudo_str2logpri_v1 sudo_str2logpri_v1

View File

@@ -181,87 +181,105 @@ ts_find_record(int fd, struct timestamp_entry *key, struct timestamp_entry *entr
/* /*
* Create a directory and any missing parent directories with the * Create a directory and any missing parent directories with the
* specified mode. * specified mode.
* Returns true on success. * Returns an fd usable with the *at() functions on success.
* Returns false on failure and displays a warning to stderr. * Returns -1 on failure, setting errno.
*/ */
static bool static int
ts_mkdirs(const char *path, uid_t owner, gid_t group, mode_t mode, ts_mkdirs(const char *path, uid_t owner, gid_t group, mode_t mode,
mode_t parent_mode, bool quiet) mode_t parent_mode, bool quiet)
{ {
bool ret; int parentfd, fd = -1;
const char *base;
mode_t omask; mode_t omask;
debug_decl(ts_mkdirs, SUDOERS_DEBUG_AUTH); debug_decl(ts_mkdirs, SUDOERS_DEBUG_AUTH);
/* Child directory we will create. */
base = sudo_basename(path);
/* umask must not be more restrictive than the file modes. */ /* umask must not be more restrictive than the file modes. */
omask = umask(ACCESSPERMS & ~(mode|parent_mode)); omask = umask(ACCESSPERMS & ~(mode|parent_mode));
ret = sudo_mkdir_parents(path, owner, group, parent_mode, quiet); parentfd = sudo_open_parent_dir(path, owner, group, parent_mode, quiet);
if (ret) { if (parentfd != -1) {
/* Create final path component. */ /* Create final path component. */
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"mkdir %s, mode 0%o, uid %d, gid %d", path, (unsigned int)mode, "mkdir %s, mode 0%o, uid %d, gid %d", path, (unsigned int)mode,
(int)owner, (int)group); (int)owner, (int)group);
if (mkdir(path, mode) != 0 && errno != EEXIST) { if (mkdirat(parentfd, base, mode) != 0 && errno != EEXIST) {
if (!quiet) if (!quiet)
sudo_warn(U_("unable to mkdir %s"), path); sudo_warn(U_("unable to mkdir %s"), path);
ret = false;
} else { } else {
if (chown(path, owner, group) != 0) { fd = openat(parentfd, base, O_RDONLY|O_NONBLOCK, 0);
if (fd == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to open %s", __func__, path);
} else if (fchown(fd, owner, group) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to chown %d:%d %s", __func__, "%s: unable to chown %d:%d %s", __func__,
(int)owner, (int)group, path); (int)owner, (int)group, path);
} }
} }
close(parentfd);
} }
umask(omask); umask(omask);
debug_return_bool(ret); debug_return_int(fd);
} }
/* /*
* Check that path is owned by timestamp_uid and not writable by * Check that path is owned by timestamp_uid and not writable by
* group or other. If path is missing and make_it is true, create * group or other. If path is missing and make_it is true, create
* the directory and its parent dirs. * the directory and its parent dirs.
* Returns true on success or false on failure, setting errno. *
* Returns an fd usable with the *at() functions on success.
* Returns -1 on failure, setting errno.
*/ */
static bool static int
ts_secure_dir(char *path, bool make_it, bool quiet) ts_secure_opendir(const char *path, bool make_it, bool quiet)
{ {
struct stat sb; int error, fd = -1;
bool ret = false; debug_decl(ts_secure_opendir, SUDOERS_DEBUG_AUTH);
debug_decl(ts_secure_dir, SUDOERS_DEBUG_AUTH);
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "checking %s", path); sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "checking %s", path);
switch (sudo_secure_dir(path, timestamp_uid, -1, &sb)) { fd = sudo_secure_open_dir(path, timestamp_uid, timestamp_gid, &error);
case SUDO_PATH_SECURE: if (fd == -1) {
ret = true; switch (error) {
break; case SUDO_PATH_MISSING:
case SUDO_PATH_MISSING: if (make_it) {
if (make_it && ts_mkdirs(path, timestamp_uid, timestamp_gid, S_IRWXU, fd = ts_mkdirs(path, timestamp_uid, timestamp_gid, S_IRWXU,
S_IRWXU|S_IXGRP|S_IXOTH, quiet)) { S_IRWXU|S_IXGRP|S_IXOTH, quiet);
ret = true; if (fd != -1)
break;
}
if (!quiet)
sudo_warn("%s", path);
break;
case SUDO_PATH_BAD_TYPE:
errno = ENOTDIR;
if (!quiet)
sudo_warn("%s", path);
break;
case SUDO_PATH_WRONG_OWNER:
if (!quiet) {
sudo_warnx(U_("%s: wrong owner, should be uid %u\n"),
path, (unsigned int)timestamp_uid);
}
errno = EACCES;
break;
case SUDO_PATH_GROUP_WRITABLE:
if (!quiet)
sudo_warnx(U_("%s is group writable"), path);
errno = EACCES;
break;
default:
if (!quiet) {
sudo_warnx("%s: internal error, unexpected error %d",
__func__, error);
errno = EINVAL;
}
break; break;
} }
errno = ENOENT;
break;
case SUDO_PATH_BAD_TYPE:
errno = ENOTDIR;
if (!quiet)
sudo_warn("%s", path);
break;
case SUDO_PATH_WRONG_OWNER:
if (!quiet) {
sudo_warnx(U_("%s is owned by uid %u, should be %u"),
path, (unsigned int) sb.st_uid,
(unsigned int) timestamp_uid);
}
errno = EACCES;
break;
case SUDO_PATH_GROUP_WRITABLE:
if (!quiet)
sudo_warnx(U_("%s is group writable"), path);
errno = EACCES;
break;
} }
debug_return_bool(ret);
debug_return_int(fd);
} }
/* /*
@@ -271,15 +289,15 @@ ts_secure_dir(char *path, bool make_it, bool quiet)
* Returns TIMESTAMP_OPEN_ERROR or TIMESTAMP_PERM_ERROR on error. * Returns TIMESTAMP_OPEN_ERROR or TIMESTAMP_PERM_ERROR on error.
*/ */
static int static int
ts_open(const char *path, int flags) ts_openat(int dfd, const char *path, int flags)
{ {
bool uid_changed = false; bool uid_changed = false;
int fd; int fd;
debug_decl(ts_open, SUDOERS_DEBUG_AUTH); debug_decl(ts_openat, SUDOERS_DEBUG_AUTH);
if (timestamp_uid != 0) if (timestamp_uid != 0)
uid_changed = set_perms(PERM_TIMESTAMP); uid_changed = set_perms(PERM_TIMESTAMP);
fd = open(path, flags, S_IRUSR|S_IWUSR); fd = openat(dfd, path, flags, S_IRUSR|S_IWUSR);
if (uid_changed && !restore_perms()) { if (uid_changed && !restore_perms()) {
/* Unable to restore permissions, should not happen. */ /* Unable to restore permissions, should not happen. */
if (fd != -1) { if (fd != -1) {
@@ -406,7 +424,7 @@ timestamp_open(const char *user, pid_t sid)
{ {
struct ts_cookie *cookie; struct ts_cookie *cookie;
char *fname = NULL; char *fname = NULL;
int tries, fd = -1; int tries, dfd = -1, fd = -1;
debug_decl(timestamp_open, SUDOERS_DEBUG_AUTH); debug_decl(timestamp_open, SUDOERS_DEBUG_AUTH);
/* Zero timeout means don't use the time stamp file. */ /* Zero timeout means don't use the time stamp file. */
@@ -416,7 +434,8 @@ timestamp_open(const char *user, pid_t sid)
} }
/* Check the validity of timestamp dir and create if missing. */ /* Check the validity of timestamp dir and create if missing. */
if (!ts_secure_dir(def_timestampdir, true, false)) dfd = ts_secure_opendir(def_timestampdir, true, false);
if (dfd == -1)
goto bad; goto bad;
/* Open time stamp file. */ /* Open time stamp file. */
@@ -427,7 +446,7 @@ timestamp_open(const char *user, pid_t sid)
for (tries = 1; ; tries++) { for (tries = 1; ; tries++) {
struct stat sb; struct stat sb;
fd = ts_open(fname, O_RDWR|O_CREAT); fd = ts_openat(dfd, user, O_RDWR|O_CREAT);
switch (fd) { switch (fd) {
case TIMESTAMP_OPEN_ERROR: case TIMESTAMP_OPEN_ERROR:
log_warning(SLOG_SEND_MAIL, N_("unable to open %s"), fname); log_warning(SLOG_SEND_MAIL, N_("unable to open %s"), fname);
@@ -453,7 +472,7 @@ timestamp_open(const char *user, pid_t sid)
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"removing time stamp file that predates boot time"); "removing time stamp file that predates boot time");
close(fd); close(fd);
unlink(fname); unlinkat(dfd, user, 0);
continue; continue;
} }
} }
@@ -473,9 +492,12 @@ timestamp_open(const char *user, pid_t sid)
cookie->sid = sid; cookie->sid = sid;
cookie->pos = -1; cookie->pos = -1;
close(dfd);
debug_return_ptr(cookie); debug_return_ptr(cookie);
bad: bad:
if (fd != -1) if (dfd != -1)
close(dfd);
if (fd >= 0)
close(fd); close(fd);
free(fname); free(fname);
debug_return_ptr(NULL); debug_return_ptr(NULL);
@@ -962,7 +984,7 @@ int
timestamp_remove(bool unlink_it) timestamp_remove(bool unlink_it)
{ {
struct timestamp_entry key, entry; struct timestamp_entry key, entry;
int fd = -1, ret = true; int dfd = -1, fd = -1, ret = true;
char *fname = NULL; char *fname = NULL;
debug_decl(timestamp_remove, SUDOERS_DEBUG_AUTH); debug_decl(timestamp_remove, SUDOERS_DEBUG_AUTH);
@@ -976,6 +998,13 @@ timestamp_remove(bool unlink_it)
} }
#endif #endif
dfd = open(def_timestampdir, O_RDONLY|O_NONBLOCK);
if (dfd == -1) {
if (errno != ENOENT)
ret = -1;
goto done;
}
if (asprintf(&fname, "%s/%s", def_timestampdir, user_name) == -1) { if (asprintf(&fname, "%s/%s", def_timestampdir, user_name) == -1) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
ret = -1; ret = -1;
@@ -984,12 +1013,12 @@ timestamp_remove(bool unlink_it)
/* For "sudo -K" simply unlink the time stamp file. */ /* For "sudo -K" simply unlink the time stamp file. */
if (unlink_it) { if (unlink_it) {
ret = unlink(fname) ? -1 : true; ret = unlinkat(dfd, user_name, 0) ? -1 : true;
goto done; goto done;
} }
/* Open time stamp file and lock it for exclusive access. */ /* Open time stamp file and lock it for exclusive access. */
fd = ts_open(fname, O_RDWR); fd = ts_openat(dfd, user_name, O_RDWR);
switch (fd) { switch (fd) {
case TIMESTAMP_OPEN_ERROR: case TIMESTAMP_OPEN_ERROR:
if (errno != ENOENT) if (errno != ENOENT)
@@ -1023,7 +1052,9 @@ timestamp_remove(bool unlink_it)
} }
done: done:
if (fd != -1) if (dfd != -1)
close(dfd);
if (fd >= 0)
close(fd); close(fd);
free(fname); free(fname);
debug_return_int(ret); debug_return_int(ret);
@@ -1035,21 +1066,17 @@ done:
bool bool
already_lectured(void) already_lectured(void)
{ {
char status_file[PATH_MAX]; bool ret = false;
struct stat sb; struct stat sb;
int len; int dfd;
debug_decl(already_lectured, SUDOERS_DEBUG_AUTH); debug_decl(already_lectured, SUDOERS_DEBUG_AUTH);
if (ts_secure_dir(def_lecture_status_dir, false, true)) { dfd = ts_secure_opendir(def_lecture_status_dir, false, true);
len = snprintf(status_file, sizeof(status_file), "%s/%s", if (dfd != -1) {
def_lecture_status_dir, user_name); ret = fstatat(dfd, user_name, &sb, AT_SYMLINK_NOFOLLOW) == 0;
if (len > 0 && len < ssizeof(status_file)) { close(dfd);
debug_return_bool(stat(status_file, &sb) == 0);
}
log_warningx(SLOG_SEND_MAIL, N_("lecture status path too long: %s/%s"),
def_lecture_status_dir, user_name);
} }
debug_return_bool(false); debug_return_bool(ret);
} }
/* /*
@@ -1059,24 +1086,16 @@ already_lectured(void)
int int
set_lectured(void) set_lectured(void)
{ {
char lecture_status[PATH_MAX]; int dfd, fd, ret = false;
int len, fd, ret = false;
debug_decl(set_lectured, SUDOERS_DEBUG_AUTH); debug_decl(set_lectured, SUDOERS_DEBUG_AUTH);
len = snprintf(lecture_status, sizeof(lecture_status), "%s/%s", /* Check the validity of timestamp dir and create if missing. */
def_lecture_status_dir, user_name); dfd = ts_secure_opendir(def_lecture_status_dir, true, false);
if (len < 0 || len >= ssizeof(lecture_status)) { if (dfd == -1)
log_warningx(SLOG_SEND_MAIL, N_("lecture status path too long: %s/%s"),
def_lecture_status_dir, user_name);
goto done;
}
/* Check the validity of lecture dir and create if missing. */
if (!ts_secure_dir(def_lecture_status_dir, true, false))
goto done; goto done;
/* Create lecture file. */ /* Create lecture file. */
fd = ts_open(lecture_status, O_WRONLY|O_CREAT|O_EXCL); fd = ts_openat(dfd, user_name, O_WRONLY|O_CREAT|O_EXCL);
switch (fd) { switch (fd) {
case TIMESTAMP_OPEN_ERROR: case TIMESTAMP_OPEN_ERROR:
/* Failed to open, not a fatal error. */ /* Failed to open, not a fatal error. */
@@ -1091,6 +1110,7 @@ set_lectured(void)
ret = true; ret = true;
break; break;
} }
close(dfd);
done: done:
debug_return_int(ret); debug_return_int(ret);