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
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
45

            
46
// TODO: Try making it not Deref and having expose+expose_mut instead; how bad is it?
47

            
48
use educe::Educe;
49
#[cfg(feature = "serde")]
50
use serde::{Deserialize, Serialize};
51

            
52
mod err;
53
mod flags;
54
mod impls;
55

            
56
pub use err::Error;
57
pub use flags::{disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed, Guard};
58

            
59
use std::ops::Deref;
60

            
61
/// A `Result` returned by the flag-manipulation functions in `safelog`.
62
pub type Result<T> = std::result::Result<T, Error>;
63

            
64
/// A wrapper type for a sensitive value.
65
///
66
/// By default, a `Sensitive<T>` behaves the same as a regular `T`, except that
67
/// attempts to turn it into a string (via `Display`, `Debug`, etc) all produce
68
/// the string `[scrubbed]`.
69
///
70
/// This behavior can be overridden locally by using
71
/// [`with_safe_logging_suppressed`] and globally with [`disable_safe_logging`].
72
144
#[derive(Educe, Clone, Copy)]
73
#[educe(
74
    Default(bound),
75
    Deref,
76
    DerefMut,
77
    Eq(bound),
78
    Hash(bound),
79
    Ord(bound),
80
    PartialEq(bound),
81
    PartialOrd(bound)
82
)]
83
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
84
#[cfg_attr(feature = "serde", serde(transparent))]
85
pub struct Sensitive<T>(T);
86

            
87
impl<T> Sensitive<T> {
88
    /// Create a new `Sensitive<T>`, wrapping a provided `value`.
89
68948
    pub fn new(value: T) -> Self {
90
68948
        Sensitive(value)
91
68948
    }
92

            
93
    /// Extract the inner value from this `Sensitive<T>`.
94
1478
    pub fn into_inner(self) -> T {
95
1478
        self.0
96
1478
    }
97

            
98
    /// Extract the inner value from this `Sensitive<T>`.
99
    #[deprecated = "Use the new into_inner method instead"]
100
2
    pub fn unwrap(sensitive: Sensitive<T>) -> T {
101
2
        sensitive.into_inner()
102
2
    }
103

            
104
    /// Converts `&Sensitive<T>` to `Sensitive<&T>`
105
2
    pub fn as_ref(&self) -> Sensitive<&T> {
106
2
        Sensitive(&self.0)
107
2
    }
108

            
109
    /// Return a reference to the inner value
110
    //
111
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-sensitivisation"
112
    // via what is usually a semantically-neutral interface.
113
18
    pub fn as_inner(&self) -> &T {
114
18
        &self.0
115
18
    }
116
}
117

            
118
/// Wrap a value as `Sensitive`.
119
///
120
/// This function is an alias for [`Sensitive::new`].
121
24
pub fn sensitive<T>(value: T) -> Sensitive<T> {
122
24
    Sensitive(value)
123
24
}
124

            
125
impl<T> From<T> for Sensitive<T> {
126
68942
    fn from(value: T) -> Self {
127
68942
        Sensitive::new(value)
128
68942
    }
129
}
130

            
131
/// Helper: Declare one or more Display-like implementations for a
132
/// Sensitive-like type.  These implementations will delegate to their std::fmt
133
/// types if safe logging is disabled, and write `[scrubbed]` otherwise.
134
macro_rules! impl_display_traits {
135
    { $($trait:ident),* } => {
136
    $(
137
        impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
138
84
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139
84
                if flags::unsafe_logging_enabled() {
140
32
                    std::fmt::$trait::fmt(&self.0, f)
141
                } else {
142
52
                    write!(f, "[scrubbed]")
143
                }
144
84
            }
145
        }
146

            
147
        impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
148
            #[inline]
149
8
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150
8
                std::fmt::$trait::fmt(&*self.0, f)
151
8
            }
152
        }
153
   )*
154
   }
