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

Commit a5fa3e0

Browse files
committed
Fix mishandling of resjunk columns in ON CONFLICT ... UPDATE tlists.
It's unusual to have any resjunk columns in an ON CONFLICT ... UPDATE list, but it can happen when MULTIEXPR_SUBLINK SubPlans are present. If it happens, the ON CONFLICT UPDATE code path would end up storing tuples that include the values of the extra resjunk columns. That's fairly harmless in the short run, but if new columns are added to the table then the values would become accessible, possibly leading to malfunctions if they don't match the datatypes of the new columns. This had escaped notice through a confluence of missing sanity checks, including * There's no cross-check that a tuple presented to heap_insert or heap_update matches the table rowtype. While it's difficult to check that fully at reasonable cost, we can easily add assertions that there aren't too many columns. * The output-column-assignment cases in execExprInterp.c lacked any sanity checks on the output column numbers, which seems like an oversight considering there are plenty of assertion checks on input column numbers. Add assertions there too. * We failed to apply nodeModifyTable's ExecCheckPlanOutput() to the ON CONFLICT UPDATE tlist. That wouldn't have caught this specific error, since that function is chartered to ignore resjunk columns; but it sure seems like a bad omission now that we've seen this bug. In HEAD, the right way to fix this is to make the processing of ON CONFLICT UPDATE tlists work the same as regular UPDATE tlists now do, that is don't add "SET x = x" entries, and use ExecBuildUpdateProjection to evaluate the tlist and combine it with old values of the not-set columns. This adds a little complication to ExecBuildUpdateProjection, but allows removal of a comparable amount of now-dead code from the planner. In the back branches, the most expedient solution seems to be to (a) use an output slot for the ON CONFLICT UPDATE projection that actually matches the target table, and then (b) invent a variant of ExecBuildProjectionInfo that can be told to not store values resulting from resjunk columns, so it doesn't try to store into nonexistent columns of the output slot. (We can't simply ignore the resjunk columns altogether; they have to be evaluated for MULTIEXPR_SUBLINK to work.) This works back to v10. In 9.6, projections work much differently and we can't cheaply give them such an option. The 9.6 version of this patch works by inserting a JunkFilter when it's necessary to get rid of resjunk columns. In addition, v11 and up have the reverse problem when trying to perform ON CONFLICT UPDATE on a partitioned table. Through a further oversight, adjust_partition_tlist() discarded resjunk columns when re-ordering the ON CONFLICT UPDATE tlist to match a partition. This accidentally prevented the storing-bogus-tuples problem, but at the cost that MULTIEXPR_SUBLINK cases didn't work, typically crashing if more than one row has to be updated. Fix by preserving resjunk columns in that routine. (I failed to resist the temptation to add more assertions there too, and to do some minor code beautification.) Per report from Andres Freund. Back-patch to all supported branches. Security: CVE-2021-32028
1 parent 3b0f6a7 commit a5fa3e0

File tree

8 files changed

+187
-59
lines changed

8 files changed

+187
-59
lines changed

src/backend/access/heap/heapam.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,10 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
18861886
Buffer vmbuffer = InvalidBuffer;
18871887
bool all_visible_cleared = false;
18881888

