log: escape control chars written to the log file

logit() wrote the message to the log file with a raw fprintf, so an
attacker-controlled filename could inject terminal escapes that an admin later
executes when cat'ing the log (CWE-117).  Route it through filtered_fwrite(),
keeping the trailing newline raw.  Also escape C1 controls (0x80-0x9f, incl CSI)
on filtered_fwrite's use_isprint=0 path, which previously caught only C0.

Reported-by: Leonid Bugaev

The C1 escaping is gated by an escape_c1 flag set only for the log path, so
--8-bit-output / iconv terminal output still passes 8-bit bytes (incl. UTF-8)
through unchanged.

(cherry picked from commit ad64e99590)
This commit is contained in:
Andrew Tridgell committed 2026-07-20 14:51:18 +10:00
1 parent 27a0de79b9
commit e7a454ea9c
1 file changed
+14 -5
+14 -5
View File
@@ -119,12 +119,20 @@ static char const *rerr_name(int code)
return NULL;
}
static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char);
static void logit(int priority, const char *buf)
{
if (logfile_was_closed)
logfile_reopen();
if (logfile_fp) {
fprintf(logfile_fp, "%s [%d] %s", timestring(time(NULL)), (int)getpid(), buf);
/* Escape control chars in the message so an attacker-controlled
* filename can't inject terminal escapes into the log an admin later
* cat's (CWE-117); keep the trailing newline raw via end_char. */
int len = strlen(buf);
char trailing = len && (buf[len-1] == '\n' || buf[len-1] == '\r') ? buf[--len] : '\0';
fprintf(logfile_fp, "%s [%d] ", timestring(time(NULL)), (int)getpid());
filtered_fwrite(logfile_fp, buf, len, 0, 1, trailing);
fflush(logfile_fp);
} else {
syslog(priority, "%s", buf);
@@ -230,7 +238,7 @@ void logfile_reopen(void)
}
}
static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, char end_char)
static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char)
{
char outbuf[1024], *ob = outbuf;
const char *end = in_buf + in_len;
@@ -242,7 +250,8 @@ static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isp
}
if ((in_buf < end - 4 && *in_buf == '\\' && in_buf[1] == '#'
&& isDigit(in_buf + 2) && isDigit(in_buf + 3) && isDigit(in_buf + 4))
|| (*in_buf != '\t' && ((use_isprint && !isPrint(in_buf)) || *(uchar*)in_buf < ' ')))
|| (*in_buf != '\t' && ((use_isprint && !isPrint(in_buf)) || *(uchar*)in_buf < ' '
|| (escape_c1 && *(uchar*)in_buf >= 0x80 && *(uchar*)in_buf <= 0x9f))))
ob += snprintf(ob, 6, "\\#%03o", *(uchar*)in_buf++);
else
*ob++ = *in_buf++;
@@ -389,7 +398,7 @@ void rwrite(enum logcode code, const char *buf, int len, int is_utf8)
ierrno = errno;
if (outbuf.len) {
char trailing = inbuf.len ? '\0' : trailing_CR_or_NL;
filtered_fwrite(f, convbuf, outbuf.len, 0, trailing);
filtered_fwrite(f, convbuf, outbuf.len, 0, 0, trailing);
if (trailing) {
trailing_CR_or_NL = '\0';
fflush(f);
@@ -412,7 +421,7 @@ void rwrite(enum logcode code, const char *buf, int len, int is_utf8)
} else
#endif
{
filtered_fwrite(f, buf, len, !allow_8bit_chars, trailing_CR_or_NL);
filtered_fwrite(f, buf, len, !allow_8bit_chars, 0, trailing_CR_or_NL);
if (trailing_CR_or_NL)
fflush(f);
}