Skip to content

Commit a73672e

Browse files
committed
Reject gateway search responses that spoof IP in message body
If the response contains an url pointing to the different IP than the one from which the response was received, reject such response.
1 parent 043725f commit a73672e

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ version = "0.14"
3333

3434
[dev-dependencies]
3535
simplelog = "0.9"
36+
test-log = "0.2"
3637
tokio = {version = "1", features = ["full"]}
3738

3839
[features]

src/aio/search.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,37 @@ pub async fn search_gateway(options: SearchOptions) -> Result<Gateway, SearchErr
2424

2525
let (addr, root_url) = handle_broadcast_resp(&from, &response_body)?;
2626

27+
match (&from, &addr) {
28+
(SocketAddr::V4(src_ip), SocketAddr::V4(url_ip)) => {
29+
if src_ip.ip() != url_ip.ip() {
30+
return Err(SearchError::SpoofedIp {
31+
src_ip: (*src_ip.ip()).into(),
32+
url_ip: (*url_ip.ip()).into(),
33+
});
34+
}
35+
}
36+
(SocketAddr::V6(src_ip), SocketAddr::V6(url_ip)) => {
37+
if src_ip.ip() != url_ip.ip() {
38+
return Err(SearchError::SpoofedIp {
39+
src_ip: (*src_ip.ip()).into(),
40+
url_ip: (*url_ip.ip()).into(),
41+
});
42+
}
43+
}
44+
(SocketAddr::V6(src_ip), SocketAddr::V4(url_ip)) => {
45+
return Err(SearchError::SpoofedIp {
46+
src_ip: (*src_ip.ip()).into(),
47+
url_ip: (*url_ip.ip()).into(),
48+
})
49+
}
50+
(SocketAddr::V4(src_ip), SocketAddr::V6(url_ip)) => {
51+
return Err(SearchError::SpoofedIp {
52+
src_ip: (*src_ip.ip()).into(),
53+
url_ip: (*url_ip.ip()).into(),
54+
})
55+
}
56+
}
57+
2758
let (control_schema_url, control_url) =
2859
run_with_timeout(options.http_timeout, get_control_urls(&addr, &root_url)).await??;
2960
let control_schema =
@@ -126,3 +157,47 @@ async fn get_control_schemas(
126157
let c = std::io::Cursor::new(&resp);
127158
parsing::parse_schemas(c)
128159
}
160+
161+
#[cfg(test)]
162+
mod tests {
163+
use super::*;
164+
use std::{
165+
net::{Ipv4Addr, SocketAddrV4},
166+
time::Duration,
167+
};
168+
use test_log::test;
169+
170+
#[test(tokio::test)]
171+
async fn ip_spoofing_in_broadcast_response() {
172+
let port = {
173+
// Not 100% reliable way to find a free port number, but should be good enough
174+
let sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await.unwrap();
175+
sock.local_addr().unwrap().port()
176+
};
177+
178+
let options = SearchOptions {
179+
bind_addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)),
180+
timeout: Some(Duration::from_secs(5)),
181+
http_timeout: Some(Duration::from_secs(1)),
182+
..Default::default()
183+
};
184+
185+
tokio::spawn(async move {
186+
tokio::time::sleep(Duration::from_secs(1)).await;
187+
188+
let sock = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
189+
190+
sock.send_to(b"location: http://1.2.3.4:5/test", (Ipv4Addr::LOCALHOST, port))
191+
.await
192+
.unwrap();
193+
});
194+
195+
let result = search_gateway(options).await;
196+
if let Err(SearchError::SpoofedIp { src_ip, url_ip }) = result {
197+
assert_eq!(src_ip, Ipv4Addr::LOCALHOST);
198+
assert_eq!(url_ip, Ipv4Addr::new(1, 2, 3, 4));
199+
} else {
200+
panic!("Unexpected result: {result:?}");
201+
}
202+
}
203+
}

src/errors.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::error;
22
use std::fmt;
33
use std::io;
4+
use std::net::IpAddr;
45
use std::str;
56
#[cfg(feature = "aio")]
67
use std::string::FromUtf8Error;
@@ -313,6 +314,13 @@ pub enum SearchError {
313314
/// Error parsing URI
314315
#[cfg(feature = "aio")]
315316
InvalidUri(hyper::http::uri::InvalidUri),
317+
/// Ip spoofing detected error
318+
SpoofedIp {
319+
/// The IP from which packet was actually received
320+
src_ip: IpAddr,
321+
/// The IP which the receiving packet pretended to be from
322+
url_ip: IpAddr,
323+
},
316324
}
317325

318326
impl From<attohttpc::Error> for SearchError {
@@ -371,6 +379,7 @@ impl fmt::Display for SearchError {
371379
SearchError::HyperError(ref e) => write!(f, "Hyper Error: {}", e),
372380
#[cfg(feature = "aio")]
373381
SearchError::InvalidUri(ref e) => write!(f, "InvalidUri Error: {}", e),
382+
SearchError::SpoofedIp { src_ip, url_ip } => write!(f, "Spoofed IP from {src_ip} spoofed as {url_ip}"),
374383
}
375384
}
376385
}
@@ -387,6 +396,7 @@ impl error::Error for SearchError {
387396
SearchError::HyperError(ref e) => Some(e),
388397
#[cfg(feature = "aio")]
389398
SearchError::InvalidUri(ref e) => Some(e),
399+
SearchError::SpoofedIp { .. } => None,
390400
}
391401
}
392402
}

0 commit comments

Comments
 (0)