Skip to content

Unbounded, unrate-limited filtered upload-pack lets an anonymous caller exhaust the blocking pool (#62 follow-up) #166

Description

@beardthelion

Follow-up to #62. PR2 (#165) bounded the upload-pack/receive-pack POST via a timeout in run_git_service, and deliberately left the sibling git paths unbounded. Verifying that scope boundary turned up a genuine, anonymously reachable resource-exhaustion path worth closing on its own.

The bug

On a public repo that carries a path-scoped visibility rule, an anonymous POST /:owner/:repo.git/git-upload-pack takes the filtered-pack branch and runs two unbounded, no-timeout git walks on tokio's blocking pool:

  • withheld_blob_oids (crates/gitlawb-node/src/api/repos.rs:629, inside spawn_blocking) -> visibility_pack::blob_paths: git rev-list --all, then one git ls-tree -rz <commit> per reachable commit.
  • smart_http::upload_pack_excluding (crates/gitlawb-node/src/api/repos.rs:647) -> build_filtered_pack (crates/gitlawb-node/src/git/smart_http.rs:288, inside spawn_blocking): git rev-list --objects --all + git pack-objects.

Neither walk has a timeout, and the git-read routes have no per-IP rate limit: git_write_routes carries rate_limit_by_ip (crates/gitlawb-node/src/server.rs:196) but git_read_routes (server.rs:384-412) carries only body limits and optional_signature. The process runs the default #[tokio::main] runtime (crates/gitlawb-node/src/main.rs:41, no max_blocking_threads override anywhere), so the blocking pool is tokio's default 512 threads with an unbounded queue.

Reachability

git_upload_pack gates on visibility_check(rules, is_public, owner, caller, "/"). For a public repo an anonymous caller (caller = None) clears the "/" gate: a subtree rule such as /secret/** does not match "/" and the public fallback allows. has_path_scoped_rule(&rules) is then true, so control enters the filtered branch. No auth, no rate limit, no timeout.

Impact

Each request pins a blocking thread for the full ref walk plus pack build, with no deadline to shed it. Concurrent anonymous requests (bounded only by the 512-thread pool) occupy the pool; further spawn_blocking work queues without bound and the tasks awaiting it hang. Per-request cost scales with commit count and tree size, so a public repo with deep history under a path-scoped rule is an amplifier. This is an availability break reachable without credentials.

What is not the bug (checked)

info_refs (smart_http.rs:19) also lacks a timeout, but it runs git --advertise-refs via tokio::process::Command::output().await (async; it does not pin a worker thread), is bounded by ref count on a valid repo, and has no attacker-controllable cost knob short of a repo-corruption state an anonymous caller cannot induce on someone else's repo. It shares the missing-timeout shape but is not the exhaustion vector. Noting it only so a fix considers it, not as the finding.

Suggested direction

  • Put the git-read routes behind the same per-IP rate limiter as the write routes (rate_limit_by_ip), or a dedicated read limiter.
  • Bound the filtered pack build: wrap the spawn_blocking in a wall-clock deadline and abort the JoinHandle on expiry, returning 504, mirroring the run_git_service timeout from PR2. A tokio::time::timeout cannot cancel spawn_blocking, but the handle can be aborted and the request failed.
  • Consider a per-node concurrency cap on in-flight filtered builds so a burst sheds with 503 rather than exhausting the pool. This overlaps Harden served-git process handling: timeout, cross-env pid cap, wiring test (follow-up to #61) #62 part 2 (the concurrency cap) and can share a mechanism.

Verified against current main. Refs #62, #165.

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:nodegitlawb-node — the serving node and REST APIkind:securityVulnerability fix or hardeningsev:mediumDegraded but workaround existssubsystem:apiNode REST API request/response surfacesubsystem:visibilityPath-scoped visibility and content withholding

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions