|
commit a64e5574e40e3e0819c82e35a7e3d2fa65febc73
|
|
Author: Willy Tarreau <w@1wt.eu>
|
|
Date: Fri Jan 11 19:38:25 2019 +0100
|
|
|
|
BUG/MAJOR: cache: fix confusion between zero and uninitialized cache key
|
|
|
|
The cache uses the first 32 bits of the uri's hash as the key to reference
|
|
the object in the cache. It makes a special case of the value zero to mean
|
|
that the object is not in the cache anymore. The problem is that when an
|
|
object hashes as zero, it's still inserted but the eb32_delete() call is
|
|
skipped, resulting in the object still being chained in the memory area
|
|
while the block has been reclaimed and used for something else. Then when
|
|
objects which were chained below it (techically any object since zero is
|
|
at the root) are deleted, the walk through the upper object may encounter
|
|
corrupted values where valid pointers were expected.
|
|
|
|
But while this should only happen statically once on 4 billion, the problem
|
|
gets worse when the cache-use conditions don't match the cache-store ones,
|
|
because cache-store runs with an uninitialized key, which can create objects
|
|
that will never be found by the lookup code, or worse, entries with a zero
|
|
key preventing eviction of the tree node and resulting in a crash. It's easy
|
|
to accidently end up on such a config because the request rules generally
|
|
can't be used to decide on the response :
|
|
|
|
http-request cache-use cache if { path_beg /images }
|
|
http-response cache-store cache
|
|
|
|
In this test, mixing traffic with /images/$RANDOM and /foo/$RANDOM will
|
|
result in random keys being inserted, some of them possibly being zero,
|
|
and crashes will quickly happen.
|
|
|
|
The fix consists in 1) always initializing the transaction's cache_hash
|
|
to zero, and 2) never storing a response for which the hash has not been
|
|
calculated, as indicated by the value zero.
|
|
|
|
It is worth noting that objects hashing as value zero will never be cached,
|
|
but given that there's only one chance among 4 billion that this happens,
|
|
this is totally harmless.
|
|
|
|
This fix must be backported to 1.9 and 1.8.
|
|
|
|
(cherry picked from commit c9036c00044a8d81561113886ecec9a9ce71bd3b)
|
|
Signed-off-by: Willy Tarreau <w@1wt.eu>
|
|
(cherry picked from commit 5a6279fcc16da479304bcabc1705e8653f274337)
|
|
Signed-off-by: William Lallemand <wlallemand@haproxy.org>
|
|
|
|
diff --git a/src/cache.c b/src/cache.c
|
|
index 667cede3..3d8ed241 100644
|
|
--- a/src/cache.c
|
|
+++ b/src/cache.c
|
|
@@ -400,7 +400,7 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
|
|
struct cache *cache = (struct cache *)rule->arg.act.p[0];
|
|
struct shared_context *shctx = shctx_ptr(cache);
|
|
struct cache_entry *object;
|
|
-
|
|
+ unsigned int key = *(unsigned int *)txn->cache_hash;
|
|
|
|
/* Don't cache if the response came from a cache */
|
|
if ((obj_type(s->target) == OBJ_TYPE_APPLET) &&
|
|
@@ -420,6 +420,10 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
|
|
if (txn->meth != HTTP_METH_GET)
|
|
goto out;
|
|
|
|
+ /* cache key was not computed */
|
|
+ if (!key)
|
|
+ goto out;
|
|
+
|
|
/* cache only 200 status code */
|
|
if (txn->status != 200)
|
|
goto out;
|
|
@@ -478,7 +482,7 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
|
|
|
|
cache_ctx->first_block = first;
|
|
|
|
- object->eb.key = (*(unsigned int *)&txn->cache_hash);
|
|
+ object->eb.key = key;
|
|
memcpy(object->hash, txn->cache_hash, sizeof(object->hash));
|
|
/* Insert the node later on caching success */
|
|
|
|
diff --git a/src/proto_http.c b/src/proto_http.c
|
|
index 7e4a8351..29a1083a 100644
|
|
--- a/src/proto_http.c
|
|
+++ b/src/proto_http.c
|
|
@@ -8210,6 +8210,7 @@ void http_init_txn(struct stream *s)
|
|
|
|
txn->flags = 0;
|
|
txn->status = -1;
|
|
+ *(unsigned int *)txn->cache_hash = 0;
|
|
|
|
txn->cookie_first_date = 0;
|
|
txn->cookie_last_date = 0;
|