1889+
/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
1890+
Assert(HeapTupleHeaderGetNatts(tup->t_data) <=
1891+
RelationGetNumberOfAttributes(relation));
1892+
18891893
/*
18901894
* Fill in tuple header fields and toast the tuple if necessary.
18911895
*
@@ -2946,6 +2950,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
29462950

29472951
Assert(ItemPointerIsValid(otid));
29482952

2953+
/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
2954+
Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
2955+
RelationGetNumberOfAttributes(relation));
2956+
29492957
/*
29502958
* Forbid this during a parallel operation, lest it allocate a combocid.
29512959
* Other workers might need that combocid for visibility checks, and we

src/backend/executor/execExpr.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,32 @@ ExecBuildProjectionInfo(List *targetList,
352352
TupleTableSlot *slot,
353353
PlanState *parent,
354354
TupleDesc inputDesc)
355+
{
356+
return ExecBuildProjectionInfoExt(targetList,
357+
econtext,
358+
slot,
359+
true,
360+
parent,
361+
inputDesc);
362+
}
363+
364+
/*
365+
* ExecBuildProjectionInfoExt
366+
*
367+
* As above, with one additional option.
368+
*
369+
* If assignJunkEntries is true (the usual case), resjunk entries in the tlist
370+
* are not handled specially: they are evaluated and assigned to the proper
371+
* column of the result slot. If assignJunkEntries is false, resjunk entries
372+
* are evaluated, but their result is discarded without assignment.
373+
*/
374+
ProjectionInfo *
375+
ExecBuildProjectionInfoExt(List *targetList,
376+
ExprContext *econtext,
377+
TupleTableSlot *slot,
378+
bool assignJunkEntries,
379+
PlanState *parent,
380+
TupleDesc inputDesc)
355381
{
356382
ProjectionInfo *projInfo = makeNode(ProjectionInfo);
357383
ExprState *state;
@@ -389,7 +415,8 @@ ExecBuildProjectionInfo(List *targetList,
389415
*/
390416
if (tle->expr != NULL &&
391417
IsA(tle->expr, Var) &&
392-
((Var *) tle->expr)->varattno > 0)
418+
((Var *) tle->expr)->varattno > 0 &&
419+
(assignJunkEntries || !tle->resjunk))
393420
{
394421
/* Non-system Var, but how safe is it? */
395422
variable = (Var *) tle->expr;
@@ -453,6 +480,10 @@ ExecBuildProjectionInfo(List *targetList,
453480
ExecInitExprRec(tle->expr, state,
454481
&state->resvalue, &state->resnull);
455482

483+
/* This makes it easy to discard resjunk results when told to. */
484+
if (!assignJunkEntries && tle->resjunk)
485+
continue;
486+
456487
/*
457488
* Column might be referenced multiple times in upper nodes, so
458489
* force value to R/O - but only if it could be an expanded datum.

src/backend/executor/execExprInterp.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
531531
* care of at compilation time. But see EEOP_INNER_VAR comments.
532532
*/
533533
Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
534+
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
534535
resultslot->tts_values[resultnum] = innerslot->tts_values[attnum];
535536
resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum];
536537

@@ -547,6 +548,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
547548
* care of at compilation time. But see EEOP_INNER_VAR comments.
548549
*/
549550
Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
551+
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
550552
resultslot->tts_values[resultnum] = outerslot->tts_values[attnum];
551553
resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum];
552554

@@ -563,6 +565,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
563565
* care of at compilation time. But see EEOP_INNER_VAR comments.
564566
*/
565567
Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
568+
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
566569
resultslot->tts_values[resultnum] = scanslot->tts_values[attnum];
567570
resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum];
568571

