Skip to content

Commit 71172a1

Browse files
committed
Change log ID from a path to UUID + path.
A UUID is now stored for each I/O log that sudo_logsrvd creates. The log ID in a restart message must now contain a log ID that matches both the I/O log path and the UUID it contains. For journaled I/O logs, only the UUID is used.
1 parent c0ec763 commit 71172a1

6 files changed

Lines changed: 278 additions & 111 deletions

File tree

logsrvd/iolog_writer.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ evlog_new(const TimeSpec *submit_time, InfoMessage * const *info_msgs,
151151
goto bad;
152152
}
153153

154+
/* Create a UUID to store in the event log. */
155+
sudo_uuid_create(uuid);
156+
if (sudo_uuid_to_string(uuid, evlog->uuid_str, sizeof(evlog->uuid_str)) == NULL) {
157+
sudo_warnx("%s", U_("unable to generate UUID"));
158+
goto bad;
159+
}
160+
154161
/* Client/peer IP address. */
155162
if ((evlog->peeraddr = strdup(closure->ipaddr)) == NULL) {
156163
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
@@ -687,6 +694,46 @@ iolog_flush_all(struct connection_closure *closure)
687694
debug_return_bool(ret);
688695
}
689696

697+
static bool
698+
iolog_store_uuid(int dfd, struct connection_closure *closure)
699+
{
700+
char uuid_str[37];
701+
int fd = -1;
702+
debug_decl(iolog_create_uuid, SUDO_DEBUG_UTIL);
703+
704+
/* Create a UUID to store in the I/O log. */
705+
sudo_uuid_create(closure->uuid);
706+
if (sudo_uuid_to_string(closure->uuid, uuid_str, sizeof(uuid_str)) == NULL) {
707+
sudo_warnx("%s", U_("unable to generate UUID"));
708+
goto bad;
709+
}
710+
711+
/* Write UUID in string form to the I/O log directory. */
712+
fd = iolog_openat(dfd, "uuid", O_CREAT|O_TRUNC|O_WRONLY);
713+
if (fd == -1) {
714+
sudo_warn(U_("unable to open %s/%s"), closure->evlog->iolog_path,
715+
"uuid");
716+
goto bad;
717+
}
718+
if (fchown(fd, iolog_get_uid(), iolog_get_gid()) != 0) {
719+
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
720+
"%s: unable to fchown %d:%d %s/uuid", __func__,
721+
(int)iolog_get_uid(), (int)iolog_get_gid(),
722+
closure->evlog->iolog_path);
723+
}
724+
if (write(fd, uuid_str, sizeof(uuid_str) - 1) != ssizeof(uuid_str) - 1) {
725+
sudo_warn(U_("unable to write %s/%s"), closure->evlog->iolog_path,
726+
"uuid");
727+
goto bad;
728+
}
729+
730+
debug_return_bool(true);
731+
bad:
732+
if (fd != -1)
733+
close(fd);
734+
debug_return_bool(false);
735+
}
736+
690737
bool
691738
iolog_init(const AcceptMessage *msg, struct connection_closure *closure)
692739
{
@@ -697,6 +744,10 @@ iolog_init(const AcceptMessage *msg, struct connection_closure *closure)
697744
if (!create_iolog_path(closure))
698745
debug_return_bool(false);
699746

747+
/* Create and store I/O log UUID */
748+
if (!iolog_store_uuid(closure->iolog_dir_fd, closure))
749+
debug_return_bool(false);
750+
700751
/* Write sudo I/O log info file */
701752
if (!iolog_write_info_file(closure->iolog_dir_fd, evlog))
702753
debug_return_bool(false);

logsrvd/logsrvd.c

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ get_free_buf(size_t len, struct connection_closure *closure)
322322
debug_return_ptr(NULL);
323323
}
324324

325-
static bool
325+
bool
326326
fmt_server_message(struct connection_closure *closure, ServerMessage *msg)
327327
{
328328
struct connection_buffer *buf = NULL;
@@ -376,16 +376,58 @@ fmt_hello_message(struct connection_closure *closure)
376376
debug_return_bool(fmt_server_message(closure, &msg));
377377
}
378378

