Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 53 additions & 7 deletions tracing-subscriber/src/fmt/fmt_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub struct Layer<
fmt_event: E,
fmt_span: format::FmtSpanConfig,
is_ansi: bool,
ansi_sanitization: bool,
log_internal_errors: bool,
_inner: PhantomData<fn(S)>,
}
Expand Down Expand Up @@ -122,6 +123,7 @@ where
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand Down Expand Up @@ -152,6 +154,7 @@ where
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand Down Expand Up @@ -185,6 +188,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
fmt_event: self.fmt_event,
fmt_span: self.fmt_span,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
make_writer,
_inner: self._inner,
Expand Down Expand Up @@ -290,6 +294,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
fmt_event: self.fmt_event,
fmt_span: self.fmt_span,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
make_writer: TestWriter::default(),
_inner: self._inner,
Expand Down Expand Up @@ -340,6 +345,19 @@ impl<S, N, E, W> Layer<S, N, E, W> {
}
}

/// Sets whether ANSI control character sanitization is enabled.
///
/// This defaults to `true` as a protective measure against terminal
/// injection attacks. If this is set to `false`, ANSI sanitization is
/// disabled and trusted ANSI control sequences in logged values are passed
/// through unchanged.
pub fn with_ansi_sanitization(self, ansi_sanitization: bool) -> Self {
Self {
ansi_sanitization,
..self
}
}

/// Sets whether to write errors from [`FormatEvent`] to the writer.
/// Defaults to true.
///
Expand Down Expand Up @@ -386,6 +404,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
fmt_event: self.fmt_event,
fmt_span: self.fmt_span,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
make_writer: f(self.make_writer),
_inner: self._inner,
Expand Down Expand Up @@ -418,6 +437,7 @@ where
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand All @@ -431,6 +451,7 @@ where
fmt_span: self.fmt_span.without_time(),
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand Down Expand Up @@ -560,6 +581,7 @@ where
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand All @@ -575,6 +597,7 @@ where
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand Down Expand Up @@ -606,6 +629,7 @@ where
make_writer: self.make_writer,
// always disable ANSI escapes in JSON mode!
is_ansi: false,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand Down Expand Up @@ -673,6 +697,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand Down Expand Up @@ -704,6 +729,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
ansi_sanitization: self.ansi_sanitization,
log_internal_errors: self.log_internal_errors,
_inner: self._inner,
}
Expand All @@ -722,6 +748,7 @@ impl<S> Default for Layer<S> {
fmt_span: format::FmtSpanConfig::default(),
make_writer: io::stdout,
is_ansi: ansi,
ansi_sanitization: true,
log_internal_errors: false,
_inner: PhantomData,
}
Expand Down Expand Up @@ -754,20 +781,32 @@ where
/// without conflicting.
///
/// [extensions]: crate::registry::Extensions
#[derive(Default)]
pub struct FormattedFields<E: ?Sized> {
_format_fields: PhantomData<fn(E)>,
was_ansi: bool,
was_ansi_sanitized: bool,
/// The formatted fields of a span.
pub fields: String,
}

impl<E: ?Sized> Default for FormattedFields<E> {
fn default() -> Self {
Self {
_format_fields: Default::default(),
was_ansi: Default::default(),
was_ansi_sanitized: true,
fields: Default::default(),
}
}
}

impl<E: ?Sized> FormattedFields<E> {
/// Returns a new `FormattedFields`.
pub fn new(fields: String) -> Self {
Self {
fields,
was_ansi: false,
was_ansi_sanitized: true,
_format_fields: PhantomData,
}
}
Expand All @@ -777,7 +816,9 @@ impl<E: ?Sized> FormattedFields<E> {
/// The returned [`format::Writer`] can be used with the
/// [`FormatFields::format_fields`] method.
pub fn as_writer(&mut self) -> format::Writer<'_> {
format::Writer::new(&mut self.fields).with_ansi(self.was_ansi)
format::Writer::new(&mut self.fields)
.with_ansi(self.was_ansi)
.with_ansi_sanitization(self.was_ansi_sanitized)
}
}

Expand All @@ -787,6 +828,7 @@ impl<E: ?Sized> fmt::Debug for FormattedFields<E> {
.field("fields", &self.fields)
.field("formatter", &format_args!("{}", std::any::type_name::<E>()))
.field("was_ansi", &self.was_ansi)
.field("was_ansi_sanitized", &self.was_ansi_sanitized)
.finish()
}
}
Expand Down Expand Up @@ -835,12 +877,13 @@ where

