commit f27f2fd061cb3b86cb48c972a217d06ee72ccd3e
parent 1abd14d4b9c9643ef9b14423e8cb633fc9d07c19
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 3 Aug 2026 21:11:13 +0200
bound number of templates we cache
Diffstat:
1 file changed, 46 insertions(+), 0 deletions(-)
diff --git a/src/backend/paivana-httpd_templates.c b/src/backend/paivana-httpd_templates.c
@@ -45,6 +45,17 @@ struct Template;
/**
+ * Maximum number of rendered paywall responses we cache per template.
+ * The cache is keyed on the client-supplied Accept-Language /
+ * Accept-Encoding headers; without a bound an attacker could send
+ * unlimited distinct header values and grow the cache without limit
+ * (memory-exhaustion DoS on the cheap pre-payment path). When the
+ * cap is reached we evict the oldest entry (FIFO).
+ */
+#define MAX_RESPONSE_CACHE_ENTRIES 128
+
+
+/**
* Entry in the cache of responses for a given template.
*/
struct ResponseCacheEntry
@@ -135,6 +146,11 @@ struct Template
struct TALER_MERCHANT_GetPrivateTemplateHandle *gt;
/**
+ * Number of entries in the template cache starting at @e rce_head.
+ */
+ unsigned int rce_length;
+
+ /**
* Kept in a DLL.
*/
struct ResponseCacheEntry *rce_head;
@@ -267,9 +283,21 @@ load_paywall (struct MHD_Connection *conn,
pos->lang)) &&
(eq (ae,
pos->ae) ) )
+ {
+ if (t->rce_head != pos)
+ {
+ /* Hit, move pos to head of DLL for proper LRU eviction */
+ GNUNET_CONTAINER_DLL_remove (t->rce_head,
+ t->rce_tail,
+ pos);
+ GNUNET_CONTAINER_DLL_insert (t->rce_head,
+ t->rce_tail,
+ pos);
+ }
return MHD_queue_response (conn,
pos->http_status,
pos->paywall);
+ }
}
{
@@ -354,6 +382,21 @@ load_paywall (struct MHD_Connection *conn,
{
struct ResponseCacheEntry *rce;
+ while (t->rce_length > MAX_RESPONSE_CACHE_ENTRIES)
+ {
+ /* Evict the oldest entry (inserted at head, so tail is oldest). */
+ struct ResponseCacheEntry *old = t->rce_tail;
+
+ GNUNET_CONTAINER_DLL_remove (t->rce_head,
+ t->rce_tail,
+ old);
+ GNUNET_assert (t->rce_length > 0);
+ t->rce_length--;
+ MHD_destroy_response (old->paywall);
+ GNUNET_free (old->ae);
+ GNUNET_free (old->lang);
+ GNUNET_free (old);
+ }
rce = GNUNET_new (struct ResponseCacheEntry);
if (NULL != lang)
rce->lang = GNUNET_strdup (lang);
@@ -361,6 +404,7 @@ load_paywall (struct MHD_Connection *conn,
rce->ae = GNUNET_strdup (ae);
rce->paywall = reply;
rce->http_status = http_status;
+ t->rce_length++;
GNUNET_CONTAINER_DLL_insert (t->rce_head,
t->rce_tail,
rce);
@@ -693,6 +737,8 @@ PAIVANA_HTTPD_unload_templates ()
{
struct ResponseCacheEntry *rce = t->rce_head;
+ GNUNET_assert (t->rce_length > 0);
+ t->rce_length--;
GNUNET_CONTAINER_DLL_remove (t->rce_head,
t->rce_tail,
rce);