sudo/sudo-1.9.13-CVE-2023-28486-7-9.patch
Radovan Sroka f8883a97a0 RHEL 8.9.0.Z ERRATUM
- Rebase to 1.9.5p2
- CVE-2023-28486 sudo: Sudo does not escape control characters in log messages
Resolves: RHEL-21825
- CVE-2023-28487 sudo: Sudo does not escape control characters in sudoreplay output
Resolves: RHEL-21831
- CVE-2023-42465 sudo: Targeted Corruption of Register and Stack Variables
Resolves: RHEL-21820

Signed-off-by: Radovan Sroka <rsroka@redhat.com>
2024-01-26 11:45:49 +01:00

911 lines
30 KiB
Diff

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);
}