@@ -573,6 +576,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
573576
{
574577
int resultnum = op->d.assign_tmp.resultnum;
575578

579+
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
576580
resultslot->tts_values[resultnum] = state->resvalue;
577581
resultslot->tts_isnull[resultnum] = state->resnull;
578582

@@ -583,6 +587,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
583587
{
584588
int resultnum = op->d.assign_tmp.resultnum;
585589

590+
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
586591
resultslot->tts_isnull[resultnum] = state->resnull;
587592
if (!resultslot->tts_isnull[resultnum])
588593
resultslot->tts_values[resultnum] =
@@ -2070,8 +2075,10 @@ ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
20702075
*
20712076
* Since we use slot_getattr(), we don't need to implement the FETCHSOME
20722077
* step explicitly, and we also needn't Assert that the attnum is in range
2073-
* --- slot_getattr() will take care of any problems.
2078+
* --- slot_getattr() will take care of any problems. Nonetheless, check
2079+
* that resultnum is in range.
20742080
*/
2081+
Assert(resultnum >= 0 && resultnum < outslot->tts_tupleDescriptor->natts);
20752082
outslot->tts_values[resultnum] =
20762083
slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
20772084
return 0;
@@ -2090,6 +2097,7 @@ ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
20902097
CheckOpSlotCompatibility(&state->steps[0], inslot);
20912098

20922099
/* See comments in ExecJustAssignInnerVar */
2100+
Assert(resultnum >= 0 && resultnum < outslot->tts_tupleDescriptor->natts);
20932101
outslot->tts_values[resultnum] =
20942102
slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
20952103
return 0;
@@ -2108,6 +2116,7 @@ ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
21082116
CheckOpSlotCompatibility(&state->steps[0], inslot);
21092117

21102118
/* See comments in ExecJustAssignInnerVar */
2119+
Assert(resultnum >= 0 && resultnum < outslot->tts_tupleDescriptor->natts);
21112120
outslot->tts_values[resultnum] =
21122121
slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
21132122
return 0;

src/backend/executor/execPartition.c

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -788,21 +788,22 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
788788
*/
789789
if (node->onConflictAction == ONCONFLICT_UPDATE)
790790
{
791+
OnConflictSetState *onconfl = makeNode(OnConflictSetState);
791792
TupleConversionMap *map;
792793

793794
map = leaf_part_rri->ri_PartitionInfo->pi_RootToPartitionMap;
794795

795796
Assert(node->onConflictSet != NIL);
796797
Assert(rootResultRelInfo->ri_onConflict != NULL);
797798

798-
leaf_part_rri->ri_onConflict = makeNode(OnConflictSetState);
799+
leaf_part_rri->ri_onConflict = onconfl;
799800

800801
/*
801802
* Need a separate existing slot for each partition, as the
802803
* partition could be of a different AM, even if the tuple
803804
* descriptors match.
804805
*/
805-
leaf_part_rri->ri_onConflict->oc_Existing =
806+
onconfl->oc_Existing =
806807
table_slot_create(leaf_part_rri->ri_RelationDesc,
807808
&mtstate->ps.state->es_tupleTable);
808809

@@ -822,17 +823,16 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
822823
* Projections and where clauses themselves don't store state
823824
* / are independent of the underlying storage.
824825
*/
825-
leaf_part_rri->ri_onConflict->oc_ProjSlot =
826+
onconfl->oc_ProjSlot =
826827
rootResultRelInfo->ri_onConflict->oc_ProjSlot;
827-
leaf_part_rri->ri_onConflict->oc_ProjInfo =
828+
onconfl->oc_ProjInfo =
828829
rootResultRelInfo->ri_onConflict->oc_ProjInfo;
829-
leaf_part_rri->ri_onConflict->oc_WhereClause =
830+
onconfl->oc_WhereClause =
830831
rootResultRelInfo->ri_onConflict->oc_WhereClause;
831832
}
832833
else
833834
{
834835
List *onconflset;
835-
TupleDesc tupDesc;
836836
bool found_whole_row;
837837

838838
/*
@@ -842,7 +842,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
842842
* pseudo-relation (INNER_VAR), and second to handle the main
843843
* target relation (firstVarno).
844844
*/
845-
onconflset = (List *) copyObject((Node *) node->onConflictSet);
845+
onconflset = copyObject(node->onConflictSet);
846846
if (part_attnos == NULL)
847847
part_attnos =
848848
convert_tuples_by_name_map(RelationGetDescr(partrel),
@@ -865,20 +865,19 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
865865
&found_whole_row);
866866
/* We ignore the value of found_whole_row. */
867867

868-
/* Finally, adjust this tlist to match the partition. */
868+
/* Finally, reorder the tlist to match the partition. */
869869
onconflset = adjust_partition_tlist(onconflset, map);
870870

871871
/* create the tuple slot for the UPDATE SET projection */
872-
tupDesc = ExecTypeFromTL(onconflset);
873-
leaf_part_rri->ri_onConflict->oc_ProjSlot =
874-
ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
875-
&TTSOpsVirtual);
872+
onconfl->oc_ProjSlot =
873+
table_slot_create(partrel,
874+
&mtstate->ps.state->es_tupleTable);
876875

877876
/* build UPDATE SET projection state */
878-
leaf_part_rri->ri_onConflict->oc_ProjInfo =
879-
ExecBuildProjectionInfo(onconflset, econtext,
880-
leaf_part_rri->ri_onConflict->oc_ProjSlot,
881-
&mtstate->ps, partrelDesc);
877+
onconfl->oc_ProjInfo =
878+
ExecBuildProjectionInfoExt(onconflset, econtext,
879+
onconfl->oc_ProjSlot, false,
880+
&mtstate->ps, partrelDesc);
882881

883882
/*
884883
* If there is a WHERE clause, initialize state where it will
@@ -907,7 +906,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
907906
RelationGetForm(partrel)->reltype,
908907
&found_whole_row);
909908
/* We ignore the value of found_whole_row. */
910-
leaf_part_rri->ri_onConflict->oc_WhereClause =
909+
onconfl->oc_WhereClause =
911910
ExecInitQual((List *) clause, &mtstate->ps);
912911
}
913912
}
@@ -1498,18 +1497,15 @@ ExecBuildSlotPartitionKeyDescription(Relation rel,
14981497

14991498
/*
15001499
* adjust_partition_tlist
1501-
* Adjust the targetlist entries for a given partition to account for
1502-
* attribute differences between parent and the partition
1500+
* Re-order the targetlist entries for a given partition to account for
1501+
* column position differences between the parent and the partition.
15031502
*
1504-
* The expressions have already been fixed, but here we fix the list to make
1505-
* target resnos match the partition's attribute numbers. This results in a
1506-
* copy of the original target list in which the entries appear in resno
1507-
* order, including both the existing entries (that may have their resno
1508-
* changed in-place) and the newly added entries for columns that don't exist
1509-
* in the parent.
1503+
* The expressions have already been fixed, but we must now re-order the
1504+
* entries in case the partition has different column order, and possibly
1505+
* add or remove dummy entries for dropped columns.
15101506
*
1511-
* Scribbles on the input tlist, so callers must make sure to make a copy
1512-
* before passing it to us.
1507+
* Although a new List is returned, this feels free to scribble on resno
1508+
* fields of the given tlist, so that should be a working copy.
15131509
*/
15141510
static List *
15151511
adjust_partition_tlist(List *tlist, TupleConversionMap *map)
@@ -1518,31 +1514,35 @@ adjust_partition_tlist(List *tlist, TupleConversionMap *map)
15181514
TupleDesc tupdesc = map->outdesc;
15191515
AttrNumber *attrMap = map->attrMap;
15201516
AttrNumber attrno;
1517+
ListCell *lc;
15211518

15221519
for (attrno = 1; attrno <= tupdesc->natts; attrno++)
15231520
{
15241521
Form_pg_attribute att_tup = TupleDescAttr(tupdesc, attrno - 1);
1522+
AttrNumber parentattrno = attrMap[attrno - 1];
15251523
TargetEntry *tle;
15261524

1527-
if (attrMap[attrno - 1] != InvalidAttrNumber)
1525+
if (parentattrno != InvalidAttrNumber)
15281526
{
1529-
Assert(!att_tup->attisdropped);
1530-
15311527
/*
15321528
* Use the corresponding entry from the parent's tlist, adjusting
1533-
* the resno the match the partition's attno.
1529+
* the resno to match the partition's attno.
15341530
*/
1535-
tle = (TargetEntry *) list_nth(tlist, attrMap[attrno - 1] - 1);
1531+
Assert(!att_tup->attisdropped);
1532+
tle = (TargetEntry *) list_nth(tlist, parentattrno - 1);
1533+
Assert(!tle->resjunk);
1534+
Assert(tle->resno == parentattrno);
15361535
tle->resno = attrno;
15371536
}
15381537
else
15391538
{
1540-
Const *expr;
1541-
15421539
/*
15431540
* For a dropped attribute in the partition, generate a dummy
1544-
* entry with resno matching the partition's attno.
1541+
* entry with resno matching the partition's attno. This should
1542+
* match what expand_targetlist() does.
15451543
*/
1544+
Const *expr;
1545+
15461546
Assert(att_tup->attisdropped);
15471547
expr = makeConst(INT4OID,
15481548
-1,
@@ -1560,6 +1560,18 @@ adjust_partition_tlist(List *tlist, TupleConversionMap *map)
15601560
new_tlist = lappend(new_tlist, tle);
15611561
}
15621562

1563+
/* Finally, attach any resjunk entries to the end of the new tlist */
1564+
foreach(lc, tlist)
1565+
{
1566+
TargetEntry *tle = (TargetEntry *) lfirst(lc);
1567+
1568+
if (tle->resjunk)
1569+
{
1570+
tle->resno = list_length(new_tlist) + 1;
1571+
new_tlist = lappend(new_tlist, tle);
1572+
}
1573+
}
1574+
15631575
return new_tlist;
15641576
}
15651577

src/backend/executor/nodeModifyTable.c

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,9 +2585,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
25852585
*/
25862586
if (node->onConflictAction == ONCONFLICT_UPDATE)
25872587
{
2588+
OnConflictSetState *onconfl = makeNode(OnConflictSetState);
25882589
ExprContext *econtext;
25892590
TupleDesc relationDesc;
2590-
TupleDesc tupDesc;
25912591

25922592
/* insert may only have one plan, inheritance is not expanded */
25932593
Assert(nplans == 1);
@@ -2603,10 +2603,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
26032603
mtstate->mt_excludedtlist = node->exclRelTlist;
26042604

26052605
/* create state for DO UPDATE SET operation */
2606-
resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
2606+
resultRelInfo->ri_onConflict = onconfl;
26072607

26082608
/* initialize slot for the existing tuple */
2609-
resultRelInfo->ri_onConflict->oc_Existing =
2609+
onconfl->oc_Existing =
26102610
table_slot_create(resultRelInfo->ri_RelationDesc,
26112611
&mtstate->ps.state->es_tupleTable);
26122612

@@ -2616,17 +2616,25 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
26162616
* into the table, and for RETURNING processing - which may access
26172617
* system attributes.
26182618
*/
2619-
tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
2620-
resultRelInfo->ri_onConflict->oc_ProjSlot =
2621-
ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
2622-
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
2619+
onconfl->oc_ProjSlot =
2620+
table_slot_create(resultRelInfo->ri_RelationDesc,
2621+
&mtstate->ps.state->es_tupleTable);
2622+
2623+
/*
2624+
* The onConflictSet tlist should already have been adjusted to emit
2625+
* the table's exact column list. It could also contain resjunk
2626+
* columns, which should be evaluated but not included in the
2627+
* projection result.
2628+
*/
2629+
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
2630+
node->onConflictSet);
26232631

26242632
/* build UPDATE SET projection state */
2625-
resultRelInfo->ri_onConflict->oc_ProjInfo =
2626-
ExecBuildProjectionInfo(node->onConflictSet, econtext,
2627-
resultRelInfo->ri_onConflict->oc_ProjSlot,
2628-
&mtstate->ps,
2629-
relationDesc);
2633+
onconfl->oc_ProjInfo =
2634+
ExecBuildProjectionInfoExt(node->onConflictSet, econtext,
2635+
onconfl->oc_ProjSlot, false,
2636+
&mtstate->ps,
2637+
relationDesc);
26302638

26312639
/* initialize state to evaluate the WHERE clause, if any */
26322640
if (node->onConflictWhere)
@@ -2635,7 +2643,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
26352643

26362644
qualexpr = ExecInitQual((List *) node->onConflictWhere,
26372645
&mtstate->ps);
2638-
resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
2646+
onconfl->oc_WhereClause = qualexpr;
26392647
}
26402648
}
26412649

0 commit comments

Comments
 (0)