Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit f50e888

Browse files
committed
Fix permission checks on constraint violation errors on partitions.
If a cross-partition UPDATE violates a constraint on the target partition, and the columns in the new partition are in different physical order than in the parent, the error message can reveal columns that the user does not have SELECT permission on. A similar bug was fixed earlier in commit 804b6b6. The cause of the bug is that the callers of the ExecBuildSlotValueDescription() function got confused when constructing the list of modified columns. If the tuple was routed from a parent, we converted the tuple to the parent's format, but the list of modified columns was grabbed directly from the child's RTE entry. ExecUpdateLockMode() had a similar issue. That lead to confusion on which columns are key columns, leading to wrong tuple lock being taken on tables referenced by foreign keys, when a row is updated with INSERT ON CONFLICT UPDATE. A new isolation test is added for that corner case. With this patch, the ri_RangeTableIndex field is no longer set for partitions that don't have an entry in the range table. Previously, it was set to the RTE entry of the parent relation, but that was confusing. NOTE: This modifies the ResultRelInfo struct, replacing the ri_PartitionRoot field with ri_RootResultRelInfo. That's a bit risky to backpatch, because it breaks any extensions accessing the field. The change that ri_RangeTableIndex is not set for partitions could potentially break extensions, too. The ResultRelInfos are visible to FDWs at least, and this patch required small changes to postgres_fdw. Nevertheless, this seem like the least bad option. I don't think these fields widely used in extensions; I don't think there are FDWs out there that uses the FDW "direct update" API, other than postgres_fdw. If there is, you will get a compilation error, so hopefully it is caught quickly. Backpatch to 11, where support for both cross-partition UPDATEs, and unique indexes on partitioned tables, were added. Reviewed-by: Amit Langote Security: CVE-2021-3393
1 parent e474abd commit f50e888

File tree

17 files changed

+371
-88
lines changed

17 files changed

+371
-88
lines changed

contrib/postgres_fdw/postgres_fdw.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,7 +1934,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
19341934
PgFdwModifyState *fmstate;
19351935
ModifyTable *plan = castNode(ModifyTable, mtstate->ps.plan);
19361936
EState *estate = mtstate->ps.state;
1937-
Index resultRelation = resultRelInfo->ri_RangeTableIndex;
1937+
Index resultRelation;
19381938
Relation rel = resultRelInfo->ri_RelationDesc;
19391939
RangeTblEntry *rte;
19401940
TupleDesc tupdesc = RelationGetDescr(rel);
@@ -1985,17 +1985,20 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
19851985
}
19861986

19871987
/*
1988-
* If the foreign table is a partition, we need to create a new RTE
1988+
* If the foreign table is a partition that doesn't have a corresponding
1989+
* RTE entry, we need to create a new RTE
19891990
* describing the foreign table for use by deparseInsertSql and
19901991
* create_foreign_modify() below, after first copying the parent's RTE and
19911992
* modifying some fields to describe the foreign partition to work on.
19921993
* However, if this is invoked by UPDATE, the existing RTE may already
19931994
* correspond to this partition if it is one of the UPDATE subplan target
19941995
* rels; in that case, we can just use the existing RTE as-is.
19951996
*/
1996-
rte = exec_rt_fetch(resultRelation, estate);
1997-
if (rte->relid != RelationGetRelid(rel))
1997+
if (resultRelInfo->ri_RangeTableIndex == 0)
19981998
{
1999+
ResultRelInfo *rootResultRelInfo = resultRelInfo->ri_RootResultRelInfo;
2000+
2001+
rte = exec_rt_fetch(rootResultRelInfo->ri_RangeTableIndex, estate);
19992002
rte = copyObject(rte);
20002003
rte->relid = RelationGetRelid(rel);
20012004
rte->relkind = RELKIND_FOREIGN_TABLE;
@@ -2007,8 +2010,15 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
20072010
* Vars contained in those expressions.
20082011
*/
20092012
if (plan && plan->operation == CMD_UPDATE &&
2010-
resultRelation == plan->rootRelation)
2013+
rootResultRelInfo->ri_RangeTableIndex == plan->rootRelation)
20112014
resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
2015+
else
2016+
resultRelation = rootResultRelInfo->ri_RangeTableIndex;
2017+
}
2018+
else
2019+
{
2020+
resultRelation = resultRelInfo->ri_RangeTableIndex;
2021+
rte = exec_rt_fetch(resultRelation, estate);
20122022
}
20132023

20142024
/* Construct the SQL command string. */

