Skip to content
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

Merged
merged 6 commits into from Jan 28, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
73 changes: 37 additions & 36 deletions draft-ietf-quic-qpack.md
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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
Copy link
Contributor Author

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 :)

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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 0x00 rather than a literal 0.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Expand Down