Write an extended I/O info log in JSON format.

This will be used by sudoreplay if it exists to get more information
about the command being replayed.
This commit is contained in:
Todd C. Miller
2020-03-29 05:05:08 -06:00
parent a644c1d1d2
commit ea9b711a70
12 changed files with 270 additions and 47 deletions

View File

@@ -64,9 +64,14 @@ struct iolog_info {
char *runas_group;
char *tty;
char *cmd;
time_t tstamp;
char *host;
struct timespec tstamp;
int lines;
int cols;
uid_t runas_uid;
gid_t runas_gid;
char **argv;
char **envp;
};
struct timing_closure {
@@ -123,7 +128,7 @@ bool iolog_mkpath(char *path);
bool iolog_nextid(char *iolog_dir, char sessid[7]);
bool iolog_open(struct iolog_file *iol, int dfd, int iofd, const char *mode);
bool iolog_rename(const char *from, const char *to);
bool iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info, char * const argv[]);
bool iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info);
char *iolog_gets(struct iolog_file *iol, char *buf, size_t nbytes, const char **errsttr);
const char *iolog_fd_to_name(int iofd);
int iolog_openat(int fdf, const char *path, int flags);

View File

@@ -221,17 +221,17 @@ iolog_fileio.lo: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_fileio.c
iolog_fileio.i: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -E -o $@ $(CPPFLAGS) $<
iolog_fileio.plog: iolog_fileio.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_fileio.c --i-file $< --output-file $@

View File

