Rebuild the query for recalculating min/max after search_path change
Cached plans for recalculating min/max values are built using pg_ivm_get_viewdef() that returns the view definition query text. Therefore, if the search_path is changed, the query text is analyzed again by SPI, and tables or functions in a wrong schema could be referenced in the plan. To fix this, we check whether the search_path is still the same as when we made the cached plan and, if it isn't, we rebuild the query text. CVE-2023-23554
This commit is contained in:
parent
14bb84c599
commit
aaaa6cff66
3 changed files with 67 additions and 2 deletions
|
|
@ -464,6 +464,40 @@ SELECT * FROM mv_ivm_min_max;
|
||||||
| | 0 | 0 | 0
|
| | 0 | 0 | 0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
-- Test MIN/MAX after search_path change
|
||||||
|
BEGIN;
|
||||||
|
SELECT create_immv('mv_ivm_min', 'SELECT MIN(j) FROM mv_base_a');
|
||||||
|
create_immv
|
||||||
|
-------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM mv_ivm_min ORDER BY 1,2,3;
|
||||||
|
min | __ivm_count_min__ | __ivm_count__
|
||||||
|
-----+-------------------+---------------
|
||||||
|
10 | 5 | 5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE SCHEMA myschema;
|
||||||
|
GRANT ALL ON SCHEMA myschema TO public;
|
||||||
|
CREATE TABLE myschema.mv_base_a (j int);
|
||||||
|
INSERT INTO myschema.mv_base_a VALUES (1);
|
||||||
|
DELETE FROM mv_base_a WHERE (i,j) = (1,10);
|
||||||
|
SELECT * FROM mv_ivm_min ORDER BY 1,2,3;
|
||||||
|
min | __ivm_count_min__ | __ivm_count__
|
||||||
|
-----+-------------------+---------------
|
||||||
|
20 | 4 | 4
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SET search_path TO myschema,public,pg_catalog;
|
||||||
|
DELETE FROM public.mv_base_a WHERE (i,j) = (2,20);
|
||||||
|
SELECT * FROM mv_ivm_min ORDER BY 1,2,3;
|
||||||
|
min | __ivm_count_min__ | __ivm_count__
|
||||||
|
-----+-------------------+---------------
|
||||||
|
30 | 3 | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
-- aggregate views with column names specified
|
-- aggregate views with column names specified
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
|
||||||
17
matview.c
17
matview.c
|
|
@ -78,6 +78,9 @@ typedef struct MV_QueryHashEntry
|
||||||
{
|
{
|
||||||
MV_QueryKey key;
|
MV_QueryKey key;
|
||||||
SPIPlanPtr plan;
|
SPIPlanPtr plan;
|
||||||
|
OverrideSearchPath *search_path; /* search_path used for parsing
|
||||||
|
* and planning */
|
||||||
|
|
||||||
} MV_QueryHashEntry;
|
} MV_QueryHashEntry;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -2807,18 +2810,27 @@ mv_FetchPreparedPlan(MV_QueryKey *key)
|
||||||
*
|
*
|
||||||
* CAUTION: this check is only trustworthy if the caller has already
|
* CAUTION: this check is only trustworthy if the caller has already
|
||||||
* locked both materialized views and base tables.
|
* locked both materialized views and base tables.
|
||||||
|
*
|
||||||
|
* Also, check whether the search_path is still the same as when we made it.
|
||||||
|
* If it isn't, we need to rebuild the query text because the result of
|
||||||
|
* pg_ivm_get_viewdef() will change.
|
||||||
*/
|
*/
|
||||||
plan = entry->plan;
|
plan = entry->plan;
|
||||||
if (plan && SPI_plan_is_valid(plan))
|
if (plan && SPI_plan_is_valid(plan) &&
|
||||||
|
OverrideSearchPathMatchesCurrent(entry->search_path))
|
||||||
return plan;
|
return plan;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Otherwise we might as well flush the cached plan now, to free a little
|
* Otherwise we might as well flush the cached plan now, to free a little
|
||||||
* memory space before we make a new one.
|
* memory space before we make a new one.
|
||||||
*/
|
*/
|
||||||
entry->plan = NULL;
|
|
||||||
if (plan)
|
if (plan)
|
||||||
SPI_freeplan(plan);
|
SPI_freeplan(plan);
|
||||||
|
if (entry->search_path)
|
||||||
|
pfree(entry->search_path);
|
||||||
|
|
||||||
|
entry->plan = NULL;
|
||||||
|
entry->search_path = NULL;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -2849,6 +2861,7 @@ mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
|
||||||
HASH_ENTER, &found);
|
HASH_ENTER, &found);
|
||||||
Assert(!found || entry->plan == NULL);
|
Assert(!found || entry->plan == NULL);
|
||||||
entry->plan = plan;
|
entry->plan = plan;
|
||||||
|
entry->search_path = GetOverrideSearchPath(TopMemoryContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,24 @@ DELETE FROM mv_base_a;
|
||||||
SELECT * FROM mv_ivm_min_max;
|
SELECT * FROM mv_ivm_min_max;
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- Test MIN/MAX after search_path change
|
||||||
|
BEGIN;
|
||||||
|
SELECT create_immv('mv_ivm_min', 'SELECT MIN(j) FROM mv_base_a');
|
||||||
|
SELECT * FROM mv_ivm_min ORDER BY 1,2,3;
|
||||||
|
|
||||||
|
CREATE SCHEMA myschema;
|
||||||
|
GRANT ALL ON SCHEMA myschema TO public;
|
||||||
|
CREATE TABLE myschema.mv_base_a (j int);
|
||||||
|
INSERT INTO myschema.mv_base_a VALUES (1);
|
||||||
|
|
||||||
|
DELETE FROM mv_base_a WHERE (i,j) = (1,10);
|
||||||
|
SELECT * FROM mv_ivm_min ORDER BY 1,2,3;
|
||||||
|
|
||||||
|
SET search_path TO myschema,public,pg_catalog;
|
||||||
|
DELETE FROM public.mv_base_a WHERE (i,j) = (2,20);
|
||||||
|
SELECT * FROM mv_ivm_min ORDER BY 1,2,3;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
-- aggregate views with column names specified
|
-- aggregate views with column names specified
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT create_immv('mv_ivm_agg(a)', 'SELECT i, SUM(j) FROM mv_base_a GROUP BY i');
|
SELECT create_immv('mv_ivm_agg(a)', 'SELECT i, SUM(j) FROM mv_base_a GROUP BY i');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue