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

fix(net): prevent panicking when finding an unknown HTTP status code #2394

Merged
merged 2 commits into from
Sep 21, 2023
Merged
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 data_structures/src/serialization_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ impl<'de> Visitor<'de> for RADRetrieveSerializationHelperVersionedVisitor {
.ok_or_else(|| de::Error::missing_field("db_version"))?;

match db_version {
0 | 1 | 2 => {
0..=2 => {
let kind = match db_version {
0 => RADType::Unknown,
1 => RADType::HttpGet,
Expand Down
60 changes: 55 additions & 5 deletions net/src/client/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,18 @@ impl TryFrom<WitnetHttpResponse> for surf::http::Response {
// Create a surf response and set all the relevant parts
// Version and headers are currently not used, but they are included here for completion and
// future readiness.
let mut res = surf::http::Response::new(status);
let code = status.status.as_u16();
let mut res =
// Panics are catched here because the `surf` library will do an `.expect()` on the
// `Result` coming from `http_types::StatusCode::try_from(u16)`, which is not guaranteed
// to be `Ok()` because the status code coming from the remote server could be not
// supported by `http_types` itself.
std::panic::catch_unwind(|| surf::http::Response::new(status)).map_err(|_| {
Copy link
Member

Choose a reason for hiding this comment

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

Could we add a comment explaining that we need to use catch_unwind because the surf library panics instead of returning a Result?

WitnetHttpError::UnknownStatusCode {
code,
msg: format!("Status code {code} is unknown or unsupported"),
}
})?;
res.set_version(Some(surf::http::Version::try_from(version)?));
res.set_body(surf::Body::from_reader(body_reader, None));
for (key, value) in headers {
Expand Down Expand Up @@ -260,12 +271,18 @@ impl From<isahc::http::StatusCode> for WitnetHttpStatusCode {
impl TryFrom<WitnetHttpStatusCode> for surf::StatusCode {
type Error = WitnetHttpError;

/// Converts a `WitnetHttpStatusCode` into a `surf::StatusCode`.
///
/// If possible, unknown status codes in normal HTTP status code ranges (e.g. 1XX, 2XX,
/// 3XX, 4XX and 5XX) are mapped to their X00 version, i.e. 522 → 500.
fn try_from(status: WitnetHttpStatusCode) -> Result<Self, Self::Error> {
let code = status.status.as_u16();
surf::StatusCode::try_from(code).map_err(|err| WitnetHttpError::UnknownStatusCode {
code,
msg: err.to_string(),
})
surf::StatusCode::try_from(code)
.or_else(|_| surf::StatusCode::try_from(code / 100 * 100))
.map_err(|err| WitnetHttpError::UnknownStatusCode {
code,
msg: err.to_string(),
})
}
}

Expand Down Expand Up @@ -345,3 +362,36 @@ impl surf::HttpClient for WitnetHttpClient {
Ok(res)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_unsupported_status_codes_wont_break_the_thing() {
// "Benevolent case": unknown status codes in normal HTTP status code ranges (e.g. 1XX, 2XX,
// 3XX, 4XX and 5XX) are mapped to their X00 version, i.e. 522 → 500.
let isahc_response = isahc::Response::builder()
.status(522)
.body(isahc::AsyncBody::empty())
.unwrap();
let wit_response = WitnetHttpResponse::from(isahc_response);
let surf_response = surf::http::Response::try_from(wit_response).unwrap();

assert_eq!(surf_response.status(), 500);

// "Malicious case": completely made-up status codes should be handled elegantly, and not
// panic
let isahc_response = isahc::Response::builder()
.status(999)
.body(isahc::AsyncBody::empty())
.unwrap();
let wit_response = WitnetHttpResponse::from(isahc_response);
let error = surf::http::Response::try_from(wit_response).unwrap_err();

assert!(matches!(
error,
WitnetHttpError::UnknownStatusCode { code: 999, .. }
));
}
}
18 changes: 9 additions & 9 deletions node/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ use witnet_node::utils::mode_consensus;
#[test]
fn test_mode_consensus() {
// The mode consensus function selects the most common item from a list
let v = vec![1, 2, 3, 3, 3];
let v = [1, 2, 3, 3, 3];
let c = mode_consensus(v.iter(), 51);
assert_eq!(c, Some(&3));

// When there is only one element, that element is the mode
let v = vec![3, 3, 3];
let v = [3, 3, 3];
let c = mode_consensus(v.iter(), 51);
assert_eq!(c, Some(&3));

let v = vec![3];
let v = [3];
let c = mode_consensus(v.iter(), 51);
assert_eq!(c, Some(&3));

// But when there is a tie, there is no consensus
let v = vec![2, 2, 2, 3, 3, 3];
let v = [2, 2, 2, 3, 3, 3];
let c = mode_consensus(v.iter(), 51);
assert_eq!(c, None);

Expand All @@ -26,15 +26,15 @@ fn test_mode_consensus() {
let c = mode_consensus(v.iter(), 51);
assert_eq!(c, None);

let v = vec![1, 2, 3, 3, 3, 3, 3, 3];
let v = [1, 2, 3, 3, 3, 3, 3, 3];
let c = mode_consensus(v.iter(), 70);
assert_eq!(c, Some(&3));

let v = vec![1, 2, 2, 3, 3, 3, 3, 3];
let v = [1, 2, 2, 3, 3, 3, 3, 3];
let c = mode_consensus(v.iter(), 70);
assert_eq!(c, None);

let v = vec![
let v = [
Some(1),
Some(1),
Some(1),
Expand All @@ -51,7 +51,7 @@ fn test_mode_consensus() {
let c = mode_consensus(v.iter(), 60);
assert_eq!(c, Some(&Some(1)));

let v = vec![
let v = [
Some(1),
Some(1),
Some(1),
Expand All @@ -68,7 +68,7 @@ fn test_mode_consensus() {
let c = mode_consensus(v.iter(), 60);
assert_eq!(c, None);

let v = vec![
let v = [
Some(1),
Some(1),
Some(1),
Expand Down
9 changes: 3 additions & 6 deletions p2p/tests/peers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,10 @@ fn p2p_peers_remove_from_new_with_index() {

// Add address
let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
let src_address = Some(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(168, 0, 0, 12)),
8080,
));
peers.add_to_new(vec![address], src_address).unwrap();
let src_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(168, 0, 0, 12)), 8080);
peers.add_to_new(vec![address], Some(src_address)).unwrap();

let index = peers.new_bucket_index(&address, &src_address.unwrap());
let index = peers.new_bucket_index(&address, &src_address);

// Remove address
assert_eq!(peers.remove_from_new_with_index(&[index]), vec![address]);
Expand Down
4 changes: 2 additions & 2 deletions rad/src/filters/deviation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,9 +608,9 @@ mod tests {

#[test]
fn test_filter_deviation_standard_integer_three_int_sigma() {
let input = vec![1, 2, 3];
let input = [1, 2, 3];
let sigmas = 1;
let expected = vec![2];
let expected = [2];

let input_vec: Vec<RadonTypes> = input
.iter()
Expand Down
4 changes: 2 additions & 2 deletions rad/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,15 @@ mod tests {
let partial_expected = vec![
input,
RadonTypes::from(RadonMap::from(
vec![
[
(
String::from("base"),
RadonTypes::from(RadonString::from("stations")),
),
(
String::from("clouds"),
RadonTypes::from(RadonMap::from(
vec![(
[(
String::from("all"),
RadonTypes::from(RadonInteger::from(75)),
)]
Expand Down
4 changes: 2 additions & 2 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ lazy_static! {
};
}

static CONFIG_HELP: &str = r#"Load configuration from this file. If not specified will try to find a configuration
static CONFIG_HELP: &str = r"Load configuration from this file. If not specified will try to find a configuration
in these paths:
- current path
- standard configuration path:
Expand All @@ -260,7 +260,7 @@ in these paths:
- C:\Users\<YOUR USER>\AppData\Roaming\witnet\witnet.toml
- /etc/witnet/witnet.toml if in a *nix platform
If no configuration is found. The default configuration is used, see `config` subcommand if
you want to know more about the default config."#;
you want to know more about the default config.";

#[test]
#[cfg(all(not(debug_assertions), feature = "telemetry"))]
Expand Down
4 changes: 2 additions & 2 deletions src/cli/wallet/with_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ pub struct ConfigParams {
concurrency: Option<usize>,
}

static WALLET_DB_HELP: &str = r#"Path to the wallet database. If not specified will use:
static WALLET_DB_HELP: &str = r"Path to the wallet database. If not specified will use:
- $XDG_DATA_HOME/witnet/wallet.db in Gnu/Linux
- $HOME/Libary/Application\ Support/witnet/wallet.db in MacOS
- {FOLDERID_LocalAppData}/witnet/wallet.db in Windows
If one of the above directories cannot be determined,
the current one will be used."#;
the current one will be used.";
6 changes: 3 additions & 3 deletions validations/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6362,7 +6362,7 @@ fn create_tally_validation_dr_liar() {
dr_pkh,
&report,
vec![rewarded[0], rewarded[1], dr_pkh],
vec![rewarded[0], rewarded[1], dr_pkh]
[rewarded[0], rewarded[1], dr_pkh]
.iter()
.cloned()
.collect::<HashSet<PublicKeyHash>>(),
Expand Down Expand Up @@ -6457,7 +6457,7 @@ fn create_tally_validation_5_reveals_1_liar_1_error() {
slashed[0],
error_witnesses[0],
],
vec![
[
rewarded[0],
rewarded[1],
rewarded[2],
Expand Down Expand Up @@ -6531,7 +6531,7 @@ fn create_tally_validation_4_commits_2_reveals() {
dr_pkh,
&report,
vec![rewarded[0], rewarded[1]],
vec![rewarded[0], rewarded[1], slashed[0], slashed[1]]
[rewarded[0], rewarded[1], slashed[0], slashed[1]]
.iter()
.cloned()
.collect::<HashSet<PublicKeyHash>>(),
Expand Down
2 changes: 1 addition & 1 deletion wallet/tests/data_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn test_radon_types_json_serialization() {
assert_eq!(serde_json::to_string(&radon_type).unwrap(), expected_json);

let radon_type = RadonTypes::from(RadonMap::from(
vec![(
[(
String::from("foo"),
RadonTypes::from(RadonString::from("bar")),
)]
Expand Down
Loading