Skip to content

Commit d919cd6

Browse files
Noah-Kennedyseanmonstar
authored andcommitted
streams: limit error resets for misbehaving connections
This change causes GOAWAYs to be issued to misbehaving connections which for one reason or another cause us to emit lots of error resets. Error resets are not generally expected from valid implementations anyways. The threshold after which we issue GOAWAYs is tunable, and will default to 1024.
1 parent a7eb14a commit d919cd6

File tree

7 files changed

+113
-4
lines changed

7 files changed

+113
-4
lines changed

src/client.rs

+25
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,12 @@ pub struct Builder {
336336
/// The stream ID of the first (lowest) stream. Subsequent streams will use
337337
/// monotonically increasing stream IDs.
338338
stream_id: StreamId,
339+
340+
/// Maximum number of locally reset streams due to protocol error across
341+
/// the lifetime of the connection.
342+
///
343+
/// When this gets exceeded, we issue GOAWAYs.
344+
local_max_error_reset_streams: Option<usize>,
339345
}
340346

341347
#[derive(Debug)]
@@ -645,6 +651,7 @@ impl Builder {
645651
initial_max_send_streams: usize::MAX,
646652
settings: Default::default(),
647653
stream_id: 1.into(),
654+
local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX),
648655
}
649656
}
650657

@@ -973,6 +980,23 @@ impl Builder {
973980
self
974981
}
975982

983+
/// Sets the maximum number of local resets due to protocol errors made by the remote end.
984+
///
985+
/// Invalid frames and many other protocol errors will lead to resets being generated for those streams.
986+
/// Too many of these often indicate a malicious client, and there are attacks which can abuse this to DOS servers.
987+
/// This limit protects against these DOS attacks by limiting the amount of resets we can be forced to generate.
988+
///
989+
/// When the number of local resets exceeds this threshold, the client will close the connection.
990+
///
991+
/// If you really want to disable this, supply [`Option::None`] here.
992+
/// Disabling this is not recommended and may expose you to DOS attacks.
993+
///
994+
/// The default value is currently 1024, but could change.
995+
pub fn max_local_error_reset_streams(&mut self, max: Option<usize>) -> &mut Self {
996+
self.local_max_error_reset_streams = max;
997+
self
998+
}
999+
9761000
/// Sets the maximum number of pending-accept remotely-reset streams.
9771001
///
9781002
/// Streams that have been received by the peer, but not accepted by the
@@ -1293,6 +1317,7 @@ where
12931317
reset_stream_duration: builder.reset_stream_duration,
12941318
reset_stream_max: builder.reset_stream_max,
12951319
remote_reset_stream_max: builder.pending_accept_reset_stream_max,
1320+
local_error_reset_streams_max: builder.local_max_error_reset_streams,
12961321
settings: builder.settings.clone(),
12971322
},
12981323
);

src/proto/connection.rs

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub(crate) struct Config {
8181
pub reset_stream_duration: Duration,
8282
pub reset_stream_max: usize,
8383
pub remote_reset_stream_max: usize,
84+
pub local_error_reset_streams_max: Option<usize>,
8485
pub settings: frame::Settings,
8586
}
8687

@@ -125,6 +126,7 @@ where
125126
.settings
126127
.max_concurrent_streams()
127128
.map(|max| max as usize),
129+
local_max_error_reset_streams: config.local_error_reset_streams_max,
128130
}
129131
}
130132
let streams = Streams::new(streams_config(&config));

src/proto/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub type WindowSize = u32;
3232
// Constants
3333
pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1; // i32::MAX as u32
3434
pub const DEFAULT_REMOTE_RESET_STREAM_MAX: usize = 20;
35+
pub const DEFAULT_LOCAL_RESET_COUNT_MAX: usize = 1024;
3536
pub const DEFAULT_RESET_STREAM_MAX: usize = 10;
3637
pub const DEFAULT_RESET_STREAM_SECS: u64 = 30;
3738
pub const DEFAULT_MAX_SEND_BUFFER_SIZE: usize = 1024 * 400;

src/proto/streams/counts.rs

+32
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ pub(super) struct Counts {
3131

3232
/// Current number of "pending accept" streams that were remotely reset
3333
num_remote_reset_streams: usize,
34+
35+
/// Maximum number of locally reset streams due to protocol error across
36+
/// the lifetime of the connection.
37+
///
38+
/// When this gets exceeded, we issue GOAWAYs.
39+
max_local_error_reset_streams: Option<usize>,
40+
41+
/// Total number of locally reset streams due to protocol error across the
42+
/// lifetime of the connection.
43+
num_local_error_reset_streams: usize,
3444
}
3545

3646
impl Counts {
@@ -46,6 +56,8 @@ impl Counts {
4656
num_local_reset_streams: 0,
4757
max_remote_reset_streams: config.remote_reset_max,
4858
num_remote_reset_streams: 0,
59+
max_local_error_reset_streams: config.local_max_error_reset_streams,
60+
num_local_error_reset_streams: 0,
4961
}
5062
}
5163

