Commit 45bdf67
NIOHTTP2: reject SP and control characters in pseudo-header values (#555)
Stacks on #554 — the diff will show that commit too until it merges.
## Motivation
`isValidPseudoHeaderValue` rejects CR, LF and NUL, which is narrower
than the HTTP/1.1 request line actually needs.
`:path` ends up as the request-target in:
```
METHOD SP request-target SP HTTP-version CRLF
```
so `:path = "/a HTTP/1.1"` serializes to `GET /a HTTP/1.1 HTTP/1.1`.
Whether a recipient reads the first or the last SP-delimited token as
the version decides what the request-target actually is, which is the
usual parser-differential setup. RFC 9112 § 3.2 wants SP in a
request-target percent-encoded, and bare HTAB and the other CTLs aren't
valid there either.
None of the HTTP/2 pseudo-headers have a grammar that allows SP or a
CTL: `:method` and `:protocol` are tokens, `:scheme` is a URI scheme,
`:authority` is host[:port], `:path` is a request-target, `:status` is
3DIGIT. So rejecting them doesn't reject anything well-formed.
Same severity caveat as #554: NIOHTTP1's
`uriOnlyContainsAllowedCharacters` already rejects SP in a
request-target by default, so this is hardening rather than something
exploitable on a stock pipeline.
## Modifications
`isValidPseudoHeaderValue` goes from `{0x00, 0x0A, 0x0D}` to `<= 0x20 ||
== 0x7F`, so the whole CTL range plus SP and DEL.
Bytes >= 0x80 stay allowed on purpose. They aren't delimiters in a
request line or header block, and rejecting them would break anyone
sending unencoded UTF-8 in `:path`.
### One behaviour change worth flagging
`validateRequestBlock` runs on send as well as receive, so a client
passing a URI with a raw space in it will now throw where it didn't
before. I'd argue that's the right outcome, since NIOHTTP1's validator
already rejects that URI by default and this makes the two layers agree,
but it is a behaviour change and it's the bit most likely to need
discussion. That's why I split it out of #554 instead of bundling them.
## Result
Pseudo-header values containing SP or any control character are
rejected.
## Testing
Added to `HTTP2FramePayloadToHTTP1CodecCRLFTests.swift`:
- `:path` with SP (`/a HTTP/1.1`), HTAB, VT, FF, DEL, SOH
- SP in `:authority`, `:method`, `:scheme`
- covered at the predicate, request-validation and server-codec levels
The existing positive cases in that file still pass untouched — `/`,
`/foo/bar`, `/foo?q=1&r=2`, `/foo#fragment`, `*`, `/foo%20bar`, `200` —
which is the check that this doesn't reject anything legitimate.
Built and ran on Linux, Swift 6.1 aarch64. `swift test -Xswiftc
-warnings-as-errors` is clean with no new warnings, the new tests pass,
and `swift format lint --strict Sources/ Tests/` is clean.
---------
Co-authored-by: George Barnett <gbarnett@apple.com>1 parent 48bfd90 commit 45bdf67
2 files changed
Lines changed: 86 additions & 1 deletion
File tree
- Sources/NIOHTTP2
- Tests/NIOHTTP2Tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
796 | 796 | | |
797 | 797 | | |
798 | 798 | | |
799 | | - | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
800 | 821 | | |
801 | 822 | | |
802 | 823 | | |
| |||
Lines changed: 64 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
315 | 315 | | |
316 | 316 | | |
317 | 317 | | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
318 | 382 | | |
0 commit comments