Skip to content

hsts: match complete directive names - #22643

Closed
arhxam wants to merge 1 commit into
curl:masterfrom
arhxam:hsts-directive-token-boundaries
Closed

arhxam wants to merge 1 commit into
curl:masterfrom
arhxam:hsts-directive-token-boundaries

Conversation

@arhxam

@arhxam arhxam commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

HSTS directive recognition used case-insensitive prefix comparisons. This made an unknown token such as includeSubDomainsExtra enable subdomain policy, while max-age-extra=1; max-age=60 caused the later valid directive to be ignored.

RFC 6797 defines a directive name as a complete HTTP token and requires unknown directives to be ignored while recognized directives are processed. Check the character following each known name so token continuations take the unknown-directive path.

The unit regression covers both known directive prefixes without changing the cache fixture. Both cases fail on the unpatched parser and pass with this change.

This bug was identified during AI-assisted source review and then independently reproduced, traced, and verified against current master and RFC 6797 section 6.1.

Tests:

  • make test TFLAGS='1660'
  • make test TFLAGS='-j4' (1,671/1,671 executed tests passed; 2,063 considered)
  • make checksrc
  • maintainer/debug build with strict compiler warnings
  • git diff --check

Ref: https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1

Treat longer HTTP tokens that begin with known names as unknown
directives. This prevents lookalike names from enabling
includeSubDomains and lets later recognized directives be processed.

Ref: https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1
@github-actions github-actions Bot added the tests label Aug 23, 2026
@bagder

bagder commented Aug 23, 2026

Copy link
Copy Markdown
Member

Thanks for spotting the problem and for your fix. Looking at the patch though, I believe this can be done even nicer by using our available parsing functions in a better way without the strange extra function:

From a1e836024bf2e5014c9d5d436600450d5297209b Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Sun, 23 Aug 2026 16:46:21 +0200
Subject: [PATCH] hsts: only match the exact strings

---
 lib/hsts.c | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/lib/hsts.c b/lib/hsts.c
index b8745a5195..7a4902c4d8 100644
--- a/lib/hsts.c
+++ b/lib/hsts.c
@@ -221,19 +221,21 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname,
     /* "explicit IP address identification of all forms is excluded."
        / RFC 6797 */
     return CURLE_OK;
 
   do {
+    struct Curl_str word;
     curlx_str_passblanks(&p);
-    if(curl_strnequal("max-age", p, 7)) {
+    if(curlx_str_cspn(&p, &word, ";=\r\n \t"))
+      break;
+    if(curlx_str_casecompare(&word, "max-age")) {
       bool quoted = FALSE;
       int rc;
 
       if(gotma)
         return CURLE_BAD_FUNCTION_ARGUMENT;
 
-      p += 7;
       curlx_str_passblanks(&p);
       if(curlx_str_single(&p, '='))
         return CURLE_BAD_FUNCTION_ARGUMENT;
       curlx_str_passblanks(&p);
 
@@ -252,23 +254,16 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname,
           return CURLE_BAD_FUNCTION_ARGUMENT;
         p++;
       }
       gotma = TRUE;
     }
-    else if(curl_strnequal("includesubdomains", p, 17)) {
+    else if(curlx_str_casecompare(&word, "includesubdomains")) {
       if(gotinc)
         return CURLE_BAD_FUNCTION_ARGUMENT;
       subdomains = TRUE;
-      p += 17;
       gotinc = TRUE;
     }
-    else {
-      /* unknown directive, do a lame attempt to skip */
-      while(*p && (*p != ';'))
-        p++;
-    }
-
     curlx_str_passblanks(&p);
     if(*p == ';')
       p++;
   } while(*p);
 
-- 
2.55.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

2 participants