Skip to content

PathAndQuery stores '?' and '#' byte offsets as u16 with no length check #855

Description

@hey-jj

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions