diff -up ./doc/sudoers.man.in.cve ./doc/sudoers.man.in --- ./doc/sudoers.man.in.cve 2021-01-09 21:12:16.000000000 +0100 +++ ./doc/sudoers.man.in 2023-12-04 16:52:54.499061280 +0100 @@ -4978,14 +4978,31 @@ can log events via syslog(3), to a local log file, or both. The log format is almost identical in both cases. +Any control characters present in the log data are formatted in octal +with a leading +\(oq#\(cq +character. +For example, a horizontal tab is stored as +\(oq#011\(cq +and an embedded carriage return is stored as +\(oq#015\(cq. +In addition, space characters in the command path are stored as +\(oq#040\(cq. +Command line arguments that contain spaces are enclosed in single quotes +(''). +This makes it possible to distinguish multiple command line arguments +from a single argument that contains spaces. +Literal single quotes and backslash characters +(\(oq\e\(cq) +in command line arguments are escaped with a backslash. .SS "Accepted command log entries" Commands that sudo runs are logged using the following format (split into multiple lines for readability): .nf .sp .RS 4n -date hostname progname: username : TTY=ttyname ; PWD=cwd ; \e - USER=runasuser ; GROUP=runasgroup ; TSID=logid ; \e +date hostname progname: username : TTY=ttyname ; CHROOT=chroot ; \e + PWD=cwd ; USER=runasuser ; GROUP=runasgroup ; TSID=logid ; \e ENV=env_vars COMMAND=command .RE .fi @@ -5034,6 +5051,9 @@ was run on, or \(lqunknown\(rq if there was no terminal present. .TP 14n +chroot +The root directory that the command was run in, if one was specified. +.TP 14n cwd The current working directory that \fBsudo\fR @@ -5058,7 +5078,7 @@ A list of environment variables specifie if specified. .TP 14n command -The actual command that was executed. +The actual command that was executed, including any command line arguments. .PP Messages are logged using the locale specified by \fIsudoers_locale\fR, @@ -5294,17 +5314,21 @@ with a few important differences: 1.\& The \fIprogname\fR -and -\fIhostname\fR -fields are not present. +field is not present. .TP 5n 2.\& -If the -\fIlog_year\fR -option is enabled, -the date will also include the year. +The +\fIhostname\fR +is only logged if the +\fIlog_host\fR +option is enabled. .TP 5n 3.\& +The date does not include the year unless the +\fIlog_year\fR +option is enabled. +.TP 5n +4.\& Lines that are longer than \fIloglinelen\fR characters (80 by default) are word-wrapped and continued on the diff -up ./doc/sudoers.mdoc.in.cve ./doc/sudoers.mdoc.in --- ./doc/sudoers.mdoc.in.cve 2021-01-09 21:12:16.000000000 +0100 +++ ./doc/sudoers.mdoc.in 2023-12-04 16:52:54.500061266 +0100 @@ -4649,12 +4649,29 @@ can log events via .Xr syslog 3 , to a local log file, or both. The log format is almost identical in both cases. +Any control characters present in the log data are formatted in octal +with a leading +.Ql # +character. +For example, a horizontal tab is stored as +.Ql #011 +and an embedded carriage return is stored as +.Ql #015 . +In addition, space characters in the command path are stored as +.Ql #040 . +Command line arguments that contain spaces are enclosed in single quotes +.Pq '' . +This makes it possible to distinguish multiple command line arguments +from a single argument that contains spaces. +Literal single quotes and backslash characters +.Pq Ql \e +in command line arguments are escaped with a backslash. .Ss Accepted command log entries Commands that sudo runs are logged using the following format (split into multiple lines for readability): .Bd -literal -offset 4n -date hostname progname: username : TTY=ttyname ; PWD=cwd ; \e - USER=runasuser ; GROUP=runasgroup ; TSID=logid ; \e +date hostname progname: username : TTY=ttyname ; CHROOT=chroot ; \e + PWD=cwd ; USER=runasuser ; GROUP=runasgroup ; TSID=logid ; \e ENV=env_vars COMMAND=command .Ed .Pp @@ -4697,6 +4714,8 @@ or was run on, or .Dq unknown if there was no terminal present. +.It chroot +The root directory that the command was run in, if one was specified. .It cwd The current working directory that .Nm sudo @@ -4716,7 +4735,7 @@ option is enabled. A list of environment variables specified on the command line, if specified. .It command -The actual command that was executed. +The actual command that was executed, including any command line arguments. .El .Pp Messages are logged using the locale specified by @@ -4938,14 +4957,17 @@ with a few important differences: .It The .Em progname -and +field is not present. +.It +The .Em hostname -fields are not present. +is only logged if the +.Em log_host +option is enabled. .It -If the +The date does not include the year unless the .Em log_year -option is enabled, -the date will also include the year. +option is enabled. .It Lines that are longer than .Em loglinelen diff -up ./doc/sudoreplay.man.in.cve ./doc/sudoreplay.man.in --- ./doc/sudoreplay.man.in.cve 2020-12-17 02:33:43.000000000 +0100 +++ ./doc/sudoreplay.man.in 2023-12-04 16:52:54.500061266 +0100 @@ -164,6 +164,15 @@ In this mode, will list available sessions in a format similar to the \fBsudo\fR log file format, sorted by file name (or sequence number). +Any control characters present in the log data are formated in octal +with a leading +\(oq#\(cq +character. +For example, a horizontal tab is displayed as +\(oq#011\(cq +and an embedded carriage return is displayed as +\(oq#015\(cq. +.sp If a \fIsearch expression\fR is specified, it will be used to restrict the IDs that are displayed. diff -up ./doc/sudoreplay.mdoc.in.cve ./doc/sudoreplay.mdoc.in --- ./doc/sudoreplay.mdoc.in.cve 2020-12-17 02:33:43.000000000 +0100 +++ ./doc/sudoreplay.mdoc.in 2023-12-04 16:52:54.500061266 +0100 @@ -156,6 +156,16 @@ In this mode, will list available sessions in a format similar to the .Nm sudo log file format, sorted by file name (or sequence number). +Any control characters present in the log data are formatted in octal +with a leading +.Ql # +character. +For example, a horizontal tab is displayed as +.Ql #011 +and an embedded carriage return is displayed as +.Ql #015 . +Space characters in the command name and arguments are also formatted in octal. +.Pp If a .Ar search expression is specified, it will be used to restrict the IDs that are displayed. diff -up ./include/sudo_lbuf.h.cve ./include/sudo_lbuf.h --- ./include/sudo_lbuf.h.cve 2020-12-17 02:33:43.000000000 +0100 +++ ./include/sudo_lbuf.h 2023-12-04 16:52:54.500061266 +0100 @@ -36,9 +36,15 @@ struct sudo_lbuf { typedef int (*sudo_lbuf_output_t)(const char *); +/* Flags for sudo_lbuf_append_esc() */ +#define LBUF_ESC_CNTRL 0x01 +#define LBUF_ESC_BLANK 0x02 +#define LBUF_ESC_QUOTE 0x04 + sudo_dso_public void sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output, int indent, const char *continuation, int cols); sudo_dso_public void sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf); sudo_dso_public bool sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char *fmt, ...) __printflike(2, 3); +sudo_dso_public bool sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char *fmt, ...) __printflike(3, 4); sudo_dso_public bool sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char *fmt, ...) __printflike(3, 4); sudo_dso_public void sudo_lbuf_print_v1(struct sudo_lbuf *lbuf); sudo_dso_public bool sudo_lbuf_error_v1(struct sudo_lbuf *lbuf); @@ -47,6 +53,7 @@ sudo_dso_public void sudo_lbuf_clearerr_ #define sudo_lbuf_init(_a, _b, _c, _d, _e) sudo_lbuf_init_v1((_a), (_b), (_c), (_d), (_e)) #define sudo_lbuf_destroy(_a) sudo_lbuf_destroy_v1((_a)) #define sudo_lbuf_append sudo_lbuf_append_v1 +#define sudo_lbuf_append_esc sudo_lbuf_append_esc_v1 #define sudo_lbuf_append_quoted sudo_lbuf_append_quoted_v1 #define sudo_lbuf_print(_a) sudo_lbuf_print_v1((_a)) #define sudo_lbuf_error(_a) sudo_lbuf_error_v1((_a)) diff -up ./lib/eventlog/eventlog.c.cve ./lib/eventlog/eventlog.c --- ./lib/eventlog/eventlog.c.cve 2023-12-04 16:52:54.497061306 +0100 +++ ./lib/eventlog/eventlog.c 2023-12-04 16:54:09.820048779 +0100 @@ -51,24 +51,13 @@ #include "sudo_compat.h" #include "sudo_debug.h" #include "sudo_eventlog.h" +#include "sudo_lbuf.h" #include "sudo_fatal.h" #include "sudo_gettext.h" #include "sudo_json.h" #include "sudo_queue.h" #include "sudo_util.h" -#define LL_HOST_STR "HOST=" -#define LL_TTY_STR "TTY=" -#define LL_CHROOT_STR "CHROOT=" -#define LL_CWD_STR "PWD=" -#define LL_USER_STR "USER=" -#define LL_GROUP_STR "GROUP=" -#define LL_ENV_STR "ENV=" -#define LL_CMND_STR "COMMAND=" -#define LL_TSID_STR "TSID=" -#define LL_EXIT_STR "EXIT=" -#define LL_SIGNAL_STR "SIGNAL=" - #define IS_SESSID(s) ( \ isalnum((unsigned char)(s)[0]) && isalnum((unsigned char)(s)[1]) && \ (s)[2] == '/' && \ @@ -96,26 +85,28 @@ new_logline(int event_type, int flags, s const struct eventlog *evlog) { const struct eventlog_config *evl_conf = eventlog_getconf(); - char *line = NULL, *evstr = NULL; const char *iolog_file; const char *tty, *tsid = NULL; char exit_str[(((sizeof(int) * 8) + 2) / 3) + 2]; char sessid[7]; - size_t len = 0; + struct sudo_lbuf lbuf; int i; debug_decl(new_logline, SUDO_DEBUG_UTIL); + sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0); + if (ISSET(flags, EVLOG_RAW) || evlog == NULL) { if (args->reason != NULL) { if (args->errstr != NULL) { - if (asprintf(&line, "%s: %s", args->reason, args->errstr) == -1) - goto oom; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s: %s", + args->reason, args->errstr); } else { - if ((line = strdup(args->reason)) == NULL) - goto oom; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s", args->reason); } + if (sudo_lbuf_error(&lbuf)) + goto oom; } - debug_return_str(line); + debug_return_str(lbuf.buf); } /* A TSID may be a sudoers-style session ID or a free-form string. */ @@ -142,163 +133,92 @@ new_logline(int event_type, int flags, s } /* - * Compute line length + * Format the log line as an lbuf, escaping control characters is + * octal form (#0nn). Error checking (ENOMEM) is done at the end */ - if (args->reason != NULL) - len += strlen(args->reason) + 3; - if (args->errstr != NULL) - len += strlen(args->errstr) + 3; - if (evlog->submithost != NULL && !evl_conf->omit_hostname) - len += sizeof(LL_HOST_STR) + 2 + strlen(evlog->submithost); - if (tty != NULL) - len += sizeof(LL_TTY_STR) + 2 + strlen(tty); - if (evlog->runchroot != NULL) - len += sizeof(LL_CHROOT_STR) + 2 + strlen(evlog->runchroot); - if (evlog->runcwd != NULL) - len += sizeof(LL_CWD_STR) + 2 + strlen(evlog->runcwd); - if (evlog->runuser != NULL) - len += sizeof(LL_USER_STR) + 2 + strlen(evlog->runuser); - if (evlog->rungroup != NULL) - len += sizeof(LL_GROUP_STR) + 2 + strlen(evlog->rungroup); - if (tsid != NULL) - len += sizeof(LL_TSID_STR) + 2 + strlen(tsid); - if (evlog->env_add != NULL) { - size_t evlen = 0; - char * const *ep; - - for (ep = evlog->env_add; *ep != NULL; ep++) - evlen += strlen(*ep) + 1; - if (evlen != 0) { - if ((evstr = malloc(evlen)) == NULL) - goto oom; - ep = evlog->env_add; - if (strlcpy(evstr, *ep, evlen) >= evlen) - goto toobig; - while (*++ep != NULL) { - if (strlcat(evstr, " ", evlen) >= evlen || - strlcat(evstr, *ep, evlen) >= evlen) - goto toobig; - } - len += sizeof(LL_ENV_STR) + 2 + evlen; - } - } - if (evlog->command != NULL) { - len += sizeof(LL_CMND_STR) - 1 + strlen(evlog->command); - if (evlog->argv != NULL) { - for (i = 1; evlog->argv[i] != NULL; i++) - len += strlen(evlog->argv[i]) + 1; - } - if (event_type == EVLOG_EXIT) { - if (args->signal_name != NULL) - len += sizeof(LL_SIGNAL_STR) + 2 + strlen(args->signal_name); - (void)snprintf(exit_str, sizeof(exit_str), "%d", args->exit_value); - len += sizeof(LL_EXIT_STR) + 2 + strlen(exit_str); - } - } - - /* - * Allocate and build up the line. - */ - if ((line = malloc(++len)) == NULL) - goto oom; - line[0] = '\0'; - if (args->reason != NULL) { - if (strlcat(line, args->reason, len) >= len || - strlcat(line, args->errstr ? " : " : " ; ", len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s", args->reason, + args->errstr ? " : " : " ; "); } if (args->errstr != NULL) { - if (strlcat(line, args->errstr, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s ; ", args->errstr); } if (evlog->submithost != NULL && !evl_conf->omit_hostname) { - if (strlcat(line, LL_HOST_STR, len) >= len || - strlcat(line, evlog->submithost, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "HOST=%s ; ", + evlog->submithost); } if (tty != NULL) { - if (strlcat(line, LL_TTY_STR, len) >= len || - strlcat(line, tty, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "TTY=%s ; ", tty); } if (evlog->runchroot != NULL) { - if (strlcat(line, LL_CHROOT_STR, len) >= len || - strlcat(line, evlog->runchroot, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "CHROOT=%s ; ", + evlog->runchroot); } if (evlog->runcwd != NULL) { - if (strlcat(line, LL_CWD_STR, len) >= len || - strlcat(line, evlog->runcwd, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "PWD=%s ; ", + evlog->runcwd); } if (evlog->runuser != NULL) { - if (strlcat(line, LL_USER_STR, len) >= len || - strlcat(line, evlog->runuser, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "USER=%s ; ", + evlog->runuser); } if (evlog->rungroup != NULL) { - if (strlcat(line, LL_GROUP_STR, len) >= len || - strlcat(line, evlog->rungroup, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; - } + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "GROUP=%s ; ", + evlog->rungroup); + } if (tsid != NULL) { - if (strlcat(line, LL_TSID_STR, len) >= len || - strlcat(line, tsid, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; - } - if (evstr != NULL) { - if (strlcat(line, LL_ENV_STR, len) >= len || - strlcat(line, evstr, len) >= len || - strlcat(line, " ; ", len) >= len) - goto toobig; - free(evstr); - evstr = NULL; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "TSID=%s ; ", tsid); + } + if (evlog->env_add != NULL && evlog->env_add[0] != NULL) { + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "ENV=%s", + evlog->env_add[0]); + for (i = 1; evlog->env_add[i] != NULL; i++) { + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, " %s", + evlog->env_add[i]); + } } if (evlog->command != NULL) { - if (strlcat(line, LL_CMND_STR, len) >= len) - goto toobig; - if (strlcat(line, evlog->command, len) >= len) - goto toobig; - if (evlog->argv != NULL) { + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK, + "COMMAND=%s", evlog->command); + if (evlog->argv != NULL && evlog->argv[0] != NULL) { for (i = 1; evlog->argv[i] != NULL; i++) { - if (strlcat(line, " ", len) >= len || - strlcat(line, evlog->argv[i], len) >= len) - goto toobig; + sudo_lbuf_append(&lbuf, " "); + if (strchr(evlog->argv[i], ' ') != NULL) { + /* Wrap args containing spaces in single quotes. */ + sudo_lbuf_append(&lbuf, "'"); + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_QUOTE, + "%s", evlog->argv[i]); + sudo_lbuf_append(&lbuf, "'"); + } else { + /* Escape quotes here too for consistency. */ + sudo_lbuf_append_esc(&lbuf, + LBUF_ESC_CNTRL|LBUF_ESC_BLANK|LBUF_ESC_QUOTE, + "%s", evlog->argv[i]); + } } } +/* if (event_type == EVLOG_EXIT) { if (args->signal_name != NULL) { - if (strlcat(line, " ; ", len) >= len || - strlcat(line, LL_SIGNAL_STR, len) >= len || - strlcat(line, args->signal_name, len) >= len) - goto toobig; + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, " ; SIGNAL=%s", + evlog->signal_name); } - if (strlcat(line, " ; ", len) >= len || - strlcat(line, LL_EXIT_STR, len) >= len || - strlcat(line, exit_str, len) >= len) - goto toobig; + if (evlog->exit_value != -1) { + (void)snprintf(exit_str, sizeof(exit_str), "%d", + evlog->exit_value); + sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, " ; EXIT=%s", + exit_str); + } } +*/ } - debug_return_str(line); + if (!sudo_lbuf_error(&lbuf)) + debug_return_str(lbuf.buf); oom: - free(evstr); + sudo_lbuf_destroy(&lbuf); sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); debug_return_str(NULL); -toobig: - free(evstr); - free(line); - sudo_warnx(U_("internal error, %s overflow"), __func__); - debug_return_str(NULL); } static void diff -up ./lib/iolog/iolog_json.c.cve ./lib/iolog/iolog_json.c --- ./lib/iolog/iolog_json.c.cve 2020-12-17 02:33:43.000000000 +0100 +++ ./lib/iolog/iolog_json.c 2023-12-04 16:52:54.500061266 +0100 @@ -443,35 +443,6 @@ iolog_parse_json_object(struct json_obje } } - /* Merge cmd and argv as sudoreplay expects. */ - if (evlog->command != NULL && evlog->argv != NULL) { - size_t len = strlen(evlog->command) + 1; - char *newcmd; - int ac; - - /* Skip argv[0], we use evlog->command instead. */ - for (ac = 1; evlog->argv[ac] != NULL; ac++) - len += strlen(evlog->argv[ac]) + 1; - - if ((newcmd = malloc(len)) == NULL) { - sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - goto done; - } - - /* TODO: optimize this. */ - if (strlcpy(newcmd, evlog->command, len) >= len) - sudo_fatalx(U_("internal error, %s overflow"), __func__); - for (ac = 1; evlog->argv[ac] != NULL; ac++) { - if (strlcat(newcmd, " ", len) >= len) - sudo_fatalx(U_("internal error, %s overflow"), __func__); - if (strlcat(newcmd, evlog->argv[ac], len) >= len) - sudo_fatalx(U_("internal error, %s overflow"), __func__); - } - - free(evlog->command); - evlog->command = newcmd; - } - ret = true; done: diff -up ./lib/util/lbuf.c.cve ./lib/util/lbuf.c --- ./lib/util/lbuf.c.cve 2020-12-17 02:33:43.000000000 +0100 +++ ./lib/util/lbuf.c 2023-12-04 16:52:54.501061253 +0100 @@ -85,6 +85,112 @@ sudo_lbuf_expand(struct sudo_lbuf *lbuf, } /* + * Escape a character in octal form (#0n) and store it as a string + * in buf, which must have at least 6 bytes available. + * Returns the length of buf, not counting the terminating NUL byte. + */ +static int +escape(unsigned char ch, char *buf) +{ + const int len = ch < 0100 ? (ch < 010 ? 3 : 4) : 5; + + /* Work backwards from the least significant digit to most significant. */ + switch (len) { + case 5: + buf[4] = (ch & 7) + '0'; + ch >>= 3; + FALLTHROUGH; + case 4: + buf[3] = (ch & 7) + '0'; + ch >>= 3; + FALLTHROUGH; + case 3: + buf[2] = (ch & 7) + '0'; + buf[1] = '0'; + buf[0] = '#'; + break; + } + buf[len] = '\0'; + + return len; +} + +/* + * Parse the format and append strings, only %s and %% escapes are supported. + * Any non-printable characters are escaped in octal as #0nn. + */ +bool +sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char *fmt, ...) +{ + unsigned int saved_len = lbuf->len; + bool ret = false; + const char *s; + va_list ap; + debug_decl(sudo_lbuf_append_esc, SUDO_DEBUG_UTIL); + + if (sudo_lbuf_error(lbuf)) + debug_return_bool(false); + +#define should_escape(ch) \ + ((ISSET(flags, LBUF_ESC_CNTRL) && iscntrl((unsigned char)ch)) || \ + (ISSET(flags, LBUF_ESC_BLANK) && isblank((unsigned char)ch))) +#define should_quote(ch) \ + (ISSET(flags, LBUF_ESC_QUOTE) && (ch == '\'' || ch == '\\')) + + va_start(ap, fmt); + while (*fmt != '\0') { + if (fmt[0] == '%' && fmt[1] == 's') { + if ((s = va_arg(ap, char *)) == NULL) + s = "(NULL)"; + while (*s != '\0') { + if (should_escape(*s)) { + if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1)) + goto done; + lbuf->len += escape(*s++, lbuf->buf + lbuf->len); + continue; + } + if (should_quote(*s)) { + if (!sudo_lbuf_expand(lbuf, 2)) + goto done; + lbuf->buf[lbuf->len++] = '\\'; + lbuf->buf[lbuf->len++] = *s++; + continue; + } + if (!sudo_lbuf_expand(lbuf, 1)) + goto done; + lbuf->buf[lbuf->len++] = *s++; + } + fmt += 2; + continue; + } + if (should_escape(*fmt)) { + if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1)) + goto done; + if (*fmt == '\'') { + lbuf->buf[lbuf->len++] = '\\'; + lbuf->buf[lbuf->len++] = *fmt++; + } else { + lbuf->len += escape(*fmt++, lbuf->buf + lbuf->len); + } + continue; + } + if (!sudo_lbuf_expand(lbuf, 1)) + goto done; + lbuf->buf[lbuf->len++] = *fmt++; + } + ret = true; + +done: + if (!ret) + lbuf->len = saved_len; + if (lbuf->size != 0) + lbuf->buf[lbuf->len] = '\0'; + va_end(ap); + + debug_return_bool(ret); +} + +/* * Parse the format and append strings, only %s and %% escapes are supported. * Any characters in set are quoted with a backslash. */ diff -up ./lib/util/util.exp.in.cve ./lib/util/util.exp.in --- ./lib/util/util.exp.in.cve 2021-01-09 21:12:16.000000000 +0100 +++ ./lib/util/util.exp.in 2023-12-04 16:52:54.501061253 +0100 @@ -95,6 +95,7 @@ sudo_json_get_len_v1 sudo_json_init_v1 sudo_json_open_array_v1 sudo_json_open_object_v1 +sudo_lbuf_append_esc_v1 sudo_lbuf_append_quoted_v1 sudo_lbuf_append_v1 sudo_lbuf_clearerr_v1 diff -up ./plugins/sudoers/sudoreplay.c.cve ./plugins/sudoers/sudoreplay.c --- ./plugins/sudoers/sudoreplay.c.cve 2023-12-04 16:52:54.498061293 +0100 +++ ./plugins/sudoers/sudoreplay.c 2023-12-04 16:52:54.501061253 +0100 @@ -62,6 +62,7 @@ #include "sudo_debug.h" #include "sudo_event.h" #include "sudo_eventlog.h" +#include "sudo_lbuf.h" #include "sudo_fatal.h" #include "sudo_gettext.h" #include "sudo_iolog.h" @@ -363,6 +364,10 @@ main(int argc, char *argv[]) if ((evlog = iolog_parse_loginfo(iolog_dir_fd, iolog_dir)) == NULL) goto done; printf(_("Replaying sudo session: %s"), evlog->command); + if (evlog->argv != NULL && evlog->argv[0] != NULL) { + for (i = 1; evlog->argv[i] != NULL; i++) + printf(" %s", evlog->argv[i]); + } /* Setup terminal if appropriate. */ if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) @@ -1291,11 +1296,57 @@ parse_expr(struct search_node_list *head debug_return_int(av - argv); } +static char * +expand_command(struct eventlog *evlog, char **newbuf) +{ + size_t len, bufsize = strlen(evlog->command) + 1; + char *cp, *buf; + int ac; + debug_decl(expand_command, SUDO_DEBUG_UTIL); + + if (evlog->argv == NULL || evlog->argv[0] == NULL || evlog->argv[1] == NULL) { + /* No arguments, we can use the command as-is. */ + *newbuf = NULL; + debug_return_str(evlog->command); + } + + /* Skip argv[0], we use evlog->command instead. */ + for (ac = 1; evlog->argv[ac] != NULL; ac++) + bufsize += strlen(evlog->argv[ac]) + 1; + + if ((buf = malloc(bufsize)) == NULL) + sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + cp = buf; + + len = strlcpy(cp, evlog->command, bufsize); + if (len >= bufsize) + sudo_fatalx(U_("internal error, %s overflow"), __func__); + cp += len; + bufsize -= len; + + for (ac = 1; evlog->argv[ac] != NULL; ac++) { + if (bufsize < 2) + sudo_fatalx(U_("internal error, %s overflow"), __func__); + *cp++ = ' '; + bufsize--; + + len = strlcpy(cp, evlog->argv[ac], bufsize); + if (len >= bufsize) + sudo_fatalx(U_("internal error, %s overflow"), __func__); + cp += len; + bufsize -= len; + } + + *newbuf = buf; + debug_return_str(buf); +} + static bool match_expr(struct search_node_list *head, struct eventlog *evlog, bool last_match) { struct search_node *sn; bool res = false, matched = last_match; + char *tofree; int rc; debug_decl(match_expr, SUDO_DEBUG_UTIL); @@ -1329,13 +1380,15 @@ match_expr(struct search_node_list *head res = strcmp(sn->u.user, evlog->submituser) == 0; break; case ST_PATTERN: - rc = regexec(&sn->u.cmdre, evlog->command, 0, NULL, 0); + rc = regexec(&sn->u.cmdre, expand_command(evlog, &tofree), + 0, NULL, 0); if (rc && rc != REG_NOMATCH) { char buf[BUFSIZ]; regerror(rc, &sn->u.cmdre, buf, sizeof(buf)); sudo_fatalx("%s", buf); } res = rc == REG_NOMATCH ? 0 : 1; + free(tofree); break; case ST_FROMDATE: res = sudo_timespeccmp(&evlog->submit_time, &sn->u.tstamp, >=); @@ -1356,12 +1409,13 @@ match_expr(struct search_node_list *head } static int -list_session(char *log_dir, regex_t *re, const char *user, const char *tty) +list_session(struct sudo_lbuf *lbuf, char *log_dir, regex_t *re, + const char *user, const char *tty) { char idbuf[7], *idstr, *cp; struct eventlog *evlog = NULL; const char *timestr; - int ret = -1; + int i, ret = -1; debug_decl(list_session, SUDO_DEBUG_UTIL); if ((evlog = iolog_parse_loginfo(-1, log_dir)) == NULL) @@ -1393,23 +1447,71 @@ list_session(char *log_dir, regex_t *re, } /* XXX - print lines + cols? */ timestr = get_timestr(evlog->submit_time.tv_sec, 1); - printf("%s : %s : ", timestr ? timestr : "invalid date", evlog->submituser); - if (evlog->submithost != NULL) - printf("HOST=%s ; ", evlog->submithost); - if (evlog->ttyname != NULL) - printf("TTY=%s ; ", evlog->ttyname); - if (evlog->runchroot != NULL) - printf("CHROOT=%s ; ", evlog->runchroot); - if (evlog->runcwd != NULL || evlog->cwd != NULL) - printf("CWD=%s ; ", evlog->runcwd ? evlog->runcwd : evlog->cwd); - printf("USER=%s ; ", evlog->runuser); - if (evlog->rungroup != NULL) - printf("GROUP=%s ; ", evlog->rungroup); - printf("TSID=%s ; COMMAND=%s\n", idstr, evlog->command); + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "%s : %s : ", + timestr ? timestr : "invalid date", evlog->submituser); + if (evlog->submithost != NULL) { + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "HOST=%s ; ", + evlog->submithost); + } + if (evlog->ttyname != NULL) { + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "TTY=%s ; ", + evlog->ttyname); + } + if (evlog->runchroot != NULL) { + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "CHROOT=%s ; ", + evlog->runchroot); + } + if (evlog->runcwd != NULL || evlog->cwd != NULL) { + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "CWD=%s ; ", + evlog->runcwd ? evlog->runcwd : evlog->cwd); + } + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "USER=%s ; ", evlog->runuser); + if (evlog->rungroup != NULL) { + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "GROUP=%s ; ", + evlog->rungroup); + } + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "TSID=%s ; ", idstr); + + /* + * If we have both command and argv from info.json we can escape + * blanks in the the command and arguments. If all we have is a + * single string containing both the command and arguments we cannot. + */ + if (evlog->argv != NULL) { + /* Command plus argv from the info.json file. */ + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK, + "COMMAND=%s", evlog->command); + if (evlog->argv[0] != NULL) { + for (i = 1; evlog->argv[i] != NULL; i++) { + sudo_lbuf_append(lbuf, " "); + if (strchr(evlog->argv[i], ' ') != NULL) { + /* Wrap args containing spaces in single quotes. */ + sudo_lbuf_append(lbuf, "'"); + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL|LBUF_ESC_QUOTE, + "%s", evlog->argv[i]); + sudo_lbuf_append(lbuf, "'"); + } else { + /* Escape quotes here too for consistency. */ + sudo_lbuf_append_esc(lbuf, + LBUF_ESC_CNTRL|LBUF_ESC_BLANK|LBUF_ESC_QUOTE, + "%s", evlog->argv[i]); + } + } + } + } else { + /* Single string from the legacy info file. */ + sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "COMMAND=%s", + evlog->command); + } - ret = 0; + if (!sudo_lbuf_error(lbuf)) { + puts(lbuf->buf); + ret = 0; + } done: + lbuf->error = 0; + lbuf->len = 0; eventlog_free(evlog); debug_return_int(ret); } @@ -1429,6 +1531,7 @@ find_sessions(const char *dir, regex_t * DIR *d; struct dirent *dp; struct stat sb; + struct sudo_lbuf lbuf; size_t sdlen, sessions_len = 0, sessions_size = 0; unsigned int i; int len; @@ -1440,6 +1543,8 @@ find_sessions(const char *dir, regex_t * #endif debug_decl(find_sessions, SUDO_DEBUG_UTIL); + sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0); + d = opendir(dir); if (d == NULL) sudo_fatal(U_("unable to open %s"), dir); @@ -1500,7 +1605,7 @@ find_sessions(const char *dir, regex_t * /* Check for dir with a log file. */ if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) { pathbuf[sdlen + len - 4] = '\0'; - list_session(pathbuf, re, user, tty); + list_session(&lbuf, pathbuf, re, user, tty); } else { /* Strip off "/log" and recurse if a non-log dir. */ pathbuf[sdlen + len - 4] = '\0'; @@ -1511,6 +1616,7 @@ find_sessions(const char *dir, regex_t * } free(sessions); } + sudo_lbuf_destroy(&lbuf); debug_return_int(0); }