From a095bd74a94530d60b3290e675a764bdf712cdca Mon Sep 17 00:00:00 2001 From: iefa m Date: Thu, 22 Oct 2026 22:47:54 +0530 Subject: [PATCH 1/2] expand_iolog_path: don't pass escape values to strftime The %{user}, %{group}, %{runas_user}, %{runas_group}, %{hostname} and %{command} values are copied into the path before it is run through strftime(3), so a '%' in one of them was treated as a conversion. In sudo_logsrvd these come from the client, and a submituser of "%D" expands to "10/22/26", re-introducing the '/' that strlcpy_no_slash() replaced. A literal "%%" in iolog_dir or iolog_file was also collapsed before strftime(3) saw it. Double any '%' in substituted values and leave "%%" alone during expansion, collapsing it afterwards via strftime(3) or by hand. --- lib/iolog/iolog_path.c | 50 +++++++++++++++++++++++++++++-- lib/iolog/regress/iolog_path/data | 36 ++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/lib/iolog/iolog_path.c b/lib/iolog/iolog_path.c index 7e7c9de2b3..a55fc5baac 100644 --- a/lib/iolog/iolog_path.c +++ b/lib/iolog/iolog_path.c @@ -37,6 +37,38 @@ #include #include +/* + * Double any '%' characters in the first len bytes of str so they + * are not interpreted as escapes when the path is expanded further. + * Returns the new length or (size_t)-1 if it would not fit in size. + */ +static size_t +escape_percent(char *str, size_t len, size_t size) +{ + size_t i, n, nesc = 0; + debug_decl(escape_percent, SUDO_DEBUG_UTIL); + + for (i = 0; i < len; i++) { + if (str[i] == '%') + nesc++; + } + if (nesc == 0) + debug_return_size_t(len); + if (len + nesc >= size) + debug_return_size_t((size_t)-1); + + /* Shift right from the end, duplicating each '%'. */ + for (i = len, n = nesc; n > 0; ) { + i--; + str[i + n] = str[i]; + if (str[i] == '%') { + n--; + str[i + n] = '%'; + } + } + debug_return_size_t(len + nesc); +} + /* * Expand any escape sequences in inpath, returning the expanded path. */ @@ -72,14 +104,20 @@ expand_iolog_path(const char *inpath, char *path, size_t pathlen, closure); if (len >= (size_t)(pathend - dst)) goto bad; + /* The value may contain '%', treat it as a literal. */ + len = escape_percent(dst, len, (size_t)(pathend - dst)); + if (len == (size_t)-1) + goto bad; dst += len; src = endbrace; continue; } } } else if (src[1] == '%') { - /* Collapse %% -> % */ - src++; + /* Copy %% as-is, it is collapsed after expansion. */ + if (dst + 1 >= pathend) + goto bad; + *dst++ = *src++; } else { /* May need strftime() */ strfit = true; @@ -117,6 +155,14 @@ expand_iolog_path(const char *inpath, char *path, size_t pathlen, memcpy(path, tmpbuf, len); dst = path + len; *dst = '\0'; + } else { + /* No strftime() needed, collapse %% -> % ourselves. */ + for (src = dst = path; *src != '\0'; src++) { + if (src[0] == '%' && src[1] == '%') + src++; + *dst++ = *src; + } + *dst = '\0'; } debug_return_bool(true); diff --git a/lib/iolog/regress/iolog_path/data b/lib/iolog/regress/iolog_path/data index 48dc87e9d3..4dec6fe091 100644 --- a/lib/iolog/regress/iolog_path/data +++ b/lib/iolog/regress/iolog_path/data @@ -94,3 +94,39 @@ su nobody/root/su +000001 +%D +nogroup +root +wheel +somehost +x%Hy +/var/log/sudo-io/%{user}/%Y +%{command}_%Y%m%d +/var/log/sudo-io/%%D/%Y +x%%Hy_%Y%m%d + +000001 +nobody +nogroup +root +wheel +somehost +su +/var/log/sudo-io/%%Y_%Y +%{seq} +/var/log/sudo-io/%%Y_%Y +00/00/01 + +000001 +50% +nogroup +root +wheel +somehost +su +/var/log/sudo-io/%{user} +%{seq} +/var/log/sudo-io/50%% +00/00/01 + From d6e1f4390ac250be93678e6a8e03d26740a42c50 Mon Sep 17 00:00:00 2001 From: iefa m Date: Tue, 27 Oct 2026 06:17:16 +0530 Subject: [PATCH 2/2] escape_percent: check for underflow when shifting the buffer --- lib/iolog/iolog_path.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/iolog/iolog_path.c b/lib/iolog/iolog_path.c index a55fc5baac..11bd73d3f9 100644 --- a/lib/iolog/iolog_path.c +++ b/lib/iolog/iolog_path.c @@ -59,7 +59,8 @@ escape_percent(char *str, size_t len, size_t size) /* Shift right from the end, duplicating each '%'. */ for (i = len, n = nesc; n > 0; ) { - i--; + if (i-- == 0) + debug_return_size_t((size_t)-1); str[i + n] = str[i]; if (str[i] == '%') { n--;