src/backend/access/common/tupconvert.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,59 @@ execute_attr_map_slot(AttrNumber *attrMap,
493493
return out_slot;
494494
}
495495

496+
/*
497+
* Perform conversion of bitmap of columns according to the map.
498+
*
499+
* The input and output bitmaps are offset by
500+
* FirstLowInvalidHeapAttributeNumber to accommodate system cols, like the
501+
* column-bitmaps in RangeTblEntry.
502+
*/
503+
Bitmapset *
504+
execute_attr_map_cols(Bitmapset *in_cols, TupleConversionMap *map)
505+
{
506+
AttrNumber *attrMap = map->attrMap;
507+
int maplen = map->outdesc->natts;
508+
Bitmapset *out_cols;
509+
int out_attnum;
510+
511+
/* fast path for the common trivial case */
512+
if (in_cols == NULL)
513+
return NULL;
514+
515+
/*
516+
* For each output column, check which input column it corresponds to.
517+
*/
518+
out_cols = NULL;
519+
520+
for (out_attnum = FirstLowInvalidHeapAttributeNumber + 1;
521+
out_attnum <= maplen;
522+
out_attnum++)
523+
{
524+
int in_attnum;
525+
526+
if (out_attnum < 0)
527+
{
528+
/* System column. No mapping. */
529+
in_attnum = out_attnum;
530+
}
531+
else if (out_attnum == 0)
532+
continue;
533+
else
534+
{
535+
/* normal user column */
536+
in_attnum = attrMap[out_attnum - 1];
537+
538+
if (in_attnum == 0)
539+
continue;
540+
}
541+
542+
if (bms_is_member(in_attnum - FirstLowInvalidHeapAttributeNumber, in_cols))
543+
out_cols = bms_add_member(out_cols, out_attnum - FirstLowInvalidHeapAttributeNumber);
544+
}
545+
546+
return out_cols;
547+
}
548+
496549
/*
497550
* Free a TupleConversionMap structure.
498551
*/

src/backend/commands/copy.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,6 +2858,7 @@ CopyFrom(CopyState cstate)
28582858
mtstate->ps.state = estate;
28592859
mtstate->operation = CMD_INSERT;
28602860
mtstate->resultRelInfo = estate->es_result_relations;
2861+
mtstate->rootResultRelInfo = estate->es_result_relations;
28612862

28622863
if (resultRelInfo->ri_FdwRoutine != NULL &&
28632864
resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)

src/backend/commands/explain.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3210,7 +3210,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
32103210
/* Should we explicitly label target relations? */
32113211
labeltargets = (mtstate->mt_nplans > 1 ||
32123212
(mtstate->mt_nplans == 1 &&
3213-
mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
3213+
mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation));
32143214

32153215
if (labeltargets)
32163216
ExplainOpenGroup("Target Tables", "Target Tables", false, es);

src/backend/commands/trigger.c

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,6 @@ int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;
6969
/* How many levels deep into trigger execution are we? */
7070
static int MyTriggerDepth = 0;
7171

72-
/*
73-
* Note that similar macros also exist in executor/execMain.c. There does not
74-
* appear to be any good header to put them into, given the structures that
75-
* they use, so we let them be duplicated. Be sure to update all if one needs
76-
* to be changed, however.
77-
*/
78-
#define GetAllUpdatedColumns(relinfo, estate) \
79-
(bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \
80-
exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols))
81-
8272
/* Local function prototypes */
8373
static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid);
8474
static void SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger);
@@ -2928,7 +2918,10 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
29282918
CMD_UPDATE))
29292919
return;
29302920

2931-
updatedCols = GetAllUpdatedColumns(relinfo, estate);
2921+
/* statement-level triggers operate on the parent table */
2922+
Assert(relinfo->ri_RootResultRelInfo == NULL);
2923+
2924+
updatedCols = ExecGetAllUpdatedCols(relinfo, estate);
29322925

29332926
LocTriggerData.type = T_TriggerData;
29342927
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
@@ -2974,10 +2967,13 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
29742967
{
29752968
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
29762969

2970+
/* statement-level triggers operate on the parent table */
2971+
Assert(relinfo->ri_RootResultRelInfo == NULL);
2972+
29772973
if (trigdesc && trigdesc->trig_update_after_statement)
29782974
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
29792975
false, NULL, NULL, NIL,
2980-
GetAllUpdatedColumns(relinfo, estate),
2976+
ExecGetAllUpdatedCols(relinfo, estate),
29812977
transition_capture);
29822978
}
29832979

