]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
HSTS: cap the list at 1,000 entries
authorDaniel Stenberg <daniel@haxx.se>
Wed, 1 Apr 2026 08:24:06 +0000 (10:24 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 1 Apr 2026 12:38:41 +0000 (14:38 +0200)
Avoid never-ending growth.

When adding more entries, it now deletes the first entry in the list,
which is the oldest added entry still held in memory. I decided to avoid
a Least Recently Used concept as I suspect with a list with this many
entries most entries have not been used, and we don't save the timestamp
of recent use anyway.

The net effect might (no matter what) be that the removed entry might
feel a bit "random" in the eyes of the user.

Verify with test 1674

Ref #21183
Closes #21190

lib/hsts.c
lib/hsts.h
tests/data/Makefile.am
tests/data/test1674 [new file with mode: 0644]
tests/unit/Makefile.inc
tests/unit/unit1674.c [new file with mode: 0644]

index c00481fe605ef09907d4c71cde9d76830534f409..ff21ad98d5bd62e9c2da3df590b10b9921cef7f0 100644 (file)
@@ -91,6 +91,20 @@ void Curl_hsts_cleanup(struct hsts **hp)
   }
 }
 
+/* append the new entry to the list after possibly removing an old entry
+   first */
+static void hsts_append(struct hsts *h, struct stsentry *sts)
+{
+  if(Curl_llist_count(&h->list) == MAX_HSTS_ENTRIES) {
+    /* It's full. Remove the first entry in the list */
+    struct Curl_llist_node *e = Curl_llist_head(&h->list);
+    struct stsentry *oldsts = Curl_node_elem(e);
+    Curl_node_remove(e);
+    hsts_free(oldsts);
+  }
+  Curl_llist_append(&h->list, sts, &sts->node);
+}
+
 static CURLcode hsts_create(struct hsts *h,
                             const char *hostname,
                             size_t hlen,
@@ -111,7 +125,7 @@ static CURLcode hsts_create(struct hsts *h,
     memcpy(sts->host, hostname, hlen);
     sts->expires = expires;
     sts->includeSubDomains = subdomains;
-    Curl_llist_append(&h->list, sts, &sts->node);
+    hsts_append(h, sts);
   }
   return CURLE_OK;
 }
index e0f6363bb355072fdc5a92289c48ce806bf60829..6bd6cdb9a5f7bc078957c972f57a9a9f7527e32e 100644 (file)
@@ -28,6 +28,8 @@
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS)
 #include "llist.h"
 
+#define MAX_HSTS_ENTRIES 1000
+
 #if defined(DEBUGBUILD) || defined(UNITTESTS)
 extern time_t deltatime;
 #endif
index c3496b133f37cf7612010a19725e41b4cbd8b8d2..0256d0b6fce8a2129d83d669d5b89743eba33bf8 100644 (file)
@@ -223,7 +223,7 @@ test1650 test1651 test1652 test1653 test1654 test1655 test1656 test1657 \
 test1658 test1659 test1660 test1661 test1662 test1663 test1664 test1665 \
 test1666 test1667 test1668 test1669 \
 \
-test1670 test1671 test1672 test1673 \
+test1670 test1671 test1672 test1673 test1674 \
 \
 test1680 test1681 test1682 test1683 test1684 test1685 \
 \
diff --git a/tests/data/test1674 b/tests/data/test1674
new file mode 100644 (file)
index 0000000..00b4408
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+unittest
+HSTS
+</keywords>
+</info>
+
+<client>
+<features>
+unittest
+HSTS
+</features>
+
+# This date is exactly "20190124 22:34:21" UTC
+<setenv>
+CURL_TIME=1548369261
+</setenv>
+<name>
+HSTS load more than 1,000 entries from file
+</name>
+
+# test 1674 renders the input file itself, then reads it
+<command>
+%LOGDIR/hsts%TESTNUMBER
+</command>
+</client>
+
+<verify>
+<stdout>
+Number of entries: 1000
+</stdout>
+<limits>
+Allocations: 1100
+</limits>
+</verify>
+</testcase>
index d8b2ed2d2edc9eafa238e6dd96c5a13a40cf62e4..0ab45a4df1dbd96bd8f65cdadae7c1e1ccde1741 100644 (file)
@@ -42,6 +42,7 @@ TESTS_C = \
   unit1650.c unit1651.c unit1652.c unit1653.c unit1654.c unit1655.c unit1656.c \
   unit1657.c unit1658.c            unit1660.c unit1661.c unit1663.c unit1664.c \
              unit1666.c unit1667.c unit1668.c unit1669.c \
+  unit1674.c \
   unit1979.c unit1980.c \
   unit2600.c unit2601.c unit2602.c unit2603.c unit2604.c unit2605.c \
   unit3200.c                                             unit3205.c \
diff --git a/tests/unit/unit1674.c b/tests/unit/unit1674.c
new file mode 100644 (file)
index 0000000..0491db9
--- /dev/null
@@ -0,0 +1,82 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "unitcheck.h"
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS)
+
+#include "urldata.h"
+#include "hsts.h"
+
+/* create a HSTS file with lots of unique host names all using the same
+   fixed expire time */
+static void render_unit1674(const char *file)
+{
+  FILE *f = curlx_fopen(file, FOPEN_WRITETEXT);
+  size_t i;
+  if(!f)
+    return;
+  for(i = 0; i < (MAX_HSTS_ENTRIES + 5); i++) {
+    curl_mfprintf(f, "host%zu.readfrom.example \"20211001 04:47:41\"\n", i);
+  }
+  curlx_fclose(f);
+}
+
+static CURLcode test_unit1674(const char *arg)
+{
+  UNITTEST_BEGIN_SIMPLE
+
+  struct hsts *h = Curl_hsts_init();
+  CURL *easy;
+  char savename[256];
+
+  abort_unless(h, "Curl_hsts_init()");
+
+  render_unit1674(arg);
+
+  curl_global_init(CURL_GLOBAL_ALL);
+  easy = curl_easy_init();
+  if(!easy) {
+    Curl_hsts_cleanup(&h);
+    abort_unless(easy, "curl_easy_init()");
+  }
+
+  Curl_hsts_loadfile(easy, h, arg);
+
+  curl_mprintf("Number of entries: %zu\n", Curl_llist_count(&h->list));
+
+  curl_msnprintf(savename, sizeof(savename), "%s.save", arg);
+  (void)Curl_hsts_save(easy, h, savename);
+  Curl_hsts_cleanup(&h);
+  curl_easy_cleanup(easy);
+
+  UNITTEST_END(curl_global_cleanup())
+}
+#else
+static CURLcode test_unit1674(const char *arg)
+{
+  UNITTEST_BEGIN_SIMPLE
+  puts("nothing to do when HTTP or HSTS are disabled");
+  UNITTEST_END_SIMPLE
+}
+#endif