@@ -50,6 +50,7 @@
#include "sudo_conf.h"
#include "sudo_debug.h"
#include "sudo_event.h"
#include "sudo_json.h"
#include "sudo_queue.h"
#include "sudo_util.h"
#include "sudo_fatal.h"
@@ -869,12 +870,12 @@ iolog_gets(struct iolog_file *iol, char *buf, size_t nbytes,
}
/*
* Write the I/O log file that contains the user and command info.
* Write the legacy I/O log file that contains the user and command info.
* This file is not compressed.
*/
bool
iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info,
char * const argv[])
static bool
iolog_write_info_file_legacy(int dfd, const char *parent,
struct iolog_info *log_info)
{
char * const *av;
FILE *fp;
@@ -896,7 +897,7 @@ iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info,
}
fprintf(fp, "%lld:%s:%s:%s:%s:%d:%d\n%s\n",
(long long)log_info->tstamp,
(long long)log_info->tstamp.tv_sec,
log_info->user ? log_info->user : "unknown",
log_info->runas_user ? log_info->runas_user : RUNAS_DEFAULT,
log_info->runas_group ? log_info->runas_group : "",
@@ -904,7 +905,7 @@ iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info,
log_info->lines, log_info->cols,
log_info->cwd ? log_info->cwd : "unknown");
fputs(log_info->cmd ? log_info->cmd : "unknown", fp);
for (av = argv + 1; *av != NULL; av++) {
for (av = log_info->argv + 1; *av != NULL; av++) {
fputc(' ', fp);
fputs(*av, fp);
}
@@ -919,6 +920,181 @@ iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info,
debug_return_bool(!error);
}
/*
* Write the "log.json" file that contains the user and command info.
* This file is not compressed.
*/
static bool
iolog_write_info_file_json(int dfd, const char *parent, struct iolog_info *info)
{
struct json_container json;
struct json_value json_value;
bool ret = false;
FILE *fp = NULL;
int fd = -1;
debug_decl(iolog_write_info_file_json, SUDO_DEBUG_UTIL);
if (info->cmd == NULL || info->user == NULL || info->runas_user == NULL)
debug_return_bool(false);
if (!sudo_json_init(&json, 4, false, false))
debug_return_bool(false);
/* Timestamp */
if (!sudo_json_open_object(&json, "timestamp"))
goto oom;
json_value.type = JSON_NUMBER;
json_value.u.number = info->tstamp.tv_sec;
if (!sudo_json_add_value(&json, "seconds", &json_value))
goto oom;
json_value.type = JSON_NUMBER;
json_value.u.number = info->tstamp.tv_nsec;
if (!sudo_json_add_value(&json, "nanoseconds", &json_value))
goto oom;
if (!sudo_json_close_object(&json))
goto oom;
json_value.type = JSON_NUMBER;
json_value.u.number = info->cols;
if (!sudo_json_add_value(&json, "columns", &json_value))
goto oom;
/* Required */
json_value.type = JSON_STRING;
json_value.u.string = info->cmd;
if (!sudo_json_add_value(&json, "command", &json_value))
goto oom;
json_value.type = JSON_NUMBER;
json_value.u.number = info->lines;
if (!sudo_json_add_value(&json, "lines", &json_value))
goto oom;
if (info->argv != NULL) {
json_value.type = JSON_ARRAY;
json_value.u.array = info->argv;
if (!sudo_json_add_value(&json, "runargv", &json_value))
goto oom;
}
if (info->envp != NULL) {
json_value.type = JSON_ARRAY;
json_value.u.array = info->envp;
if (!sudo_json_add_value(&json, "runenv", &json_value))
goto oom;
}
if (info->runas_group!= NULL) {
if (info->runas_uid != (uid_t)-1) {
json_value.type = JSON_ID;
json_value.u.id = info->runas_gid;
if (!sudo_json_add_value(&json, "rungid", &json_value))
goto oom;
}
json_value.type = JSON_STRING;
json_value.u.string = info->runas_group;
if (!sudo_json_add_value(&json, "rungroup", &json_value))
goto oom;
}
if (info->runas_uid != (uid_t)-1) {
json_value.type = JSON_ID;
json_value.u.id = info->runas_uid;
if (!sudo_json_add_value(&json, "runuid", &json_value))
goto oom;
}
/* Required */
json_value.type = JSON_STRING;
json_value.u.string = info->runas_user;
if (!sudo_json_add_value(&json, "runuser", &json_value))
goto oom;
if (info->cwd != NULL) {
json_value.type = JSON_STRING;
json_value.u.string = info->cwd;
if (!sudo_json_add_value(&json, "submitcwd", &json_value))
goto oom;
}
if (info->host != NULL) {
json_value.type = JSON_STRING;
json_value.u.string = info->host;
if (!sudo_json_add_value(&json, "submithost", &json_value))
goto oom;
}
/* Required */
json_value.type = JSON_STRING;
json_value.u.string = info->user;
if (!sudo_json_add_value(&json, "submituser", &json_value))
goto oom;
if (info->tty != NULL) {
json_value.type = JSON_STRING;
json_value.u.string = info->tty;
if (!sudo_json_add_value(&json, "ttyname", &json_value))
goto oom;
}
fd = iolog_openat(dfd, "log.json", O_CREAT|O_TRUNC|O_WRONLY);
if (fd == -1 || (fp = fdopen(fd, "w")) == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to open %s/log.json", parent);
goto done;
}
fd = -1;
if (fchown(fd, iolog_uid, iolog_gid) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to fchown %d:%d %s/log", __func__,
(int)iolog_uid, (int)iolog_gid, parent);
}
fprintf(fp, "{%s\n}\n", sudo_json_get_buf(&json));
fflush(fp);
if (ferror(fp)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to write to I/O log file %s/log.json", parent);
goto done;
}
ret = true;
goto done;
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
done:
sudo_json_free(&json);
if (fp != NULL)
fclose(fp);
if (fd != -1)
close(fd);
debug_return_bool(ret);
}
/*
* Write the I/O log and log.json files that contain user and command info.
* These files are not compressed.
*/
bool
iolog_write_info_file(int dfd, const char *parent,
struct iolog_info *log_info)
{
debug_decl(iolog_write_info_file, SUDO_DEBUG_UTIL);
if (!iolog_write_info_file_legacy(dfd, parent, log_info))
debug_return_bool(false);
if (!iolog_write_info_file_json(dfd, parent, log_info))
debug_return_bool(false);
debug_return_bool(true);
}
/*
* Map IOFD_* -> name.
*/

View File

@@ -95,6 +95,8 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
*/
if ((li = calloc(1, sizeof(*li))) == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
li->runas_uid = (uid_t)-1;
li->runas_gid = (gid_t)-1;
if (getdelim(&buf, &bufsize, '\n', fp) == -1 ||
getdelim(&li->cwd, &cwdsize, '\n', fp) == -1 ||
getdelim(&li->cmd, &cmdsize, '\n', fp) == -1) {
@@ -122,7 +124,7 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
goto bad;
}
*ep = '\0';
li->tstamp = sudo_strtonum(cp, 0, TIME_T_MAX, &errstr);
li->tstamp.tv_sec = sudo_strtonum(cp, 0, TIME_T_MAX, &errstr);
if (errstr != NULL) {
sudo_warn(U_("%s: time stamp %s: %s"), iolog_dir, cp, errstr);
goto bad;
@@ -427,6 +429,7 @@ iolog_free_loginfo(struct iolog_info *li)
free(li->runas_group);
free(li->tty);
free(li->cmd);
free(li->host);
free(li);
}
}