@@ -3049,7 +3045,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
30493045
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
30503046
LocTriggerData.tg_oldtable = NULL;
30513047
LocTriggerData.tg_newtable = NULL;
3052-
updatedCols = GetAllUpdatedColumns(relinfo, estate);
3048+
updatedCols = ExecGetAllUpdatedCols(relinfo, estate);
30533049
for (i = 0; i < trigdesc->numtriggers; i++)
30543050
{
30553051
Trigger *trigger = &trigdesc->triggers[i];
@@ -3149,7 +3145,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
31493145

31503146
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
31513147
true, oldslot, newslot, recheckIndexes,
3152-
GetAllUpdatedColumns(relinfo, estate),
3148+
ExecGetAllUpdatedCols(relinfo, estate),
31533149
transition_capture);
31543150
}
31553151
}

src/backend/executor/execMain.c

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,6 @@ static char *ExecBuildSlotValueDescription(Oid reloid,
100100
int maxfieldlen);
101101
static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
102102

103-
/*
104-
* Note that GetAllUpdatedColumns() also exists in commands/trigger.c. There does
105-
* not appear to be any good header to put it into, given the structures that
106-
* it uses, so we let them be duplicated. Be sure to update both if one needs
107-
* to be changed, however.
108-
*/
109-
#define GetInsertedColumns(relinfo, estate) \
110-
(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->insertedCols)
111-
#define GetUpdatedColumns(relinfo, estate) \
112-
(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols)
113-
#define GetAllUpdatedColumns(relinfo, estate) \
114-
(bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \
115-
exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols))
116-
117103
/* end of local decls */
118104

119105

@@ -1277,7 +1263,7 @@ void
12771263
InitResultRelInfo(ResultRelInfo *resultRelInfo,
12781264
Relation resultRelationDesc,
12791265
Index resultRelationIndex,
1280-
Relation partition_root,
1266+
ResultRelInfo *partition_root_rri,
12811267
int instrument_options)
12821268
{
12831269
List *partition_check = NIL;
@@ -1342,7 +1328,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
13421328
partition_check = RelationGetPartitionQual(resultRelationDesc);
13431329

13441330
resultRelInfo->ri_PartitionCheck = partition_check;
1345-
resultRelInfo->ri_PartitionRoot = partition_root;
1331+
resultRelInfo->ri_RootResultRelInfo = partition_root_rri;
13461332
resultRelInfo->ri_PartitionInfo = NULL; /* may be set later */
13471333
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
13481334
}
@@ -1840,13 +1826,14 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
18401826
* back to the root table's rowtype so that val_desc in the error message
18411827
* matches the input tuple.
18421828
*/
1843-
if (resultRelInfo->ri_PartitionRoot)
1829+
if (resultRelInfo->ri_RootResultRelInfo)
18441830
{
1831+
ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo;
18451832
TupleDesc old_tupdesc;
18461833
AttrNumber *map;
18471834

1848-
root_relid = RelationGetRelid(resultRelInfo->ri_PartitionRoot);
1849-
tupdesc = RelationGetDescr(resultRelInfo->ri_PartitionRoot);
1835+
root_relid = RelationGetRelid(rootrel->ri_RelationDesc);
1836+
tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
18501837

18511838
old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
18521839
/* a reverse map */
@@ -1860,16 +1847,17 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
18601847
if (map != NULL)
18611848
slot = execute_attr_map_slot(map, slot,
18621849
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
1850+
modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate),
1851+
ExecGetUpdatedCols(rootrel, estate));
18631852
}
18641853
else
18651854
{
18661855
root_relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
18671856
tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
1857+
modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate),
1858+
ExecGetUpdatedCols(resultRelInfo, estate));
18681859
}
18691860

