chunkloris: granian (asgi h2c)

part of the chunkloris per-chunk amplification survey. this page is the per-server record for granian (ASGI h2c) under http/2 (h2c) data frames.

at a glance

  • server: granian (ASGI h2c) 1.7.6
  • runtime: python-3.12
  • ecosystem: python
  • concurrency model: event-loop
  • parser: Rust hyper/h2 via granian
  • delivery granularity: per-frame
  • chunk-limit helper: none exposed by the framework
  • verdict: per-frame β€” the parser/dispatcher boundary delivers one event per protocol frame (h2 / h3 DATA frame, or ws data frame). cpu cost under paced mode b is measurable per frame.
  • scaling exponent (mode a): 0.97 (wall time vs N, log-log slope across common cells)
  • scaling exponent (mode b): 1.00

measurements

all cells run on a 1-vcpu docker container. cpu cost is derived from the target container’s cgroup v2 cpu.stat usage_usec delta around each cell.

modeNwall (s)server cpu %Β΅s / framebasisok
A-h2-bridge50,0000.841107.818.133server-cpu-cgroupβœ“
A-h2-bridge100,0001.574105.516.604server-cpu-cgroupβœ“
A-h2-bridge250,0004.022101.516.322server-cpu-cgroupβœ“
B-h2-paced-100us50,0005.42530.332.856server-cpu-cgroupβœ“
B-h2-paced-100us100,00010.82327.830.057server-cpu-cgroupβœ“
B-h2-paced-100us250,00027.14326.628.821server-cpu-cgroupβœ“

parser path β€” source citations

  • Granian HTTP version option β€” ? β†’ source

what this means

the parser/dispatcher path on this server delivers one event per protocol frame (a http/2 (h2c) data frames DATA frame or ws frame), so an attacker who sends a request body as N one-byte frames consumes roughly N Γ— (mode-b Β΅s/frame) of server cpu on a single core.

what to do today

  • if this is an h2 origin, prefer a frontend that terminates h2 into h1 with proxy_request_buffering on upstream.
  • consider imposing a per-stream DATA-frame credit (count, not bytes) before forwarding the body to the application handler.
  • HTTP/2 byte-level flow control (WINDOW_UPDATE) does not bound the number of frames; configure stream-frame-rate limits where the implementation exposes them.

reproducer

the full reproducer for this server is in the paper repo. the docker container pins granian (ASGI h2c) 1.7.6 and constrains the test container to a single cpu (--cpus=1). the prober script implements mode a (bridge-coalesced) and mode b (paced 100 Β΅s) per the methodology section.

see the draft pdf for the full per-framework discussion.

on this page