155
}
156

            
157
/// A wrapper suitable for logging and including in errors
158
///
159
/// This is a newtype around `Box<Sensitive<T>>`.
160
///
161
/// This is useful particularly in errors,
162
/// where the box can help reduce the size of error variants
163
/// (for example ones containing large values like an `OwnedChanTarget`).
164
///
165
/// `BoxSensitive<T>` dereferences to [`Sensitive<T>`].
166
//
167
// Making it be a newtype rather than a type alias allows us to implement
168
// `into_inner` and `From<T>` and so on.
169
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
170
pub struct BoxSensitive<T>(Box<Sensitive<T>>);
171

            
172
impl<T> From<T> for BoxSensitive<T> {
173
2
    fn from(t: T) -> BoxSensitive<T> {
174
2
        BoxSensitive(Box::new(sensitive(t)))
175
2
    }
176
}
177

            
178
impl<T> BoxSensitive<T> {
179
    /// Return the innermost `T`
180
2
    pub fn into_inner(self) -> T {
181
2
        // TODO want unstable Box::into_inner(self.0) rust-lang/rust/issues/80437
182
2
        let unboxed = *self.0;
183
2
        unboxed.into_inner()
184
2
    }
185
}
186

            
187
impl<T> Deref for BoxSensitive<T> {
188
    type Target = Sensitive<T>;
189

            
190
2
    fn deref(&self) -> &Sensitive<T> {
191
2
        &self.0
192
2
    }
193
}
194

            
195
impl_display_traits! {
196
    Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
197
}
198

            
199
/// A `redactable` object is one where we know a way to display _part_ of it
200
/// when we are running with safe logging enabled.
201
///
202
/// For example, instead of referring to a user as `So-and-So` or `[scrubbed]`,
203
/// this trait would allow referring to the user as `S[...]`.
204
///
205
/// # Privacy notes
206
///
207
/// Displaying some information about an object is always less safe than
208
/// displaying no information about it!
209
///
210
/// For example, in an environment with only a small number of users, the first
211
/// letter of a user's name might be plenty of information to identify them
212
/// uniquely.
213
///
214
/// Even if a piece of redacted information is safe on its own, several pieces
215
/// of redacted information, when taken together, can be enough for an adversary
216
/// to infer more than you want.  For example, if you log somebody's first
217
/// initial, month of birth, and last-two-digits of ID number, you have just
218
/// discarded 99.9% of potential individuals from the attacker's consideration.
219
pub trait Redactable: std::fmt::Display + std::fmt::Debug {
220
    /// As `Display::fmt`, but produce a redacted representation.
221
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
222
    /// As `Debug::fmt`, but produce a redacted representation.
223
4
    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224
4
        self.display_redacted(f)
225
4
    }
226
    /// Return a smart pointer that will display or debug this object as its
227
    /// redacted form.
228
40
    fn redacted(&self) -> Redacted<&Self> {
229
40
        Redacted(self)
230
40
    }
231
    /// Return a smart pointer that redacts this object if `redact` is true.
232
3832
    fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
233
3832
        if redact {
234
6
            MaybeRedacted(either::Either::Right(Redacted(self)))
235
        } else {
236
3826
            MaybeRedacted(either::Either::Left(self))
237
        }
238
3832
    }
239
}
240

            
241
impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
242
116
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243
116
        (*self).display_redacted(f)
244
116
    }
245
}
246

            
247
/// A wrapper around a `Redactable` that displays it in redacted format.
248
#[derive(Educe, Clone, Copy)]
249
#[educe(
250
    Default(bound),
251
    Deref,
252
    DerefMut,
253
    Eq(bound),
254
    Hash(bound),
255
    Ord(bound),
256
    PartialEq(bound),
257
    PartialOrd(bound)
258
)]
259
#[derive(derive_more::From)]
260
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
261
#[cfg_attr(feature = "serde", serde(transparent))]
262
pub struct Redacted<T: Redactable>(T);
263

            
264
impl<T: Redactable> Redacted<T> {
265
    /// Create a new `Redacted`.
266
2
    pub fn new(value: T) -> Self {
267
2
        Self(value)
268
2
    }
269

            
270
    /// Consume this wrapper and return its inner value.
271
2
    pub fn unwrap(self) -> T {
272
2
        self.0
273
2
    }
274

            
275
    /// Converts `&Redacted<T>` to `Redacted<&T>`
276
    pub fn as_ref(&self) -> Redacted<&T> {
277
        Redacted(&self.0)
278
    }
279

            
280
    /// Return a reference to the inner value
281
    //
282
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-redaction"
283
    // via what is usually a semantically-neutral interface.
284
    pub fn as_inner(&self) -> &T {
285
        &self.0
286
    }
287
}
288

            
289
impl<T: Redactable> std::fmt::Display for Redacted<T> {
290
40
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291
40
        if flags::unsafe_logging_enabled() {
292
2
            std::fmt::Display::fmt(&self.0, f)
293
        } else {
294
38
            self.0.display_redacted(f)
295
        }
296
40
    }
297
}
298

            
299
impl<T: Redactable> std::fmt::Debug for Redacted<T> {
300
6
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301
6
        if flags::unsafe_logging_enabled() {
302
2
            std::fmt::Debug::fmt(&self.0, f)
303
        } else {
304
4
            self.0.debug_redacted(f)
305
        }
306
6
    }
307
}
308

            
309
/// An object that may or may not be redacted.
310
///
311
/// Used to implement conditional redaction
312
#[derive(Clone, derive_more::Display)]
313
pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
314

            
315
impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
316
4
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317
        use std::fmt::Debug;
318
4
        match &self.0 {
319
2
            either::Either::Left(v) => Debug::fmt(v, f),
320
2
            either::Either::Right(v) => Debug::fmt(v, f),
321
        }
322
4
    }