379+
static char *
380+
gen_log_id(const unsigned char *uuid, const char *path,
381+
struct connection_closure *closure)
382+
{
383+
size_t b64_size, id_len, pathlen = strlen(path);
384+
char *b64_id = NULL;
385+
unsigned char *id;
386+
debug_decl(gen_log_id, SUDO_DEBUG_UTIL);
387+
388+
/* Log ID is 16-byte UUID + path. */
389+
id_len = 16 + pathlen;
390+
if ((id = malloc(id_len)) == NULL) {
391+
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
392+
goto done;
393+
}
394+
memcpy(id, uuid, 16);
395+
memcpy(id + 16, path, pathlen);
396+
397+
/* Base64 encode log ID */
398+
b64_size = ((id_len + 2) / 3 * 4) + 1;
399+
if ((b64_id = malloc(b64_size)) == NULL) {
400+
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
401+
goto done;
402+
}
403+
if (sudo_base64_encode(id, id_len, b64_id, b64_size) == (size_t)-1) {
404+
sudo_warnx("%s", U_("unable to base64 log ID"));
405+
free(b64_id);
406+
b64_id = NULL;
407+
}
408+
409+
done:
410+
free(id);
411+
debug_return_str(b64_id);
412+
}
413+
379414
bool
380-
fmt_log_id_message(const char *id, struct connection_closure *closure)
415+
fmt_log_id_message(const unsigned char uuid[restrict static 16],
416+
const char *path, struct connection_closure *closure)
381417
{
382418
ServerMessage msg = SERVER_MESSAGE__INIT;
419+
bool ret = false;
383420
debug_decl(fmt_log_id_message, SUDO_DEBUG_UTIL);
384421

385-
msg.u.log_id = (char *)id;
422+
/* Generate log_id from path and closure's UUID */
386423
msg.type_case = SERVER_MESSAGE__TYPE_LOG_ID;
424+
msg.u.log_id = gen_log_id(uuid, path, closure);
425+
if (msg.u.log_id != NULL) {
426+
ret = fmt_server_message(closure, &msg);
427+
free(msg.u.log_id);
428+
}
387429

388-
debug_return_bool(fmt_server_message(closure, &msg));
430+
debug_return_bool(ret);
389431
}
390432

391433
static bool
@@ -592,14 +634,6 @@ handle_restart(const RestartMessage *msg, const uint8_t *buf, size_t len,
592634
"%s: received RestartMessage for %s from %s", __func__, msg->log_id,
593635
source);
594636

595-
/* The log_id is used to create a path name, prevent path traversal. */
596-
if (contains_dot_dot(msg->log_id)) {
597-
sudo_warnx(U_("%s: %s"), source,
598-
U_("RestartMessage log_id path traversal attack"));
599-
closure->errstr = _("invalid RestartMessage");
600-
debug_return_bool(false);
601-
}
602-
603637
/* Only I/O logs are restartable. */
604638
closure->log_io = true;
605639

