Skip to content

Commit 1438ab9

Browse files
committed
fix(net): potential deadlock
1 parent f269803 commit 1438ab9

4 files changed

Lines changed: 76 additions & 47 deletions

File tree

.github/workflows/pull_request.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
rustflags: ""
2525
env:
2626
RUSTFLAGS: ${{ matrix.rustflags }}
27+
RUST_BACKTRACE: 1
2728
steps:
2829
- &checkout
2930
name: Check out repository
@@ -45,10 +46,10 @@ jobs:
4546
uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # v1.5.2
4647

4748
- name: Run tests (all features)
48-
run: cargo test --workspace --verbose --all-features --no-fail-fast ${{ runner.os == 'macOS' && '-- --test-threads=1' || '' }}
49+
run: cargo test --workspace --verbose --all-features --no-fail-fast
4950

5051
- name: Run tests (no features)
51-
run: cargo test --workspace --verbose --no-fail-fast ${{ runner.os == 'macOS' && '-- --test-threads=1' || '' }}
52+
run: cargo test --workspace --verbose --no-fail-fast
5253

5354
miri:
5455
name: Miri

cryprot-net/src/lib.rs

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ pub struct StreamManager {
6868
acceptor: QuicStreamAcceptor,
6969
cmd_send: mpsc::UnboundedSender<Cmd>,
7070
cmd_recv: mpsc::UnboundedReceiver<Cmd>,
71+
maps: StreamMaps,
72+
}
73+
74+
#[derive(Default)]
75+
struct StreamMaps {
7176
pending: HashMap<UniqueId, StreamSend>,
7277
accepted: HashMap<UniqueId, (QuicRecvStream, usize)>,
7378
}
@@ -150,8 +155,7 @@ impl StreamManager {
150155
acceptor,
151156
cmd_send,
152157
cmd_recv,
153-
pending: Default::default(),
154-
accepted: Default::default(),
158+
maps: Default::default(),
155159
}
156160
}
157161

@@ -171,47 +175,32 @@ impl StreamManager {
171175
Self::accepted(stream, self.cmd_send.clone());
172176
}
173177
Ok(None) => {
174-
debug!("remote closed");
175-
return;
178+
debug!("remote closed, draining pending commands");
179+
break;
176180
}
177181
Err(err) => {
178-
error!(%err, "unable to accept stream");
179-
return;
182+
error!(%err, "unable to accept stream, draining pending commands");
183+
break;
180184
}
181185
}
182186
}
183187
Some(cmd) = self.cmd_recv.recv() => { // recv() is cancel safe
184188
debug!(?cmd, "received cmd");
185-
match cmd {
186-
Cmd::NewStream {uid, stream_return} => {
187-
if let Some(accepted) = self.accepted.remove(&uid) {
188-
if stream_return.send(accepted).is_err() {
189-
debug!("accepted remote stream but local receiver is closed");
190-
}
191-
debug!("sending new stream to receiver");
192-
continue;
193-
}
194-
match self.pending.entry(uid) {
195-
Entry::Occupied(occupied_entry) => {
196-
panic!("Duplicate unique id: {:?}", occupied_entry.key())
197-
},
198-
Entry::Vacant(vacant_entry) => {vacant_entry.insert(stream_return);},
199-
}
200-
}
201-
Cmd::AcceptedStream {uid, stream, bytes_read} => {
202-
if let Some(stream_ret) = self.pending.remove(&uid) {
203-
if stream_ret.send((stream, bytes_read)).is_err() {
204-
debug!("accepted remote stream but local receiver is closed");
205-
}
206-
} else {
207-
debug!("accepted stream but no pending");
208-
self.accepted.insert(uid, (stream, bytes_read));
209-
}
210-
}
211-
}
189+
self.maps.handle_cmd(cmd);
212190
}
213191
}
214192
}
193+
// The QUIC acceptor is done (remote closed or error), but there may be
194+
// in-flight `Self::accepted` tasks that already received a stream and
195+
// are reading the UniqueId. Drain remaining commands so those streams
196+
// are matched with pending requests.
197+
while let Ok(cmd) = self.cmd_recv.try_recv() {
198+
debug!(?cmd, "received cmd (draining)");
199+
self.maps.handle_cmd(cmd);
200+
if self.maps.pending.is_empty() {
201+
break;
202+
}
203+
}
215204
}
216205

