New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Editorial suggestions from Benjamin Kaduk's IESG review (qpack) #4789
Changes from all commits
5086a86
91e2df8
532638b
2084871
3e96620
a3d2fa9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -140,8 +140,8 @@ HTTP field line: | |
HTTP field value: | ||
|
||
: Data associated with a field name, composed from all field line values with | ||
that field name in that section, concatenated together and separated with | ||
commas. | ||
that field name in that section, concatenated together with | ||
comma separators. | ||
|
||
Field section: | ||
|
||
|
@@ -349,7 +349,7 @@ A Section Acknowledgment instruction ({{header-acknowledgment}}) implies that | |
the decoder has received all dynamic table state necessary to decode the field | ||
section. If the Required Insert Count of the acknowledged field section is | ||
greater than the current Known Received Count, Known Received Count is updated | ||
to the value of the Required Insert Count. | ||
to that Required Insert Count value. | ||
|
||
An Insert Count Increment instruction ({{insert-count-increment}}) increases the | ||
Known Received Count by its Increment parameter. See {{new-table-entries}} for | ||
|
@@ -960,7 +960,7 @@ table. | |
MaxValue = TotalNumberOfInserts + MaxEntries | ||
|
||
# MaxWrapped is the largest possible value of | ||
# ReqInsertCount that is 0 mod 2*MaxEntries | ||
# ReqInsertCount that is 0 mod 2 * MaxEntries | ||
MaxWrapped = floor(MaxValue / FullRange) * FullRange | ||
ReqInsertCount = MaxWrapped + EncodedInsertCount - 1 | ||
|
||
|
@@ -1092,7 +1092,8 @@ line MUST always be encoded with a literal representation. In particular, when a | |
peer sends a field line that it received represented as a literal field line | ||
with the 'N' bit set, it MUST use a literal representation to forward this field | ||
line. This bit is intended for protecting field values that are not to be put | ||
at risk by compressing them; see {{security-considerations}} for more details. | ||
at risk by compressing them; see {{probing-dynamic-table-state}} for more | ||
details. | ||
|
||
The fourth ('T') bit indicates whether the reference is to the static or dynamic | ||
table. The 4-bit prefix integer ({{prefixed-integers}}) that follows is used to | ||
|
@@ -1209,9 +1210,9 @@ the dynamic table state. If a guess is compressed into a shorter length, the | |
attacker can observe the encoded length and infer that the guess was correct. | ||
|
||
This is possible even over the Transport Layer Security Protocol (TLS, see | ||
{{?TLS=RFC8446}}), because while TLS provides confidentiality protection for | ||
content, it only provides a limited amount of protection for the length of that | ||
content. | ||
{{?TLS=RFC8446}}) and the QUIC Transport Protocol (see {{QUIC-TRANSPORT}}), | ||
because while TLS and QUIC provide confidentiality protection for content, they | ||
only provide a limited amount of protection for the length of that content. | ||
|
||
Note: | ||
|
||
|
@@ -1241,12 +1242,12 @@ recovered successfully. However, values with low entropy remain vulnerable. | |
Attacks of this nature are possible any time that two mutually distrustful | ||
entities control requests or responses that are placed onto a single HTTP/3 | ||
connection. If the shared QPACK compressor permits one entity to add entries to | ||
the dynamic table, and the other to access those entries to encode chosen field | ||
lines, then the attacker can learn the state of the table by observing the | ||
length of the encoded output. | ||
the dynamic table, and the other to refer to those entries while encoding | ||
chosen field lines, then the attacker (the second entity) can learn the state | ||
of the table by observing the length of the encoded output. | ||
|
||
Having requests or responses from mutually distrustful entities occurs when an | ||
intermediary either: | ||
For example, requests or responses from mutually distrustful entities can occur | ||
when an intermediary either: | ||
|
||
* sends requests from multiple clients on a single connection toward an origin | ||
server, or | ||
|
@@ -1256,6 +1257,7 @@ intermediary either: | |
|
||
Web browsers also need to assume that requests made on the same connection by | ||
different web origins ({{?RFC6454}}) are made by mutually distrustful entities. | ||
Other scenarios involving mutually distrustful entities are also possible. | ||
|
||
### Mitigation | ||
|
||
|
@@ -1283,15 +1285,6 @@ different values. This penalty could cause a large number of attempts to guess | |
a field value to result in the field not being compared to the dynamic table | ||
entries in future messages, effectively preventing further guesses. | ||
|
||
Note: | ||
|
||
: Simply removing entries corresponding to the field from the dynamic table can | ||
be ineffectual if the attacker has a reliable way of causing values to be | ||
reinstalled. For example, a request to load an image in a web browser | ||
typically includes the Cookie header field (a potentially highly valued target | ||
for this sort of attack), and web sites can easily force an image to be | ||
loaded, thereby refreshing the entry in the dynamic table. | ||
|
||
This response might be made inversely proportional to the length of the | ||
field value. Disabling access to the dynamic table for a given field name might | ||
occur for shorter values more quickly or with higher probability than for longer | ||
|
@@ -1302,6 +1295,15 @@ re-encoded by an intermediary without knowledge of which entity constructed a | |
given message, the intermediary could inadvertently merge compression contexts | ||
that the original encoder had specifically kept separate. | ||
|
||
Note: | ||
|
||
: Simply removing entries corresponding to the field from the dynamic table can | ||
be ineffectual if the attacker has a reliable way of causing values to be | ||
reinstalled. For example, a request to load an image in a web browser | ||
typically includes the Cookie header field (a potentially highly valued target | ||
for this sort of attack), and web sites can easily force an image to be | ||
loaded, thereby refreshing the entry in the dynamic table. | ||
|
||
### Never-Indexed Literals | ||
|
||
Implementations can also choose to protect sensitive fields by not compressing | ||
|
@@ -1350,11 +1352,11 @@ An attacker can try to cause an endpoint to exhaust its memory. QPACK is | |
designed to limit both the peak and stable amounts of memory allocated by an | ||
endpoint. | ||
|
||
The amount of memory used by the encoder is limited by the protocol using | ||
QPACK through the definition of the maximum size of the dynamic table, and the | ||
maximum number of blocking streams. In HTTP/3, these values are controlled by | ||
the decoder through the settings parameters SETTINGS_QPACK_MAX_TABLE_CAPACITY | ||
and SETTINGS_QPACK_BLOCKED_STREAMS, respectively (see | ||
QPACK uses the definition of the maximum size of the dynamic table and the | ||
maximum number of blocking streams to limit the amount of memory the encoder can | ||
cause the decoder to consume. In HTTP/3, these values are controlled by the | ||
decoder through the settings parameters SETTINGS_QPACK_MAX_TABLE_CAPACITY and | ||
SETTINGS_QPACK_BLOCKED_STREAMS, respectively (see | ||
{{maximum-dynamic-table-capacity}} and {{blocked-streams}}). The limit on the | ||
size of the dynamic table takes into account the size of the data stored in the | ||
dynamic table, plus a small allowance for overhead. The limit on the number of | ||
|
@@ -1366,14 +1368,14 @@ A decoder can limit the amount of state memory used for the dynamic table by | |
setting an appropriate value for the maximum size of the dynamic table. In | ||
HTTP/3, this is realized by setting an appropriate value for the | ||
SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter. An encoder can limit the amount of | ||
state memory it uses by signaling a lower dynamic table size than the decoder | ||
allows (see {{eviction}}). | ||
state memory it uses by choosing a smaller dynamic table size than the decoder | ||
allows and signaling this to the decoder (see {{set-dynamic-capacity}}). | ||
|
||
A decoder can limit the amount of state memory used for blocked streams by | ||
setting an appropriate value for the maximum number of blocked streams. In | ||
HTTP/3, this is realized by setting an appropriate value for the | ||
QPACK_BLOCKED_STREAMS parameter. Streams which risk becoming blocked consume no | ||
additional state memory on the encoder. | ||
SETTINGS_QPACK_BLOCKED_STREAMS parameter. Streams which risk becoming blocked | ||
consume no additional state memory on the encoder. | ||
|
||
An encoder allocates memory to track all dynamic table references in | ||
unacknowledged field sections. An implementation can directly limit the amount | ||
|
@@ -1734,9 +1736,8 @@ Stream: Decoder | |
1 0 :path /sample/path | ||
2 0 custom-key custom-value | ||
^-- acknowledged --^ | ||
3 0 :authority www.example.com | ||
Size=215 | ||
|
||
4 0 :authority www.example.com | ||
Size=217 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The table hasn't changed from the previous state, properly shown as 217 bytes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't catch this when @afrind updated examples at my request. Thanks. |
||
~~~ | ||
|
||
## Dynamic Table Insert, Eviction | ||
|
@@ -1807,8 +1808,8 @@ for line in field_lines: | |
|
||
# encode the prefix | ||
if requiredInsertCount == 0: | ||
encodeIndexReference(prefixBuffer, 0, 0, 8) | ||
encodeIndexReference(prefixBuffer, 0, 0, 7) | ||
encodeInteger(prefixBuffer, 0, 0, 8) | ||
encodeInteger(prefixBuffer, 0, 0, 7) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. encodeIndexReference() is shown earlier with a different signature. I know pythonic things do have optional parameters, but this seems to match the encodeInteger() signature perfectly, so I assume it was just an incomplete edit. Though, stylistically perhaps the second argument should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good catch, thanks. The prefix is integers, not references. |
||
else: | ||
wireRIC = ( | ||
requiredInsertCount | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can "access" those entries, you're done already :)