logsrvd/logsrvd.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ struct connection_closure {
118118
bool read_instead_of_write;
119119
bool write_instead_of_read;
120120
bool temporary_write_event;
121+
unsigned char uuid[16];
121122
#ifdef HAVE_STRUCT_IN6_ADDR
122123
char ipaddr[INET6_ADDRSTRLEN];
123124
#else
@@ -200,7 +201,8 @@ extern struct client_message_switch cms_local;
200201
bool start_protocol(struct connection_closure *closure);
201202
void connection_close(struct connection_closure *closure);
202203
bool schedule_commit_point(const TimeSpec *commit_point, struct connection_closure *closure);
203-
bool fmt_log_id_message(const char *id, struct connection_closure *closure);
204+
bool fmt_server_message(struct connection_closure *closure, ServerMessage *msg);
205+
bool fmt_log_id_message(const unsigned char uuid[restrict static 16], const char *path, struct connection_closure *closure);
204206
bool schedule_error_message(const char *errstr, struct connection_closure *closure);
205207
struct connection_buffer *get_free_buf(size_t, struct connection_closure *closure);
206208
struct connection_closure *connection_closure_alloc(int fd, bool tls, bool relay_only, struct sudo_event_base *base);

logsrvd/logsrvd_journal.c

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,13 @@ journal_fdopen(int fd, const char *journal_path,
8585
}
8686

8787
static int
88-
journal_mkstemp(const char *parent_dir, char *pathbuf, size_t pathsize)
88+
journal_mkuuid(const char *parent_dir, char *pathbuf, size_t pathsize)
8989
{
9090
int len, dfd = -1, fd = -1;
9191
mode_t dirmode, oldmask;
92-
char *template;
93-
debug_decl(journal_mkstemp, SUDO_DEBUG_UTIL);
92+
unsigned char uuid[16];
93+
char uuid_str[37];
94+
debug_decl(journal_mkuuid, SUDO_DEBUG_UTIL);
9495

9596
/* umask must not be more restrictive than the file modes. */
9697
dirmode = logsrvd_conf_iolog_mode() | S_IXUSR;
@@ -100,24 +101,35 @@ journal_mkstemp(const char *parent_dir, char *pathbuf, size_t pathsize)
100101
dirmode |= S_IXOTH;
101102
oldmask = umask(ACCESSPERMS & ~dirmode);
102103

103-
len = snprintf(pathbuf, pathsize, "%s/%s/%s",
104-
logsrvd_conf_relay_dir(), parent_dir, RELAY_TEMPLATE);
105-
if ((size_t)len >= pathsize) {
106-
errno = ENAMETOOLONG;
107-
sudo_warn("%s/%s/%s", logsrvd_conf_relay_dir(), parent_dir,
108-
RELAY_TEMPLATE);
109-
goto done;
110-
}
111-
dfd = sudo_open_parent_dir(pathbuf, logsrvd_conf_iolog_uid(),
112-
logsrvd_conf_iolog_gid(), S_IRWXU|S_IXGRP|S_IXOTH, false);
113-
if (dfd == -1) {
114-
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
115-
"unable to create parent dir for %s", pathbuf);
116-
goto done;
117-
}
118-
template = &pathbuf[(size_t)len - (sizeof(RELAY_TEMPLATE) - 1)];
119-
if ((fd = mkostempsat(dfd, template, 0, 0)) == -1) {
120-
sudo_warn(U_("%s: %s"), "mkstemp", pathbuf);
104+
do {
105+
sudo_uuid_create(uuid);
106+
if (sudo_uuid_to_string(uuid, uuid_str, sizeof(uuid_str)) == NULL) {
107+
sudo_warnx("%s", U_("unable to generate UUID"));
108+
goto done;
109+
}
110+
111+
len = snprintf(pathbuf, pathsize, "%s/%s/%s",
112+
logsrvd_conf_relay_dir(), parent_dir, uuid_str);
113+
if ((size_t)len >= pathsize) {
114+
errno = ENAMETOOLONG;
115+
sudo_warn("%s/%s/%s",
116+
logsrvd_conf_relay_dir(), parent_dir, uuid_str);
117+
goto done;
118+
}
119+
if (dfd == -1) {
120+
dfd = sudo_open_parent_dir(pathbuf, logsrvd_conf_iolog_uid(),
121+
logsrvd_conf_iolog_gid(), S_IRWXU|S_IXGRP|S_IXOTH, false);
122+
if (dfd == -1) {
123+
sudo_debug_printf(
124+
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
125+
"unable to create parent dir for %s", pathbuf);
126+
goto done;
127+
}
128+
}
129+
fd = openat(dfd, uuid_str, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
130+
} while (fd == -1 && errno == EEXIST);
131+
if (fd == -1) {
132+
sudo_warn(U_("%s: %s"), "openat", pathbuf);
121133
goto done;
122134
}
123135

@@ -139,7 +151,7 @@ journal_create(struct connection_closure *closure)
139151
int fd;
140152
debug_decl(journal_create, SUDO_DEBUG_UTIL);
141153

142-
fd = journal_mkstemp("incoming", journal_path, sizeof(journal_path));
154+
fd = journal_mkuuid("incoming", journal_path, sizeof(journal_path));
143155
if (fd == -1) {
144156
closure->errstr = _("unable to create journal file");
145157
debug_return_bool(false);
@@ -183,7 +195,7 @@ journal_finish(struct connection_closure *closure)
183195
rewind(closure->journal);
184196

185197
/* Move journal to the outgoing directory. */
186-
fd = journal_mkstemp("outgoing", outgoing_path, sizeof(outgoing_path));
198+
fd = journal_mkuuid("outgoing", outgoing_path, sizeof(outgoing_path));
187199
if (fd == -1) {
188200
closure->errstr = _("unable to rename journal file");
189201
debug_return_bool(false);
@@ -405,23 +417,30 @@ static bool
405417
journal_restart(const RestartMessage *msg, const uint8_t *buf, size_t buflen,
406418
struct connection_closure *closure)
407419
{
420+
char uuid_str[37], journal_path[PATH_MAX];
421+
unsigned char uuid[16];
408422
struct timespec target;
409423
int fd, len;
410-
char *cp, journal_path[PATH_MAX];
411424
debug_decl(journal_restart, SUDO_DEBUG_UTIL);
412425

413-
/* Strip off leading hostname from log_id. */
414-
if ((cp = strchr(msg->log_id, '/')) != NULL) {
415-
if (cp != msg->log_id)
416-
cp++;
417-
} else {
418-
cp = msg->log_id;
426+
/* A journal log_id must be a base64-encoded UUID. */
427+
if (strlen(msg->log_id) != ((16 + 2) / 3 * 4))
428+
goto parse_err;
429+
if (sudo_base64_decode(msg->log_id, uuid, sizeof(uuid)) != sizeof(uuid))
430+
goto parse_err;
431+
if (sudo_uuid_to_string(uuid, uuid_str, sizeof(uuid_str)) == NULL) {
432+
parse_err:
433+
sudo_warnx(U_("%s: %s"), __func__, U_("unable to parse log_id"));
434+
closure->errstr = _("unable to open journal file");
435+
debug_return_bool(false);
419436
}
437+
438+
/* Journal files are stored by UUID. */
420439
len = snprintf(journal_path, sizeof(journal_path), "%s/incoming/%s",
421-
logsrvd_conf_relay_dir(), cp);
440+
logsrvd_conf_relay_dir(), uuid_str);
422441
if (len >= ssizeof(journal_path)) {
423442
errno = ENAMETOOLONG;
424-
sudo_warn("%s/incoming/%s", logsrvd_conf_relay_dir(), cp);
443+
sudo_warn("%s/incoming/%s", logsrvd_conf_relay_dir(), uuid_str);
425444
closure->errstr = _("unable to open journal file");
426445
debug_return_bool(false);
427446
}
@@ -498,7 +517,19 @@ journal_accept(const AcceptMessage *msg, const uint8_t *buf, size_t len,
498517

499518
if (msg->expect_iobufs) {
500519
/* Send log ID to client for restarting connections. */
501-
if (!fmt_log_id_message(closure->journal_path, closure))
520+
unsigned char uuid[16];
521+
const char *uuid_str;
522+
523+
/* Journaled I/O log files are files stored by UUID. */
524+
uuid_str = strrchr(closure->journal_path, '/');
525+
if (uuid_str == NULL)
526+
debug_return_bool(false);
527+
uuid_str++;
528+
529+
/* Parse UUID and format as a log ID (base64-encoded). */
530+
if (sudo_uuid_from_string(uuid_str, uuid) != 0)
531+
debug_return_bool(false);
532+
if (!fmt_log_id_message(uuid, "", closure))
502533
debug_return_bool(false);
503534
if (sudo_ev_add(closure->evbase, closure->write_ev,
504535
logsrvd_conf_server_timeout(), false) == -1) {

0 commit comments

Comments
 (0)