Skip to content

[Detail Bug] Service: HTTP Range validation blocks access to the last page (PageId 65535) #127

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_89d327b3-b883-4365-b6a3-46b6701342a9/bugs/bug_b9539ee3-5368-4f10-817b-72cc6a911b05

Introduced in 2c1010e by @shikhar on Sep 5, 2025

Summary

  • Context: The service uses a page-based architecture with PAGE_SIZE = 16 MiB and PageId = u16.
  • Bug: Off-by-one error in MAX_RANGE_END calculation makes the last page (65535) inaccessible.
  • Actual vs. expected: Actual: route validation requires last_byte < MAX_RANGE_END, so any byte at offset 65535 * PAGE_SIZE (start of page 65535) is rejected with HTTP 416. Expected: all pages 0..=65535 should be addressable.
  • Impact: Objects larger than ~1.099 TiB cannot serve their last ~16 MiB of data.

Code with Bug

src/service/mod.rs

pub const MAX_RANGE_END: u64 = PAGE_SIZE * PageId::MAX as u64; // <-- BUG 🔴 computes start of last page, not exclusive end

src/service/routes.rs

) if first_byte <= last_byte && last_byte < super::MAX_RANGE_END => { // <-- BUG 🔴 makes bytes at offset == MAX_RANGE_END unreachable
    Ok(Self(first_byte..(last_byte + 1)))
}

Explanation

PageId::MAX is 65535, so PAGE_SIZE * PageId::MAX equals the start of page 65535. Because the route layer enforces last_byte < MAX_RANGE_END, the maximum readable byte becomes MAX_RANGE_END - 1, which is the last byte of page 65534—making page 65535 entirely inaccessible.

With PAGE_SIZE = 16,777,216 bytes:

  • Current MAX_RANGE_END = 16,777,216 * 65,535 = 1,099,494,850,560 (start of page 65535)
  • Expected exclusive upper bound to allow page 65535: 16,777,216 * 65,536 = 1,099,511,627,776

Codebase Inconsistency

Other parts of the codebase explicitly support PageId values up to 65535, conflicting with the service’s range limit:

src/cache.rs

struct CacheKeyHeader(
    /// 16 bits page ID
    [u8; 5],
);

src/cache.rs

page_id in 0u16..=u16::MAX

Failing Test

Add to the src/service/mod.rs tests module:

#[test]
fn max_range_end_allows_all_pages() {
    let last_page_start = u64::from(PageId::MAX) * PAGE_SIZE;
    let last_page_end = last_page_start + PAGE_SIZE;
    assert!(
        last_page_end <= MAX_RANGE_END,
        "Page {} is inaccessible: requires byterange.end = {} but MAX_RANGE_END = {}",
        PageId::MAX, last_page_end, MAX_RANGE_END
    );
}

Current output:

thread 'service::tests::max_range_end_allows_all_pages' panicked at src/service/mod.rs:642:9:
Page 65535 is inaccessible: requires byterange.end = 1099511627776 but MAX_RANGE_END = 1099494850560

Recommended Fix

src/service/mod.rs

// Before (incorrect)
pub const MAX_RANGE_END: u64 = PAGE_SIZE * PageId::MAX as u64;

// After (correct)
pub const MAX_RANGE_END: u64 = PAGE_SIZE * (PageId::MAX as u64 + 1);

Also update the assertion comment in src/service/mod.rs to match the intended exclusive bound semantics.

History

This bug was introduced in commit 2c1010e (initial commit). Commit d5d5ab7 later fixed an identical off-by-one pattern in translating byte ranges to pages, but did not correct MAX_RANGE_END.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions