Add support for restarting I/O log transfers.

This commit is contained in:
Todd C. Miller
2019-10-24 20:04:30 -06:00
parent 8a16e62a88
commit e76d2e8201
6 changed files with 330 additions and 64 deletions

View File

@@ -108,7 +108,8 @@ SHELL = @SHELL@
PROGS = logsrvd sendlog
LOGSRVD_OBJS = iolog_writer.o logsrvd.o log_server.pb-c.o protobuf-c.o
LOGSRVD_OBJS = iolog_util.o iolog_writer.o logsrvd.o log_server.pb-c.o \
protobuf-c.o
SENDLOG_OBJS = sendlog.o iolog_util.o log_server.pb-c.o protobuf-c.o
@@ -232,14 +233,16 @@ iolog_writer.o: $(srcdir)/iolog_writer.c $(devdir)/log_server.pb-c.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(srcdir)/logsrvd.h $(srcdir)/protobuf-c/protobuf-c.h \
$(sudoers_srcdir)/iolog.h $(top_builddir)/config.h
$(sudoers_srcdir)/iolog.h $(sudoers_srcdir)/iolog_util.h \
$(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_writer.c
iolog_writer.i: $(srcdir)/iolog_writer.c $(devdir)/log_server.pb-c.h \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(srcdir)/logsrvd.h $(srcdir)/protobuf-c/protobuf-c.h \
$(sudoers_srcdir)/iolog.h $(top_builddir)/config.h
$(sudoers_srcdir)/iolog.h $(sudoers_srcdir)/iolog_util.h \
$(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
iolog_writer.plog: iolog_writer.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_writer.c --i-file $< --output-file $@

View File

@@ -42,7 +42,7 @@
#include "sudo_debug.h"
#include "sudo_util.h"
#include "sudo_fatal.h"
#include "iolog.h"
#include "iolog_util.h"
#include "logsrvd.h"
/* I/O log file names relative to iolog_dir. */
@@ -294,7 +294,7 @@ create_iolog_dir(struct iolog_details *details, struct connection_closure *closu
closure->iolog_dir_fd = open(closure->iolog_dir, O_RDONLY);
if (closure->iolog_dir_fd == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"%s", path);
"%s", closure->iolog_dir);
goto bad;
}
@@ -414,6 +414,151 @@ iolog_init(ExecMessage *msg, struct connection_closure *closure)
debug_return_bool(true);
}
/*
* Read the next record from the timing file.
* Return 0 on success, 1 on EOF and -1 on error.
*/
static int
read_timing_record(FILE *fp, struct timing_closure *timing)
{
char line[LINE_MAX];
debug_decl(read_timing_record, SUDO_DEBUG_UTIL)
/* Read next record from timing file. */
if (fgets(line, sizeof(line), fp) == NULL) {
/* EOF or error reading timing file, we are done. */
if (feof(fp))
debug_return_int(1); /* EOF */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"error reading timing file");
debug_return_int(-1);
}
/* Parse timing file record. */
line[strcspn(line, "\n")] = '\0';
if (!parse_timing(line, timing)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"invalid timing file line: %s", line);
debug_return_int(-1);
}
debug_return_int(0);
}
bool
iolog_restart(RestartMessage *msg, struct connection_closure *closure)
{
struct timespec target;
struct timing_closure timing;
FILE *fp = NULL;
int i, fd;
off_t length;
debug_decl(iolog_init, SUDO_DEBUG_UTIL)
target.tv_sec = msg->resume_point->tv_sec;
target.tv_nsec = msg->resume_point->tv_nsec;
/* We use iolog_dir_fd in calls to openat(2) */
if ((closure->iolog_dir = strdup(msg->log_id)) == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"strdup");
goto bad;
}
closure->iolog_dir_fd = open(closure->iolog_dir, O_RDONLY);
if (closure->iolog_dir_fd == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"%s", closure->iolog_dir);
goto bad;
}
/* Open existing I/O log files. */
for (i = 0; i < IOFD_MAX; i++) {
closure->io_fds[i] = openat(closure->iolog_dir_fd, iolog_names[i],
O_RDWR, 0600);
}
/* Dup IOFD_TIMING to a stream for easier processing. */
if (closure->io_fds[IOFD_TIMING] == -1)
goto bad;
if ((fd = dup(closure->io_fds[IOFD_TIMING])) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"dup");
goto bad;
}
if ((fp = fdopen(fd, "r")) == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"fdopen");
goto bad;
}
/* Parse timing file until we reach the target point. */
/* XXX - split up */
for (;;) {
if (read_timing_record(fp, &timing) != 0)
goto bad;
sudo_timespecadd(&timing.delay, &closure->elapsed_time,
&closure->elapsed_time);
if (timing.event < IOFD_TIMING) {
if (closure->io_fds[timing.event] == -1) {
/* Missing log file. */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"iofd %d referenced but not open", timing.event);
goto bad;
}
length = lseek(closure->io_fds[timing.event], timing.u.nbytes,
SEEK_CUR);
if (length == -1) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"lseek(%d, %lld, SEEK_CUR", closure->io_fds[timing.event],
(long long)timing.u.nbytes);
goto bad;
}
if (ftruncate(closure->io_fds[timing.event], length) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"ftruncate(%d, %lld)", closure->io_fds[IOFD_TIMING], length);
goto bad;
}
}
if (sudo_timespeccmp(&closure->elapsed_time, &target, >=)) {
if (sudo_timespeccmp(&closure->elapsed_time, &target, ==))
break;
/* Mismatch between resume point and stored log. */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"resume point mismatch, target [%lld, %ld], have [%lld, %ld]",
(long long)target.tv_sec, target.tv_nsec,
(long long)closure->elapsed_time.tv_sec,
closure->elapsed_time.tv_nsec);
goto bad;
}
}
/* Update timing file position after determining resume point. */
#ifdef HAVE_FSEEKO
length = ftello(fp);
#else
length = ftell(fp);
#endif
fclose(fp);
if (lseek(closure->io_fds[IOFD_TIMING], length, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"lseek(%d, %lld, SEEK_SET", closure->io_fds[IOFD_TIMING], length);
goto bad;
}
if (ftruncate(closure->io_fds[IOFD_TIMING], length) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"ftruncate(%d, %lld)", closure->io_fds[IOFD_TIMING], length);
goto bad;
}
/* Ready to log I/O buffers. */
debug_return_bool(true);
bad:
if (fp != NULL)
fclose(fp);
debug_return_bool(false);
}
static bool
iolog_write(int iofd, void *buf, size_t len, struct connection_closure *closure)
{

View File

@@ -156,6 +156,18 @@ fmt_log_id_message(const char *id, struct connection_buffer *buf)
debug_return_bool(fmt_server_message(buf, &msg));
}
static bool
fmt_error_message(const char *errstr, struct connection_buffer *buf)
{
ServerMessage msg = SERVER_MESSAGE__INIT;
debug_decl(fmt_error_message, SUDO_DEBUG_UTIL)
msg.error = (char *)errstr;
msg.type_case = SERVER_MESSAGE__TYPE_ERROR;
debug_return_bool(fmt_server_message(buf, &msg));
}
/*
* Parse an ExecMessage
*/
@@ -211,6 +223,7 @@ handle_exit(ExitMessage *msg, struct connection_closure *closure)
"unexpected state %d", closure->state);
debug_return_bool(false);
}
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received ExitMessage", __func__);
/* Sudo I/O logs don't store this info. */
if (msg->signal != NULL && msg->signal[0] != '\0') {
@@ -250,12 +263,26 @@ handle_restart(RestartMessage *msg, struct connection_closure *closure)
"unexpected state %d", closure->state);
debug_return_bool(false);
}
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received RestartMessage for %s",
__func__, msg->log_id);
/* TODO */
closure->state = RESTARTING;
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"server restart not implemented");
if (!iolog_restart(msg, closure)) {
sudo_debug_printf(SUDO_DEBUG_WARN, "%s: unable to restart I/O log", __func__);
/* XXX - structured error message so client can send from beginning */
if (!fmt_error_message("unable to restart log", &closure->write_buf))
debug_return_bool(false);
sudo_ev_del(NULL, closure->read_ev);
if (sudo_ev_add(NULL, closure->write_ev, NULL, false) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to add server write event");
debug_return_bool(false);
}
closure->state = ERROR;
debug_return_bool(true);
}
closure->state = RUNNING;
debug_return_bool(true);
}
static bool
@@ -491,7 +518,8 @@ server_msg_cb(int fd, int what, void *v)
buf->off = 0;
buf->len = 0;
sudo_ev_del(NULL, closure->write_ev);
if (closure->state == FLUSHED || closure->state == SHUTDOWN)
if (closure->state == FLUSHED || closure->state == SHUTDOWN ||
closure->state == ERROR)
goto finished;
}
debug_return;

