From ce1dfbd978d0d504893357e462bb7460cb89d7b4 Mon Sep 17 00:00:00 2001 From: philomathic_life <15947783+zacknewman@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:12:39 -0700 Subject: [PATCH 1/4] allow domains of length 254 with a trailing dot --- src/server_name.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/server_name.rs b/src/server_name.rs index 64dec3c..bc7048d 100644 --- a/src/server_name.rs +++ b/src/server_name.rs @@ -323,11 +323,20 @@ const fn validate(input: &[u8]) -> Result<(), InvalidDnsNameError> { /// "Labels must be 63 characters or less." const MAX_LABEL_LENGTH: usize = 63; - /// https://devblogs.microsoft.com/oldnewthing/20120412-00/?p=7873 - const MAX_NAME_LENGTH: usize = 253; - - if input.len() > MAX_NAME_LENGTH { - return Err(InvalidDnsNameError); + /// The maximum lenght of a domain name is 253 when there is no trailing dot. + /// When there is a trailing dot, the maximum length is 254. This is true since + /// a domain with a trailing dot gets transformed into the same wire-format value + /// as one without. + const MAX_NAME_LENGTH_WITH_TRAILING_DOT: usize = 254; + + match input.len() { + 0 | 255.. => return Err(InvalidDnsNameError), + // This is a `const fn`, so we cannot use + // `Option::unwrap_or_else(|| unreachable!("bug in [u8]::len()")). + MAX_NAME_LENGTH_WITH_TRAILING_DOT if *input.last().unwrap() != b'.' => { + return Err(InvalidDnsNameError) + } + _ => (), } let mut idx = 0; @@ -838,6 +847,7 @@ mod tests { ("numeric-only-middle-label.4.com", true), ("1000-sans.badssl.com", true), ("twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfi", true), + ("twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourc.", true), ("twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourc", false), ]; From e2548e3e306f88f156f90710d8b57b0399bb12f9 Mon Sep 17 00:00:00 2001 From: philomathic_life <15947783+zacknewman@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:29:19 -0700 Subject: [PATCH 2/4] Fix test for domains of length 254 with a trailing dot. The test I added actually had length of 255. --- src/server_name.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server_name.rs b/src/server_name.rs index bc7048d..c503b56 100644 --- a/src/server_name.rs +++ b/src/server_name.rs @@ -847,7 +847,7 @@ mod tests { ("numeric-only-middle-label.4.com", true), ("1000-sans.badssl.com", true), ("twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfi", true), - ("twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourc.", true), + ("twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfour.", true), ("twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourc", false), ]; From 2d7ff92330b2cbd8b8c3a0106a01f6887ca71747 Mon Sep 17 00:00:00 2001 From: philomathic_life <15947783+zacknewman@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:52:37 -0700 Subject: [PATCH 3/4] account for MSRV Forgot to change my local rust version to the MSRV. --- src/server_name.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/server_name.rs b/src/server_name.rs index c503b56..6153e59 100644 --- a/src/server_name.rs +++ b/src/server_name.rs @@ -331,11 +331,15 @@ const fn validate(input: &[u8]) -> Result<(), InvalidDnsNameError> { match input.len() { 0 | 255.. => return Err(InvalidDnsNameError), - // This is a `const fn`, so we cannot use - // `Option::unwrap_or_else(|| unreachable!("bug in [u8]::len()")). - MAX_NAME_LENGTH_WITH_TRAILING_DOT if *input.last().unwrap() != b'.' => { - return Err(InvalidDnsNameError) - } + MAX_NAME_LENGTH_WITH_TRAILING_DOT => if let Some(lst) = input.last() { + if *lst != b'.' { + return Err(InvalidDnsNameError); + } + } else { + // Ideally this would be `unreachable`, but that can't happen until + // MSRV is updated to a version where it is `const`. + panic!("there is a bug in [u8]::len()"); + }, _ => (), } From ad30c10b9a951fd0af0d307f3248e74af0b6c565 Mon Sep 17 00:00:00 2001 From: philomathic_life <15947783+zacknewman@users.noreply.github.com> Date: Sun, 9 Feb 2025 10:52:50 -0700 Subject: [PATCH 4/4] easier to understand and added links to the relevant rfc sections --- src/server_name.rs | 50 ++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/server_name.rs b/src/server_name.rs index 6153e59..b7c2543 100644 --- a/src/server_name.rs +++ b/src/server_name.rs @@ -320,27 +320,43 @@ const fn validate(input: &[u8]) -> Result<(), InvalidDnsNameError> { use State::*; let mut state = Start; - /// "Labels must be 63 characters or less." + /// The maximum length of a label in wire format is 63 octets per + /// [RFC 1035 § 2.3.4](https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4). const MAX_LABEL_LENGTH: usize = 63; - /// The maximum lenght of a domain name is 253 when there is no trailing dot. - /// When there is a trailing dot, the maximum length is 254. This is true since - /// a domain with a trailing dot gets transformed into the same wire-format value - /// as one without. - const MAX_NAME_LENGTH_WITH_TRAILING_DOT: usize = 254; - - match input.len() { - 0 | 255.. => return Err(InvalidDnsNameError), - MAX_NAME_LENGTH_WITH_TRAILING_DOT => if let Some(lst) = input.last() { - if *lst != b'.' { - return Err(InvalidDnsNameError); + /// The maximum length of a domain in wire format is 255 octets per + /// [RFC 1035 § 2.3.4](https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4). + const MAX_NAME_LENGTH: usize = 255; + + match input.last() { + // Empty domains are not valid. + None => return Err(InvalidDnsNameError), + // `input` is a domain in _representation format_, but length requirments are based on the + // wire format. For representation formats that forbid the root domain (represented as either `b" "` or + // `b"."`), allow a trailing `b'.'`, and where each "character"/`u8` in a label is transformed to a `u8` + // (e.g., there are no "escape characters"); then the formula is simple: add `1` when there is a trailing + // `b'.'` otherwise add `2`. + // + // Examples + // + // * "ab.c" gets transformed to the wire format [0x02, 0x61, 0x62, 0x01, 0x63, 0x00]. As we see the length + // of the representation format is 4 and the length of the wire format is 6 and 4 + 2 = 6. + // * "ab.c." gets transformed to the wire format [0x02, 0x61, 0x62, 0x01, 0x63, 0x00]. As we see the length + // of the representation format is 5 and the length of the wire format is 6 and 5 + 1 = 6. + // + // Note when `input` is the root domain this calculation is incorrect; however we fail later anyway. + // Alternatively we could explicitly error here when `*lst` is `b' '` or `*lst` is `b'.'` and the length + // is `1`. We elect to defer to the parsing code below. + Some(lst) => match input.len().checked_add(if *lst == b'.' { 1 } else { 2 }) { + // `usize::MAX >= u16::MAX > u8::MAX = 255 = MAX_NAME_LENGTH`; thus we know `input` is invalid since + // it's too long. + None => return Err(InvalidDnsNameError), + Some(len) => { + if len > MAX_NAME_LENGTH { + return Err(InvalidDnsNameError); + } } - } else { - // Ideally this would be `unreachable`, but that can't happen until - // MSRV is updated to a version where it is `const`. - panic!("there is a bug in [u8]::len()"); }, - _ => (), } let mut idx = 0;