Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: more Send and Sync safety documentation #4471

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/rust/s2n-tls/src/callbacks/async_cb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::pin::Pin;
/// if it wants to run an asynchronous operation (disk read, network call).
/// The application can return an error ([Err(Error::application())])
/// to indicate connection failure.
pub trait ConnectionFuture: 'static + Send {
pub trait ConnectionFuture: 'static + Send + Sync {
fn poll(
self: Pin<&mut Self>,
connection: &mut Connection,
Expand Down
2 changes: 1 addition & 1 deletion bindings/rust/s2n-tls/src/callbacks/client_hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<F: 'static + Send + Future<Output = Result<Config, Error>>> ConfigResolver<
}
}

impl<F: 'static + Send + Future<Output = Result<Config, Error>>> ConnectionFuture
impl<F: 'static + Send + Sync + Future<Output = Result<Config, Error>>> ConnectionFuture
for ConfigResolver<F>
{
fn poll(
Expand Down
23 changes: 20 additions & 3 deletions bindings/rust/s2n-tls/src/callbacks/pkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,30 @@ pub struct PrivateKeyOperation {

/// # Safety
///
/// Safety: s2n_async_pkey_op objects can be sent across threads
/// NonNull / the raw s2n_async_pkey_op pointer isn't Send because its data may
/// be aliased (two pointers could point to the same raw memory). However, the
/// PrivateKeyOperation interface ensures that only one owned PrivateKeyOperation
/// can exist for each s2n_async_pkey_op C object.
///
/// In particular, the only method of obtaining a PrivateKeyOperation is via
/// PrivateKeyCallback::handle_operation, which returns unique memory owned by
/// the application.
///
/// No mechanism enforces this. Library developers MUST ensure that new methods
/// do not expose the raw s2n_async_pkey_op pointer, return owned PrivateKeyOperation
/// objects, or allow the creation of PrivateKeyOperations from raw pointers.
///
unsafe impl Send for PrivateKeyOperation {}

/// # Safety
///
/// Safety: All C methods that mutate the s2n_async_pkey_op are wrapped
/// in Rust methods that require a mutable reference.
/// NonNull / the raw s2n_async_pkey_op pointer isn't Sync because it allows access
/// to mutable pointers even from immutable references. However, the PrivateKeyOperation
/// interface enforces that all mutating methods correctly require self or &mut self.
///
/// No mechanism enforces this. Library developers MUST ensure that new methods
/// correctly use either self, &self or &mut self depending on their behavior.
///
unsafe impl Sync for PrivateKeyOperation {}

impl PrivateKeyOperation {
Expand Down
59 changes: 52 additions & 7 deletions bindings/rust/s2n-tls/src/cert_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ impl CertificateChain<'_> {
}
}

/// # Safety
///
/// Caller must ensure ptr is a valid reference to a [`s2n_cert_chain_and_key`] object
/// Caller must ensure they are not creating a duplicate CertificateChain (see Send safety note).
pub(crate) unsafe fn from_ptr_reference<'a>(
ptr: NonNull<s2n_cert_chain_and_key>,
) -> CertificateChain<'a> {
Expand Down Expand Up @@ -72,16 +76,40 @@ impl CertificateChain<'_> {
self.len() == 0
}

pub(crate) fn as_mut_ptr(&mut self) -> NonNull<s2n_cert_chain_and_key> {
/// # Safety
///
/// Caller must ensure they are not creating the possibility of duplicate
/// CertificateChain (see Send safety note).
/// This should ONLY be used to pass the CertificateChain to C methods.
pub(crate) unsafe fn as_mut_ptr(&mut self) -> NonNull<s2n_cert_chain_and_key> {
self.ptr
}
}

// # Safety
//
// s2n_cert_chain_and_key objects can be sent across threads.
/// # Safety
///
/// NonNull / the raw s2n_cert_chain_and_key pointer isn't Send because its data
/// may be aliased (two pointers could point to the same raw memory). However,
/// the CertificateChain interface ensures that only one owned CertificateChain
/// can exist for each s2n_cert_chain_and_key C object.
/// Additionally, the CertificateChain is immutable once created.
///
/// No mechanism enforces this. Library developers MUST ensure that new methods
/// do not expose the raw s2n_cert_chain_and_key pointer, return owned CertificateChain objects,
/// or allow the creation of CertificateChains from raw pointers. Failing that,
/// no method should take a &mut CertificateChain argument.
unsafe impl Send for CertificateChain<'_> {}

