safelog/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46
47// TODO: Try making it not Deref and having expose+expose_mut instead; how bad is it?
48
49use educe::Educe;
50#[cfg(feature = "serde")]
51use serde::{Deserialize, Serialize};
52
53mod err;
54mod flags;
55mod impls;
56
57pub use err::Error;
58pub use flags::{disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed, Guard};
59
60use std::ops::Deref;
61
62/// A `Result` returned by the flag-manipulation functions in `safelog`.
63pub type Result<T> = std::result::Result<T, Error>;
64
65/// A wrapper type for a sensitive value.
66///
67/// By default, a `Sensitive<T>` behaves the same as a regular `T`, except that
68/// attempts to turn it into a string (via `Display`, `Debug`, etc) all produce
69/// the string `[scrubbed]`.
70///
71/// This behavior can be overridden locally by using
72/// [`with_safe_logging_suppressed`] and globally with [`disable_safe_logging`].
73#[derive(Educe, Clone, Copy)]
74#[educe(
75    Default(bound),
76    Deref,
77    DerefMut,
78    Eq(bound),
79    Hash(bound),
80    Ord(bound),
81    PartialEq(bound),
82    PartialOrd(bound)
83)]
84#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
85#[cfg_attr(feature = "serde", serde(transparent))]
86pub struct Sensitive<T>(T);
87
88impl<T> Sensitive<T> {
89    /// Create a new `Sensitive<T>`, wrapping a provided `value`.
90    pub fn new(value: T) -> Self {
91        Sensitive(value)
92    }
93
94    /// Extract the inner value from this `Sensitive<T>`.
95    pub fn into_inner(self) -> T {
96        self.0
97    }
98
99    /// Extract the inner value from this `Sensitive<T>`.
100    #[deprecated = "Use the new into_inner method instead"]
101    pub fn unwrap(sensitive: Sensitive<T>) -> T {
102        sensitive.into_inner()
103    }
104
105    /// Converts `&Sensitive<T>` to `Sensitive<&T>`
106    pub fn as_ref(&self) -> Sensitive<&T> {
107        Sensitive(&self.0)
108    }
109
110    /// Return a reference to the inner value
111    //
112    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-sensitivisation"
113    // via what is usually a semantically-neutral interface.
114    pub fn as_inner(&self) -> &T {
115        &self.0
116    }
117}
118
119/// Wrap a value as `Sensitive`.
120///
121/// This function is an alias for [`Sensitive::new`].
122pub fn sensitive<T>(value: T) -> Sensitive<T> {
123    Sensitive(value)
124}
125
126impl<T> From<T> for Sensitive<T> {
127    fn from(value: T) -> Self {
128        Sensitive::new(value)
129    }
130}
131
132/// Helper: Declare one or more Display-like implementations for a
133/// Sensitive-like type.  These implementations will delegate to their std::fmt
134/// types if safe logging is disabled, and write `[scrubbed]` otherwise.
135macro_rules! impl_display_traits {
136    { $($trait:ident),* } => {
137    $(
138        impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
139            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140                if flags::unsafe_logging_enabled() {
141                    std::fmt::$trait::fmt(&self.0, f)
142                } else {
143                    write!(f, "[scrubbed]")
144                }
145            }
146        }
147
148        impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
149            #[inline]
150            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151                std::fmt::$trait::fmt(&*self.0, f)
152            }
153        }
154   )*
155   }
156}
157
158/// A wrapper suitable for logging and including in errors
159///
160/// This is a newtype around `Box<Sensitive<T>>`.
161///
162/// This is useful particularly in errors,
163/// where the box can help reduce the size of error variants
164/// (for example ones containing large values like an `OwnedChanTarget`).
165///
166/// `BoxSensitive<T>` dereferences to [`Sensitive<T>`].
167//
168// Making it be a newtype rather than a type alias allows us to implement
169// `into_inner` and `From<T>` and so on.
170#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
171pub struct BoxSensitive<T>(Box<Sensitive<T>>);
172
173impl<T> From<T> for BoxSensitive<T> {
174    fn from(t: T) -> BoxSensitive<T> {
175        BoxSensitive(Box::new(sensitive(t)))
176    }
177}
178
179impl<T> BoxSensitive<T> {
180    /// Return the innermost `T`
181    pub fn into_inner(self) -> T {
182        // TODO want unstable Box::into_inner(self.0) rust-lang/rust/issues/80437
183        let unboxed = *self.0;
184        unboxed.into_inner()
185    }
186}
187
188impl<T> Deref for BoxSensitive<T> {
189    type Target = Sensitive<T>;
190
191    fn deref(&self) -> &Sensitive<T> {
192        &self.0
193    }
194}
195
196impl_display_traits! {
197    Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
198}
199
200/// A `redactable` object is one where we know a way to display _part_ of it
201/// when we are running with safe logging enabled.
202///
203/// For example, instead of referring to a user as `So-and-So` or `[scrubbed]`,
204/// this trait would allow referring to the user as `S[...]`.
205///
206/// # Privacy notes
207///
208/// Displaying some information about an object is always less safe than
209/// displaying no information about it!
210///
211/// For example, in an environment with only a small number of users, the first
212/// letter of a user's name might be plenty of information to identify them
213/// uniquely.
214///
215/// Even if a piece of redacted information is safe on its own, several pieces
216/// of redacted information, when taken together, can be enough for an adversary
217/// to infer more than you want.  For example, if you log somebody's first
218/// initial, month of birth, and last-two-digits of ID number, you have just
219/// discarded 99.9% of potential individuals from the attacker's consideration.
220pub trait Redactable: std::fmt::Display + std::fmt::Debug {
221    /// As `Display::fmt`, but produce a redacted representation.
222    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
223    /// As `Debug::fmt`, but produce a redacted representation.
224    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        self.display_redacted(f)
226    }
227    /// Return a smart pointer that will display or debug this object as its
228    /// redacted form.
229    fn redacted(&self) -> Redacted<&Self> {
230        Redacted(self)
231    }
232    /// Return a smart pointer that redacts this object if `redact` is true.
233    fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
234        if redact {
235            MaybeRedacted(either::Either::Right(Redacted(self)))
236        } else {
237            MaybeRedacted(either::Either::Left(self))
238        }
239    }
240}
241
242impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
243    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        (*self).display_redacted(f)
245    }
246}
247
248/// A wrapper around a `Redactable` that displays it in redacted format.
249#[derive(Educe, Clone, Copy)]
250#[educe(
251    Default(bound),
252    Deref,
253    DerefMut,
254    Eq(bound),
255    Hash(bound),
256    Ord(bound),
257    PartialEq(bound),
258    PartialOrd(bound)
259)]
260#[derive(derive_more::From)]
261#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
262#[cfg_attr(feature = "serde", serde(transparent))]
263pub struct Redacted<T: Redactable>(T);
264
265impl<T: Redactable> Redacted<T> {
266    /// Create a new `Redacted`.
267    pub fn new(value: T) -> Self {
268        Self(value)
269    }
270
271    /// Consume this wrapper and return its inner value.
272    pub fn unwrap(self) -> T {
273        self.0
274    }
275
276    /// Converts `&Redacted<T>` to `Redacted<&T>`
277    pub fn as_ref(&self) -> Redacted<&T> {
278        Redacted(&self.0)
279    }
280
281    /// Return a reference to the inner value
282    //
283    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-redaction"
284    // via what is usually a semantically-neutral interface.
285    pub fn as_inner(&self) -> &T {
286        &self.0
287    }
288}
289
290impl<T: Redactable> std::fmt::Display for Redacted<T> {
291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292        if flags::unsafe_logging_enabled() {
293            std::fmt::Display::fmt(&self.0, f)
294        } else {
295            self.0.display_redacted(f)
296        }
297    }
298}
299
300impl<T: Redactable> std::fmt::Debug for Redacted<T> {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        if flags::unsafe_logging_enabled() {
303            std::fmt::Debug::fmt(&self.0, f)
304        } else {
305            self.0.debug_redacted(f)
306        }
307    }
308}
309
310/// An object that may or may not be redacted.
311///
312/// Used to implement conditional redaction
313#[derive(Clone, derive_more::Display)]
314pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
315
316impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        use std::fmt::Debug;
319        match &self.0 {
320            either::Either::Left(v) => Debug::fmt(v, f),
321            either::Either::Right(v) => Debug::fmt(v, f),
322        }
323    }
324}
325
326#[cfg(test)]
327mod test {
328    // @@ begin test lint list maintained by maint/add_warning @@
329    #![allow(clippy::bool_assert_comparison)]
330    #![allow(clippy::clone_on_copy)]
331    #![allow(clippy::dbg_macro)]
332    #![allow(clippy::mixed_attributes_style)]
333    #![allow(clippy::print_stderr)]
334    #![allow(clippy::print_stdout)]
335    #![allow(clippy::single_char_pattern)]
336    #![allow(clippy::unwrap_used)]
337    #![allow(clippy::unchecked_duration_subtraction)]
338    #![allow(clippy::useless_vec)]
339    #![allow(clippy::needless_pass_by_value)]
340    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
341    use super::*;
342    use serial_test::serial;
343    use static_assertions::{assert_impl_all, assert_not_impl_any};
344
345    #[test]
346    fn clone_bound() {
347        // Here we'll make sure that educe bounds work about the way we expect.
348        #[derive(Clone)]
349        struct A;
350        struct B;
351
352        let _x = Sensitive(A).clone();
353        let _y = Sensitive(B);
354
355        assert_impl_all!(Sensitive<A> : Clone);
356        assert_not_impl_any!(Sensitive<B> : Clone);
357    }
358
359    #[test]
360    #[serial]
361    fn debug_vec() {
362        type SVec = Sensitive<Vec<u32>>;
363
364        let mut sv = SVec::default();
365        assert!(sv.is_empty());
366        sv.push(104);
367        sv.push(49);
368        assert_eq!(sv.len(), 2);
369
370        assert!(!flags::unsafe_logging_enabled());
371        assert_eq!(format!("{:?}", &sv), "[scrubbed]");
372        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
373        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
374        let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
375        assert_eq!(normal, "[104, 49]");
376
377        let _g = disable_safe_logging().unwrap();
378        assert_eq!(format!("{:?}", &sv), "[104, 49]");
379
380        assert_eq!(sv, SVec::from(vec![104, 49]));
381        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
382        assert_eq!(*sv, vec![104, 49]);
383    }
384
385    #[test]
386    #[serial]
387    #[allow(deprecated)]
388    fn deprecated() {
389        type SVec = Sensitive<Vec<u32>>;
390        let sv = Sensitive(vec![104, 49]);
391
392        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
393    }
394
395    #[test]
396    #[serial]
397    fn display_various() {
398        let val = Sensitive::<u32>::new(0x0ed19a);
399
400        let closure1 = || {
401            format!(
402                "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
403                &val, &val, &val, &val, &val, &val,
404            )
405        };
406        let s1 = closure1();
407        let s2 = with_safe_logging_suppressed(closure1);
408        assert_eq!(
409            s1,
410            "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
411        );
412        assert_eq!(
413            s2,
414            "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
415        );
416
417        let n = 1.0E32;
418        let val = Sensitive::<f64>::new(n);
419        let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
420        let closure2 = || format!("{:?}, {}, {:e}, {:E}", &val, &val, &val, &val);
421        let s1 = closure2();
422        let s2 = with_safe_logging_suppressed(closure2);
423        assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
424        assert_eq!(s2, expect);
425
426        let ptr: *const u8 = std::ptr::null();
427        let val = Sensitive::new(ptr);
428        let expect = format!("{:?}, {:p}", ptr, ptr);
429        let closure3 = || format!("{:?}, {:p}", val, val);
430        let s1 = closure3();
431        let s2 = with_safe_logging_suppressed(closure3);
432        assert_eq!(s1, "[scrubbed], [scrubbed]");
433        assert_eq!(s2, expect);
434    }
435
436    #[test]
437    #[serial]
438    fn box_sensitive() {
439        let b: BoxSensitive<_> = "hello world".into();
440
441        assert_eq!(b.clone().into_inner(), "hello world");
442
443        let closure = || format!("{} {:?}", b, b);
444        assert_eq!(closure(), "[scrubbed] [scrubbed]");
445        assert_eq!(
446            with_safe_logging_suppressed(closure),
447            r#"hello world "hello world""#
448        );
449
450        assert_eq!(b.len(), 11);
451    }
452
453    #[test]
454    #[serial]
455    fn test_redacted() {
456        let localhost = std::net::Ipv4Addr::LOCALHOST;
457        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
458
459        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
460        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
461
462        let closure = |b| {
463            format!(
464                "{} {:?}",
465                localhost.maybe_redacted(b),
466                localhost.maybe_redacted(b)
467            )
468        };
469        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
470        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
471
472        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
473    }
474}