FWIW, I don't think this is an *error* in the specification per se. Both
2^16-2 and 2^16-1 to describe the exact same structure, precisely because a
length of 2^16-1 is impossible. One isn't inherently more correct than the
other. The question is which is clearer, the loose bound or the tight bound.

Indeed I would argue that the loose bound, 2^16-1, is a _better_ way to
describe the structure than the tight bound, 2^16-2. (I would also argue
that a minimum length of 1 is a better way to describe it than 2, but
that's less crucial.) The bounds here communicate two things to the TLS
implementor:

1. What size of length prefix to use, which is implicitly determined by the
upper bound
2. What checks to apply on the length you've parsed out

The key thing to keep in mind is that the size of the length prefix already
bounds the lengths (here 0..2^16-1), so any checks you write from (2) are *in
addition to* the bounds you get from the length prefix. When the written
bounds are equal to the implicit bounds, you don't need to write a check.
Where either upper or lower bound are different (e.g. opaque
legacy_session_id<0..32>), you need to write an extra check.

That is, you would parse opaque legacy_session_id<0..32> like this:

  legacy_session_id = client_hello.read_u8_length_prefix()
  if len(legacy_session_id) > 32:
    raise DecodeError()

But you would parse opaque extension_data<0..2^16-1> like this:

  extension_data = extension.read_u16_length_prefix()
  # No need to check additional bounds because u16 is already <0..2^16-1>

Now, the NamedGroup field is a u16-prefixed list of at least one
NamedGroup. We actually just want to parse it like this:

  named_group_list = extension_data.read_u16_length_prefix()
  if len(named_group_list) == 0:
    raise DecodeError()

But when we write it as NamedGroup named_group_list<2..2^16-2>, it *looks
like* you have to parse it like:

  named_group_list = extension_data.read_u16_length_prefix()
  if len(named_group_list) < 2 or len(named_group_list) > 2**16 - 2:
    raise DecodeError()

To know that these do the same thing you have to observe that:
1. It is not possible to have a NamedGroup of length less than 2
2. NamedGroup is always exactly two bytes, so it is not possible to have a
NamedGroup list of length 2^16-1.

This is extra work for the implementer and makes it more likely that
they'll misunderstand it. Or perhaps they'll get used to the TLSWG writing
things in this weird way and miss a case when they *do* need to enforce an
upper bound. This is particularly fraught because TLS structures are
variable-length. Here is a contrived example to demonstrate the silliness
of this convention:

enum { len1000(1},  len30001(2) } Type;
struct TwoPossibleLengths {
    Type type;
    select (type) {
       case len1000: opaque padding[999];
       case len30001: opaque padding[30000];
    }
};

struct {
  TwoPossibleLengths list<1000..65002>;
} Confusing;

If you look at this field, you might think that this is actually imposing
some limit that is tighter than its length prefix, and that you need to
check more things. In reality, it's not. It's fairly clear that the minimum
length of a TwoPossibleLengths is 1000. It's much less obvious that the
maximum length of a series of TwoPossibleLengths, constrained to be under
2^16-1, is 65002. So I think this is a far, far better way to write this
same structure.

struct {
  TwoPossibleLengths list<1..2^16-1>;
} MuchMuchClearer;

It describes the exact same structure, but now it's obvious the intended
parse was that you check the length is non-zero and leave the upper-bound
set based on its length prefix. Now, obviously this is a contrived example
and that instance of knapsack is quite a bit harder than the NamedGroup
instance. But I think it demonstrates why the simpler loose bound
convention is better for readers of the specification than the one that
relies on the reader to do more math.

David


On Thu, May 8, 2025 at 8:54 PM Sean Turner <s...@sn3rd.com> wrote:

> Paul,
>
> You can marked this one as “verified" if you want. I submitted a PR to fix
> this in -rfc8446bis; see:
> https://github.com/tlswg/tls13-spec/pull/1380
>
> spt
>
> On May 8, 2025, at 4:05 AM, RFC Errata System <rfc-edi...@rfc-editor.org>
> wrote:
>
> The following errata report has been submitted for RFC8446,
> "The Transport Layer Security (TLS) Protocol Version 1.3".
>
> --------------------------------------
> You may review the report below and at:
> https://www.rfc-editor.org/errata/eid8411
>
> --------------------------------------
> Type: Technical
> Reported by: Albin Johansson <albin.johans...@vector.com>
>
> Section: 4.2.7
>
> Original Text
> -------------
> struct {
>    NamedGroup named_group_list<2..2^16-1>;
> } NamedGroupList;
>
> Corrected Text
> --------------
> struct {
>    NamedGroup named_group_list<2..2^16-2>;
> } NamedGroupList;
>
> Notes
> -----
> The specified maximum legal length of the named_group_list vector in the
> NamedGroupList structure is 2^16-1 bytes. This is invalid because
> NamedGroup is an enum that occupies two bytes, but 2^16-1 is not an exact
> multiple of the element size (2 bytes), as required in Section 3.4. It
> appears that the intended upper bound should be 2^16-2 bytes instead.
>
> Instructions:
> -------------
> This erratum is currently posted as "Reported". (If it is spam, it
> will be removed shortly by the RFC Production Center.) Please
> use "Reply All" to discuss whether it should be verified or
> rejected. When a decision is reached, the verifying party
> will log in to change the status and edit the report, if necessary.
>
> --------------------------------------
> RFC8446 (draft-ietf-tls-tls13-28)
> --------------------------------------
> Title               : The Transport Layer Security (TLS) Protocol Version
> 1.3
> Publication Date    : August 2018
> Author(s)           : E. Rescorla
> Category            : PROPOSED STANDARD
> Source              : Transport Layer Security
> Stream              : IETF
> Verifying Party     : IESG
>
>
> _______________________________________________
> TLS mailing list -- tls@ietf.org
> To unsubscribe send an email to tls-le...@ietf.org
>
_______________________________________________
TLS mailing list -- tls@ietf.org
To unsubscribe send an email to tls-le...@ietf.org

Reply via email to