@@ -66,6 +78,26 @@ impl Counts {
6678
self.num_send_streams != 0 || self.num_recv_streams != 0
6779
}
6880

81+
/// Returns true if we can issue another local reset due to protocol error.
82+
pub fn can_inc_num_local_error_resets(&self) -> bool {
83+
if let Some(max) = self.max_local_error_reset_streams {
84+
max > self.num_local_error_reset_streams
85+
} else {
86+
true
87+
}
88+
}
89+
90+
pub fn inc_num_local_error_resets(&mut self) {
91+
assert!(self.can_inc_num_local_error_resets());
92+
93+
// Increment the number of remote initiated streams
94+
self.num_local_error_reset_streams += 1;
95+
}
96+
97+
pub(crate) fn max_local_error_resets(&self) -> Option<usize> {
98+
self.max_local_error_reset_streams
99+
}
100+
69101
/// Returns true if the receive stream concurrency can be incremented
70102
pub fn can_inc_num_recv_streams(&self) -> bool {
71103
self.max_recv_streams > self.num_recv_streams

src/proto/streams/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,10 @@ pub struct Config {
6969

7070
/// Maximum number of remote initiated streams
7171
pub remote_max_initiated: Option<usize>,
72+
73+
/// Maximum number of locally reset streams due to protocol error across
74+
/// the lifetime of the connection.
75+
///
76+
/// When this gets exceeded, we issue GOAWAYs.
77+
pub local_max_error_reset_streams: Option<usize>,
7278
}

src/proto/streams/streams.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -1542,10 +1542,24 @@ impl Actions {
15421542
) -> Result<(), Error> {
15431543
if let Err(Error::Reset(stream_id, reason, initiator)) = res {
15441544
debug_assert_eq!(stream_id, stream.id);
1545-
// Reset the stream.
1546-
self.send
1547-
.send_reset(reason, initiator, buffer, stream, counts, &mut self.task);
1548-
Ok(())
1545+
1546+
if counts.can_inc_num_local_error_resets() {
1547+
counts.inc_num_local_error_resets();
1548+
1549+
// Reset the stream.
1550+
self.send
1551+
.send_reset(reason, initiator, buffer, stream, counts, &mut self.task);
1552+
Ok(())
1553+
} else {
1554+
tracing::warn!(
1555+
"reset_on_recv_stream_err; locally-reset streams reached limit ({:?})",
1556+
counts.max_local_error_resets().unwrap(),
1557+
);
1558+
Err(Error::library_go_away_data(
1559+
Reason::ENHANCE_YOUR_CALM,
1560+
"too_many_internal_resets",
1561+
))
1562+
}
15491563
} else {
15501564
res
15511565
}

src/server.rs

+29
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,12 @@ pub struct Builder {
252252

253253
/// Maximum amount of bytes to "buffer" for writing per stream.
254254
max_send_buffer_size: usize,
255+
256+
/// Maximum number of locally reset streams due to protocol error across
257+
/// the lifetime of the connection.
258+
///
259+
/// When this gets exceeded, we issue GOAWAYs.
260+
local_max_error_reset_streams: Option<usize>,
255261
}
256262

257263
/// Send a response back to the client
@@ -650,6 +656,8 @@ impl Builder {
650656
settings: Settings::default(),
651657
initial_target_connection_window_size: None,
652658
max_send_buffer_size: proto::DEFAULT_MAX_SEND_BUFFER_SIZE,
659+
660+
local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX),
653661
}
654662
}
655663

@@ -887,6 +895,24 @@ impl Builder {
887895
self
888896
}
889897

898+
/// Sets the maximum number of local resets due to protocol errors made by the remote end.
899+
///
900+
/// Invalid frames and many other protocol errors will lead to resets being generated for those streams.
901+
/// Too many of these often indicate a malicious client, and there are attacks which can abuse this to DOS servers.
902+
/// This limit protects against these DOS attacks by limiting the amount of resets we can be forced to generate.
903+
///
904+
/// When the number of local resets exceeds this threshold, the server will issue GOAWAYs with an error code of
905+
/// `ENHANCE_YOUR_CALM` to the client.
906+
///
907+
/// If you really want to disable this, supply [`Option::None`] here.
908+
/// Disabling this is not recommended and may expose you to DOS attacks.
909+
///
910+
/// The default value is currently 1024, but could change.
911+
pub fn max_local_error_reset_streams(&mut self, max: Option<usize>) -> &mut Self {
912+
self.local_max_error_reset_streams = max;
913+
self
914+
}
915+
890916
/// Sets the maximum number of pending-accept remotely-reset streams.
891917
///
892918
/// Streams that have been received by the peer, but not accepted by the
@@ -1361,6 +1387,9 @@ where
13611387
reset_stream_duration: self.builder.reset_stream_duration,
13621388
reset_stream_max: self.builder.reset_stream_max,
13631389
remote_reset_stream_max: self.builder.pending_accept_reset_stream_max,
1390+
local_error_reset_streams_max: self
1391+
.builder
1392+
.local_max_error_reset_streams,
13641393
settings: self.builder.settings.clone(),
13651394
},
13661395
);

0 commit comments

Comments
 (0)