<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     category="info"
     docName="draft-srivastava-websocket-pmce-state-reset-00"
     ipr="trust200902"
     submissionType="IETF"
     consensus="false"
     xml:lang="en"
     version="3">

  <front>
    <title abbrev="PMCE State After Refused Messages">Compression Context State After Refused Messages in WebSocket Per-Message Compression</title>

    <seriesInfo name="Internet-Draft" value="draft-srivastava-websocket-pmce-state-reset-00"/>

    <author fullname="Aviral Srivastava" initials="A." surname="Srivastava">
      <address>
        <email>aviralyash27@gmail.com</email>
      </address>
    </author>

    <area>Applications and Real-Time</area>

    <keyword>WebSocket</keyword>
    <keyword>compression</keyword>
    <keyword>permessage-deflate</keyword>
    <keyword>context takeover</keyword>

    <abstract>
      <t>
        RFC 7692 defines Per-Message Compression Extensions for the WebSocket
        Protocol, including the "permessage-deflate" extension. When context
        takeover is in effect, the LZ77 sliding window is retained across
        messages, so the decompression of one message can depend on the
        plaintext of earlier messages.
      </t>
      <t>
        RFC 7692 does not state what the sliding window contains after a
        message has been successfully decompressed but subsequently refused by
        a check applied to the decompressed plaintext, such as UTF-8
        validation, a payload size limit, or an application-level policy. An
        implementation that retains the refused plaintext in the window
        violates no stated requirement, yet the content of a refused message
        can then influence the decompression of a later message that is
        accepted.
      </t>
      <t>
        This document describes the gap, contrasts it with the corresponding
        situation in QPACK where RFC 9204 specifies the required behavior, and
        recommends behavior for implementations. It defines no new protocol
        element and updates no existing specification.
      </t>
    </abstract>
  </front>

  <middle>

    <section anchor="introduction">
      <name>Introduction</name>
      <t>
        The "permessage-deflate" extension defined in <xref target="RFC7692"/>
        compresses WebSocket message payloads using DEFLATE
        <xref target="RFC1951"/>. Unless "no_context_takeover" has been
        negotiated, the LZ77 sliding window persists across messages on a
        connection. This is the behavior that makes the extension effective:
        repeated content across messages compresses well precisely because the
        window retains earlier plaintext.
      </t>
      <t>
        Retaining state across messages means the interpretation of one message
        can depend on the content of earlier ones. A back-reference in a later
        message resolves against whatever the window currently holds.
      </t>
      <t>
        Implementations commonly apply checks to the decompressed plaintext
        after decompression has completed. UTF-8 validity for text messages is
        required by <xref target="RFC6455"/>. Payload size limits are common in
        deployment. Applications frequently apply their own content policy.
        When such a check refuses a message, decompression has already
        succeeded and the plaintext is already in the window.
      </t>
      <t>
        <xref target="RFC7692"/> does not say what the window should contain at
        that point. This document describes that gap and its consequences, and
        recommends behavior.
      </t>
    </section>

    <section anchor="terminology">
      <name>Terminology</name>
      <t>
        The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
        "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>",
        "<bcp14>SHALL NOT</bcp14>", "<bcp14>SHOULD</bcp14>",
        "<bcp14>SHOULD NOT</bcp14>", "<bcp14>RECOMMENDED</bcp14>",
        "<bcp14>NOT RECOMMENDED</bcp14>", "<bcp14>MAY</bcp14>", and
        "<bcp14>OPTIONAL</bcp14>" in this document are to be interpreted as
        described in BCP&nbsp;14 <xref target="RFC2119"/>
        <xref target="RFC8174"/> when, and only when, they appear in all
        capitals, as shown here.
      </t>
      <t>
        This document uses "refused message" to mean a message whose payload
        was successfully decompressed by the extension, and which was then
        rejected by a check applied to the decompressed plaintext. A message
        that fails to decompress is not a refused message in this sense.
      </t>
    </section>

    <section anchor="gap">
      <name>The Unspecified Case</name>

      <section anchor="what-is-specified">
        <name>What RFC 7692 Specifies</name>
        <t>
          Section 7.1.1.1 of <xref target="RFC7692"/> describes the
          "server_no_context_takeover" extension parameter, and Section 7.1.1.2
          describes "client_no_context_takeover". Absent these parameters, an
          endpoint retains the LZ77 sliding window across messages.
        </t>
        <t>
          Section 7.2.2 of <xref target="RFC7692"/> describes decompression and
          the conditions under which the sliding window is reset. The
          specification is written in terms of successful message processing
          and of decompression failure. It does not address the case where
          decompression succeeds and the message is refused afterwards.
        </t>
      </section>

      <section anchor="what-is-not-specified">
        <name>What Is Not Specified</name>
        <t>
          Consider a connection with context takeover in effect:
        </t>
        <ol>
          <li>
            An endpoint receives message A. The payload decompresses
            successfully. The resulting plaintext enters the sliding window,
            because that is what context takeover requires.
          </li>
          <li>
            A check applied after decompression refuses message A. The check
            may be UTF-8 validation, a payload length limit, an opcode
            constraint, or an application policy.
          </li>
          <li>
            The endpoint continues processing the connection.
          </li>
          <li>
            The endpoint receives message B, which decompresses successfully
            and passes every check. Message B contains an LZ77 back-reference
            that resolves into the region of the window occupied by the
            plaintext of message A.
          </li>
          <li>
            Message B is delivered to the application. Its content includes
            material from message A, which was refused.
          </li>
        </ol>
        <t>
          At no point in this sequence does an implementation violate a stated
          requirement of <xref target="RFC7692"/>. Retaining the plaintext of a
          refused message in the window is neither required nor forbidden.
        </t>
        <t>
          The consequence is that a check applied to decompressed plaintext
          does not, on its own, prevent that plaintext from reaching the
          application. It prevents delivery of the message that carried it. The
          content remains available to be reproduced by a subsequent message
          that the same check accepts.
        </t>
      </section>

      <section anchor="observed">
        <name>Observed Behavior</name>
        <t>
          Implementations vary in how they handle this case, and the variation
          does not appear to be the result of differing interpretations of
          <xref target="RFC7692"/>. It appears to result from properties of the
          surrounding implementation.
        </t>
        <t>
          Some implementations terminate the connection whenever a
          post-decompression check fails. In those implementations the sequence
          in <xref target="what-is-not-specified"/> cannot complete, because
          there is no connection on which message B could arrive. The
          compression context is not reset; it simply becomes unreachable.
        </t>
        <t>
          Other implementations allow a post-decompression check to fail
          without terminating the connection, either by default or under a
          configuration option. In those implementations the sequence can
          complete.
        </t>
        <t>
          The distinction that determines the outcome is therefore the
          connection lifecycle policy of the surrounding implementation, not
          any property of the compression extension. An implementation that is
          safe for this reason may cease to be safe if its termination behavior
          is made configurable, or if its compression codec is exposed as an
          interface that other software drives directly.
        </t>
        <t>
          A related concern was raised against one implementation in a public
          issue report <xref target="NETTY6209"/>, which discusses the state of
          the compression context following an error. The general question of
          what the window should contain after a refused message does not
          appear to have been treated in the specification.
        </t>
      </section>
    </section>

    <section anchor="qpack">
      <name>Comparison with QPACK</name>
      <t>
        QPACK <xref target="RFC9204"/> presents a structurally similar hazard.
        The dynamic table persists across field sections on a connection, so
        the interpretation of one field section can depend on earlier ones. If
        a field section were refused after decoding, and the dynamic table
        retained its effects, a later accepted field section could reference
        entries derived from the refused one.
      </t>
      <t>
        Section 6 of <xref target="RFC9204"/> addresses this directly. It
        specifies that certain decoding failures <bcp14>MUST</bcp14> be treated
        as connection errors of the relevant type. The result is that a QPACK
        implementation cannot continue on a connection whose decoder state is
        in question, because the specification does not permit it to.
      </t>
      <t>
        The two cases are worth stating side by side:
      </t>
      <ul>
        <li>
          Both protocols maintain state across message or field-section
          boundaries, and in both the state affects how later input is
          interpreted.
        </li>
        <li>
          RFC 9204 states what must happen when that state is in question. An
          implementation that continues is non-conforming.
        </li>
        <li>
          RFC 7692 does not state what must happen. An implementation that
          continues is conforming.
        </li>
      </ul>
      <t>
        The difference in outcome between implementations of the two protocols
        appears to follow from this difference in what the specifications
        require, rather than from differences in implementation quality.
      </t>
    </section>

    <section anchor="recommendations">
      <name>Recommendations</name>
      <t>
        The following recommendations are addressed to implementers of
        <xref target="RFC7692"/> and to applications built on it. They are
        stated as recommendations rather than as updates to
        <xref target="RFC7692"/>; this document does not update that
        specification.
      </t>

      <section anchor="rec-implementations">
        <name>For Implementations</name>
        <t>
          When a message is refused by a check applied to its decompressed
          plaintext, an implementation <bcp14>SHOULD</bcp14> do one of the
          following:
        </t>
        <ol>
          <li>
            Fail the WebSocket Connection as described in Section 7.1.7 of
            <xref target="RFC6455"/>, so that no further message is processed
            using the affected compression context; or
          </li>
          <li>
            Reset the LZ77 sliding window for the affected direction, so that
            the plaintext of the refused message cannot be referenced by a
            later message.
          </li>
        </ol>
        <t>
          The first option is what several implementations already do as a
          consequence of their error handling. Making it an explicit
          consequence of the refusal, rather than an incidental one, means the
          property is preserved if the surrounding error handling changes.
        </t>
        <t>
          An implementation that continues processing a connection after
          refusing a message, without resetting the window,
          <bcp14>SHOULD</bcp14> document that behavior, so that applications
          relying on post-decompression checks are aware that a refused
          message's content remains referenceable.
        </t>
      </section>

      <section anchor="rec-apis">
        <name>For Implementations Exposing Codec Interfaces</name>
        <t>
          An implementation that exposes its compression codec as an interface
          which other software may drive directly, separately from its own
          connection handling, <bcp14>SHOULD</bcp14> document whether the
          safety of that codec depends on connection termination performed
          elsewhere.
        </t>
        <t>
          Software driving such an interface does not necessarily inherit the
          error handling of the implementation that provides it. A codec whose
          safety in practice depends on a connection teardown performed by a
          separate component is not safe when driven without that component.
        </t>
      </section>

      <section anchor="rec-config">
        <name>For Configurable Termination Behavior</name>
        <t>
          Where an implementation provides a configuration option that
          suppresses connection termination on protocol violations, the
          documentation for that option <bcp14>SHOULD</bcp14> state which
          components the option governs.
        </t>
        <t>
          An option described in general terms as controlling behavior on
          protocol violations may in practice govern only some of the
          components that can refuse a message, leaving the compression context
          outside its scope. An operator selecting such an option is unlikely
          to infer that from the option's name.
        </t>
      </section>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>
        The concern described in this document is that a check applied to
        decompressed plaintext may not have the effect an application expects.
        Refusing a message prevents its delivery. It does not necessarily
        prevent its content from being delivered later, inside a message that
        the same check accepts.
      </t>
      <t>
        Applications that rely on post-decompression validation as a security
        control should be aware of this. A UTF-8 validity check, a payload size
        limit, or a content policy applied after decompression constrains which
        messages are delivered, not which plaintext can reach the application.
      </t>
      <t>
        The severity of this in any particular deployment depends on factors
        outside the scope of this document, including whether the affected
        compression context is shared between security principals. In the
        common case where a WebSocket connection carries traffic for a single
        client, both the refused and the accepted message originate from that
        client, and the content that reaches the application is content the
        client already possessed. In that case the concern is the bypass of a
        validation control rather than disclosure of information belonging to
        another party.
      </t>
      <t>
        Implementations that terminate the connection when a message is refused
        are not affected by the sequence described here, because the affected
        compression context becomes unreachable. As noted in
        <xref target="observed"/>, this property may be incidental to the
        implementation's error handling rather than a deliberate defense, and
        may therefore not survive changes to that error handling.
      </t>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>This document has no IANA actions.</t>
    </section>

  </middle>

  <back>
    <references>
      <name>References</name>

      <references>
        <name>Normative References</name>

        <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author initials="S." surname="Bradner" fullname="S. Bradner"/>
            <date year="1997" month="March"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
          <seriesInfo name="DOI" value="10.17487/RFC2119"/>
        </reference>

        <reference anchor="RFC6455" target="https://www.rfc-editor.org/info/rfc6455">
          <front>
            <title>The WebSocket Protocol</title>
            <author initials="I." surname="Fette" fullname="I. Fette"/>
            <author initials="A." surname="Melnikov" fullname="A. Melnikov"/>
            <date year="2011" month="December"/>
          </front>
          <seriesInfo name="RFC" value="6455"/>
          <seriesInfo name="DOI" value="10.17487/RFC6455"/>
        </reference>

        <reference anchor="RFC7692" target="https://www.rfc-editor.org/info/rfc7692">
          <front>
            <title>Compression Extensions for WebSocket</title>
            <author initials="T." surname="Yoshino" fullname="T. Yoshino"/>
            <date year="2015" month="December"/>
          </front>
          <seriesInfo name="RFC" value="7692"/>
          <seriesInfo name="DOI" value="10.17487/RFC7692"/>
        </reference>

        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author initials="B." surname="Leiba" fullname="B. Leiba"/>
            <date year="2017" month="May"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
          <seriesInfo name="DOI" value="10.17487/RFC8174"/>
        </reference>
      </references>

      <references>
        <name>Informative References</name>

        <reference anchor="RFC1951" target="https://www.rfc-editor.org/info/rfc1951">
          <front>
            <title>DEFLATE Compressed Data Format Specification version 1.3</title>
            <author initials="P." surname="Deutsch" fullname="P. Deutsch"/>
            <date year="1996" month="May"/>
          </front>
          <seriesInfo name="RFC" value="1951"/>
          <seriesInfo name="DOI" value="10.17487/RFC1951"/>
        </reference>

        <reference anchor="RFC9204" target="https://www.rfc-editor.org/info/rfc9204">
          <front>
            <title>QPACK: Field Compression for HTTP/3</title>
            <author initials="C." surname="Krasic" fullname="C. Krasic"/>
            <author initials="M." surname="Bishop" fullname="M. Bishop"/>
            <author initials="A." surname="Frindell" fullname="A. Frindell" role="editor"/>
            <date year="2022" month="June"/>
          </front>
          <seriesInfo name="RFC" value="9204"/>
          <seriesInfo name="DOI" value="10.17487/RFC9204"/>
        </reference>

        <reference anchor="NETTY6209" target="https://github.com/netty/netty/issues/6209">
          <front>
            <title>WebSocket permessage-deflate decoder state after error</title>
            <author>
              <organization>Netty Project</organization>
            </author>
            <date year="2016"/>
          </front>
          <refcontent>GitHub issue netty/netty#6209</refcontent>
        </reference>
      </references>
    </references>

    <section anchor="acknowledgements" numbered="false">
      <name>Acknowledgements</name>
      <t>
        The comparison with QPACK arose from examining implementations of both
        protocols side by side. The author thanks the maintainers of the
        WebSocket and HTTP/3 implementations whose source and issue history
        made that comparison possible.
      </t>
    </section>

  </back>
</rfc>
