tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

lib.rs (7079B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #![cfg_attr(oom_with = "hook", feature(alloc_error_hook))]
      6 #![cfg_attr(oom_with = "alloc_error_panic", feature(panic_oom_payload))]
      7 
      8 use arrayvec::ArrayString;
      9 use std::alloc::{GlobalAlloc, Layout};
     10 use std::cmp;
     11 use std::ops::Deref;
     12 use std::os::raw::c_char;
     13 use std::os::raw::c_int;
     14 use std::os::raw::c_void;
     15 use std::panic;
     16 use std::panic::PanicHookInfo;
     17 
     18 #[link(name = "wrappers")]
     19 extern "C" {
     20    // We can't use MOZ_Crash directly because it may be weakly linked
     21    // and rust can't handle that.
     22    fn RustMozCrash(filename: *const c_char, line: c_int, reason: *const c_char) -> !;
     23 }
     24 
     25 /// Truncate a string at the closest unicode character boundary
     26 /// ```
     27 /// assert_eq!(str_truncate_valid("éà", 3), "é");
     28 /// assert_eq!(str_truncate_valid("éà", 4), "éè");
     29 /// ```
     30 fn str_truncate_valid(s: &str, mut mid: usize) -> &str {
     31    loop {
     32        if let Some(res) = s.get(..mid) {
     33            return res;
     34        }
     35        mid -= 1;
     36    }
     37 }
     38 
     39 /// Similar to ArrayString, but with terminating nul character.
     40 #[derive(Debug, PartialEq)]
     41 struct ArrayCString<const CAP: usize> {
     42    inner: ArrayString<CAP>,
     43 }
     44 
     45 impl<S: AsRef<str>, const CAP: usize> From<S> for ArrayCString<CAP> {
     46    /// Contrary to ArrayString::from, truncates at the closest unicode
     47    /// character boundary.
     48    /// ```
     49    /// assert_eq!(ArrayCString::<4>::from("éà"),
     50    ///            ArrayCString::<4>::from("é"));
     51    /// assert_eq!(&*ArrayCString::<4>::from("éà"), "é\0");
     52    /// ```
     53    fn from(s: S) -> Self {
     54        let s = s.as_ref();
     55        let len = cmp::min(s.len(), CAP - 1);
     56        let mut result = Self {
     57            inner: ArrayString::from(str_truncate_valid(s, len)).unwrap(),
     58        };
     59        result.inner.push('\0');
     60        result
     61    }
     62 }
     63 
     64 impl<const CAP: usize> Deref for ArrayCString<CAP> {
     65    type Target = str;
     66 
     67    fn deref(&self) -> &str {
     68        self.inner.as_str()
     69    }
     70 }
     71 
     72 fn panic_hook(info: &PanicHookInfo) {
     73    // Try to handle &str/String payloads, which should handle 99% of cases.
     74    let payload = info.payload();
     75    let message = if let Some(layout) = oom_hook::oom_layout(payload) {
     76        unsafe {
     77            oom_hook::RustHandleOOM(layout.size());
     78        }
     79    } else if let Some(s) = payload.downcast_ref::<&str>() {
     80        s
     81    } else if let Some(s) = payload.downcast_ref::<String>() {
     82        s.as_str()
     83    } else {
     84        // Not the most helpful thing, but seems unlikely to happen
     85        // in practice.
     86        "Unhandled rust panic payload!"
     87    };
     88    let (filename, line) = if let Some(loc) = info.location() {
     89        (loc.file(), loc.line())
     90    } else {
     91        ("unknown.rs", 0)
     92    };
     93    // Copy the message and filename to the stack in order to safely add
     94    // a terminating nul character (since rust strings don't come with one
     95    // and RustMozCrash wants one).
     96    let message = ArrayCString::<512>::from(message);
     97    let filename = ArrayCString::<512>::from(filename);
     98    unsafe {
     99        RustMozCrash(
    100            filename.as_ptr() as *const c_char,
    101            line as c_int,
    102            message.as_ptr() as *const c_char,
    103        );
    104    }
    105 }
    106 
    107 /// Configure a panic hook to redirect rust panics to MFBT's MOZ_Crash.
    108 #[no_mangle]
    109 pub extern "C" fn install_rust_hooks() {
    110    panic::set_hook(Box::new(panic_hook));
    111    #[cfg(oom_with = "hook")]
    112    use std::alloc::set_alloc_error_hook;
    113    #[cfg(oom_with = "hook")]
    114    set_alloc_error_hook(oom_hook::hook);
    115 }
    116 
    117 mod oom_hook {
    118    #[cfg(oom_with = "alloc_error_panic")]
    119    use std::alloc::AllocErrorPanicPayload;
    120    use std::alloc::Layout;
    121    use std::any::Any;
    122 
    123    #[inline(always)]
    124    pub fn oom_layout(_payload: &dyn Any) -> Option<Layout> {
    125        #[cfg(oom_with = "alloc_error_panic")]
    126        return _payload
    127            .downcast_ref::<AllocErrorPanicPayload>()
    128            .map(|p| p.layout());
    129        #[cfg(not(oom_with = "alloc_error_panic"))]
    130        return None;
    131    }
    132 
    133    extern "C" {
    134        pub fn RustHandleOOM(size: usize) -> !;
    135    }
    136 
    137    #[cfg(oom_with = "hook")]
    138    pub fn hook(layout: Layout) {
    139        unsafe {
    140            RustHandleOOM(layout.size());
    141        }
    142    }
    143 }
    144 
    145 extern "C" {
    146    fn malloc(size: usize) -> *mut c_void;
    147 
    148    fn free(ptr: *mut c_void);
    149 
    150    fn calloc(nmemb: usize, size: usize) -> *mut c_void;
    151 
    152    fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
    153 
    154    #[cfg(windows)]
    155    fn _aligned_malloc(size: usize, align: usize) -> *mut c_void;
    156 
    157    #[cfg(windows)]
    158    fn _aligned_free(ptr: *mut c_void);
    159 
    160    #[cfg(not(windows))]
    161    fn memalign(align: usize, size: usize) -> *mut c_void;
    162 }
    163 
    164 #[cfg(windows)]
    165 unsafe fn memalign(align: usize, size: usize) -> *mut c_void {
    166    _aligned_malloc(size, align)
    167 }
    168 
    169 pub struct GeckoAlloc;
    170 
    171 #[inline(always)]
    172 fn need_memalign(layout: Layout) -> bool {
    173    // mozjemalloc guarantees a minimum alignment of 16 for all sizes, except
    174    // for size classes below 16 (4 and 8).
    175    layout.align() > layout.size() || layout.align() > 16
    176 }
    177 
    178 unsafe impl GlobalAlloc for GeckoAlloc {
    179    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
    180        if need_memalign(layout) {
    181            memalign(layout.align(), layout.size()) as *mut u8
    182        } else {
    183            malloc(layout.size()) as *mut u8
    184        }
    185    }
    186 
    187    // On Windows, _aligned_free must be used to free memory allocated with
    188    // _aligned_malloc. Except when mozjemalloc is enabled, in which case
    189    // _aligned_malloc-allocated memory can be freed with free.
    190    #[cfg(all(windows, not(feature = "moz_memory")))]
    191    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
    192        if need_memalign(layout) {
    193            _aligned_free(ptr as *mut c_void)
    194        } else {
    195            free(ptr as *mut c_void)
    196        }
    197    }
    198 
    199    #[cfg(any(not(windows), feature = "moz_memory"))]
    200    unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
    201        free(ptr as *mut c_void)
    202    }
    203 
    204    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
    205        if need_memalign(layout) {
    206            let ptr = self.alloc(layout);
    207            if !ptr.is_null() {
    208                std::ptr::write_bytes(ptr, 0, layout.size());
    209            }
    210            ptr
    211        } else {
    212            calloc(1, layout.size()) as *mut u8
    213        }
    214    }
    215 
    216    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
    217        let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
    218        if need_memalign(new_layout) {
    219            let new_ptr = self.alloc(new_layout);
    220            if !new_ptr.is_null() {
    221                let size = std::cmp::min(layout.size(), new_size);
    222                std::ptr::copy_nonoverlapping(ptr, new_ptr, size);
    223                self.dealloc(ptr, layout);
    224            }
    225            new_ptr
    226        } else {
    227            realloc(ptr as *mut c_void, new_size) as *mut u8
    228        }
    229    }
    230 }
    231 
    232 #[global_allocator]
    233 static A: GeckoAlloc = GeckoAlloc;