scan_path_and_query stores the ? and # byte offsets as u16 with no bound on the input length. A delimiter past byte offset 65535 is recorded wrapped, so path() and query() slice out of bounds and panic or return the wrong substring. Version: http 1.4.2, default features.
Reproducer
Panic:
use http::uri::PathAndQuery;
use std::convert::TryFrom;
fn main() {
let mut s = String::from("/");
while s.len() < 65530 { s.push('a'); }
s.push('?');
while s.len() < 65539 { s.push('a'); }
s.push('#');
s.push_str("frag");
let pq = PathAndQuery::try_from(s.as_bytes()).unwrap();
let _ = pq.path();
}
Wrong split:
use http::Uri;
fn main() {
let mut p = String::from("/");
while p.len() < 65536 { p.push('a'); }
p.push('?');
p.push_str("x=1");
let uri = Uri::builder().scheme("http").authority("example.com")
.path_and_query(p).build().unwrap();
assert_eq!(uri.path(), "/");
assert_eq!(uri.query().unwrap().len(), 65539);
assert_eq!(uri.to_string().len(), 65559);
}
Observed vs expected
First case: try_from succeeds, storing query = 65530 while the retained data is 3 bytes ("/aa"). path() evaluates &self.data[..65530] and panics with end byte index 65530 is out of bounds for string of length 3 at src/uri/path.rs:138. query() panics the same way at src/uri/path.rs:188. try_from should reject the input, which is what InvalidUri exists for, or produce a value whose accessors are total. PathAndQuery documents no panic for path() or query().
Second case: uri.path() returns "/" for a URI whose real path is 65536 bytes, and uri.query() returns the 65539-byte remainder. uri.to_string() still emits the full 65559 bytes. Expected: path() returns the 65536-byte path, or the builder rejects the input. A proxy that authorizes on uri.path() sees / while forwarding uri.to_string(), which carries the real path.
The crate's only length limit, MAX_LEN = u16::MAX - 1, is enforced in Uri::from_shared at src/uri/mod.rs:296 and in no PathAndQuery constructor.
Root cause
src/uri/path.rs:436 (query = i as u16;) and src/uri/path.rs:441 (fragment = Some(i as u16)), with no bound on i. Consumed at src/uri/path.rs:134-146 and 183-190.
Scope
Both cases are reachable through PathAndQuery::try_from and through Uri::builder().path_and_query(..).build(). Which failure appears depends on where the wrapped offset lands. A ? at offset exactly 65535 stores 65535, which is the NONE sentinel, so query() returns None while a query is present. Reproduces in debug and release.
scan_path_and_querystores the?and#byte offsets asu16with no bound on the input length. A delimiter past byte offset 65535 is recorded wrapped, sopath()andquery()slice out of bounds and panic or return the wrong substring. Version: http 1.4.2, default features.Reproducer
Panic:
Wrong split:
Observed vs expected
First case:
try_fromsucceeds, storingquery = 65530while the retained data is 3 bytes ("/aa").path()evaluates&self.data[..65530]and panics withend byte index 65530 is out of bounds for string of length 3at src/uri/path.rs:138.query()panics the same way at src/uri/path.rs:188.try_fromshould reject the input, which is whatInvalidUriexists for, or produce a value whose accessors are total.PathAndQuerydocuments no panic forpath()orquery().Second case:
uri.path()returns"/"for a URI whose real path is 65536 bytes, anduri.query()returns the 65539-byte remainder.uri.to_string()still emits the full 65559 bytes. Expected:path()returns the 65536-byte path, or the builder rejects the input. A proxy that authorizes onuri.path()sees/while forwardinguri.to_string(), which carries the real path.The crate's only length limit,
MAX_LEN = u16::MAX - 1, is enforced inUri::from_sharedat src/uri/mod.rs:296 and in noPathAndQueryconstructor.Root cause
src/uri/path.rs:436 (
query = i as u16;) and src/uri/path.rs:441 (fragment = Some(i as u16)), with no bound oni. Consumed at src/uri/path.rs:134-146 and 183-190.Scope
Both cases are reachable through
PathAndQuery::try_fromand throughUri::builder().path_and_query(..).build(). Which failure appears depends on where the wrapped offset lands. A?at offset exactly 65535 stores 65535, which is theNONEsentinel, soquery()returnsNonewhile a query is present. Reproduces in debug and release.