Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock
Please unblock package rust-spin RUSTSEC-2023-0031 was filed aginst the rust-spin package. While I do not believe any applications in Debian are affected by this issue I would still rather we avoided shipping packages with known soundness bugs where possible. The upstream patch is a bit larger than I would ideally like, but I don't think it would be appropriate to try and re-write the patch. The package has functioning autopkgtests running the upstream testsuite with various feature settings. The results of my local tests on the new version were the same as the results on debci for the version currently in testing/unstable (some feature settingss are known to fail to build). The patch also adds a new regression test related to the changes. unblock rust-spin/0.9.5-2
diff -Nru rust-spin-0.9.5/debian/changelog rust-spin-0.9.5/debian/changelog --- rust-spin-0.9.5/debian/changelog 2023-02-23 11:34:54.000000000 +0000 +++ rust-spin-0.9.5/debian/changelog 2023-04-13 23:42:04.000000000 +0000 @@ -1,3 +1,10 @@ +rust-spin (0.9.5-2) unstable; urgency=medium + + * Team upload. + * Add patch for RUSTSEC-2023-0031 (Closes: #1034374) + + -- Peter Michael Green <plugw...@debian.org> Thu, 13 Apr 2023 23:42:04 +0000 + rust-spin (0.9.5-1) unstable; urgency=medium * Team upload. diff -Nru rust-spin-0.9.5/debian/patches/RUSTSEC-2023-0031.patch rust-spin-0.9.5/debian/patches/RUSTSEC-2023-0031.patch --- rust-spin-0.9.5/debian/patches/RUSTSEC-2023-0031.patch 1970-01-01 00:00:00.000000000 +0000 +++ rust-spin-0.9.5/debian/patches/RUSTSEC-2023-0031.patch 2023-04-13 23:39:43.000000000 +0000 @@ -0,0 +1,277 @@ +commit 2a018b69870853118d7cc8a55562ff905c67d271 +Author: UnknownEclipse <43258146+unknownecli...@users.noreply.github.com> +Date: Mon Apr 3 01:36:30 2023 -0700 + + Fix #148 (UB in `try_call_once`) (#149) + + * Fix UB in `try_call_once` and add regression test. + + * Fix MSRV + + * Clean up `try_call_once` impl + + * Remove unused import + +diff --git a/src/once.rs b/src/once.rs +index 0bfc7c1..31700dc 100644 +--- a/src/once.rs ++++ b/src/once.rs +@@ -130,8 +130,6 @@ mod status { + } + use self::status::{AtomicStatus, Status}; + +-use core::hint::unreachable_unchecked as unreachable; +- + impl<T, R: RelaxStrategy> Once<T, R> { + /// Performs an initialization routine once and only once. The given closure + /// will be executed if this is the first time `call_once` has been called, +@@ -208,111 +206,92 @@ impl<T, R: RelaxStrategy> Once<T, R> { + /// } + /// ``` + pub fn try_call_once<F: FnOnce() -> Result<T, E>, E>(&self, f: F) -> Result<&T, E> { +- // SAFETY: We perform an Acquire load because if this were to return COMPLETE, then we need +- // the preceding stores done while initializing, to become visible after this load. +- let mut status = self.status.load(Ordering::Acquire); ++ if let Some(value) = self.get() { ++ Ok(value) ++ } else { ++ self.try_call_once_slow(f) ++ } ++ } + +- if status == Status::Incomplete { +- match self.status.compare_exchange( ++ #[cold] ++ fn try_call_once_slow<F: FnOnce() -> Result<T, E>, E>(&self, f: F) -> Result<&T, E> { ++ loop { ++ let xchg = self.status.compare_exchange( + Status::Incomplete, + Status::Running, +- // SAFETY: Success ordering: We do not have to synchronize any data at all, as the +- // value is at this point uninitialized, so Relaxed is technically sufficient. We +- // will however have to do a Release store later. However, the success ordering +- // must always be at least as strong as the failure ordering, so we choose Acquire +- // here anyway. + Ordering::Acquire, +- // SAFETY: Failure ordering: While we have already loaded the status initially, we +- // know that if some other thread would have fully initialized this in between, +- // then there will be new not-yet-synchronized accesses done during that +- // initialization that would not have been synchronized by the earlier load. Thus +- // we use Acquire to ensure when we later call force_get() in the last match +- // statement, if the status was changed to COMPLETE, that those accesses will become +- // visible to us. + Ordering::Acquire, +- ) { +- Ok(_must_be_state_incomplete) => { +- // The compare-exchange succeeded, so we shall initialize it. +- +- // We use a guard (Finish) to catch panics caused by builder +- let finish = Finish { +- status: &self.status, +- }; +- let val = match f() { +- Ok(val) => val, +- Err(err) => { +- // If an error occurs, clean up everything and leave. +- core::mem::forget(finish); +- self.status.store(Status::Incomplete, Ordering::Release); +- return Err(err); +- } +- }; +- unsafe { +- // SAFETY: +- // `UnsafeCell`/deref: currently the only accessor, mutably +- // and immutably by cas exclusion. +- // `write`: pointer comes from `MaybeUninit`. +- (*self.data.get()).as_mut_ptr().write(val); +- }; +- // If there were to be a panic with unwind enabled, the code would +- // short-circuit and never reach the point where it writes the inner data. +- // The destructor for Finish will run, and poison the Once to ensure that other +- // threads accessing it do not exhibit unwanted behavior, if there were to be +- // any inconsistency in data structures caused by the panicking thread. +- // +- // However, f() is expected in the general case not to panic. In that case, we +- // simply forget the guard, bypassing its destructor. We could theoretically +- // clear a flag instead, but this eliminates the call to the destructor at +- // compile time, and unconditionally poisons during an eventual panic, if +- // unwinding is enabled. +- core::mem::forget(finish); +- +- // SAFETY: Release is required here, so that all memory accesses done in the +- // closure when initializing, become visible to other threads that perform Acquire +- // loads. +- // +- // And, we also know that the changes this thread has done will not magically +- // disappear from our cache, so it does not need to be AcqRel. +- self.status.store(Status::Complete, Ordering::Release); ++ ); + +- // This next line is mainly an optimization. +- return unsafe { Ok(self.force_get()) }; ++ match xchg { ++ Ok(_must_be_state_incomplete) => { ++ // Impl is defined after the match for readability ++ } ++ Err(Status::Panicked) => panic!("Once panicked"), ++ Err(Status::Running) => match self.poll() { ++ Some(v) => return Ok(v), ++ None => continue, ++ }, ++ Err(Status::Complete) => { ++ return Ok(unsafe { ++ // SAFETY: The status is Complete ++ self.force_get() ++ }); ++ } ++ Err(Status::Incomplete) => { ++ // The compare_exchange failed, so this shouldn't ever be reached, ++ // however if we decide to switch to compare_exchange_weak it will ++ // be safer to leave this here than hit an unreachable ++ continue; + } +- // The compare-exchange failed, so we know for a fact that the status cannot be +- // INCOMPLETE, or it would have succeeded. +- Err(other_status) => status = other_status, + } +- } + +- Ok(match status { +- // SAFETY: We have either checked with an Acquire load, that the status is COMPLETE, or +- // initialized it ourselves, in which case no additional synchronization is needed. +- Status::Complete => unsafe { self.force_get() }, +- Status::Panicked => panic!("Once panicked"), +- Status::Running => self.poll().unwrap_or_else(|| { +- if cfg!(debug_assertions) { +- unreachable!("Encountered INCOMPLETE when polling Once") +- } else { +- // SAFETY: This poll is guaranteed never to fail because the API of poll +- // promises spinning if initialization is in progress. We've already +- // checked that initialisation is in progress, and initialisation is +- // monotonic: once done, it cannot be undone. We also fetched the status +- // with Acquire semantics, thereby guaranteeing that the later-executed +- // poll will also agree with us that initialization is in progress. Ergo, +- // this poll cannot fail. +- unsafe { +- unreachable(); +- } +- } +- }), ++ // The compare-exchange succeeded, so we shall initialize it. + +- // SAFETY: The only invariant possible in addition to the aforementioned ones at the +- // moment, is INCOMPLETE. However, the only way for this match statement to be +- // reached, is if we lost the CAS (otherwise we would have returned early), in +- // which case we know for a fact that the state cannot be changed back to INCOMPLETE as +- // `Once`s are monotonic. +- Status::Incomplete => unsafe { unreachable() }, +- }) ++ // We use a guard (Finish) to catch panics caused by builder ++ let finish = Finish { ++ status: &self.status, ++ }; ++ let val = match f() { ++ Ok(val) => val, ++ Err(err) => { ++ // If an error occurs, clean up everything and leave. ++ core::mem::forget(finish); ++ self.status.store(Status::Incomplete, Ordering::Release); ++ return Err(err); ++ } ++ }; ++ unsafe { ++ // SAFETY: ++ // `UnsafeCell`/deref: currently the only accessor, mutably ++ // and immutably by cas exclusion. ++ // `write`: pointer comes from `MaybeUninit`. ++ (*self.data.get()).as_mut_ptr().write(val); ++ }; ++ // If there were to be a panic with unwind enabled, the code would ++ // short-circuit and never reach the point where it writes the inner data. ++ // The destructor for Finish will run, and poison the Once to ensure that other ++ // threads accessing it do not exhibit unwanted behavior, if there were to be ++ // any inconsistency in data structures caused by the panicking thread. ++ // ++ // However, f() is expected in the general case not to panic. In that case, we ++ // simply forget the guard, bypassing its destructor. We could theoretically ++ // clear a flag instead, but this eliminates the call to the destructor at ++ // compile time, and unconditionally poisons during an eventual panic, if ++ // unwinding is enabled. ++ core::mem::forget(finish); ++ ++ // SAFETY: Release is required here, so that all memory accesses done in the ++ // closure when initializing, become visible to other threads that perform Acquire ++ // loads. ++ // ++ // And, we also know that the changes this thread has done will not magically ++ // disappear from our cache, so it does not need to be AcqRel. ++ self.status.store(Status::Complete, Ordering::Release); ++ ++ // This next line is mainly an optimization. ++ return unsafe { Ok(self.force_get()) }; ++ } + } + + /// Spins until the [`Once`] contains a value. +@@ -547,7 +526,9 @@ impl<'a> Drop for Finish<'a> { + mod tests { + use std::prelude::v1::*; + ++ use std::sync::atomic::AtomicU32; + use std::sync::mpsc::channel; ++ use std::sync::Arc; + use std::thread; + + use super::*; +@@ -706,6 +687,51 @@ mod tests { + } + } + ++ #[test] ++ fn try_call_once_err() { ++ let once = Once::<_, Spin>::new(); ++ let shared = Arc::new((once, AtomicU32::new(0))); ++ ++ let (tx, rx) = channel(); ++ ++ let t0 = { ++ let shared = shared.clone(); ++ thread::spawn(move || { ++ let (once, called) = &*shared; ++ ++ once.try_call_once(|| { ++ called.fetch_add(1, Ordering::AcqRel); ++ tx.send(()).unwrap(); ++ thread::sleep(std::time::Duration::from_millis(50)); ++ Err(()) ++ }) ++ .ok(); ++ }) ++ }; ++ ++ let t1 = { ++ let shared = shared.clone(); ++ thread::spawn(move || { ++ rx.recv().unwrap(); ++ let (once, called) = &*shared; ++ assert_eq!( ++ called.load(Ordering::Acquire), ++ 1, ++ "leader thread did not run first" ++ ); ++ ++ once.call_once(|| { ++ called.fetch_add(1, Ordering::AcqRel); ++ }); ++ }) ++ }; ++ ++ t0.join().unwrap(); ++ t1.join().unwrap(); ++ ++ assert_eq!(shared.1.load(Ordering::Acquire), 2); ++ } ++ + // This is sort of two test cases, but if we write them as separate test methods + // they can be executed concurrently and then fail some small fraction of the + // time. diff -Nru rust-spin-0.9.5/debian/patches/series rust-spin-0.9.5/debian/patches/series --- rust-spin-0.9.5/debian/patches/series 2023-02-23 11:34:54.000000000 +0000 +++ rust-spin-0.9.5/debian/patches/series 2023-04-13 23:40:22.000000000 +0000 @@ -1 +1,2 @@ remove-portable-atomic.patch +RUSTSEC-2023-0031.patch