diff --git a/backport-CVE-2023-28486_CVE-2023-28487.patch b/backport-CVE-2023-28486_CVE-2023-28487.patch new file mode 100644 index 0000000000000000000000000000000000000000..73e40cfefae592bbeb26c94481779e68de3ad90d --- /dev/null +++ b/backport-CVE-2023-28486_CVE-2023-28487.patch @@ -0,0 +1,847 @@ +From 334daf92b31b79ce68ed75e2ee14fca265f029ca Mon Sep 17 00:00:00 2001 +From: "Todd C. Miller" +Date: Wed, 18 Jan 2023 08:21:34 -0700 +Subject: [PATCH] Escape control characters in log messages and "sudoreplay -l" + output. The log message contains user-controlled strings that could include + things like terminal control characters. Space characters in the command + path are now also escaped. + +Command line arguments that contain spaces are surrounded with +single quotes and any literal single quote or backslash characters +are escaped with a backslash. This makes it possible to distinguish +multiple command line arguments from a single argument that contains +spaces. + +Issue found by Matthieu Barjole and Victor Cutillas of Synacktiv +(https://synacktiv.com). +--- + include/sudo_lbuf.h | 7 ++ + lib/iolog/iolog_json.c | 29 ------- + lib/util/lbuf.c | 106 +++++++++++++++++++++++ + lib/util/util.exp.in | 1 + + logsrvd/eventlog.c | 161 ++++++++++++----------------------- + plugins/sudoers/logging.c | 138 +++++++++--------------------- + plugins/sudoers/sudoreplay.c | 132 +++++++++++++++++++++++++--- + 7 files changed, 329 insertions(+), 245 deletions(-) + +diff --git a/include/sudo_lbuf.h b/include/sudo_lbuf.h +index 4c81780..9404193 100644 +--- a/include/sudo_lbuf.h ++++ b/include/sudo_lbuf.h +@@ -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 ++ + __dso_public void sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output, int indent, const char *continuation, int cols); + __dso_public void sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf); + __dso_public bool sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char *fmt, ...) __printflike(2, 3); ++__dso_public bool sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char *fmt, ...) __printflike(3, 4); + __dso_public bool sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char *fmt, ...) __printflike(3, 4); + __dso_public void sudo_lbuf_print_v1(struct sudo_lbuf *lbuf); + __dso_public bool sudo_lbuf_error_v1(struct sudo_lbuf *lbuf); +@@ -47,6 +53,7 @@ __dso_public void sudo_lbuf_clearerr_v1(struct sudo_lbuf *lbuf); + #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 --git a/lib/iolog/iolog_json.c b/lib/iolog/iolog_json.c +index db4d785..652b876 100644 +--- a/lib/iolog/iolog_json.c ++++ b/lib/iolog/iolog_json.c +@@ -421,35 +421,6 @@ iolog_parse_json_object(struct json_object *object, struct iolog_info *li) + } + } + +- /* Merge cmd and argv as sudoreplay expects. */ +- if (li->cmd != NULL && li->argv != NULL) { +- size_t len = strlen(li->cmd) + 1; +- char *newcmd; +- int ac; +- +- /* Skip argv[0], we use li->cmd instead. */ +- for (ac = 1; li->argv[ac] != NULL; ac++) +- len += strlen(li->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, li->cmd, len) >= len) +- sudo_fatalx(U_("internal error, %s overflow"), __func__); +- for (ac = 1; li->argv[ac] != NULL; ac++) { +- if (strlcat(newcmd, " ", len) >= len) +- sudo_fatalx(U_("internal error, %s overflow"), __func__); +- if (strlcat(newcmd, li->argv[ac], len) >= len) +- sudo_fatalx(U_("internal error, %s overflow"), __func__); +- } +- +- free(li->cmd); +- li->cmd = newcmd; +- } +- + ret = true; + + done: +diff --git a/lib/util/lbuf.c b/lib/util/lbuf.c +index f17ae0c..a9a57c9 100644 +--- a/lib/util/lbuf.c ++++ b/lib/util/lbuf.c +@@ -87,6 +87,112 @@ sudo_lbuf_expand(struct sudo_lbuf *lbuf, int extra) + debug_return_bool(true); + } + ++/* ++ * 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; ++ ++ case 4: ++ buf[3] = (ch & 7) + '0'; ++ ch >>= 3; ++ ++ 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 --git a/lib/util/util.exp.in b/lib/util/util.exp.in +index 6095623..881b35b 100644 +--- a/lib/util/util.exp.in ++++ b/lib/util/util.exp.in +@@ -94,6 +94,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 --git a/logsrvd/eventlog.c b/logsrvd/eventlog.c +index 2c7920c..c5f37d4 100644 +--- a/logsrvd/eventlog.c ++++ b/logsrvd/eventlog.c +@@ -48,6 +48,7 @@ + #include "log_server.pb-c.h" + #include "sudo_gettext.h" /* must be included before sudo_compat.h */ + #include "sudo_compat.h" ++#include "sudo_lbuf.h" + #include "sudo_fatal.h" + #include "sudo_json.h" + #include "sudo_queue.h" +@@ -80,14 +81,15 @@ static char * + new_logline(const char *message, const char *errstr, + const struct iolog_details *details) + { +- char *line = NULL, *evstr = NULL; + const char *iolog_file = details->iolog_file; + char sessid[7]; + const char *tsid = NULL; +- 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); ++ + /* A TSID may be a sudoers-style session ID or a free-form string. */ + if (iolog_file != NULL) { + if (IS_SESSID(iolog_file)) { +@@ -105,128 +107,77 @@ new_logline(const char *message, const char *errstr, + } + + /* +- * Compute line length +- */ +- if (message != NULL) +- len += strlen(message) + 3; +- if (errstr != NULL) +- len += strlen(errstr) + 3; +- len += sizeof(LL_HOST_STR) + 2 + strlen(details->submithost); +- len += sizeof(LL_TTY_STR) + 2 + strlen(details->ttyname); +- len += sizeof(LL_CWD_STR) + 2 + strlen(details->cwd); +- if (details->runuser != NULL) +- len += sizeof(LL_USER_STR) + 2 + strlen(details->runuser); +- if (details->rungroup != NULL) +- len += sizeof(LL_GROUP_STR) + 2 + strlen(details->rungroup); +- if (tsid != NULL) +- len += sizeof(LL_TSID_STR) + 2 + strlen(tsid); +- if (details->env_add != NULL) { +- size_t evlen = 0; +- char * const *ep; +- +- for (ep = details->env_add; *ep != NULL; ep++) +- evlen += strlen(*ep) + 1; +- if (evlen != 0) { +- if ((evstr = malloc(evlen)) == NULL) +- goto oom; +- ep = details->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 (details->command != NULL) { +- len += sizeof(LL_CMND_STR) - 1 + strlen(details->command); +- if (details->argc > 1) { +- for (i = 1; i < details->argc; i++) +- len += strlen(details->argv[i]) + 1; +- } +- } +- +- /* +- * Allocate and build up the line. ++ * Format the log line as an lbuf, escaping control characters in ++ * octal form (#0nn). Error checking (ENOMEM) is done at the end. + */ +- if ((line = malloc(++len)) == NULL) +- goto oom; +- line[0] = '\0'; +- + if (message != NULL) { +- if (strlcat(line, message, len) >= len || +- strlcat(line, errstr ? " : " : " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s", message, ++ errstr ? " : " : " ; "); + } + if (errstr != NULL) { +- if (strlcat(line, errstr, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; +- } +- if (strlcat(line, LL_HOST_STR, len) >= len || +- strlcat(line, details->submithost, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; +- if (strlcat(line, LL_TTY_STR, len) >= len || +- strlcat(line, details->ttyname, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; +- if (strlcat(line, LL_CWD_STR, len) >= len || +- strlcat(line, details->cwd, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s ; ", errstr); ++ } ++ if (details->submithost != NULL) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", ++ LL_HOST_STR, details->submithost); ++ } ++ if (details->ttyname != NULL) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", ++ LL_TTY_STR, details->ttyname); ++ } ++ if (details->cwd != NULL) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", ++ LL_CWD_STR, details->cwd); ++ } + if (details->runuser != NULL) { +- if (strlcat(line, LL_USER_STR, len) >= len || +- strlcat(line, details->runuser, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", ++ LL_USER_STR, details->runuser); + } + if (details->rungroup != NULL) { +- if (strlcat(line, LL_GROUP_STR, len) >= len || +- strlcat(line, details->rungroup, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", ++ LL_GROUP_STR, details->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, "%s%s ; ", ++ LL_TSID_STR, tsid); + } ++ if (details->env_add != NULL && details->env_add[0] != NULL) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s", ++ LL_ENV_STR, details->env_add[0]); ++ for (i = 1; details->env_add[i] != NULL; i++) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, " %s", ++ details->env_add[i]); ++ } ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "; "); ++ } ++ + if (details->command != NULL) { +- if (strlcat(line, LL_CMND_STR, len) >= len) +- goto toobig; +- if (strlcat(line, details->command, len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK, ++ "%s%s", LL_CMND_STR, details->command); + if (details->argc > 1) { + for (i = 1; i < details->argc; i++) { +- if (strlcat(line, " ", len) >= len || +- strlcat(line, details->argv[i], len) >= len) +- goto toobig; ++ sudo_lbuf_append(&lbuf, " "); ++ if (strchr(details->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", details->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", details->argv[i]); ++ } + } + } + } ++ if (!sudo_lbuf_error(&lbuf)) ++ debug_return_str(lbuf.buf); + +- debug_return_str(line); +-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 bool +diff --git a/plugins/sudoers/logging.c b/plugins/sudoers/logging.c +index a68fba7..de5bb30 100644 +--- a/plugins/sudoers/logging.c ++++ b/plugins/sudoers/logging.c +@@ -50,6 +50,7 @@ + #include + + #include "sudoers.h" ++#include "sudo_lbuf.h" + + #ifndef HAVE_GETADDRINFO + # include "compat/getaddrinfo.h" +@@ -945,14 +946,16 @@ should_mail(int status) + char * + new_logline(const char *message, const char *errstr) + { +- char *line = NULL, *evstr = NULL; + #ifndef SUDOERS_NO_SEQ + char sessid[7]; + #endif + const char *tsid = NULL; +- size_t len = 0; ++ struct sudo_lbuf lbuf; ++ int i; + debug_decl(new_logline, SUDOERS_DEBUG_LOGGING); + ++ sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0); ++ + #ifndef SUDOERS_NO_SEQ + /* A TSID may be a sudoers-style session ID or a free-form string. */ + if (sudo_user.iolog_file != NULL) { +@@ -972,119 +975,58 @@ new_logline(const char *message, const char *errstr) + #endif + + /* +- * Compute line length ++ * Format the log line as an lbuf, escaping control characters in ++ * octal form (#0nn). Error checking (ENOMEM) is done at the end. + */ +- if (message != NULL) +- len += strlen(message) + 3; +- if (errstr != NULL) +- len += strlen(errstr) + 3; +- len += sizeof(LL_TTY_STR) + 2 + strlen(user_tty); +- len += sizeof(LL_CWD_STR) + 2 + strlen(user_cwd); +- if (runas_pw != NULL) +- len += sizeof(LL_USER_STR) + 2 + strlen(runas_pw->pw_name); +- if (runas_gr != NULL) +- len += sizeof(LL_GROUP_STR) + 2 + strlen(runas_gr->gr_name); +- if (tsid != NULL) +- len += sizeof(LL_TSID_STR) + 2 + strlen(tsid); +- if (sudo_user.env_vars != NULL) { +- size_t evlen = 0; +- char * const *ep; +- +- for (ep = sudo_user.env_vars; *ep != NULL; ep++) +- evlen += strlen(*ep) + 1; +- if (evlen != 0) { +- if ((evstr = malloc(evlen)) == NULL) +- goto oom; +- evstr[0] = '\0'; +- for (ep = sudo_user.env_vars; *ep != NULL; ep++) { +- strlcat(evstr, *ep, evlen); +- strlcat(evstr, " ", evlen); /* NOTE: last one will fail */ +- } +- len += sizeof(LL_ENV_STR) + 2 + evlen; +- } +- } +- if (user_cmnd != NULL) { +- /* Note: we log "sudo -l command arg ..." as "list command arg ..." */ +- len += sizeof(LL_CMND_STR) - 1 + strlen(user_cmnd); +- if (ISSET(sudo_mode, MODE_CHECK)) +- len += sizeof("list ") - 1; +- if (user_args != NULL) +- len += strlen(user_args) + 1; +- } +- +- /* +- * Allocate and build up the line. +- */ +- if ((line = malloc(++len)) == NULL) +- goto oom; +- line[0] = '\0'; +- + if (message != NULL) { +- if (strlcat(line, message, len) >= len || +- strlcat(line, errstr ? " : " : " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s", message, ++ errstr ? " : " : " ; "); + } + if (errstr != NULL) { +- if (strlcat(line, errstr, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s ; ", errstr); ++ } ++ if (user_tty != NULL) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", LL_TTY_STR, ++ user_tty); ++ } ++ if (user_cwd != NULL) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", LL_CWD_STR, ++ user_cwd); + } +- if (strlcat(line, LL_TTY_STR, len) >= len || +- strlcat(line, user_tty, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; +- if (strlcat(line, LL_CWD_STR, len) >= len || +- strlcat(line, user_cwd, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; + if (runas_pw != NULL) { +- if (strlcat(line, LL_USER_STR, len) >= len || +- strlcat(line, runas_pw->pw_name, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", LL_USER_STR, ++ runas_pw->pw_name); + } + if (runas_gr != NULL) { +- if (strlcat(line, LL_GROUP_STR, len) >= len || +- strlcat(line, runas_gr->gr_name, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", LL_GROUP_STR, ++ runas_gr->gr_name); + } + if (tsid != NULL) { +- if (strlcat(line, LL_TSID_STR, len) >= len || +- strlcat(line, tsid, len) >= len || +- strlcat(line, " ; ", len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s ; ", LL_TSID_STR, ++ tsid); + } +- 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; ++ if (sudo_user.env_vars != NULL && sudo_user.env_vars[0] != NULL) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s", ++ LL_ENV_STR, sudo_user.env_vars[0]); ++ for (i = 1; sudo_user.env_vars[i] != NULL; i++) { ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, " %s", ++ sudo_user.env_vars[i]); ++ } ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "; "); + } + if (user_cmnd != NULL) { +- if (strlcat(line, LL_CMND_STR, len) >= len) +- goto toobig; +- if (ISSET(sudo_mode, MODE_CHECK) && strlcat(line, "list ", len) >= len) +- goto toobig; +- if (strlcat(line, user_cmnd, len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK, "%s", LL_CMND_STR); ++ if (ISSET(sudo_mode, MODE_CHECK)) ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK, "%s", "list "); ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK, "%s", user_cmnd); + if (user_args != NULL) { +- if (strlcat(line, " ", len) >= len || +- strlcat(line, user_args, len) >= len) +- goto toobig; ++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK|LBUF_ESC_QUOTE, " %s", user_args); + } + } + +- debug_return_str(line); +-oom: +- free(evstr); ++ if (!sudo_lbuf_error(&lbuf)) ++ debug_return_str(lbuf.buf); ++ 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); + } +diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c +index a8f7b61..2c58c38 100644 +--- a/plugins/sudoers/sudoreplay.c ++++ b/plugins/sudoers/sudoreplay.c +@@ -64,6 +64,7 @@ + #include "sudo_conf.h" + #include "sudo_debug.h" + #include "sudo_event.h" ++#include "sudo_lbuf.h" + #include "sudo_util.h" + + #ifdef HAVE_GETOPT_LONG +@@ -364,6 +365,11 @@ main(int argc, char *argv[]) + if ((li = iolog_parse_loginfo(iolog_dir_fd, iolog_dir)) == NULL) + goto done; + printf(_("Replaying sudo session: %s"), li->cmd); ++ if (li->argv != NULL && li->argv[0] != NULL) { ++ for (i = 1; li->argv[i] != NULL; i++) ++ printf(" %s", li->argv[i]); ++ } ++ + + /* Setup terminal if appropriate. */ + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) +@@ -1292,11 +1298,57 @@ parse_expr(struct search_node_list *head, char *argv[], bool sub_expr) + debug_return_int(av - argv); + } + ++static char * ++expand_command(struct iolog_info *evlog, char **newbuf) ++{ ++ size_t len, bufsize = strlen(evlog->cmd) + 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 cmd as-is. */ ++ *newbuf = NULL; ++ debug_return_str(evlog->cmd); ++ } ++ ++ /* Skip argv[0], we use evlog->cmd 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->cmd, 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 iolog_info *log, bool last_match) + { + struct search_node *sn; + bool res = false, matched = last_match; ++ char *tofree; + int rc; + debug_decl(match_expr, SUDO_DEBUG_UTIL); + +@@ -1330,13 +1382,14 @@ match_expr(struct search_node_list *head, struct iolog_info *log, bool last_matc + res = strcmp(sn->u.user, log->user) == 0; + break; + case ST_PATTERN: +- rc = regexec(&sn->u.cmdre, log->cmd, 0, NULL, 0); ++ rc = regexec(&sn->u.cmdre, expand_command(log, &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(&log->tstamp, &sn->u.tstamp, >=); +@@ -1357,12 +1410,12 @@ match_expr(struct search_node_list *head, struct iolog_info *log, bool last_matc + } + + 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 iolog_info *li = NULL; + const char *timestr; +- int ret = -1; ++ int i, ret = -1; + debug_decl(list_session, SUDO_DEBUG_UTIL); + + if ((li = iolog_parse_loginfo(-1, log_dir)) == NULL) +@@ -1389,18 +1442,67 @@ list_session(char *log_dir, regex_t *re, const char *user, const char *tty) + } + /* XXX - print lines + cols? */ + 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); +- if (li->runas_group) +- printf("GROUP=%s ; ", li->runas_group); +- if (li->host) +- printf("HOST=%s ; ", li->host); +- printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd); ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "%s : %s : ", ++ timestr ? timestr : "invalid date", li->user); ++ if (li->tty != NULL) { ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "TTY=%s ; ", ++ li->tty); ++ } ++ if (li->cwd != NULL) { ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "CWD=%s ; ", ++ li->cwd); ++ } ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "USER=%s ; ", li->runas_user); ++ if (li->runas_group != NULL) { ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "GROUP=%s ; ", ++ li->runas_group); ++ } ++ if (li->host != NULL) { ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "HOST=%s ; ", ++ li->host); ++ } ++ 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 (li->argv != NULL) { ++ /* Command plus argv from the info.json file. */ ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK, ++ "COMMAND=%s", li->cmd); ++ if (li->argv[0] != NULL) { ++ for (i = 1; li->argv[i] != NULL; i++) { ++ sudo_lbuf_append(lbuf, " "); ++ if (strchr(li->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", li->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", li->argv[i]); ++ } ++ } ++ } ++ } else { ++ /* Single string from the legacy info file. */ ++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "COMMAND=%s", ++ li->cmd); ++ } + +- ret = 0; ++ if (!sudo_lbuf_error(lbuf)) { ++ puts(lbuf->buf); ++ ret = 0; ++ } + + done: ++ lbuf->error = 0; ++ lbuf->len = 0; + iolog_free_loginfo(li); + debug_return_int(ret); + } +@@ -1420,6 +1522,7 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty) + 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; +@@ -1431,6 +1534,8 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty) + #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); +@@ -1491,7 +1596,7 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty) + /* 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'; +@@ -1502,6 +1607,7 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty) + } + free(sessions); + } ++ sudo_lbuf_destroy(&lbuf); + + debug_return_int(0); + } +-- +2.27.0 + diff --git a/sudo.spec b/sudo.spec index ddd8139b013b91e18be5e9fb5215cd0f7d3ea68a..2a0255dcef96109d7262040c3ae36d4568c83775 100644 --- a/sudo.spec +++ b/sudo.spec @@ -1,6 +1,6 @@ Name: sudo Version: 1.9.2 -Release: 10 +Release: 11 Summary: Allows restricted root access for specified users License: ISC URL: http://www.courtesan.com/sudo/ @@ -34,6 +34,7 @@ Patch20: backport-Fix-a-potential-use-after-free-bug-with-cvtsudoers-f.patch Patch21: backport-Fix-memory-leak-of-pass-in-converse.patch Patch22: backport-sudo_passwd_cleanup-Set-auth-data-to-NULL-after-free.patch Patch23: backport-CVE-2023-22809.patch +Patch24: backport-CVE-2023-28486_CVE-2023-28487.patch Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: pam @@ -174,6 +175,9 @@ install -p -c -m 0644 %{SOURCE3} $RPM_BUILD_ROOT/etc/pam.d/sudo-i %exclude %{_pkgdocdir}/ChangeLog %changelog +* Tue Mar 28 2023 wangcheng - 1.9.2-11 +- Fix CVE-2023-28486 and CVE-2023-28487 + * Mon Jan 30 2023 wangyu - 1.9.2-10 - Fix CVE-2023-22809.