Skip to content

apt: unknown architectures and uploads past the component cap are refused - #897

Merged
schmitch merged 1 commit into
mainfrom
claude/761-apt-component-arch-caps
Nov 4, 2026
Merged

schmitch merged 1 commit into
mainfrom
claude/761-apt-component-arch-caps

Conversation

@schmitch

@schmitch schmitch commented Nov 3, 2026

Copy link
Copy Markdown
Member

Fixes #761

A writer could push a hosted apt repository's index past the 10,000 files the host allows. Packages with many Section: cN/… components and made-up architectures did it. After that, InRelease, Packages and key.asc answered 500 for every reader. Now neither can grow without bound, and an upload that would break the index is refused with 400.

What changed

  • Architectures are dpkg's (crates/formats/apt/src/control.rs). ARCHITECTURES is a sorted table of the 206 names dpkg-architecture -L lists (dpkg 1.22.6). known_architecture accepts these plus all. A package's control file (Control::parse) is checked against it, and so is an imported index entry (Entry::problem). The syntactic valid_architecture stays for reading paths and ids, so packages already stored keep being served.
  • Components are capped at 8 (control::MAX_COMPONENTS). In the worst case, 8 components × 207 architectures × 6 files + 5 = 9,941 files, which is under MAX_INDEX_FILES. A unit test asserts this.
  • How the cap is enforced. A plugin can now declare a bound on the distinct values of a field of its stored summaries:
    • FormatPlugin::summary_caps returns a list of SummaryCap { field, max, what }, and the default has none. apt caps component.
    • The hosted publish path (crates/core/src/repo/hosted.rs) refuses a version whose summary would add a value past a cap. It answers 400 naming the cap: the repository holds 8 components, the most it may hold (8): "c8" would be one more.
    • A value the repository already holds passes. A redeploy leaves out the version it replaces.
    • The check runs twice. Once before anything reaches S3, which also covers an import's dry-run plan. Then again inside the publish transaction.
    • In the transaction, a new value is counted under a FOR NO KEY UPDATE lock on the repository row, so two concurrent uploads can't both take the last place. That lock does not conflict with the FOR KEY SHARE every component insert takes, so ordinary uploads don't wait on it.
    • Imports go through the same path. The source's component is merged into the summary before the check, so an import reports such a package as failed.
  • Docs: docs/apt.md (hosted repositories and limitations) and docs/format-plugins.md (summary_caps).

Tests

  • crates/formats/apt:
    • architectures_are_dpkgs: the table is sorted, and known and unknown names are told apart.
    • the_worst_case_index_fits_the_hosts_limit.
    • a_control_file_needs_its_identity now covers Architecture: a1 and linux-amd64.
    • the_component_is_capped: the capped field is the one in the stored summary.
  • crates/server/tests/apt.rs → architectures_and_components_are_bounded:
    • An upload with Architecture: a1 gets 400. dpkg architectures, including musl-linux-riscv64 and all, are taken.
    • main plus 7 more components fill the repository, and a ninth answers 400 naming the cap.
    • An existing component still takes packages.
    • InRelease verifies and lists the 8 components. Packages and key.asc serve.
    • Deleting a component's last package frees its place.
  • Without the fix:
    • The apt unit test fails: Architecture: a1 parses.
    • The server test fails at the a1 upload (201).
    • With only the architecture check restored, the server test fails at the ninth component (201).
  • Run locally: cargo fmt --all --check; cargo clippy with -D warnings and all targets on forge-plugin-api, forge-plugin, forge-core, forge-db, forge-format-apt and forge-server; tests of forge-format-apt, forge-plugin, forge-plugin-api and forge-db; forge-core's lib and hosted_write; forge-server's apt and apt_import; cargo xtask changelog --check.

Changelog

docs/changes/2.2.0/761.fixed.md

Not fixed here


Generated by Claude Code

A writer could take a hosted apt repository's index past the 10,000
files it may hold with packages of many components and made-up
architectures, after which InRelease, Packages and key.asc answered 500
for every reader.

- A package's Architecture (and an imported entry's) must be `all` or
  one of dpkg's architectures (`dpkg-architecture -L`, dpkg 1.22.6).
- A hosted repository holds at most 8 components: 8 x 207 architectures
  x 6 files stay under the host's limit. A plugin declares such a cap on
  a field of its stored summaries (`FormatPlugin::summary_caps`); the
  hosted publish path refuses a publish or an import that would add a
  value past it with 400 naming the cap, counting new values under a
  `FOR NO KEY UPDATE` lock on the repository row so that concurrent
  uploads cannot both take the last place.

Fixes #761

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJc6XYdgQSLMWWdx7fb
@schmitch
schmitch added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit e711a0b Nov 4, 2026
19 checks passed
@schmitch
schmitch deleted the claude/761-apt-component-arch-caps branch November 4, 2026 06:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

apt: a writer can break a hosted repository's indexes for everyone with packages of many components and architectures

2 participants