Make sure that socks4 auth doesn't have any 0 bytes.

Try to do it in constant time, to avoid even the smell of
side-channel attacks.
This commit is contained in:
Nick Mathewson 2022-09-30 08:09:26 -04:00
parent 3ae062911b
commit f12202d707
3 changed files with 46 additions and 16 deletions

1
Cargo.lock generated
View File

@ -3934,6 +3934,7 @@ dependencies = [
"arbitrary", "arbitrary",
"caret", "caret",
"hex-literal", "hex-literal",
"subtle",
"thiserror", "thiserror",
"tor-bytes", "tor-bytes",
"tor-error", "tor-error",

View File

@ -19,6 +19,7 @@ proxy-handshake = []
[dependencies] [dependencies]
arbitrary = { version = "1.0.1", optional = true, features = ["derive"] } arbitrary = { version = "1.0.1", optional = true, features = ["derive"] }
caret = { path = "../caret", version = "0.2.0" } caret = { path = "../caret", version = "0.2.0" }
subtle = "2"
thiserror = "1" thiserror = "1"
tor-bytes = { path = "../tor-bytes", version = "0.5.1" } tor-bytes = { path = "../tor-bytes", version = "0.5.1" }
tor-error = { path = "../tor-error", version = "0.3.2" } tor-error = { path = "../tor-error", version = "0.3.2" }

View File

@ -233,6 +233,43 @@ impl AsRef<str> for SocksHostname {
} }
} }
impl SocksAuth {
/// Check whether this authentication is well-formed and compatible with the
/// provided SOCKS version.
///
/// Return an error if not.
fn validate(&self, version: SocksVersion) -> Result<()> {
match self {
SocksAuth::NoAuth => {}
SocksAuth::Socks4(data) => {
if version != SocksVersion::V4 || contains_zeros(data) {
return Err(Error::Syntax);
}
}
SocksAuth::Username(user, pass) => {
if version != SocksVersion::V5
|| user.len() > u8::MAX as usize
|| pass.len() > u8::MAX as usize
{
return Err(Error::Syntax);
}
}
}
Ok(())
}
}
/// Return true if b contains at least one zero.
///
/// Try to run in constant time.
fn contains_zeros(b: &[u8]) -> bool {
use subtle::{Choice, ConstantTimeEq};
let c: Choice = b
.iter()
.fold(Choice::from(0), |seen_any, byte| seen_any | byte.ct_eq(&0));
c.unwrap_u8() != 0
}
impl SocksRequest { impl SocksRequest {
/// Create a SocksRequest with a given set of fields. /// Create a SocksRequest with a given set of fields.
/// ///
@ -252,22 +289,7 @@ impl SocksRequest {
if port == 0 && cmd.requires_port() { if port == 0 && cmd.requires_port() {
return Err(Error::Syntax); return Err(Error::Syntax);
} }
match &auth { auth.validate(version)?;
SocksAuth::NoAuth => {}
SocksAuth::Socks4(_) => {
if version != SocksVersion::V4 {
return Err(Error::Syntax);
}
}
SocksAuth::Username(user, pass) => {
if version != SocksVersion::V5
|| user.len() > u8::MAX as usize
|| pass.len() > u8::MAX as usize
{
return Err(Error::Syntax);
}
}
}
Ok(SocksRequest { Ok(SocksRequest {
version, version,
@ -371,4 +393,10 @@ mod test {
); );
assert!(matches!(e, Err(Error::Syntax))); assert!(matches!(e, Err(Error::Syntax)));
} }
#[test]
fn test_contains_zeros() {
assert!(contains_zeros(b"Hello\0world"));
assert!(!contains_zeros(b"Hello world"));
}
} }