Lock individual records in the timestamp file instead of the entire

file.  This will make it possible for multiple sudo processes using
the same tty to serialize their timestamp lookups.
This commit is contained in:
Todd C. Miller
2015-09-07 06:06:08 -06:00
parent 98a15d9879
commit 00142c91fa
11 changed files with 422 additions and 333 deletions

View File

@@ -229,9 +229,6 @@
/* Define to 1 if you have the `fgetln' function. */ /* Define to 1 if you have the `fgetln' function. */
#undef HAVE_FGETLN #undef HAVE_FGETLN
/* Define to 1 if you have the `flock' function. */
#undef HAVE_FLOCK
/* Define to 1 if you have the `fnmatch' function. */ /* Define to 1 if you have the `fnmatch' function. */
#undef HAVE_FNMATCH #undef HAVE_FNMATCH

15
configure vendored
View File

@@ -15165,9 +15165,8 @@ fi
test -z "$with_pam" && AUTH_EXCL_DEF="PAM" test -z "$with_pam" && AUTH_EXCL_DEF="PAM"
;; ;;
*-*-gnu*) *-*-gnu*)
# lockf() is broken on the Hurd -- use flock instead # lockf() is broken on the Hurd
ac_cv_func_lockf=no ac_cv_func_lockf=no
ac_cv_func_flock=yes
;; ;;
*-*-ultrix*) *-*-ultrix*)
OS="ultrix" OS="ultrix"
@@ -15477,9 +15476,8 @@ fi
fi fi
;; ;;
*-*-nextstep*) *-*-nextstep*)
# lockf() is broken on the NeXT -- use flock instead # lockf() is broken on the NeXT
ac_cv_func_lockf=no ac_cv_func_lockf=no
ac_cv_func_flock=yes
RTLD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES" RTLD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES"
RTLD_PRELOAD_ENABLE_VAR="DYLD_FORCE_FLAT_NAMESPACE" RTLD_PRELOAD_ENABLE_VAR="DYLD_FORCE_FLAT_NAMESPACE"
;; ;;
@@ -18759,13 +18757,12 @@ fi
done done
fi fi
for ac_func in lockf flock for ac_func in lockf
do : do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "lockf" "ac_cv_func_lockf"
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if test "x$ac_cv_func_lockf" = xyes; then :
if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF cat >>confdefs.h <<_ACEOF
#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 #define HAVE_LOCKF 1
_ACEOF _ACEOF
break break
fi fi

View File