View File

@@ -552,7 +552,7 @@ do_logfile_sudo(const char *reason, const struct iolog_details *details)
goto done;
}
if ((timeptr = localtime(&details->submit_time)) != NULL) {
if ((timeptr = localtime(&details->submit_time.tv_sec)) != NULL) {
/* strftime() does not guarantee to NUL-terminate so we must check. */
timebuf[sizeof(timebuf) - 1] = '\0';
if (strftime(timebuf, sizeof(timebuf), timefmt, timeptr) != 0 &&

View File

@@ -145,11 +145,14 @@ iolog_details_fill(struct iolog_details *details, TimeSpec *submit_time,
memset(details, 0, sizeof(*details));
/* Submit time. */
details->submit_time = submit_time->tv_sec;
details->submit_time.tv_sec = submit_time->tv_sec;
details->submit_time.tv_nsec = submit_time->tv_nsec;
/* Default values */
details->lines = 24;
details->columns = 80;
details->runuid = (uid_t)-1;
details->rungid = (gid_t)-1;
/* Pull out values by key from info array. */
for (idx = 0; idx < infolen; idx++) {
@@ -211,6 +214,18 @@ iolog_details_fill(struct iolog_details *details, TimeSpec *submit_time,
}
continue;
}
if (strcmp(key, "rungid") == 0) {
if (!has_numval(info)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"rungid specified but not a number");
} else if (info->numval <= 0 || info->numval > INT_MAX) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"rungid (%" PRId64 ") out of range", info->numval);
} else {
details->rungid = info->numval;
}
continue;
}
if (strcmp(key, "rungroup") == 0) {
if (has_strval(info)) {
if ((details->rungroup = strdup(info->strval)) == NULL) {
@@ -225,6 +240,18 @@ iolog_details_fill(struct iolog_details *details, TimeSpec *submit_time,
}
continue;
}
if (strcmp(key, "runuid") == 0) {
if (!has_numval(info)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"runuid specified but not a number");
} else if (info->numval <= 0 || info->numval > INT_MAX) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"runuid (%" PRId64 ") out of range", info->numval);
} else {
details->runuid = info->numval;
}
continue;
}
if (strcmp(key, "runuser") == 0) {
if (has_strval(info)) {
if ((details->runuser = strdup(info->strval)) == NULL) {
@@ -595,12 +622,16 @@ iolog_details_write(struct iolog_details *details,
log_info.runas_group = details->rungroup;
log_info.tty = details->ttyname;
log_info.cmd = details->command;
log_info.host = details->submithost;
log_info.tstamp = details->submit_time;
log_info.lines = details->lines;
log_info.cols = details->columns;
log_info.runas_uid = details->runuid;
log_info.runas_gid = details->rungid;
log_info.argv = details->argv;
debug_return_bool(iolog_write_info_file(closure->iolog_dir_fd,
details->iolog_path, &log_info, details->argv));
details->iolog_path, &log_info));
}
static bool

View File

@@ -59,10 +59,12 @@ struct iolog_details {
char *ttyname;
char **argv;
char **env_add;
time_t submit_time;
struct timespec submit_time;
int argc;
int lines;
int columns;
uid_t runuid;
gid_t rungid;
char sessid[7];
};

View File

@@ -631,8 +631,8 @@ fmt_accept_message(struct client_closure *closure)
}
/* Sudo I/O logs only store start time in seconds. */
tv.tv_sec = log_info->tstamp;
tv.tv_nsec = 0;
tv.tv_sec = log_info->tstamp.tv_sec;
tv.tv_nsec = log_info->tstamp.tv_nsec;
accept_msg.submit_time = &tv;
/* Client will send IoBuffer messages. */

View File