if extensions.get_mut::<FormattedFields<N>>().is_none() {
let mut fields = FormattedFields::<N>::new(String::new());
fields.was_ansi = self.is_ansi;
fields.was_ansi_sanitized = self.ansi_sanitization;
if self
.fmt_fields
.format_fields(fields.as_writer().with_ansi(self.is_ansi), attrs)
.format_fields(fields.as_writer(), attrs)
.is_ok()
{
fields.was_ansi = self.is_ansi;
extensions.insert(fields);
} else {
eprintln!(
Expand Down Expand Up @@ -875,12 +918,13 @@ where
}

let mut fields = FormattedFields::<N>::new(String::new());
fields.was_ansi = self.is_ansi;
fields.was_ansi_sanitized = self.ansi_sanitization;
if self
.fmt_fields
.format_fields(fields.as_writer().with_ansi(self.is_ansi), values)
.format_fields(fields.as_writer(), values)
.is_ok()
{
fields.was_ansi = self.is_ansi;
extensions.insert(fields);
}
}
Expand Down Expand Up @@ -995,7 +1039,9 @@ where
.fmt_event
.format_event(
&ctx,
format::Writer::new(&mut buf).with_ansi(self.is_ansi),
format::Writer::new(&mut buf)
.with_ansi(self.is_ansi)
.with_ansi_sanitization(self.ansi_sanitization),
event,
)
.is_ok()
Expand Down
52 changes: 34 additions & 18 deletions tracing-subscriber/src/fmt/format/escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

use std::fmt::{self, Write};

/// A wrapper that implements `fmt::Debug` and `fmt::Display` and escapes ANSI sequences on-the-fly.
/// This avoids creating intermediate strings while providing security against terminal injection.
pub(super) struct Escape<T>(pub(super) T);
/// A wrapper that conditionally escapes ANSI sequences when formatted.
pub(super) struct AnsiValue<T> {
pub(super) value: T,
pub(super) sanitize: bool,
}

impl<T> AnsiValue<T> {
pub(super) fn new(value: T, sanitize: bool) -> Self {
Self { value, sanitize }
}
}

/// Helper struct that escapes ANSI sequences as characters are written
struct EscapingWriter<'a, 'b> {
Expand All @@ -17,35 +25,43 @@ impl<'a, 'b> fmt::Write for EscapingWriter<'a, 'b> {
for ch in s.chars() {
match ch {
// C0 control characters that can be used in terminal escape sequences
'\x1b' => self.inner.write_str("\\x1b")?, // ESC
'\x07' => self.inner.write_str("\\x07")?, // BEL
'\x08' => self.inner.write_str("\\x08")?, // BS
'\x0c' => self.inner.write_str("\\x0c")?, // FF
'\x7f' => self.inner.write_str("\\x7f")?, // DEL
'\x1b' => self.inner.write_str("\\x1b")?, // ESC
'\x07' => self.inner.write_str("\\x07")?, // BEL
'\x08' => self.inner.write_str("\\x08")?, // BS
'\x0c' => self.inner.write_str("\\x0c")?, // FF
'\x7f' => self.inner.write_str("\\x7f")?, // DEL

// C1 control characters (\x80-\x9f) - 8-bit control codes
// These can be used as alternative escape sequences in some terminals
ch if ch as u32 >= 0x80 && ch as u32 <= 0x9f => {
write!(self.inner, "\\u{{{:x}}}", ch as u32)?
},
}

_ => self.inner.write_char(ch)?,
}
}
Ok(())
}
}

impl<T: fmt::Debug> fmt::Debug for Escape<T> {
impl<T: fmt::Debug> fmt::Debug for AnsiValue<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut escaping_writer = EscapingWriter { inner: f };
write!(escaping_writer, "{:?}", self.0)
if self.sanitize {
let mut escaping_writer = EscapingWriter { inner: f };
write!(escaping_writer, "{:?}", self.value)
} else {
write!(f, "{:?}", self.value)
}
}
}

impl<T: fmt::Display> fmt::Display for Escape<T> {
impl<T: fmt::Display> fmt::Display for AnsiValue<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut escaping_writer = EscapingWriter { inner: f };
write!(escaping_writer, "{}", self.0)
if self.sanitize {
let mut escaping_writer = EscapingWriter { inner: f };
write!(escaping_writer, "{}", self.value)
} else {
write!(f, "{}", self.value)
}
}
}
}
Loading
Loading