1870-
modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate),
1871-
GetUpdatedColumns(resultRelInfo, estate));
1872-
18731861
val_desc = ExecBuildSlotValueDescription(root_relid,
18741862
slot,
18751863
tupdesc,
@@ -1901,8 +1889,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
19011889
TupleDesc tupdesc = RelationGetDescr(rel);
19021890
TupleConstr *constr = tupdesc->constr;
19031891
Bitmapset *modifiedCols;
1904-
Bitmapset *insertedCols;
1905-
Bitmapset *updatedCols;
19061892

19071893
Assert(constr || resultRelInfo->ri_PartitionCheck);
19081894

@@ -1928,12 +1914,12 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
19281914
* rowtype so that val_desc shown error message matches the
19291915
* input tuple.
19301916
*/
1931-
if (resultRelInfo->ri_PartitionRoot)
1917+
if (resultRelInfo->ri_RootResultRelInfo)
19321918
{
1919+
ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo;
19331920
AttrNumber *map;
19341921

1935-
rel = resultRelInfo->ri_PartitionRoot;
1936-
tupdesc = RelationGetDescr(rel);
1922+
tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
19371923
/* a reverse map */
19381924
map = convert_tuples_by_name_map_if_req(orig_tupdesc,
19391925
tupdesc,
@@ -1946,11 +1932,13 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
19461932
if (map != NULL)
19471933
slot = execute_attr_map_slot(map, slot,
19481934
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
1935+
modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate),
1936+
ExecGetUpdatedCols(rootrel, estate));
1937+
rel = rootrel->ri_RelationDesc;
19491938
}
1950-
1951-
insertedCols = GetInsertedColumns(resultRelInfo, estate);
1952-
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
1953-
modifiedCols = bms_union(insertedCols, updatedCols);
1939+
else
1940+
modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate),
1941+
ExecGetUpdatedCols(resultRelInfo, estate));
19541942
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
19551943
slot,
19561944
tupdesc,
@@ -1977,13 +1965,13 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
19771965
Relation orig_rel = rel;
19781966

19791967
/* See the comment above. */
1980-
if (resultRelInfo->ri_PartitionRoot)
1968+
if (resultRelInfo->ri_RootResultRelInfo)
19811969
{
1970+
ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo;
19821971
TupleDesc old_tupdesc = RelationGetDescr(rel);
19831972
AttrNumber *map;
19841973

1985-
rel = resultRelInfo->ri_PartitionRoot;
1986-
tupdesc = RelationGetDescr(rel);
1974+
tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
19871975
/* a reverse map */
19881976
map = convert_tuples_by_name_map_if_req(old_tupdesc,
19891977
tupdesc,
@@ -1996,11 +1984,13 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
19961984
if (map != NULL)
19971985
slot = execute_attr_map_slot(map, slot,
19981986
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
1987+
modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate),
1988+
ExecGetUpdatedCols(rootrel, estate));
1989+
rel = rootrel->ri_RelationDesc;
19991990
}
2000-
2001-
insertedCols = GetInsertedColumns(resultRelInfo, estate);
2002-
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
2003-
modifiedCols = bms_union(insertedCols, updatedCols);
1991+
else
1992+
modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate),
1993+
ExecGetUpdatedCols(resultRelInfo, estate));
20041994
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
20051995
slot,
20061996
tupdesc,
@@ -2069,8 +2059,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
20692059
{
20702060
char *val_desc;
20712061
Bitmapset *modifiedCols;
2072-
Bitmapset *insertedCols;
2073-
Bitmapset *updatedCols;
20742062

20752063
switch (wco->kind)
20762064
{
@@ -2085,13 +2073,13 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
20852073
*/
20862074
case WCO_VIEW_CHECK:
20872075
/* See the comment in ExecConstraints(). */
2088-
if (resultRelInfo->ri_PartitionRoot)
2076+
if (resultRelInfo->ri_RootResultRelInfo)
20892077
{
2078+
ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo;
20902079
TupleDesc old_tupdesc = RelationGetDescr(rel);
20912080
AttrNumber *map;
20922081

2093-
rel = resultRelInfo->ri_PartitionRoot;
2094-
tupdesc = RelationGetDescr(rel);
2082+
tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
20952083
/* a reverse map */
20962084
map = convert_tuples_by_name_map_if_req(old_tupdesc,
20972085
tupdesc,
@@ -2104,11 +2092,14 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
21042092
if (map != NULL)
21052093
slot = execute_attr_map_slot(map, slot,
21062094
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
2107-
}
21082095

2109-
insertedCols = GetInsertedColumns(resultRelInfo, estate);
2110-
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
2111-
modifiedCols = bms_union(insertedCols, updatedCols);
2096+
modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate),
2097+
ExecGetUpdatedCols(rootrel, estate));
2098+
rel = rootrel->ri_RelationDesc;
2099+
}
2100+
else
2101+
modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate),
2102+
ExecGetUpdatedCols(resultRelInfo, estate));
21122103
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
21132104
slot,
21142105
tupdesc,
@@ -2322,7 +2313,7 @@ ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo)
23222313
* been modified, then we can use a weaker lock, allowing for better
23232314
* concurrency.
23242315
*/
2325-
updatedCols = GetAllUpdatedColumns(relinfo, estate);
2316+
updatedCols = ExecGetAllUpdatedCols(relinfo, estate);
23262317
keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
23272318
INDEX_ATTR_BITMAP_KEY);
23282319

0 commit comments

Comments
 (0)