@@ -1828,24 +1828,24 @@ match_command.plog: match_command.i
match_digest.lo: $(srcdir)/match_digest.c $(devdir)/def_data.h \
$(devdir)/gram.h $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \
$(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(incdir)/sudo_debug.h $(incdir)/sudo_digest.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(srcdir)/defaults.h \
$(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/sudo_nss.h \
$(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/match_digest.c
match_digest.i: $(srcdir)/match_digest.c $(devdir)/def_data.h \
$(devdir)/gram.h $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \
$(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(incdir)/sudo_debug.h $(incdir)/sudo_digest.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(srcdir)/defaults.h \
$(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/sudo_nss.h \
$(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -E -o $@ $(CPPFLAGS) $<
match_digest.plog: match_digest.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/match_digest.c --i-file $< --output-file $@

View File

@@ -490,17 +490,22 @@ write_info_log(int dfd, char *iolog_dir, struct iolog_details *details)
/* XXX - just use iolog_info in the first place? */
memset(&iolog_info, 0, sizeof(iolog_info));
time(&iolog_info.tstamp);
iolog_info.cwd = (char *)details->cwd;
iolog_info.user = (char *)details->user;
iolog_info.runas_user = details->runas_pw->pw_name;
iolog_info.runas_group = details->runas_gr ? details->runas_gr->gr_name: NULL;
iolog_info.tty = (char *)details->tty;
iolog_info.cwd = (char *)details->cwd;
iolog_info.cmd = (char *)details->command;
iolog_info.host = (char *)details->host;
sudo_gettime_real(&iolog_info.tstamp);
iolog_info.lines = details->lines;
iolog_info.cols = details->cols;
iolog_info.runas_uid = details->runas_pw->pw_uid;
iolog_info.runas_gid = details->runas_gr ? details->runas_gr->gr_gid: (gid_t)-1;
iolog_info.argv = (char **)details->argv;
iolog_info.envp = (char **)details->user_env;
if (!iolog_write_info_file(dfd, iolog_dir, &iolog_info, details->argv)) {
if (!iolog_write_info_file(dfd, iolog_dir, &iolog_info)) {
log_warning(SLOG_SEND_MAIL,
N_("unable to write to I/O log file: %s"), strerror(errno));
warned = true;

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2018-2019 Todd C. Miller <Todd.Miller@sudo.ws>
* Copyright (c) 2018-2020 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -133,9 +133,9 @@ validate_iolog_info(const char *log_dir)
return false;
}
if (info->tstamp < now - 10 || info->tstamp > now + 10) {
if (info->tstamp.tv_sec < now - 10 || info->tstamp.tv_sec > now + 10) {
sudo_warnx("bad tstamp: want %lld got %lld", (long long)now,
(long long)info->tstamp);
(long long)info->tstamp.tv_sec);
return false;
}

View File

@@ -124,7 +124,7 @@ struct search_node {
bool or;
union {
regex_t cmdre;
time_t tstamp;
struct timespec tstamp;
char *cwd;
char *tty;
char *user;
@@ -1224,8 +1224,9 @@ parse_expr(struct search_node_list *head, char *argv[], bool sub_expr)
if (regcomp(&sn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)
sudo_fatalx(U_("invalid regular expression: %s"), *av);
} else if (type == ST_TODATE || type == ST_FROMDATE) {
sn->u.tstamp = get_date(*av);
if (sn->u.tstamp == -1)
sn->u.tstamp.tv_sec = get_date(*av);
sn->u.tstamp.tv_nsec = 0;
if (sn->u.tstamp.tv_sec == -1)
sudo_fatalx(U_("could not parse date \"%s\""), *av);
} else {
sn->u.ptr = *av;
@@ -1283,10 +1284,10 @@ match_expr(struct search_node_list *head, struct iolog_info *log, bool last_matc
res = rc == REG_NOMATCH ? 0 : 1;
break;
case ST_FROMDATE:
res = log->tstamp >= sn->u.tstamp;
res = sudo_timespeccmp(&log->tstamp, &sn->u.tstamp, >=);
break;
case ST_TODATE:
res = log->tstamp <= sn->u.tstamp;
res = sudo_timespeccmp(&log->tstamp, &sn->u.tstamp, <=);
break;
default:
sudo_fatalx(U_("unknown search type %d"), sn->type);
@@ -1332,7 +1333,7 @@ list_session(char *log_dir, regex_t *re, const char *user, const char *tty)
idstr = cp;
}
/* XXX - print lines + cols? */
timestr = get_timestr(li->tstamp, 1);
timestr = get_timestr(li->tstamp.tv_sec, 1);
printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",
timestr ? timestr : "invalid date",
li->user, li->tty, li->cwd, li->runas_user);