217206
// not taking &self to work around borrow issue
@@ -224,17 +213,54 @@ impl StreamManager {
224213
return;
225214
}
226215
};
227-
cmd_send
228-
.send(Cmd::AcceptedStream {
229-
uid,
230-
stream,
231-
bytes_read,
232-
})
233-
.expect("cmd_rcv is owned by StreamManager")
216+
// StreamManager may have already exited if the connection closed
217+
let _ = cmd_send.send(Cmd::AcceptedStream {
218+
uid,
219+
stream,
220+
bytes_read,
221+
});
234222
});
235223
}
236224
}
237225

226+
impl StreamMaps {
227+
fn handle_cmd(&mut self, cmd: Cmd) {
228+
match cmd {
229+
Cmd::NewStream { uid, stream_return } => {
230+
if let Some(accepted) = self.accepted.remove(&uid) {
231+
if stream_return.send(accepted).is_err() {
232+
debug!("accepted remote stream but local receiver is closed");
233+
}
234+
debug!("sending new stream to receiver");
235+
return;
236+
}
237+
match self.pending.entry(uid) {
238+
Entry::Occupied(occupied_entry) => {
239+
panic!("Duplicate unique id: {:?}", occupied_entry.key())
240+
}
241+
Entry::Vacant(vacant_entry) => {
242+
vacant_entry.insert(stream_return);
243+
}
244+
}
245+
}
246+
Cmd::AcceptedStream {
247+
uid,
248+
stream,
249+
bytes_read,
250+
} => {
251+
if let Some(stream_ret) = self.pending.remove(&uid) {
252+
if stream_ret.send((stream, bytes_read)).is_err() {
253+
debug!("accepted remote stream but local receiver is closed");
254+
}
255+
} else {
256+
debug!("accepted stream but no pending");
257+
self.accepted.insert(uid, (stream, bytes_read));
258+
}
259+
}
260+
}
261+
}
262+
}
263+
238264
/// Possible connection errors.
239265
#[derive(thiserror::Error, Debug)]
240266
pub enum ConnectionError {

cryprot-net/src/testing.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use std::net::{Ipv4Addr, SocketAddr};
1+
use std::{
2+
net::{Ipv4Addr, SocketAddr},
3+
};
24

35
use anyhow::Context;
46
use s2n_quic::{Client, Server, client::Connect, provider::limits::Limits};

cryprot-ot/src/silent_ot.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ mod tests {
652652
}
653653
}
654654

655-
#[tokio::test]
655+
#[tokio::test(flavor = "multi_thread")]
656656
async fn correlated_silent_ot() {
657657
let _g = init_tracing();
658658
let (c1, c2) = local_conn().await.unwrap();
@@ -674,7 +674,7 @@ mod tests {
674674
check_correlated(&r_ot, &s_ot, Some(&choices), delta);
675675
}
676676

677-
#[tokio::test]
677+
#[tokio::test(flavor = "multi_thread")]
678678
async fn random_silent_ot() {
679679
let _g = init_tracing();
680680
let (c1, c2) = local_conn().await.unwrap();
@@ -689,7 +689,7 @@ mod tests {
689689
check_random(count, &s_ot, &r_ot[..], &choices);
690690
}
691691

692-
#[tokio::test]
692+
#[tokio::test(flavor = "multi_thread")]
693693
async fn test_rot_trait_for_silent_ot() {
694694
let _g = init_tracing();
695695
let (c1, c2) = local_conn().await.unwrap();
@@ -704,7 +704,7 @@ mod tests {
704704
check_random(count, &s_ot, &r_ot, &c);
705705
}
706706

707-
#[tokio::test]
707+
#[tokio::test(flavor = "multi_thread")]
708708
async fn test_malicious_silent_ot() {
709709
let _g = init_tracing();
710710
let (c1, c2) = local_conn().await.unwrap();

0 commit comments

Comments
 (0)