/// # Safety
///
/// NonNull / the raw s2n_cert_chain_and_key pointer isn't Sync because it allows
/// access to mutable pointers even from immutable references. However, the CertificateChain
/// interface enforces that all mutating methods correctly require &mut self.
///
/// No mechanism enforces this. Library developers MUST ensure that new methods
/// correctly use either &self or &mut self depending on their behavior.
unsafe impl Sync for CertificateChain<'_> {}

impl Drop for CertificateChain<'_> {
fn drop(&mut self) {
if self.is_owned {
Expand Down Expand Up @@ -148,7 +176,24 @@ impl<'a> Certificate<'a> {
}
}

// # Safety
//
// Certificates just reference data in the chain, so share the Send-ness of the chain.
/// # Safety
///
/// NonNull / the raw s2n_cert pointer isn't Send because its data
/// may be aliased (two pointers could point to the same raw memory). Multiple
/// Certificates can reference the same memory, since multiple iterators over
/// CertificateChain can exist at once. However, the Certificate is still Send
/// because it is immutable.
///
/// No mechanism enforces this. Library developers MUST ensure that the Certificate
/// is NEVER mutated. No method should take a &mut Certificate argument.
unsafe impl Send for Certificate<'_> {}

/// # Safety
///
/// NonNull / the raw s2n_cert pointer isn't Sync because it allows access
/// to mutable pointers even from immutable references. However, the Certificate is
/// still Sync because it is immutable.
///
/// No mechanism enforces this. Library developers MUST ensure that the Certificate
/// is NEVER mutated. No method should take a &mut Certificate argument.
unsafe impl Sync for Certificate<'_> {}
45 changes: 40 additions & 5 deletions bindings/rust/s2n-tls/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,39 @@ pub struct Config(NonNull<s2n_config>);

/// # Safety
///
/// Safety: s2n_config objects can be sent across threads
/// NonNull / the raw s2n_config pointer isn't Send because its data may be aliased
/// (two pointers could point to the same raw memory). Because Config implements
/// Clone, multiple Configs are expected to point to the same raw memory.
/// However, the Config is still Send because it is immutable after creation.
///
/// For example: an application can create and then clone a Config, creating two
/// Configs representing the same C s2n_config struct. However, neither Config can
/// modify that shared memory, so multiple threads accessing it remains safe.
///
/// No mechanism enforces this. Library developers MUST ensure that the Config
/// is NEVER mutated. No method should take a &mut Config argument.
///
/// The Context stored on a Config must also be Send. The Context is essentially
/// a field on the Config, just stored in C instead of in Rust.
/// A test exists to enforce this. In particular, note that the Context includes
/// a reference counter, but that reference counter is safely atomic and can be
/// safely incremented by different Configs on different threads.
///
unsafe impl Send for Config {}

/// # Safety
///
/// Safety: All C methods that mutate the s2n_config are wrapped
/// in Rust methods that require a mutable reference.
/// NonNull / the raw s2n_config pointer isn't Sync because it allows access
/// to mutable pointers even from immutable references. However, the Config is
/// still Sync because it is immutable after creation.
///
/// No mechanism enforces this. Library developers MUST ensure that the Config
/// is NEVER mutated. No method should take a &mut Config argument.
///
/// The Context stored on a Config must also be Sync. The Context is essentially
/// a field on the Config, just stored in C instead of in Rust.
/// A test exists to enforce this.
///
unsafe impl Sync for Config {}

impl Config {
Expand Down Expand Up @@ -61,7 +87,13 @@ impl Config {
config
}

pub(crate) fn as_mut_ptr(&mut self) -> *mut s2n_config {
/// # Safety
///
/// This method must ONLY be used:
/// - In the Builder
/// - To call non-mutating C methods
/// The Config must NOT be modified after being built!
pub(crate) unsafe fn as_mut_ptr(&mut self) -> *mut s2n_config {
self.0.as_ptr()
}

Expand Down Expand Up @@ -730,7 +762,10 @@ impl Builder {
}

fn as_mut_ptr(&mut self) -> *mut s2n_config {
self.config.as_mut_ptr()
// Safety:
// The mutable pointer is only used inside the builder, which does not allow
// modification of complete Configs or risk thread safety.
unsafe { self.config.as_mut_ptr() }
}
}

Expand Down
64 changes: 62 additions & 2 deletions bindings/rust/s2n-tls/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,56 @@ impl fmt::Debug for Connection {

/// # Safety
///
/// s2n_connection objects can be sent across threads
/// NonNull / the raw s2n_connection pointer isn't Send because its data may be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a note in general for your doc comments. It's not important to know why the raw pointer isn't Send/Sync. I would prefer it if you started out with why the Connection/Config/CertChain is Send/Sync. If you need to elaborate on why the raw s2n_connection pointer isn't Send/Sync, maybe do that after? But I think most people are just looking for why this Rust struct was tagged with Send/Sync.

So with that in mind, I would probably restructure your comment like:
The Connection is Send because the existing interface ensures that only one owned Connection can exist for each s2n_connection C object.
The raw s2n_connection pointer it wraps is not Send, and therefore library developers MUST ensure that new methods do not expose the raw s2n_connection pointer...

/// aliased (two pointers could point to the same raw memory).
///
/// For example: Two NonNull<s2n_connection> objects can both reference the same memory.
/// If you sent one of the NonNulls to another thread, then both threads would
/// own references to the same memory and therefore mutate it at the same time,
/// violating thread safety.
///
/// However, the Connection is Send because the interface ensures that only one
/// owned Connection can exist for each s2n_connection C object.
///
/// No mechanism enforces this. Library developers MUST ensure that new methods
/// do not expose the raw s2n_connection pointer, return owned Connection objects,
/// or allow the creation of Connections from raw pointers. Because C methods like
/// callbacks can expose the raw pointers, pay particular attention to the thread
/// safety of callbacks.
///
/// The Context stored on a Connection must also be Send. The Context is essentially
/// a field on the Connection, just stored in C instead of in Rust.
/// A test exists to enforce this.
///
unsafe impl Send for Connection {}

/// # Safety
///
/// NonNull / the raw s2n_connection pointer isn't Sync because it allows access
/// to mutable pointers even from immutable references.
///
/// For example: Multiple immutable references to the same NonNull<s2n_connection>
/// are allowed to exist at once. If one &NonNull is sent to another thread, and
/// as_ptr() is called on both &NonNull (allowed because NonNull<T> implements From for &T),
/// then both threads can obtain *mut s2n_connection pointers to the same memory
/// and therefore mutate it at the same time, violating thread safety.
///
/// However, the Connection is Sync because the interface enforces that all mutating
/// methods correctly require &mut self.
///
/// No mechanism enforces this. Library developers MUST ensure that new methods
/// correctly use either &self or &mut self depending on their behavior.
///
/// Note: Although non-mutating methods like getters should be thread-safe by definition,
/// technically the only thread safety guarantee provided by the underlying C library
/// is that s2n_send and s2n_recv can be called concurrently.
///
/// The Context stored on a Connection must also be Sync. The Context is essentially
/// a field on the Connection, just stored in C instead of in Rust.
/// A test exists to enforce this.
///
unsafe impl Sync for Connection {}

impl Connection {
pub fn new(mode: Mode) -> Self {
crate::init::init();
Expand Down Expand Up @@ -107,13 +154,19 @@ impl Connection {
Self::new(Mode::Server)
}

pub(crate) fn as_ptr(&mut self) -> *mut s2n_connection {
/// # Safety
///
/// Caller must ensure they are not creating the possibility of duplicate
/// Connections (see Send safety note).
/// This should ONLY be used to pass the Connection to C methods.
pub(crate) unsafe fn as_ptr(&mut self) -> *mut s2n_connection {
self.connection.as_ptr()
}

/// # Safety
///
/// Caller must ensure s2n_connection is a valid reference to a [`s2n_connection`] object
/// Caller must ensure they are not creating a duplicate Connection (see Send safety note).
pub(crate) unsafe fn from_raw(connection: NonNull<s2n_connection>) -> Self {
Self { connection }
}
Expand Down Expand Up @@ -1017,4 +1070,11 @@ mod tests {
fn assert_send<T: 'static + Send>() {}
assert_send::<Context>();
}

// ensure the connection context is sync
#[test]
fn context_sync_test() {
fn assert_sync<T: 'static + Sync>() {}
assert_sync::<Context>();
}
}
Loading