Fix potential TOCTOU when creating time stamp directory and file.
This commit is contained in:
@@ -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);
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
Reference in New Issue
Block a user