Skip to content

Commit

Permalink
document all remaining unsafe usages
Browse files Browse the repository at this point in the history
adds some debug assertions where appropriate
  • Loading branch information
robjtede committed Aug 22, 2020
1 parent 75d86a6 commit 6ce9957
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 24 deletions.
3 changes: 2 additions & 1 deletion actix-http/src/config.rs
Expand Up @@ -17,7 +17,7 @@ const DATE_VALUE_LENGTH: usize = 29;
pub enum KeepAlive {
/// Keep alive in seconds
Timeout(usize),
/// Relay on OS to shutdown tcp connection
/// Rely on OS to shutdown tcp connection
Os,
/// Disabled
Disabled,
Expand Down Expand Up @@ -209,6 +209,7 @@ impl Date {
date.update();
date
}

fn update(&mut self) {
self.pos = 0;
write!(
Expand Down
4 changes: 3 additions & 1 deletion actix-http/src/h1/decoder.rs
Expand Up @@ -76,12 +76,14 @@ pub(crate) trait MessageType: Sized {
let name =
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();

// SAFETY: httparse checks header value is valid UTF-8
// SAFETY: httparse already checks header value is only visible ASCII bytes
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
let value = unsafe {
HeaderValue::from_maybe_shared_unchecked(
slice.slice(idx.value.0..idx.value.1),
)
};

match name {
header::CONTENT_LENGTH => {
if let Ok(s) = value.to_str() {
Expand Down
4 changes: 4 additions & 0 deletions actix-http/src/h1/dispatcher.rs
Expand Up @@ -314,11 +314,15 @@ where
Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)),
}
}

if written == write_buf.len() {
// SAFETY: setting length to 0 is safe
// skips one length check vs truncate
unsafe { write_buf.set_len(0) }
} else {
write_buf.advance(written);
}

Ok(false)
}

Expand Down
69 changes: 58 additions & 11 deletions actix-http/src/h1/encoder.rs
Expand Up @@ -129,89 +129,133 @@ pub(crate) trait MessageType: Sized {
.chain(extra_headers.inner.iter());

// write headers
let mut pos = 0;

let mut has_date = false;
let mut remaining = dst.capacity() - dst.len();

let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
let mut remaining = dst.capacity() - dst.len();

// tracks bytes written since last buffer resize
// since buf is a raw pointer to a bytes container storage but is written to without the
// container's knowledge, this is used to sync the containers cursor after data is written
let mut pos = 0;

for (key, value) in headers {
match *key {
CONNECTION => continue,
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
DATE => {
has_date = true;
}
DATE => has_date = true,
_ => (),
}

let k = key.as_str().as_bytes();
let k_len = k.len();

match value {
map::Value::One(ref val) => {
let v = val.as_ref();
let v_len = v.len();
let k_len = k.len();

// key length + value length + colon + space + \r\n
let len = k_len + v_len + 4;

if len > remaining {
// not enough room in buffer for this header; reserve more space

// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}

pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();

// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
}
// use upper Camel-Case

// SAFETY: on each write, it is enough to ensure that the advancement of the
// cursor matches the number of bytes written
unsafe {
// use upper Camel-Case
if camel_case {
write_camel_case(k, from_raw_parts_mut(buf, k_len))
} else {
write_data(k, buf, k_len)
}

buf = buf.add(k_len);

write_data(b": ", buf, 2);
buf = buf.add(2);

write_data(v, buf, v_len);
buf = buf.add(v_len);

write_data(b"\r\n", buf, 2);
buf = buf.add(2);
pos += len;
remaining -= len;
}

pos += len;
remaining -= len;
}

map::Value::Multi(ref vec) => {
for val in vec {
let v = val.as_ref();
let v_len = v.len();
let k_len = k.len();
let len = k_len + v_len + 4;

if len > remaining {
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();

// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
}
// use upper Camel-Case

// SAFETY: on each write, it is enough to ensure that the advancement of
// the cursor matches the number of bytes written
unsafe {
if camel_case {
write_camel_case(k, from_raw_parts_mut(buf, k_len));
} else {
write_data(k, buf, k_len);
}

buf = buf.add(k_len);

write_data(b": ", buf, 2);
buf = buf.add(2);

write_data(v, buf, v_len);
buf = buf.add(v_len);

write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};

pos += len;
remaining -= len;
}
}
}
}

// final cursor synchronization with the bytes container
//
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
Expand Down Expand Up @@ -477,7 +521,10 @@ impl<'a> io::Write for Writer<'a> {
}
}

/// # Safety
/// Callers must ensure that the given length matches given value length.
unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
debug_assert_eq!(value.len(), len);
copy_nonoverlapping(value.as_ptr(), buf, len);
}

Expand Down
8 changes: 5 additions & 3 deletions actix-http/src/h2/dispatcher.rs
Expand Up @@ -227,9 +227,11 @@ where
if !has_date {
let mut bytes = BytesMut::with_capacity(29);
self.config.set_date_header(&mut bytes);
res.headers_mut().insert(DATE, unsafe {
HeaderValue::from_maybe_shared_unchecked(bytes.freeze())
});
res.headers_mut().insert(
DATE,
// SAFETY: serialized date-times are known ASCII strings
unsafe { HeaderValue::from_maybe_shared_unchecked(bytes.freeze()) },
);
}

res
Expand Down
5 changes: 3 additions & 2 deletions actix-http/src/macros.rs
Expand Up @@ -38,7 +38,7 @@ macro_rules! downcast {
/// Downcasts generic body to a specific type.
pub fn downcast_ref<T: $name + 'static>(&self) -> Option<&T> {
if self.__private_get_type_id__().0 == std::any::TypeId::of::<T>() {
// Safety: external crates cannot override the default
// SAFETY: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
// it requires returning a private type. We can therefore
// rely on the returned `TypeId`, which ensures that this
Expand All @@ -48,10 +48,11 @@ macro_rules! downcast {
None
}
}

/// Downcasts a generic body to a mutable specific type.
pub fn downcast_mut<T: $name + 'static>(&mut self) -> Option<&mut T> {
if self.__private_get_type_id__().0 == std::any::TypeId::of::<T>() {
// Safety: external crates cannot override the default
// SAFETY: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
// it requires returning a private type. We can therefore
// rely on the returned `TypeId`, which ensures that this
Expand Down
15 changes: 9 additions & 6 deletions actix-http/src/ws/mask.rs
Expand Up @@ -7,6 +7,8 @@ use std::slice;
struct ShortSlice<'a>(&'a mut [u8]);

impl<'a> ShortSlice<'a> {
/// # Safety
/// Given slice must be shorter than 8 bytes.
unsafe fn new(slice: &'a mut [u8]) -> Self {
// Sanity check for debug builds
debug_assert!(slice.len() < 8);
Expand Down Expand Up @@ -46,13 +48,13 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
}
}

#[inline]
// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so
// inefficient, it could be done better. The compiler does not understand that
// a `ShortSlice` must be smaller than a u64.
#[inline]
#[allow(clippy::needless_pass_by_value)]
fn xor_short(buf: ShortSlice<'_>, mask: u64) {
// Unsafe: we know that a `ShortSlice` fits in a u64
// SAFETY: we know that a `ShortSlice` fits in a u64
unsafe {
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
let mut b: u64 = 0;
Expand All @@ -64,8 +66,9 @@ fn xor_short(buf: ShortSlice<'_>, mask: u64) {
}
}

/// # Safety
/// Caller must ensure the buffer has the correct size and alignment.
#[inline]
// Unsafe: caller must ensure the buffer has the correct size and alignment
unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
// Assert correct size and alignment in debug builds
debug_assert!(buf.len().trailing_zeros() >= 3);
Expand All @@ -74,9 +77,9 @@ unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
}

#[inline]
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned
// u64 mid section.
#[inline]
fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
let start_ptr = buf.as_ptr() as usize;
let end_ptr = start_ptr + buf.len();
Expand All @@ -91,13 +94,13 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr);
let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr);

// Unsafe: we know the middle section is correctly aligned, and the outer
// SAFETY: we know the middle section is correctly aligned, and the outer
// sections are smaller than 8 bytes
unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) }
} else {
// We didn't cross even one aligned boundary!

// Unsafe: The outer sections are smaller than 8 bytes
// SAFETY: The outer sections are smaller than 8 bytes
unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) }
}
}
Expand Down

0 comments on commit 6ce9957

Please sign in to comment.