Skip to content

Commit 37558d5

Browse files
authored
subscriber: allow ansi sanitization to be disabled (#3484)
## Motivation The changes made in #3368 to sanitize ANSI control characters to avoid terminal injection attacks also break colored output and other control characters that users may legitimately wish to use. There is no mechanism to disable this sanitization. ## Solution Add `with_ansi_sanitization` method to allow turning off the sanitization of ansi output. Sanitizing output remains the default behavior. Fixes: #3369
1 parent efc690f commit 37558d5

File tree

10 files changed

+246
-56
lines changed

10 files changed

+246
-56
lines changed

.github/workflows/CI.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,10 @@ jobs:
269269
- tracing
270270
steps:
271271
- uses: actions/checkout@v4
272-
- name: Install Rust 1.81
272+
- name: Install Rust 1.85
273273
uses: dtolnay/rust-toolchain@stable
274274
with:
275-
toolchain: 1.81
275+
toolchain: 1.85
276276
target: wasm32-unknown-unknown
277277
- name: install test runner for wasm
278278
uses: taiki-e/install-action@wasm-pack

tracing-attributes/tests/ui/fail/async_instrument.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ error[E0277]: `(&str,)` doesn't implement `std::fmt::Display`
3030
--> tests/ui/fail/async_instrument.rs:14:57
3131
|
3232
14 | async fn opaque_unsatisfied() -> impl std::fmt::Display {
33-
| _________________________________________________________-
33+
| _________________________________________________________^
3434
15 | | ("",)
3535
16 | | }
3636
| | ^

tracing-subscriber/src/filter/env/field.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ impl Match {
169169
})?
170170
// TODO: validate field name
171171
.to_string();
172+
#[allow(clippy::result_large_err)]
172173
let value = parts
173174
.next()
174175
.map(|part| match regex {

tracing-subscriber/src/fmt/fmt_layer.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub struct Layer<
7272
fmt_event: E,
7373
fmt_span: format::FmtSpanConfig,
7474
is_ansi: bool,
75+
ansi_sanitization: bool,
7576
log_internal_errors: bool,
7677
_inner: PhantomData<fn(S)>,
7778
}
@@ -122,6 +123,7 @@ where
122123
fmt_span: self.fmt_span,
123124
make_writer: self.make_writer,
124125
is_ansi: self.is_ansi,
126+
ansi_sanitization: self.ansi_sanitization,
125127
log_internal_errors: self.log_internal_errors,
126128
_inner: self._inner,
127129
}
@@ -152,6 +154,7 @@ where
152154
fmt_span: self.fmt_span,
153155
make_writer: self.make_writer,
154156
is_ansi: self.is_ansi,
157+
ansi_sanitization: self.ansi_sanitization,
155158
log_internal_errors: self.log_internal_errors,
156159
_inner: self._inner,
157160
}
@@ -185,6 +188,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
185188
fmt_event: self.fmt_event,
186189
fmt_span: self.fmt_span,
187190
is_ansi: self.is_ansi,
191+
ansi_sanitization: self.ansi_sanitization,
188192
log_internal_errors: self.log_internal_errors,
189193
make_writer,
190194
_inner: self._inner,
@@ -290,6 +294,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
290294
fmt_event: self.fmt_event,
291295
fmt_span: self.fmt_span,
292296
is_ansi: self.is_ansi,
297+
ansi_sanitization: self.ansi_sanitization,
293298
log_internal_errors: self.log_internal_errors,
294299
make_writer: TestWriter::default(),
295300
_inner: self._inner,
@@ -340,6 +345,19 @@ impl<S, N, E, W> Layer<S, N, E, W> {
340345
}
341346
}
342347