323
}
324

            
325
#[cfg(test)]
326
mod test {
327
    // @@ begin test lint list maintained by maint/add_warning @@
328
    #![allow(clippy::bool_assert_comparison)]
329
    #![allow(clippy::clone_on_copy)]
330
    #![allow(clippy::dbg_macro)]
331
    #![allow(clippy::mixed_attributes_style)]
332
    #![allow(clippy::print_stderr)]
333
    #![allow(clippy::print_stdout)]
334
    #![allow(clippy::single_char_pattern)]
335
    #![allow(clippy::unwrap_used)]
336
    #![allow(clippy::unchecked_duration_subtraction)]
337
    #![allow(clippy::useless_vec)]
338
    #![allow(clippy::needless_pass_by_value)]
339
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
340
    use super::*;
341
    use serial_test::serial;
342
    use static_assertions::{assert_impl_all, assert_not_impl_any};
343

            
344
    #[test]
345
    fn clone_bound() {
346
        // Here we'll make sure that educe bounds work about the way we expect.
347
        #[derive(Clone)]
348
        struct A;
349
        struct B;
350

            
351
        let _x = Sensitive(A).clone();
352
        let _y = Sensitive(B);
353

            
354
        assert_impl_all!(Sensitive<A> : Clone);
355
        assert_not_impl_any!(Sensitive<B> : Clone);
356
    }
357

            
358
    #[test]
359
    #[serial]
360
    fn debug_vec() {
361
        type SVec = Sensitive<Vec<u32>>;
362

            
363
        let mut sv = SVec::default();
364
        assert!(sv.is_empty());
365
        sv.push(104);
366
        sv.push(49);
367
        assert_eq!(sv.len(), 2);
368

            
369
        assert!(!flags::unsafe_logging_enabled());
370
        assert_eq!(format!("{:?}", &sv), "[scrubbed]");
371
        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
372
        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
373
        let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
374
        assert_eq!(normal, "[104, 49]");
375

            
376
        let _g = disable_safe_logging().unwrap();
377
        assert_eq!(format!("{:?}", &sv), "[104, 49]");
378

            
379
        assert_eq!(sv, SVec::from(vec![104, 49]));
380
        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
381
        assert_eq!(*sv, vec![104, 49]);
382
    }
383

            
384
    #[test]
385
    #[serial]
386
    #[allow(deprecated)]
387
    fn deprecated() {
388
        type SVec = Sensitive<Vec<u32>>;
389
        let sv = Sensitive(vec![104, 49]);
390

            
391
        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
392
    }
393

            
394
    #[test]
395
    #[serial]
396
    fn display_various() {
397
        let val = Sensitive::<u32>::new(0x0ed19a);
398

            
399
        let closure1 = || {
400
            format!(
401
                "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
402
                &val, &val, &val, &val, &val, &val,
403
            )
404
        };
405
        let s1 = closure1();
406
        let s2 = with_safe_logging_suppressed(closure1);
407
        assert_eq!(
408
            s1,
409
            "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
410
        );
411
        assert_eq!(
412
            s2,
413
            "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
414
        );
415

            
416
        let n = 1.0E32;
417
        let val = Sensitive::<f64>::new(n);
418
        let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
419
        let closure2 = || format!("{:?}, {}, {:e}, {:E}", &val, &val, &val, &val);
420
        let s1 = closure2();
421
        let s2 = with_safe_logging_suppressed(closure2);
422
        assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
423
        assert_eq!(s2, expect);
424

            
425
        let ptr: *const u8 = std::ptr::null();
426
        let val = Sensitive::new(ptr);
427
        let expect = format!("{:?}, {:p}", ptr, ptr);
428
        let closure3 = || format!("{:?}, {:p}", val, val);
429
        let s1 = closure3();
430
        let s2 = with_safe_logging_suppressed(closure3);
431
        assert_eq!(s1, "[scrubbed], [scrubbed]");
432
        assert_eq!(s2, expect);
433
    }
434

            
435
    #[test]
436
    #[serial]
437
    fn box_sensitive() {
438
        let b: BoxSensitive<_> = "hello world".into();
439

            
440
        assert_eq!(b.clone().into_inner(), "hello world");
441

            
442
        let closure = || format!("{} {:?}", b, b);
443
        assert_eq!(closure(), "[scrubbed] [scrubbed]");
444
        assert_eq!(
445
            with_safe_logging_suppressed(closure),
446
            r#"hello world "hello world""#
447
        );
448

            
449
        assert_eq!(b.len(), 11);
450
    }
451

            
452
    #[test]
453
    #[serial]
454
    fn test_redacted() {
455
        let localhost = std::net::Ipv4Addr::LOCALHOST;
456
        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
457

            
458
        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
459
        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
460

            
461
        let closure = |b| {
462
            format!(
463
                "{} {:?}",
464
                localhost.maybe_redacted(b),
465
                localhost.maybe_redacted(b)
466
            )
467
        };
468
        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
469
        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
470

            
471
        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
472
    }
473
}