BUG/MEDIUM: cache: use the correct time reference when comparing dates

The cache makes use of dates advertised by external components, such
as "last-modified" or "date". As such these are wall-clock dates, and
not internal dates. However, all comparisons are mistakenly made based
on the internal monotonic date which is designed to drift from the wall
clock one in order to catch up with stolen time (which can sometimes be
intense in VMs). As such after some run time some objects may fail to
validate or fail to expire depending on the direction of the drift. This
is particularly visible when applying an offset to the internal time to
force it to wrap soon after startup, as it will be shifted up to 49.7
days in the future depending on the current date; what happens in this
case is that the reg-test "cache_expires.vtc" fails on the 3rd test by
returning stale contents from the cache at the date of this commit.

It is really important that all external dates are compared against
"date" and not "now" for this reason.

This fix needs to be backported to all versions.

(cherry picked from commit 9b5d57dfd5047b167896e08128c029dc7d3615b8)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit c188486a2872a8fc6bd934b2fc1c421f068e18e0)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit eb039d10920a631a2946a9c1562c997b2a84700f)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit ee0e12c55fb2853055fda7eb0694a267f4549265)
Signed-off-by: Willy Tarreau <w@1wt.eu>
diff --git a/src/cache.c b/src/cache.c
index ad4e715..ecf62fb 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -136,7 +136,7 @@
 struct cache_entry {
 	unsigned int complete;    /* An entry won't be valid until complete is not null. */
 	unsigned int latest_validation;     /* latest validation date */
-	unsigned int expire;      /* expiration date */
+	unsigned int expire;      /* expiration date (wall clock time) */
 	unsigned int age;         /* Origin server "Age" header value */
 
 	struct eb32_node eb;     /* ebtree node used to hold the cache object */
@@ -188,7 +188,7 @@
 	if (memcmp(entry->hash, hash, sizeof(entry->hash)))
 		return NULL;
 
-	if (entry->expire > now.tv_sec) {
+	if (entry->expire > date.tv_sec) {
 		return entry;
 	} else {
 		delete_entry(entry);
@@ -249,7 +249,7 @@
 		 * when we find them. Calling delete_entry would be too costly
 		 * so we simply call eb32_delete. The secondary_entry count will
 		 * be updated when we try to insert a new entry to this list. */
-		if (entry->expire <= now.tv_sec) {
+		if (entry->expire <= date.tv_sec) {
 			eb32_delete(&entry->eb);
 			entry->eb.key = 0;
 		}
@@ -258,7 +258,7 @@
 	}
 
 	/* Expired entry */
-	if (entry && entry->expire <= now.tv_sec) {
+	if (entry && entry->expire <= date.tv_sec) {
 		eb32_delete(&entry->eb);
 		entry->eb.key = 0;
 		entry = NULL;
@@ -283,7 +283,7 @@
 	while (prev) {
 		entry = container_of(prev, struct cache_entry, eb);
 		prev = eb32_prev_dup(prev);
-		if (entry->expire <= now.tv_sec) {
+		if (entry->expire <= date.tv_sec) {
 			eb32_delete(&entry->eb);
 			entry->eb.key = 0;
 		}
@@ -315,7 +315,7 @@
 	struct eb32_node *prev = NULL;
 	struct cache_entry *entry = NULL;
 	unsigned int entry_count = 0;
-	unsigned int last_clear_ts = now.tv_sec;
+	unsigned int last_clear_ts = date.tv_sec;
 
 	struct eb32_node *node = eb32_insert(&cache->entries, &new_entry->eb);
 
@@ -338,7 +338,7 @@
 			 * space. In order to avoid going over the same list too
 			 * often, we first check the timestamp of the last check
 			 * performed. */
-			if (last_clear_ts == now.tv_sec) {
+			if (last_clear_ts == date.tv_sec) {
 				/* Too many entries for this primary key, clear the
 				 * one that was inserted. */
 				eb32_delete(node);
@@ -351,7 +351,7 @@
 				/* Still too many entries for this primary key, delete
 				 * the newly inserted one. */
 				entry = container_of(prev, struct cache_entry, eb);
-				entry->last_clear_ts = now.tv_sec;
+				entry->last_clear_ts = date.tv_sec;
 				eb32_delete(node);
 				node->key = 0;
 				return NULL;
@@ -811,8 +811,8 @@
 				/* A request having an expiring date earlier
 				 * than the current date should be considered as
 				 * stale. */
-				expires = (expires_val >= now.tv_sec) ?
-					(expires_val - now.tv_sec) : 0;
+				expires = (expires_val >= date.tv_sec) ?
+					(expires_val - date.tv_sec) : 0;
 			}
 			else {
 				/* Following RFC 7234#5.3, an invalid date
@@ -886,7 +886,7 @@
 	/* Fallback on the current time if no "Last-Modified" or "Date" header
 	 * was found. */
 	if (!last_modified)
-		last_modified = now.tv_sec;
+		last_modified = date.tv_sec;
 
 	return last_modified;
 }
@@ -1120,7 +1120,7 @@
 	 * is set by the end of this function (in case of concurrent accesses to
 	 * the same resource). This way the second access will find an existing
 	 * but not yet usable entry in the tree and will avoid storing its data. */
-	object->expire = now.tv_sec + 2;
+	object->expire = date.tv_sec + 2;
 
 	memcpy(object->hash, txn->cache_hash, sizeof(object->hash));
 	if (vary_signature)
@@ -1224,8 +1224,8 @@
 	if (cache_ctx) {
 		cache_ctx->first_block = first;
 		/* store latest value and expiration time */
-		object->latest_validation = now.tv_sec;
-		object->expire = now.tv_sec + effective_maxage;
+		object->latest_validation = date.tv_sec;
+		object->expire = date.tv_sec + effective_maxage;
 		return ACT_RET_CONT;
 	}
 
@@ -1416,7 +1416,7 @@
 	char *end;
 
 	chunk_reset(&trash);
-	age = MAX(0, (int)(now.tv_sec - cache_ptr->latest_validation)) + cache_ptr->age;
+	age = MAX(0, (int)(date.tv_sec - cache_ptr->latest_validation)) + cache_ptr->age;
 	if (unlikely(age > CACHE_ENTRY_MAX_AGE))
 		age = CACHE_ENTRY_MAX_AGE;
 	end = ultoa_o(age, b_head(&trash), b_size(&trash));
@@ -2602,13 +2602,13 @@
 			entry = container_of(node, struct cache_entry, eb);
 			next_key = node->key + 1;
 
-			if (entry->expire > now.tv_sec) {
+			if (entry->expire > date.tv_sec) {
 				chunk_printf(&trash, "%p hash:%u vary:0x", entry, read_u32(entry->hash));
 				for (i = 0; i < HTTP_CACHE_SEC_KEY_LEN; ++i)
 					chunk_appendf(&trash, "%02x", (unsigned char)entry->secondary_key[i]);
 				chunk_appendf(&trash, " size:%u (%u blocks), refcount:%u, expire:%d\n",
 					      block_ptr(entry)->len, block_ptr(entry)->block_count,
-					      block_ptr(entry)->refcount, entry->expire - (int)now.tv_sec);
+					      block_ptr(entry)->refcount, entry->expire - (int)date.tv_sec);
 			} else {
 				/* time to remove that one */
 				delete_entry(entry);