348+
/// Sets whether ANSI control character sanitization is enabled.
349+
///
350+
/// This defaults to `true` as a protective measure against terminal
351+
/// injection attacks. If this is set to `false`, ANSI sanitization is
352+
/// disabled and trusted ANSI control sequences in logged values are passed
353+
/// through unchanged.
354+
pub fn with_ansi_sanitization(self, ansi_sanitization: bool) -> Self {
355+
Self {
356+
ansi_sanitization,
357+
..self
358+
}
359+
}
360+
343361
/// Sets whether to write errors from [`FormatEvent`] to the writer.
344362
/// Defaults to true.
345363
///
@@ -386,6 +404,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
386404
fmt_event: self.fmt_event,
387405
fmt_span: self.fmt_span,
388406
is_ansi: self.is_ansi,
407+
ansi_sanitization: self.ansi_sanitization,
389408
log_internal_errors: self.log_internal_errors,
390409
make_writer: f(self.make_writer),
391410
_inner: self._inner,
@@ -418,6 +437,7 @@ where
418437
fmt_span: self.fmt_span,
419438
make_writer: self.make_writer,
420439
is_ansi: self.is_ansi,
440+
ansi_sanitization: self.ansi_sanitization,
421441
log_internal_errors: self.log_internal_errors,
422442
_inner: self._inner,
423443
}
@@ -431,6 +451,7 @@ where
431451
fmt_span: self.fmt_span.without_time(),
432452
make_writer: self.make_writer,
433453
is_ansi: self.is_ansi,
454+
ansi_sanitization: self.ansi_sanitization,
434455
log_internal_errors: self.log_internal_errors,
435456
_inner: self._inner,
436457
}
@@ -560,6 +581,7 @@ where
560581
fmt_span: self.fmt_span,
561582
make_writer: self.make_writer,
562583
is_ansi: self.is_ansi,
584+
ansi_sanitization: self.ansi_sanitization,
563585
log_internal_errors: self.log_internal_errors,
564586
_inner: self._inner,
565587
}
@@ -575,6 +597,7 @@ where
575597
fmt_span: self.fmt_span,
576598
make_writer: self.make_writer,
577599
is_ansi: self.is_ansi,
600+
ansi_sanitization: self.ansi_sanitization,
578601
log_internal_errors: self.log_internal_errors,
579602
_inner: self._inner,
580603
}
@@ -606,6 +629,7 @@ where
606629
make_writer: self.make_writer,
607630
// always disable ANSI escapes in JSON mode!
608631
is_ansi: false,
632+
ansi_sanitization: self.ansi_sanitization,
609633
log_internal_errors: self.log_internal_errors,
610634
_inner: self._inner,
611635
}
@@ -673,6 +697,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
673697
fmt_span: self.fmt_span,
674698
make_writer: self.make_writer,
675699
is_ansi: self.is_ansi,
700+
ansi_sanitization: self.ansi_sanitization,
676701
log_internal_errors: self.log_internal_errors,
677702
_inner: self._inner,
678703
}
@@ -704,6 +729,7 @@ impl<S, N, E, W> Layer<S, N, E, W> {
704729
fmt_span: self.fmt_span,
705730
make_writer: self.make_writer,
706731
is_ansi: self.is_ansi,
732+
ansi_sanitization: self.ansi_sanitization,
707733
log_internal_errors: self.log_internal_errors,
708734
_inner: self._inner,
709735
}
@@ -722,6 +748,7 @@ impl<S> Default for Layer<S> {
722748
fmt_span: format::FmtSpanConfig::default(),
723749
make_writer: io::stdout,
724750
is_ansi: ansi,
751+
ansi_sanitization: true,
725752
log_internal_errors: false,
726753
_inner: PhantomData,
727754
}
@@ -754,20 +781,32 @@ where
754781
/// without conflicting.
755782
///
756783
/// [extensions]: crate::registry::Extensions
757-
#[derive(Default)]
758784
pub struct FormattedFields<E: ?Sized> {
759785
_format_fields: PhantomData<fn(E)>,
760786
was_ansi: bool,
787+
was_ansi_sanitized: bool,
761788
/// The formatted fields of a span.
762789
pub fields: String,
763790
}
764791

792+
impl<E: ?Sized> Default for FormattedFields<E> {
793+
fn default() -> Self {
794+
Self {
795+
_format_fields: Default::default(),
796+
was_ansi: Default::default(),
797+
was_ansi_sanitized: true,
798+
fields: Default::default(),
799+
}
800+
}
801+
}
802+
765803
impl<E: ?Sized> FormattedFields<E> {
766804
/// Returns a new `FormattedFields`.
767805
pub fn new(fields: String) -> Self {
768806
Self {
769807
fields,
770808
was_ansi: false,
809+
was_ansi_sanitized: true,
771810
_format_fields: PhantomData,
772811
}
773812
}
@@ -777,7 +816,9 @@ impl<E: ?Sized> FormattedFields<E> {
777816
/// The returned [`format::Writer`] can be used with the
778817
/// [`FormatFields::format_fields`] method.
779818
pub fn as_writer(&mut self) -> format::Writer<'_> {
780-
format::Writer::new(&mut self.fields).with_ansi(self.was_ansi)
819+
format::Writer::new(&mut self.fields)
820+
.with_ansi(self.was_ansi)
821+
.with_ansi_sanitization(self.was_ansi_sanitized)
781822
}
782823
}
783824

@@ -787,6 +828,7 @@ impl<E: ?Sized> fmt::Debug for FormattedFields<E> {
787828
.field("fields", &self.fields)
788829
.field("formatter", &format_args!("{}", std::any::type_name::<E>()))
789830
.field("was_ansi", &self.was_ansi)
831+
.field("was_ansi_sanitized", &self.was_ansi_sanitized)
790832
.finish()
791833
}
792834
}
@@ -835,12 +877,13 @@ where
835877

