Prevent query cache update under shared lock.
authorTatsuo Ishii <ishii@sraoss.co.jp>
Wed, 14 Jun 2023 02:17:38 +0000 (11:17 +0900)
committerTatsuo Ishii <ishii@sraoss.co.jp>
Wed, 14 Jun 2023 02:17:38 +0000 (11:17 +0900)
In https://www.pgpool.net/mantisbt/view.php?id=795 it was pointed out
that expired query cache entry can be modified under shared lock. This
could cause shared memory corruption used by query cache. In order to
fix this, we temporarily release the shared lock and then acquire an
exclusive lock before modifying the cache entry. This could create a
window and we need to get the cache entry meta data again to make sure
that the meta data has not been modified by someone else.

Back-patch to V4.4 stable where shared locking for query cache was
introduced.

src/query_cache/pool_memqcache.c

index 89510f73ee05cd398962d885bda9bac630274830..c1ae800d8fe4f5140f773f52fd8105be75666c2c 100644 (file)
@@ -2734,12 +2734,37 @@ static POOL_CACHEID * pool_find_item_on_shmem_cache(POOL_QUERY_HASH * query_hash
                now = time(NULL);
                if (now > (cih->timestamp + cih->expire))
                {
-                       ereport(DEBUG1,
-                                       (errmsg("memcache finding item"),
-                                        errdetail("cache expired: now: %ld timestamp: %ld",
-                                                          now, cih->timestamp + cih->expire)));
-                       pool_delete_item_shmem_cache(c);
-                       return NULL;
+                       /*
+                        * We need to acquire an exclusive lock before removing the cache
+                        * entry.  Since a lock escalation from shared lock to exclusive
+                        * lock is not supported, we need to release the lock then acquire
+                        * an exclusive lock.
+                        */
+                       pool_shmem_unlock();
+                       pool_shmem_lock(POOL_MEMQ_EXCLUSIVE_LOCK);
+                       /*
+                        * There is a window between pool_shmem_unlock() and
+                        * pool_shmem_lock().  We need to get POOL_CACHEID and
+                        * POOL_CACHE_ITEM_HEADER again because they could have been
+                        * modified by someone else.
+                        */
+                       c = pool_hash_search(query_hash);
+                       if (!c)
+                       {
+                               return NULL;
+                       }
+
+                       cih = item_header(block_address(c->blockid), c->itemid);
+                       now = time(NULL);
+                       if (now > (cih->timestamp + cih->expire))
+                       {
+                               ereport(DEBUG1,
+                                               (errmsg("memcache finding item"),
+                                                errdetail("cache expired: now: %ld timestamp: %ld",
+                                                                  now, cih->timestamp + cih->expire)));
+                               pool_delete_item_shmem_cache(c);
+                               return NULL;
+                       }
                }
        }