@@ -1943,9 +1943,8 @@ case "$host" in
test -z "$with_pam" && AUTH_EXCL_DEF="PAM" test -z "$with_pam" && AUTH_EXCL_DEF="PAM"
;; ;;
*-*-gnu*) *-*-gnu*)
# lockf() is broken on the Hurd -- use flock instead # lockf() is broken on the Hurd
ac_cv_func_lockf=no ac_cv_func_lockf=no
ac_cv_func_flock=yes
;; ;;
*-*-ultrix*) *-*-ultrix*)
OS="ultrix" OS="ultrix"
@@ -2119,9 +2118,8 @@ case "$host" in
fi fi
;; ;;
*-*-nextstep*) *-*-nextstep*)
# lockf() is broken on the NeXT -- use flock instead # lockf() is broken on the NeXT
ac_cv_func_lockf=no ac_cv_func_lockf=no
ac_cv_func_flock=yes
RTLD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES" RTLD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES"
RTLD_PRELOAD_ENABLE_VAR="DYLD_FORCE_FLAT_NAMESPACE" RTLD_PRELOAD_ENABLE_VAR="DYLD_FORCE_FLAT_NAMESPACE"
;; ;;
@@ -2521,7 +2519,7 @@ AC_CHECK_FUNCS_ONCE([seteuid])
if test X"$with_interfaces" != X"no"; then if test X"$with_interfaces" != X"no"; then
AC_CHECK_FUNCS([getifaddrs], [AC_CHECK_FUNCS([freeifaddrs])]) AC_CHECK_FUNCS([getifaddrs], [AC_CHECK_FUNCS([freeifaddrs])])
fi fi
AC_CHECK_FUNCS([lockf flock], [break]) AC_CHECK_FUNCS([lockf], [break])
AC_CHECK_FUNCS([innetgr], [ AC_CHECK_FUNCS([innetgr], [
AC_CHECK_DECLS([innetgr], [], [], [ AC_CHECK_DECLS([innetgr], [], [], [
AC_INCLUDES_DEFAULT AC_INCLUDES_DEFAULT

View File

@@ -182,6 +182,8 @@ __dso_public char *sudo_new_key_val_v1(const char *key, const char *value);
#define SUDO_UNLOCK 4 /* unlock a file */ #define SUDO_UNLOCK 4 /* unlock a file */
__dso_public bool sudo_lock_file_v1(int fd, int action); __dso_public bool sudo_lock_file_v1(int fd, int action);
#define sudo_lock_file(_a, _b) sudo_lock_file_v1((_a), (_b)) #define sudo_lock_file(_a, _b) sudo_lock_file_v1((_a), (_b))
__dso_public bool sudo_lock_region_v1(int fd, int action, off_t len);
#define sudo_lock_region(_a, _b, _c) sudo_lock_region_v1((_a), (_b), (_c))
/* parseln.c */ /* parseln.c */
__dso_public ssize_t sudo_parseln_v1(char **buf, size_t *bufsize, unsigned int *lineno, FILE *fp); __dso_public ssize_t sudo_parseln_v1(char **buf, size_t *bufsize, unsigned int *lineno, FILE *fp);

View File

@@ -22,9 +22,6 @@
#include <config.h> #include <config.h>
#include <sys/types.h> #include <sys/types.h>
#ifdef HAVE_FLOCK
# include <sys/file.h>
#endif /* HAVE_FLOCK */
#include <stdlib.h> #include <stdlib.h>
#ifdef HAVE_STRING_H #ifdef HAVE_STRING_H
# include <string.h> # include <string.h>
@@ -46,16 +43,22 @@
#include "sudo_debug.h" #include "sudo_debug.h"
/* /*
* Lock/unlock a file. * Lock/unlock all or part of a file.
*/ */
#ifdef HAVE_LOCKF #ifdef HAVE_LOCKF
bool bool
sudo_lock_file_v1(int fd, int lockit) sudo_lock_file_v1(int fd, int type)
{ {
int op = 0; return sudo_lock_region_v1(fd, type, 0);
debug_decl(sudo_lock_file, SUDO_DEBUG_UTIL) }
switch (lockit) { bool
sudo_lock_region_v1(int fd, int type, off_t len)
{
int op;
debug_decl(sudo_lock_region, SUDO_DEBUG_UTIL)
switch (type) {
case SUDO_LOCK: case SUDO_LOCK:
op = F_LOCK; op = F_LOCK;
break; break;
@@ -65,48 +68,32 @@ sudo_lock_file_v1(int fd, int lockit)
case SUDO_UNLOCK: case SUDO_UNLOCK:
op = F_ULOCK; op = F_ULOCK;
break; break;
default:
debug_return_bool(false);
} }
debug_return_bool(lockf(fd, op, 0) == 0); debug_return_bool(lockf(fd, op, len) == 0);
}
#elif defined(HAVE_FLOCK)
bool
sudo_lock_file_v1(int fd, int lockit)
{
int op = 0;
debug_decl(sudo_lock_file, SUDO_DEBUG_UTIL)
switch (lockit) {
case SUDO_LOCK:
op = LOCK_EX;
break;
case SUDO_TLOCK:
op = LOCK_EX | LOCK_NB;
break;
case SUDO_UNLOCK:
op = LOCK_UN;
break;
}
debug_return_bool(flock(fd, op) == 0);
} }
#else #else
bool bool
sudo_lock_file_v1(int fd, int lockit) sudo_lock_file_v1(int fd, int type)
{
return sudo_lock_region_v1(fd, type, 0);
}
bool
sudo_lock_region_v1(int fd, int type, off_t len)
{ {
#ifdef F_SETLK
int func; int func;
struct flock lock; struct flock lock;
debug_decl(sudo_lock_file, SUDO_DEBUG_UTIL) debug_decl(sudo_lock_file, SUDO_DEBUG_UTIL)
lock.l_start = 0; lock.l_start = 0;
lock.l_len = 0; lock.l_len = len;
lock.l_pid = getpid(); lock.l_pid = 0;
lock.l_type = (lockit == SUDO_UNLOCK) ? F_UNLCK : F_WRLCK; lock.l_type = (type == SUDO_UNLOCK) ? F_UNLCK : F_WRLCK;
lock.l_whence = SEEK_SET; lock.l_whence = len ? SEEK_CUR : SEEK_SET;
func = (lockit == SUDO_LOCK) ? F_SETLKW : F_SETLK; func = (type == SUDO_LOCK) ? F_SETLKW : F_SETLK;
debug_return_bool(fcntl(fd, func, &lock) == 0); debug_return_bool(fcntl(fd, func, &lock) == 0);
#else
return true;
#endif
} }
#endif #endif

View File

@@ -66,6 +66,7 @@ sudo_lbuf_error_v1
sudo_lbuf_init_v1 sudo_lbuf_init_v1
sudo_lbuf_print_v1 sudo_lbuf_print_v1
sudo_lock_file_v1 sudo_lock_file_v1
sudo_lock_region_v1
sudo_new_key_val_v1 sudo_new_key_val_v1
sudo_parse_gids_v1 sudo_parse_gids_v1
sudo_parseln_v1 sudo_parseln_v1

View File

@@ -48,13 +48,15 @@ static struct passwd *get_authpw(int);
/* /*
* Returns true if the user successfully authenticates, false if not * Returns true if the user successfully authenticates, false if not
* or -1 on error. * or -1 on fatal error.
*/ */
static int static int
check_user_interactive(int validated, int mode, struct passwd *auth_pw) check_user_interactive(int validated, int mode, struct passwd *auth_pw)
{ {
int status, rval = -1; int status = TS_ERROR;
int rval = -1;
char *prompt; char *prompt;
void *cookie;
bool lectured; bool lectured;
debug_decl(check_user_interactive, SUDOERS_DEBUG_AUTH) debug_decl(check_user_interactive, SUDOERS_DEBUG_AUTH)
@@ -62,10 +64,11 @@ check_user_interactive(int validated, int mode, struct passwd *auth_pw)
if (ISSET(mode, MODE_IGNORE_TICKET)) if (ISSET(mode, MODE_IGNORE_TICKET))
SET(validated, FLAG_CHECK_USER); SET(validated, FLAG_CHECK_USER);
if (build_timestamp(auth_pw) == -1) /* Open timestamp file and check its status. */
goto done; cookie = timestamp_open(user_name, user_sid);
if (timestamp_lock(cookie, auth_pw))
status = timestamp_status(cookie, auth_pw);
status = timestamp_status(auth_pw);
switch (status) { switch (status) {
case TS_FATAL: case TS_FATAL:
/* Fatal error (usually setuid failure), unsafe to proceed. */ /* Fatal error (usually setuid failure), unsafe to proceed. */
@@ -97,21 +100,23 @@ check_user_interactive(int validated, int mode, struct passwd *auth_pw)
goto done; goto done;
rval = verify_user(auth_pw, prompt, validated, NULL); /* XXX */ rval = verify_user(auth_pw, prompt, validated, NULL); /* XXX */
if (rval == true && lectured) { if (rval == true && lectured)
if (set_lectured() == -1) (void)set_lectured(); /* lecture error not fatal */
rval = -1;
}
free(prompt); free(prompt);
break; break;
} }
/* Only update timestamp if user was validated. */ /*
* Only update timestamp if user was validated.
* Failure to update the timestamp is not a fatal error.
*/
if (rval == true && ISSET(validated, VALIDATE_SUCCESS) && if (rval == true && ISSET(validated, VALIDATE_SUCCESS) &&
!ISSET(mode, MODE_IGNORE_TICKET) && status != TS_ERROR) { !ISSET(mode, MODE_IGNORE_TICKET) && status != TS_ERROR) {
if (update_timestamp(auth_pw) == -1) (void)timestamp_update(cookie, auth_pw);
rval = -1;
} }
done: done:
if (cookie != NULL)
timestamp_close(cookie);
debug_return_int(rval); debug_return_int(rval);
} }

View File

@@ -26,9 +26,8 @@
#define TS_CURRENT 0 #define TS_CURRENT 0
#define TS_OLD 1 #define TS_OLD 1
#define TS_MISSING 2 #define TS_MISSING 2
#define TS_NOFILE 3 #define TS_ERROR 3
#define TS_ERROR 4 #define TS_FATAL 4
#define TS_FATAL 5
/* /*
* Time stamps are now stored in a single file which contains multiple * Time stamps are now stored in a single file which contains multiple
@@ -41,6 +40,7 @@
#define TS_GLOBAL 0x01 #define TS_GLOBAL 0x01
#define TS_TTY 0x02 #define TS_TTY 0x02
#define TS_PPID 0x03 #define TS_PPID 0x03
#define TS_LOCKEXCL 0x04
/* Time stamp flags */ /* Time stamp flags */
#define TS_DISABLED 0x01 /* entry disabled */ #define TS_DISABLED 0x01 /* entry disabled */
@@ -61,9 +61,12 @@ struct timestamp_entry {
} u; } u;
}; };
void *timestamp_open(const char *user, pid_t sid);
void timestamp_close(void *vcookie);
bool timestamp_lock(void *vcookie, struct passwd *pw);
bool timestamp_update(void *vcookie, struct passwd *pw);
int timestamp_status(void *vcookie, struct passwd *pw);
bool already_lectured(int status); bool already_lectured(int status);
int update_timestamp(struct passwd *pw); int set_lectured(void);
int build_timestamp(struct passwd *pw);
int timestamp_status(struct passwd *pw);
#endif /* SUDOERS_CHECK_H */ #endif /* SUDOERS_CHECK_H */

View File

@@ -725,7 +725,7 @@ sudoers_policy_invalidate(int remove)
user_cmnd = "kill"; user_cmnd = "kill";
/* XXX - plugin API should support a return value for fatal errors. */ /* XXX - plugin API should support a return value for fatal errors. */
remove_timestamp(remove); timestamp_remove(remove);
sudoers_cleanup(); sudoers_cleanup();
debug_return; debug_return;

View File

@@ -238,8 +238,7 @@ bool user_is_exempt(void);
char *expand_prompt(const char *old_prompt, const char *auth_user); char *expand_prompt(const char *old_prompt, const char *auth_user);
/* timestamp.c */ /* timestamp.c */
int remove_timestamp(bool); int timestamp_remove(bool unlinkit);
int set_lectured(void);
/* sudo_auth.c */ /* sudo_auth.c */
bool sudo_auth_needs_end_session(void); bool sudo_auth_needs_end_session(void);

View File

@@ -21,6 +21,11 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#if defined(HAVE_STDINT_H)
# include <stdint.h>
#elif defined(HAVE_INTTYPES_H)
# include <inttypes.h>
#endif
#ifdef HAVE_STRING_H #ifdef HAVE_STRING_H
# include <string.h> # include <string.h>
#endif /* HAVE_STRING_H */ #endif /* HAVE_STRING_H */
@@ -45,12 +50,30 @@
#define TIMESTAMP_OPEN_ERROR -1 #define TIMESTAMP_OPEN_ERROR -1
#define TIMESTAMP_PERM_ERROR -2 #define TIMESTAMP_PERM_ERROR -2
static char timestamp_file[PATH_MAX]; /*
static off_t timestamp_hint = (off_t)-1; * Each user has a single time stamp file that contains multiple records.
static struct timestamp_entry timestamp_key; * Records are locked to ensure that changes are serialized.
*
* The first record is of type TS_LOCKEXCL and is used to gain exclusive
* access to create new records. This is a short-term lock and sudo
* should not sleep while holding it (or the user will not be able to sudo).
* The TS_LOCKEXCL entry must be unlocked before locking the actual record.
*/
/* TODO: unlock on suspend, make locking interruptible */
/* TODO: use pread/pwrite when possible */
struct ts_cookie {
int fd;
pid_t sid;
off_t pos;
char *fname;
struct timestamp_entry key;
};
/* /*
* Returns true if entry matches key, else false. * Returns true if entry matches key, else false.
* We don't match on the sid or actual time stamp.
*/ */
static bool static bool
ts_match_record(struct timestamp_entry *key, struct timestamp_entry *entry) ts_match_record(struct timestamp_entry *key, struct timestamp_entry *entry)
@@ -98,8 +121,7 @@ ts_find_record(int fd, struct timestamp_entry *key, struct timestamp_entry *entr
debug_decl(ts_find_record, SUDOERS_DEBUG_AUTH) debug_decl(ts_find_record, SUDOERS_DEBUG_AUTH)
/* /*
* Look for a matching record. * Find a matching record (does not match sid or time stamp value).
* We don't match on the sid or actual time stamp.
*/ */
while (read(fd, &cur, sizeof(cur)) == sizeof(cur)) { while (read(fd, &cur, sizeof(cur)) == sizeof(cur)) {
if (cur.size != sizeof(cur)) { if (cur.size != sizeof(cur)) {
@@ -120,73 +142,6 @@ ts_find_record(int fd, struct timestamp_entry *key, struct timestamp_entry *entr
debug_return_bool(false); debug_return_bool(false);
} }
/*
* Find matching record to update or append a new one.
* Returns true if the entry was written successfully, else false.
*/
static bool
ts_update_record(int fd, struct timestamp_entry *entry, off_t timestamp_hint)
{
struct timestamp_entry cur;
ssize_t nwritten;
off_t old_eof = (off_t)-1;
debug_decl(ts_update_record, SUDOERS_DEBUG_AUTH)
/* First try the hint if one is given. */
if (timestamp_hint != (off_t)-1) {
if (lseek(fd, timestamp_hint, SEEK_SET) != -1) {
if (read(fd, &cur, sizeof(cur)) == sizeof(cur)) {
if (ts_match_record(entry, &cur)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"found existing time stamp record using hint");
goto found_it;
}
}
}
}
/* Search for matching record. */
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"searching for time stamp record");
lseek(fd, (off_t)0, SEEK_SET);
if (ts_find_record(fd, entry, &cur)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"found existing time stamp record");
found_it:
/* back up over old record */
lseek(fd, (off_t)0 - (off_t)cur.size, SEEK_CUR);
} else {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"appending new time stamp record");
old_eof = lseek(fd, (off_t)0, SEEK_CUR);
}
/* Overwrite existing record or append to end. */
nwritten = write(fd, entry, sizeof(struct timestamp_entry));
if ((size_t)nwritten == sizeof(struct timestamp_entry))
debug_return_bool(true);
if (nwritten == -1) {
log_warning(SLOG_SEND_MAIL,
N_("unable to write to %s"), timestamp_file);
} else {
log_warningx(SLOG_SEND_MAIL,
N_("unable to write to %s"), timestamp_file);
}
/* Truncate on partial write to be safe. */
if (nwritten > 0 && old_eof != (off_t)-1) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"short write, truncating partial time stamp record");
if (ftruncate(fd, old_eof) != 0) {
sudo_warn(U_("unable to truncate time stamp file to %lld bytes"),
(long long)old_eof);
}
}
debug_return_bool(false);
}
/* /*
* Create a directory and any missing parent directories with the * Create a directory and any missing parent directories with the
* specified mode. * specified mode.
@@ -287,37 +242,17 @@ ts_secure_dir(char *path, bool make_it, bool quiet)
} }
/* /*
* Fills in the timestamp_file[] global variable. * Open the specified timestamp or lecture file and set the
* Returns the length of timestamp_file. * close on exec flag.
*/ * Returns open file descriptor on success.
int
build_timestamp(struct passwd *pw)
{
int len;
debug_decl(build_timestamp, SUDOERS_DEBUG_AUTH)
len = snprintf(timestamp_file, sizeof(timestamp_file), "%s/%s",
def_timestampdir, user_name);
if (len <= 0 || (size_t)len >= sizeof(timestamp_file)) {
log_warningx(SLOG_SEND_MAIL,
N_("timestamp path too long: %s/%s"), def_timestampdir, user_name);
len = -1;
}
debug_return_int(len);
}
/*
* Open and lock the specified timestamp or lecture file.
* Returns open and locked file descriptor on success.
* Returns TIMESTAMP_OPEN_ERROR or TIMESTAMP_PERM_ERROR on error. * Returns TIMESTAMP_OPEN_ERROR or TIMESTAMP_PERM_ERROR on error.
*/ */
static int static int
open_timestamp(const char *path, int flags) ts_open(const char *path, int flags)
{ {
bool uid_changed = false; bool uid_changed = false;
int fd; int fd;
debug_decl(open_timestamp, SUDOERS_DEBUG_AUTH) debug_decl(ts_open, SUDOERS_DEBUG_AUTH)
if (timestamp_uid != 0) if (timestamp_uid != 0)
uid_changed = set_perms(PERM_TIMESTAMP); uid_changed = set_perms(PERM_TIMESTAMP);
@@ -332,179 +267,320 @@ open_timestamp(const char *path, int flags)
} }
} }
if (fd >= 0) if (fd >= 0)
sudo_lock_file(fd, SUDO_LOCK); (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
debug_return_int(fd); debug_return_int(fd);
} }
/* static ssize_t
* Update the time on the timestamp file/dir or create it if necessary. ts_write(int fd, const char *fname, struct timestamp_entry *entry)
* Returns true on success, false on failure or -1 on setuid failure.
*/
int
update_timestamp(struct passwd *pw)
{ {
struct timestamp_entry entry; ssize_t nwritten;
int rval = false; off_t old_eof;
int fd = -1; debug_decl(ts_write, SUDOERS_DEBUG_AUTH)
debug_decl(update_timestamp, SUDOERS_DEBUG_AUTH)
/* Zero timeout means don't update the time stamp file. */ old_eof = lseek(fd, (off_t)0, SEEK_CUR);
if (def_timestamp_timeout == 0) nwritten = write(fd, entry, entry->size);
goto done; if ((size_t)nwritten != entry->size) {
if (nwritten == -1) {
log_warning(SLOG_SEND_MAIL,
N_("unable to write to %s"), fname);
} else {
log_warningx(SLOG_SEND_MAIL,
N_("unable to write to %s"), fname);
}
/* Check/create parent directories as needed. */ /* Truncate on partial write to be safe (assumes end of file). */
if (!ts_secure_dir(def_timestampdir, true, false)) if (nwritten > 0) {
goto done; sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"short write, truncating partial time stamp record");
/* Fill in time stamp. */ if (ftruncate(fd, old_eof) != 0) {
memcpy(&entry, &timestamp_key, sizeof(struct timestamp_entry)); sudo_warn(U_("unable to truncate time stamp file to %lld bytes"),
if (sudo_gettime_mono(&entry.ts) == -1) { (long long)old_eof);
log_warning(0, N_("unable to read the clock")); }
goto done; }
debug_return_size_t(-1);
} }
debug_return_size_t(nwritten);
/* Open time stamp file and lock it for exclusive access. */
fd = open_timestamp(timestamp_file, O_RDWR|O_CREAT);
switch (fd) {
case TIMESTAMP_OPEN_ERROR:
log_warning(SLOG_SEND_MAIL, N_("unable to open %s"), timestamp_file);
goto done;
case TIMESTAMP_PERM_ERROR:
/* Already logged set_perms/restore_perms error. */
rval = -1;
goto done;
}
/* Update record or append a new one. */
ts_update_record(fd, &entry, timestamp_hint);
close(fd);
rval = true;
done:
debug_return_int(rval);
} }
/* /*
* Check the timestamp file and directory and return their status. * Full in struct timestamp_entry with the specified flags
* Returns one of TS_CURRENT, TS_OLD, TS_MISSING, TS_NOFILE, TS_ERROR. * based on auth user pw. Does not set the time stamp.
*/ */
int static void
timestamp_status(struct passwd *pw) ts_fill(struct timestamp_entry *entry, struct passwd *pw, int flags)
{ {
struct timestamp_entry entry;
struct timespec diff, timeout;
int status = TS_ERROR; /* assume the worst */
struct stat sb; struct stat sb;
int fd = -1; debug_decl(ts_fill, SUDOERS_DEBUG_AUTH)
debug_decl(timestamp_status, SUDOERS_DEBUG_AUTH)
/* Reset time stamp offset hint. */ memset(entry, 0, sizeof(*entry));
timestamp_hint = (off_t)-1; entry->version = TS_VERSION;
entry->size = sizeof(*entry);
/* Zero timeout means ignore time stamp files. */ entry->type = TS_GLOBAL; /* may be overriden below */
if (def_timestamp_timeout == 0) { entry->flags = flags;
status = TS_OLD; /* XXX - could also be TS_MISSING */
goto done;
}
/* Ignore time stamp files in an insecure directory. */
if (!ts_secure_dir(def_timestampdir, false, false)) {
if (errno != ENOENT) {
status = TS_ERROR;
goto done;
}
status = TS_MISSING; /* not insecure, just missing */
}
/*
* Create a key used for matching entries in the time stamp file.
* The actual time stamp in the key is used below as the time "now".
*/
memset(&timestamp_key, 0, sizeof(timestamp_key));
timestamp_key.version = TS_VERSION;
timestamp_key.size = sizeof(timestamp_key);
timestamp_key.type = TS_GLOBAL; /* may be overriden below */
if (pw != NULL) { if (pw != NULL) {
timestamp_key.auth_uid = pw->pw_uid; entry->auth_uid = pw->pw_uid;
} else { } else {
timestamp_key.flags = TS_ANYUID; entry->flags |= TS_ANYUID;
} }
timestamp_key.sid = user_sid; entry->sid = user_sid;
if (def_tty_tickets) { if (def_tty_tickets) {
if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) { if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) {
/* tty-based time stamp */ /* tty-based time stamp */
timestamp_key.type = TS_TTY; entry->type = TS_TTY;
timestamp_key.u.ttydev = sb.st_rdev; entry->u.ttydev = sb.st_rdev;
} else { } else {
/* ppid-based time stamp */ /* ppid-based time stamp */
timestamp_key.type = TS_PPID; entry->type = TS_PPID;
timestamp_key.u.ppid = getppid(); entry->u.ppid = getppid();
} }
} }
if (sudo_gettime_mono(&timestamp_key.ts) == -1) {
log_warning(0, N_("unable to read the clock")); debug_return;
status = TS_ERROR; }
/*
* Open the user's time stamp file.
* Returns a cookie or NULL on error, does not lock the file.
*/
void *
timestamp_open(const char *user, pid_t sid)
{
struct ts_cookie *cookie = NULL;
char *fname = NULL;
int fd = -1;
debug_decl(timestamp_open, SUDOERS_DEBUG_AUTH)
/* Zero timeout means don't use the time stamp file. */
if (def_timestamp_timeout == 0) {
errno = ENOENT;
goto bad;
} }
/* If the time stamp dir is missing there is nothing to do. */ /* Sanity check timestamp dir and create if missing. */
if (status == TS_MISSING) if (!ts_secure_dir(def_timestampdir, true, false))
goto done; goto bad;
/* Open time stamp file and lock it for exclusive access. */ /* Open time stamp file. */
fd = open_timestamp(timestamp_file, O_RDWR); if (asprintf(&fname, "%s/%s", def_timestampdir, user) == -1) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
fd = ts_open(fname, O_RDWR|O_CREAT);
switch (fd) { switch (fd) {
case TIMESTAMP_OPEN_ERROR: case TIMESTAMP_OPEN_ERROR:
status = TS_MISSING; log_warning(SLOG_SEND_MAIL, N_("unable to open %s"), fname);
goto done; goto bad;
case TIMESTAMP_PERM_ERROR: case TIMESTAMP_PERM_ERROR:
/* Already logged set_perms/restore_perms error. */ /* Already logged set_perms/restore_perms error. */
status = TS_FATAL; goto bad;
}
/* XXX - if mtime on file predates boot time ignore/unlink? */
/* Allocate and fill in cookie to store state. */
cookie = malloc(sizeof(*cookie));
if (cookie == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
cookie->fd = fd;
cookie->fname = fname;
cookie->sid = sid;
cookie->pos = -1;
debug_return_ptr(cookie);
bad:
if (fd != -1)
close(fd);
free(cookie);
free(fname);
debug_return_ptr(NULL);
}
/*
* Lock a record in the time stamp file for exclusive access.
* If the record does not exist, it is created (as disabled).
*/
bool
timestamp_lock(void *vcookie, struct passwd *pw)
{
struct ts_cookie *cookie = vcookie;
struct timestamp_entry entry;
ssize_t nread;
debug_decl(timestamp_lock, SUDOERS_DEBUG_AUTH)
if (cookie == NULL) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"called with a NULL cookie!");
debug_return_bool(false);
}
/*
* Take a lock on the "write" record (the first record in the file).
* This will let us seek for the record or extend as needed
* without colliding with anyone else.
*/
if (lseek(cookie->fd, 0, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek to %lld", (long long)cookie->pos);
debug_return_bool(false);
}
sudo_lock_region(cookie->fd, SUDO_LOCK, sizeof(struct timestamp_entry));
/* Make sure the first record is of type TS_LOCKEXCL. */
memset(&entry, 0, sizeof(entry));
nread = read(cookie->fd, &entry, sizeof(entry));
if (nread == 0) {
/* New file, add TS_LOCKEXCL record. */
entry.version = TS_VERSION;
entry.size = sizeof(entry);
entry.type = TS_LOCKEXCL;
if (ts_write(cookie->fd, cookie->fname, &entry) == -1)
debug_return_bool(false);
} else if (entry.type != TS_LOCKEXCL) {
/* Old sudo record, convert it to TS_LOCKEXCL. */
entry.type = TS_LOCKEXCL;
memset((char *)&entry + offsetof(struct timestamp_entry, type), 0,
nread - offsetof(struct timestamp_entry, type));
if (ts_write(cookie->fd, cookie->fname, &entry) == -1)
debug_return_bool(false);
}
/* Search for record that matches the key or append a new one. */
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"searching for time stamp record");
ts_fill(&cookie->key, pw, TS_DISABLED);
if (ts_find_record(cookie->fd, &cookie->key, &entry)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"found existing time stamp record");
cookie->pos = lseek(cookie->fd, 0, SEEK_CUR) - (off_t)entry.size;
} else {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"appending new time stamp record");
cookie->pos = lseek(cookie->fd, 0, SEEK_CUR);
if (ts_write(cookie->fd, cookie->fname, &cookie->key) == -1)
debug_return_bool(false);
}
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"time stamp position is %lld", (long long)cookie->pos);
/* Unlock the TS_LOCKEXCL record. */
lseek(cookie->fd, 0, SEEK_SET);
sudo_lock_region(cookie->fd, SUDO_UNLOCK, sizeof(struct timestamp_entry));
/* Lock the real record (may sleep). */
lseek(cookie->fd, cookie->pos, SEEK_SET);
sudo_lock_region(cookie->fd, SUDO_LOCK, (off_t)entry.size);
debug_return_bool(true);
}
void
timestamp_close(void *vcookie)
{
struct ts_cookie *cookie = vcookie;
debug_decl(timestamp_close, SUDOERS_DEBUG_AUTH)
if (cookie != NULL) {
close(cookie->fd);
free(cookie->fname);
free(cookie);
}
debug_return;
}
/*
* Check the time stamp file and directory and return their status.
* Called with the file position before the locked record to read.
* Returns one of TS_CURRENT, TS_OLD, TS_MISSING, TS_ERROR, TS_FATAL.
* Fills in fdp with an open file descriptor positioned at the
* appropriate (and locked) record.
*/
int
timestamp_status(void *vcookie, struct passwd *pw)
{
struct ts_cookie *cookie = vcookie;
struct timestamp_entry entry;
struct timespec diff, now, timeout;
int status = TS_ERROR; /* assume the worst */
ssize_t nread;
debug_decl(timestamp_status, SUDOERS_DEBUG_AUTH)
/* Zero timeout means don't use time stamp files. */
if (def_timestamp_timeout == 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"timestamps disabled");
status = TS_OLD;
goto done;
}
if (cookie == NULL || cookie->pos < 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"NULL cookie or invalid position");
status = TS_OLD;
goto done; goto done;
} }
/* Ignore and clear time stamp file if mtime predates boot time. */ /*
if (fstat(fd, &sb) == 0) { * Seek to the record position and read it.
struct timespec boottime, mtime; * If the file was truncated we might get all zeroes,
* in which case we need to unlock and try again (XXX - todo)
mtim_get(&sb, mtime); */
if (get_boottime(&boottime) && sudo_timespeccmp(&mtime, &boottime, <)) { if (lseek(cookie->fd, cookie->pos, SEEK_SET) == -1) {
ignore_result(ftruncate(fd, (off_t)0)); sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
status = TS_MISSING; "unable to seek to %lld", (long long)cookie->pos);
goto done; debug_return_int(TS_ERROR);
} }
nread = read(cookie->fd, &entry, sizeof(entry));
if (nread != sizeof(entry)) {
/* short read, should not happen */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"short read (%zd vs %zu), truncated time stamp file?",
nread, sizeof(entry));
/* XXX - could this happen if truncated? check. */
debug_return_int(TS_ERROR);
} }
/* Read existing record, if any. */ /* Make sure what we read matched the expected record. */
if (!ts_find_record(fd, &timestamp_key, &entry)) { if (entry.version != TS_VERSION || entry.size != nread) {
status = TS_MISSING; /* do something else? */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"invalid time stamp file @ %lld", (long long)cookie->pos);
status = TS_OLD;
goto done; goto done;
} }
/* Set record position hint for use by update_timestamp() */
timestamp_hint = lseek(fd, (off_t)0, SEEK_CUR);
if (timestamp_hint != (off_t)-1)
timestamp_hint -= entry.size;
if (ISSET(entry.flags, TS_DISABLED)) { if (ISSET(entry.flags, TS_DISABLED)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"time stamp record disabled");
status = TS_OLD; /* disabled via sudo -k */ status = TS_OLD; /* disabled via sudo -k */
goto done; goto done;
} }
if (entry.type != TS_GLOBAL && entry.sid != timestamp_key.sid) { if (entry.type != TS_GLOBAL && entry.sid != cookie->sid) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"time stamp record sid mismatch");
status = TS_OLD; /* belongs to different session */ status = TS_OLD; /* belongs to different session */
goto done; goto done;
} }
/* Negative timeouts only expire manually (sudo -k). */ /* Negative timeouts only expire manually (sudo -k). */
if (def_timestamp_timeout < 0) { if (def_timestamp_timeout < 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"time stamp record does not expire");
status = TS_CURRENT; status = TS_CURRENT;
goto done; goto done;
} }
/* Compare stored time stamp with current time. */ /* Compare stored time stamp with current time. */
sudo_timespecsub(&timestamp_key.ts, &entry.ts, &diff); if (sudo_gettime_mono(&now) == -1) {
log_warning(0, N_("unable to read the clock"));
status = TS_ERROR;
goto done;
}
sudo_timespecsub(&now, &entry.ts, &diff);
timeout.tv_sec = 60 * def_timestamp_timeout; timeout.tv_sec = 60 * def_timestamp_timeout;
timeout.tv_nsec = ((60.0 * def_timestamp_timeout) - (double)timeout.tv_sec) timeout.tv_nsec = ((60.0 * def_timestamp_timeout) - (double)timeout.tv_sec)
* 1000000000.0; * 1000000000.0;
@@ -517,11 +593,11 @@ timestamp_status(struct passwd *pw)
N_("ignoring time stamp from the future")); N_("ignoring time stamp from the future"));
status = TS_OLD; status = TS_OLD;
SET(entry.flags, TS_DISABLED); SET(entry.flags, TS_DISABLED);
ts_update_record(fd, &entry, timestamp_hint); ts_write(cookie->fd, cookie->fname, &entry);
} }
#else #else
/* Check for bogus (future) time in the stampfile. */ /* Check for bogus (future) time in the stampfile. */
sudo_timespecsub(&entry.ts, &timestamp_key.ts, &diff); sudo_timespecsub(&entry.ts, &now, &diff);
timeout.tv_sec *= 2; timeout.tv_sec *= 2;
if (sudo_timespeccmp(&diff, &timeout, >)) { if (sudo_timespeccmp(&diff, &timeout, >)) {
time_t tv_sec = (time_t)entry.ts.tv_sec; time_t tv_sec = (time_t)entry.ts.tv_sec;
@@ -530,7 +606,7 @@ timestamp_status(struct passwd *pw)
4 + ctime(&tv_sec)); 4 + ctime(&tv_sec));
status = TS_OLD; status = TS_OLD;
SET(entry.flags, TS_DISABLED); SET(entry.flags, TS_DISABLED);
ts_update_record(fd, &entry, timestamp_hint); ts_write(cookie->fd, cookie->fname, &entry);
} }
#endif /* CLOCK_MONOTONIC */ #endif /* CLOCK_MONOTONIC */
} else { } else {
@@ -538,57 +614,79 @@ timestamp_status(struct passwd *pw)
} }
done: done:
if (fd != -1)
close(fd);
debug_return_int(status); debug_return_int(status);
} }
/*
* Update the time on the time stamp file/dir or create it if necessary.
* Returns true on success, false on failure or -1 on setuid failure.
*/
bool
timestamp_update(void *vcookie, struct passwd *pw)
{
struct ts_cookie *cookie = vcookie;
int rval = false;
debug_decl(timestamp_update, SUDOERS_DEBUG_AUTH)
/* Zero timeout means don't use time stamp files. */
if (def_timestamp_timeout == 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"timestamps disabled");
goto done;
}
if (cookie == NULL || cookie->pos < 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"NULL cookie or invalid position");
goto done;
}
/* Update timestamp in key and enable it. */
CLR(cookie->key.flags, TS_DISABLED);
if (sudo_gettime_mono(&cookie->key.ts) == -1) {
log_warning(0, N_("unable to read the clock"));
goto done;
}
/* Write out the locked record. */
if (lseek(cookie->fd, cookie->pos, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek to %lld", (long long)cookie->pos);
goto done;
}
if (ts_write(cookie->fd, cookie->fname, &cookie->key) != -1)
rval = true;
done:
debug_return_int(rval);
}
/* /*
* Remove the timestamp entry or file if unlink_it is set. * Remove the timestamp entry or file if unlink_it is set.
* Returns true on success, false on failure or -1 on setuid failure. * Returns true on success, false on failure or -1 on setuid failure.
* A missing timestamp entry is not considered an error. * A missing timestamp entry is not considered an error.
*/ */
int int
remove_timestamp(bool unlink_it) timestamp_remove(bool unlink_it)
{ {
struct timestamp_entry entry; struct timestamp_entry key, entry;
int fd, rval = true; int fd = -1, rval = true;
debug_decl(remove_timestamp, SUDOERS_DEBUG_AUTH) char *fname = NULL;
debug_decl(timestamp_remove, SUDOERS_DEBUG_AUTH)
if (build_timestamp(NULL) == -1) { if (asprintf(&fname, "%s/%s", def_timestampdir, user_name) == -1) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
rval = -1; rval = -1;
goto done; goto done;
} }
/* For "sudo -K" simply unlink the time stamp file. */ /* For "sudo -K" simply unlink the time stamp file. */
if (unlink_it) { if (unlink_it) {
rval = unlink(timestamp_file) ? -1 : true; rval = unlink(fname) ? -1 : true;
goto done; goto done;
} }
/*
* Create a key used for matching entries in the time stamp file.
*/
memset(&timestamp_key, 0, sizeof(timestamp_key));
timestamp_key.version = TS_VERSION;
timestamp_key.size = sizeof(timestamp_key);
timestamp_key.type = TS_GLOBAL; /* may be overriden below */
timestamp_key.flags = TS_ANYUID;
if (def_tty_tickets) {
struct stat sb;
if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) {
/* tty-based time stamp */
timestamp_key.type = TS_TTY;
timestamp_key.u.ttydev = sb.st_rdev;
} else {
/* ppid-based time stamp */
timestamp_key.type = TS_PPID;
timestamp_key.u.ppid = getppid();
}
}
/* Open time stamp file and lock it for exclusive access. */ /* Open time stamp file and lock it for exclusive access. */
fd = open_timestamp(timestamp_file, O_RDWR); fd = ts_open(fname, O_RDWR);
switch (fd) { switch (fd) {
case TIMESTAMP_OPEN_ERROR: case TIMESTAMP_OPEN_ERROR:
if (errno != ENOENT) if (errno != ENOENT)
@@ -599,23 +697,25 @@ remove_timestamp(bool unlink_it)
rval = -1; rval = -1;
goto done; goto done;
} }
/* Lock first record to gain exclusive access. */
sudo_lock_region(fd, SUDO_LOCK, sizeof(struct timestamp_entry));
/* /*
* Find matching entries and invalidate them. * Find matching entries and invalidate them.
*/ */
while (ts_find_record(fd, &timestamp_key, &entry)) { ts_fill(&key, NULL, TS_DISABLED);
/* Set record position hint for use by update_timestamp() */ while (ts_find_record(fd, &key, &entry)) {
timestamp_hint = lseek(fd, (off_t)0, SEEK_CUR); /* Back up and disable the entry. */
if (timestamp_hint != (off_t)-1) lseek(fd, (off_t)0 - sizeof(entry), SEEK_CUR);
timestamp_hint -= (off_t)entry.size;
/* Disable the entry. */
SET(entry.flags, TS_DISABLED); SET(entry.flags, TS_DISABLED);
if (!ts_update_record(fd, &entry, timestamp_hint)) if (ts_write(fd, fname, &entry) == -1)
rval = false; rval = false;
} }
close(fd);
done: done:
if (fd != -1)
close(fd);
free(fname);
debug_return_int(rval); debug_return_int(rval);
} }
@@ -666,7 +766,7 @@ set_lectured(void)
goto done; goto done;
/* Create lecture file. */ /* Create lecture file. */
fd = open_timestamp(lecture_status, O_RDWR|O_CREAT|O_TRUNC); fd = ts_open(lecture_status, 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. */