836878
if extensions.get_mut::<FormattedFields<N>>().is_none() {
837879
let mut fields = FormattedFields::<N>::new(String::new());
880+
fields.was_ansi = self.is_ansi;
881+
fields.was_ansi_sanitized = self.ansi_sanitization;
838882
if self
839883
.fmt_fields
840-
.format_fields(fields.as_writer().with_ansi(self.is_ansi), attrs)
884+
.format_fields(fields.as_writer(), attrs)
841885
.is_ok()
842886
{
843-
fields.was_ansi = self.is_ansi;
844887
extensions.insert(fields);
845888
} else {
846889
eprintln!(
@@ -875,12 +918,13 @@ where
875918
}
876919

877920
let mut fields = FormattedFields::<N>::new(String::new());
921+
fields.was_ansi = self.is_ansi;
922+
fields.was_ansi_sanitized = self.ansi_sanitization;
878923
if self
879924
.fmt_fields
880-
.format_fields(fields.as_writer().with_ansi(self.is_ansi), values)
925+
.format_fields(fields.as_writer(), values)
881926
.is_ok()
882927
{
883-
fields.was_ansi = self.is_ansi;
884928
extensions.insert(fields);
885929
}
886930
}
@@ -995,7 +1039,9 @@ where
9951039
.fmt_event
9961040
.format_event(
9971041
&ctx,
998-
format::Writer::new(&mut buf).with_ansi(self.is_ansi),
1042+
format::Writer::new(&mut buf)
1043+
.with_ansi(self.is_ansi)
1044+
.with_ansi_sanitization(self.ansi_sanitization),
9991045
event,
10001046
)
10011047
.is_ok()

tracing-subscriber/src/fmt/format/escape.rs

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22
33
use std::fmt::{self, Write};
44

5-
/// A wrapper that implements `fmt::Debug` and `fmt::Display` and escapes ANSI sequences on-the-fly.
6-
/// This avoids creating intermediate strings while providing security against terminal injection.
7-
pub(super) struct Escape<T>(pub(super) T);
5+
/// A wrapper that conditionally escapes ANSI sequences when formatted.
6+
pub(super) struct EscapeGuard<T> {
7+
pub(super) value: T,
8+
pub(super) sanitize: bool,
9+
}
10+
11+
impl<T> EscapeGuard<T> {
12+
pub(super) fn new(value: T, sanitize: bool) -> Self {
13+
Self { value, sanitize }
14+
}
15+
}
816

917
/// Helper struct that escapes ANSI sequences as characters are written
1018
struct EscapingWriter<'a, 'b> {
@@ -17,35 +25,43 @@ impl<'a, 'b> fmt::Write for EscapingWriter<'a, 'b> {
1725
for ch in s.chars() {
1826
match ch {
1927
// C0 control characters that can be used in terminal escape sequences
20-
'\x1b' => self.inner.write_str("\\x1b")?, // ESC
21-
'\x07' => self.inner.write_str("\\x07")?, // BEL
22-
'\x08' => self.inner.write_str("\\x08")?, // BS
23-
'\x0c' => self.inner.write_str("\\x0c")?, // FF
24-
'\x7f' => self.inner.write_str("\\x7f")?, // DEL
25-
28+
'\x1b' => self.inner.write_str("\\x1b")?, // ESC
29+
'\x07' => self.inner.write_str("\\x07")?, // BEL
30+
'\x08' => self.inner.write_str("\\x08")?, // BS
31+
'\x0c' => self.inner.write_str("\\x0c")?, // FF
32+
'\x7f' => self.inner.write_str("\\x7f")?, // DEL
33+
2634
// C1 control characters (\x80-\x9f) - 8-bit control codes
2735
// These can be used as alternative escape sequences in some terminals
2836
ch if ch as u32 >= 0x80 && ch as u32 <= 0x9f => {
2937
write!(self.inner, "\\u{{{:x}}}", ch as u32)?
30-
},
31-
38+
}
39+
3240
_ => self.inner.write_char(ch)?,
3341
}
3442
}
3543
Ok(())
3644
}
3745
}
3846

39-
impl<T: fmt::Debug> fmt::Debug for Escape<T> {
47+
impl<T: fmt::Debug> fmt::Debug for EscapeGuard<T> {
4048
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41-
let mut escaping_writer = EscapingWriter { inner: f };
42-
write!(escaping_writer, "{:?}", self.0)
49+
if self.sanitize {
50+
let mut escaping_writer = EscapingWriter { inner: f };
51+
write!(escaping_writer, "{:?}", self.value)
52+
} else {
53+
write!(f, "{:?}", self.value)
54+
}
4355
}
4456
}
4557

46-
impl<T: fmt::Display> fmt::Display for Escape<T> {
58+
impl<T: fmt::Display> fmt::Display for EscapeGuard<T> {
4759
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48-
let mut escaping_writer = EscapingWriter { inner: f };
49-
write!(escaping_writer, "{}", self.0)
60+
if self.sanitize {
61+
let mut escaping_writer = EscapingWriter { inner: f };
62+
write!(escaping_writer, "{}", self.value)
63+
} else {
64+
write!(f, "{}", self.value)
65+
}
5066
}
51-
}
67+
}

0 commit comments

Comments
 (0)