View File

@@ -66,10 +66,10 @@ struct iolog_details {
enum connection_status {
INITIAL,
RUNNING,
RESTARTING,
EXITED,
SHUTDOWN,
FLUSHED
FLUSHED,
ERROR
};
union sockaddr_union {
@@ -109,6 +109,7 @@ struct connection_closure {
/* iolog.c */
bool iolog_init(ExecMessage *msg, struct connection_closure *closure);
bool iolog_restart(RestartMessage *msg, struct connection_closure *closure);
int store_iobuf(int iofd, IoBuffer *msg, struct connection_closure *closure);
int store_suspend(CommandSuspend *msg, struct connection_closure *closure);
int store_winsize(ChangeWindowSize *msg, struct connection_closure *closure);

View File

@@ -59,7 +59,7 @@
static gzFile io_fds[IOFD_MAX];
/* I/O log file names relative to iolog_dir. */
/* I/O log file names relative to the iolog dir. */
/* XXX - duplicated with server */
static const char *iolog_names[] = {
"stdin", /* IOFD_STDIN */
@@ -74,8 +74,8 @@ static const char *iolog_names[] = {
static void
usage(void)
{
fprintf(stderr, "usage: %s [-h host] [-p port] /path/to/iolog\n",
getprogname());
fprintf(stderr, "usage: %s [-h host] [-i iolog-id] [-p port] "
"[-r restart-point] /path/to/iolog\n", getprogname());
exit(1);
}
@@ -138,7 +138,7 @@ connect_server(const char *host, const char *port)
}
/*
* Free client closure allocated by client_closure_alloc()
* Free client closure contents.
*/
static void
client_closure_free(struct client_closure *closure)
@@ -151,7 +151,8 @@ client_closure_free(struct client_closure *closure)
free(closure->read_buf.data);
free(closure->write_buf.data);
free(closure->buf);
free(closure);
// XXX - avoid use after free
//free(closure);
}
debug_return;
@@ -431,6 +432,42 @@ done:
debug_return_bool(ret);
}
/*
* Build and format a RestartMessage wrapped in a ClientMessage.
* Stores the wire format message in the closure's write buffer.
* Returns true on success, false on failure.
*/
static bool
fmt_restart_message(struct client_closure *closure)
{
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
RestartMessage restart_msg = RESTART_MESSAGE__INIT;
TimeSpec tv = TIME_SPEC__INIT;
bool ret = false;
debug_decl(fmt_restart_message, SUDO_DEBUG_UTIL)
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: sending RestartMessage, [%lld, %ld]", __func__,
(long long)closure->restart->tv_sec, closure->restart->tv_nsec);
tv.tv_sec = closure->restart->tv_sec;
tv.tv_nsec = closure->restart->tv_nsec;
restart_msg.resume_point = &tv;
restart_msg.log_id = (char *)closure->iolog_id;
/* Schedule ClientMessage */
client_msg.restart_msg = &restart_msg;
client_msg.type_case = CLIENT_MESSAGE__TYPE_RESTART_MSG;
ret = fmt_client_message(&closure->write_buf, &client_msg);
if (ret) {
if (sudo_ev_add(NULL, closure->write_ev, NULL, false) == -1)
ret = false;
}
ret = true;
debug_return_bool(ret);
}
/*
* Build and format an ExitMessage wrapped in a ClientMessage.
* Stores the wire format message in the closure's write buffer.
@@ -603,6 +640,7 @@ fmt_next_iolog(struct client_closure *closure)
}
/* TODO: fill write buffer with multiple messages */
again:
switch (read_timing_record(timing)) {
case 0:
/* OK */
@@ -616,6 +654,16 @@ fmt_next_iolog(struct client_closure *closure)
debug_return_bool(false);
}
/* Track elapsed time for comparison with commit points. */
sudo_timespecadd(&timing->delay, &closure->elapsed, &closure->elapsed);
/* If we have a restart point, ignore records until we hit it. */
if (closure->restart != NULL) {
if (sudo_timespeccmp(closure->restart, &closure->elapsed, >=))
goto again;
closure->restart = NULL; /* caught up */
}
switch (timing->event) {
case IO_EVENT_STDIN:
ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_STDIN_BUF, closure, buf);
@@ -643,9 +691,6 @@ fmt_next_iolog(struct client_closure *closure)
break;
}
/* Track elapsed time for comparison with commit points. */
sudo_timespecadd(&timing->delay, &closure->elapsed, &closure->elapsed);
debug_return_bool(ret);
}
@@ -660,6 +705,7 @@ client_message_completion(struct client_closure *closure)
switch (closure->state) {
case SEND_EXEC:
case SEND_RESTART:
closure->state = SEND_IO;
/* FALLTHROUGH */
case SEND_IO:
@@ -744,7 +790,7 @@ handle_log_id(char *id, struct client_closure *closure)
debug_decl(handle_log_id, SUDO_DEBUG_UTIL)
printf("remote log ID: %s\n", id);
if ((closure->iolog_dir = strdup(id)) == NULL)
if ((closure->iolog_id = strdup(id)) == NULL)
sudo_fatal(NULL);
debug_return_bool(true);
}
@@ -797,9 +843,14 @@ handle_server_message(uint8_t *buf, size_t len,
switch (msg->type_case) {
case SERVER_MESSAGE__TYPE_HELLO:
if ((ret = handle_server_hello(msg->hello, closure))) {
if (sudo_timespecisset(closure->restart)) {
closure->state = SEND_RESTART;
ret = fmt_restart_message(closure);
} else {
closure->state = SEND_EXEC;
ret = fmt_exec_message(closure);
}
}
break;
case SERVER_MESSAGE__TYPE_COMMIT_POINT:
ret = handle_commit_point(msg->commit_point, closure);
@@ -930,20 +981,23 @@ bad:
}
/*
* Allocate a new connection closure.
* Initialize a new client closure
*/
static struct client_closure *
client_closure_alloc(int sock, struct iolog_info *log_info)
static bool
client_closure_fill(struct client_closure *closure, int sock,
struct timespec *restart, const char *iolog_id,
struct iolog_info *log_info)
{
struct client_closure *closure;
debug_decl(client_closure_alloc, SUDO_DEBUG_UTIL)
debug_decl(client_closure_fill, SUDO_DEBUG_UTIL)
if ((closure = calloc(1, sizeof(*closure))) == NULL)
debug_return_ptr(NULL);
memset(closure, 0, sizeof(*closure));
closure->state = RECV_HELLO;
closure->log_info = log_info;
closure->restart = restart;
closure->iolog_id = iolog_id;
closure->bufsize = 10240;
closure->buf = malloc(closure->bufsize);
if (closure->buf == NULL)
@@ -969,10 +1023,10 @@ client_closure_alloc(int sock, struct iolog_info *log_info)
if (closure->write_ev == NULL)
goto bad;
debug_return_ptr(closure);
debug_return_bool(true);
bad:
client_closure_free(closure);
debug_return_ptr(NULL);
debug_return_bool(false);
}
/*
@@ -1003,16 +1057,55 @@ iolog_open_all(const char *iolog_path)
debug_return_bool(true);
}
/*
* Parse a timespec on the command line of the form
* seconds[,nanoseconds]
*/
static bool
parse_timespec(struct timespec *ts, const char *strval)
{
long long tv_sec;
long tv_nsec;
char *ep;
debug_decl(parse_timespec, SUDO_DEBUG_UTIL)
errno = 0;
tv_sec = strtoll(strval, &ep, 10);
if (strval == ep || (*ep != ',' && *ep != '\0'))
debug_return_bool(false);
#if TIME_T_MAX != LLONG_MAX
if (tv_sec > TIME_T_MAX)
debug_return_bool(false);
#endif
if (tv_sec < 0 || (errno == ERANGE && tv_sec == LLONG_MAX))
debug_return_bool(false);
strval = ep + 1;
errno = 0;
tv_nsec = strtol(strval, &ep, 10);
if (strval == ep || *ep != '\0')
debug_return_bool(false);
if (tv_nsec < 0 || (errno == ERANGE && tv_nsec == LONG_MAX))
debug_return_bool(false);
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: parsed timespec [%lld, %ld]",
__func__, tv_sec, tv_nsec);
ts->tv_sec = (time_t)tv_sec;
ts->tv_nsec = tv_nsec;
debug_return_bool(true);
}
int
main(int argc, char *argv[])
{
struct client_closure *closure;
struct client_closure closure;
struct sudo_event_base *evbase;
struct iolog_info *log_info;
const char *host = "localhost";
const char *port = DEFAULT_PORT_STR;
char fname[PATH_MAX];
char *iolog_path;
struct timespec restart = { 0, 0 };
char fname[PATH_MAX], *iolog_path;
const char *iolog_id = NULL;
int ch, sock;
debug_decl_vars(main, SUDO_DEBUG_MAIN)
@@ -1030,14 +1123,21 @@ main(int argc, char *argv[])
if (protobuf_c_version_number() < 1003000)
sudo_fatalx("Protobuf-C version 1.3 or higher required");
while ((ch = getopt(argc, argv, "h:p:")) != -1) {
while ((ch = getopt(argc, argv, "h:i:p:r:")) != -1) {
switch (ch) {
case 'h':
host = optarg;
break;
case 'i':
iolog_id = optarg;
break;
case 'p':
port = optarg;
break;
case 'r':
if (!parse_timespec(&restart, optarg))
goto bad;
break;
default:
usage();
}
@@ -1045,6 +1145,11 @@ main(int argc, char *argv[])
argc -= optind;
argv += optind;
if (sudo_timespecisset(&restart) != (iolog_id != NULL)) {
sudo_warnx("must specify both restart point and iolog ID");
usage();
}
/* Remaining arg should be path to I/O log file to send. */
if (argc != 1)
usage();
@@ -1071,22 +1176,20 @@ main(int argc, char *argv[])
sudo_fatal(NULL);
sudo_ev_base_setdef(evbase);
if ((closure = client_closure_alloc(sock, log_info)) == NULL)
if (!client_closure_fill(&closure, sock, &restart, iolog_id, log_info))
goto bad;
/* Add read event for the server hello message and enter event loop. */
if (sudo_ev_add(evbase, closure->read_ev, NULL, false) == -1)
if (sudo_ev_add(evbase, closure.read_ev, NULL, false) == -1)
goto bad;
sudo_ev_dispatch(evbase);
if (!sudo_timespeccmp(&closure->elapsed, &closure->committed, ==)) {
sudo_warnx("commit point mismatch, expected [%lld, %ld], got [%lld, %ld]",
(long long)closure->elapsed.tv_sec, closure->elapsed.tv_nsec,
(long long)closure->committed.tv_sec, closure->committed.tv_nsec);
}
if (closure->state != FINISHED) {
sudo_warnx("exited prematurely with state %d", closure->state);
if (closure.state != FINISHED) {
sudo_warnx("exited prematurely with state %d", closure.state);
sudo_warnx("elapsed time sent to server [%lld, %ld]",
(long long)closure.elapsed.tv_sec, closure.elapsed.tv_nsec);
sudo_warnx("commit point received from server [%lld, %ld]",
(long long)closure.committed.tv_sec, closure.committed.tv_nsec);
goto bad;
}
printf("I/O log transmitted successfully\n");

View File

@@ -33,25 +33,10 @@
#define IOFD_TIMING 5
#define IOFD_MAX 6
#if 0
struct timing_closure {
struct timespec delay;
int event;
union {
struct {
int lines;
int columns;
} winsize;
size_t nbytes;
} u;
char *buf;
size_t bufsize;
};
#endif
enum client_state {
ERROR,
RECV_HELLO,
SEND_RESTART,
SEND_EXEC,
SEND_IO,
SEND_EXIT,
@@ -68,6 +53,7 @@ struct connection_buffer {
};
struct client_closure {
struct timespec *restart;
struct timespec elapsed;
struct timespec committed;
struct timing_closure timing;
@@ -76,7 +62,7 @@ struct client_closure {
struct sudo_event *read_ev;
struct sudo_event *write_ev;
struct iolog_info *log_info;
char *iolog_dir;
const char *iolog_id;
char *buf; /* XXX */
size_t bufsize; /* XXX */
enum client_state state;