Skip to content

Commit 36c9f7d

Browse files
committed
Fix null-pointer crash in postgres_fdw's conversion_error_callback.
Commit c7b7311 adjusted conversion_error_callback to always use information from the query's rangetable, to avoid doing catalog lookups in an already-failed transaction. However, as a result of the utterly inadequate documentation for make_tuple_from_result_row, I failed to realize that fsstate could be NULL in some contexts. That led to a crash if we got a conversion error in such a context. Fix by falling back to the previous coding when fsstate is NULL. Improve the commentary, too. Per report from Andrey Borodin. Back-patch to 9.6, like the previous patch. Discussion: https://postgr.es/m/08916396-55E4-4D68-AB3A-BD6066F9E5C0@yandex-team.ru
1 parent 0de8f9b commit 36c9f7d

File tree

3 files changed

+82
-40
lines changed

3 files changed

+82
-40
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2880,6 +2880,9 @@ SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
28802880
WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
28812881
ERROR: invalid input syntax for integer: "foo"
28822882
CONTEXT: whole-row reference to foreign table "ftx"
2883+
ANALYZE ft1; -- ERROR
2884+
ERROR: invalid input syntax for integer: "foo"
2885+
CONTEXT: column "c8" of foreign table "ft1"
28832886
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
28842887
-- ===================================================================
28852888
-- subtransaction

contrib/postgres_fdw/postgres_fdw.c

Lines changed: 78 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ typedef struct PgFdwAnalyzeState
244244
typedef struct ConversionLocation
245245
{
246246
AttrNumber cur_attno; /* attribute number being processed, or 0 */
247-
ForeignScanState *fsstate; /* plan node being processed */
247+
Relation rel; /* foreign table being processed, or NULL */
248+
ForeignScanState *fsstate; /* plan node being processed, or NULL */
248249
} ConversionLocation;
249250

250251
/* Callback argument for ec_member_matches_foreign */
@@ -4396,7 +4397,12 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
43964397
* rel is the local representation of the foreign table, attinmeta is
43974398
* conversion data for the rel's tupdesc, and retrieved_attrs is an
43984399
* integer list of the table column numbers present in the PGresult.
4400+
* fsstate is the ForeignScan plan node's execution state.
43994401
* temp_context is a working context that can be reset after each tuple.
4402+
*
4403+
* Note: either rel or fsstate, but not both, can be NULL. rel is NULL
4404+
* if we're processing a remote join, while fsstate is NULL in a non-query
4405+
* context such as ANALYZE, or if we're processing a non-scan query node.
44004406
*/
44014407
static HeapTuple
44024408
make_tuple_from_result_row(PGresult *res,
@@ -4427,6 +4433,10 @@ make_tuple_from_result_row(PGresult *res,
44274433
*/
44284434
oldcontext = MemoryContextSwitchTo(temp_context);
44294435

4436+
/*
4437+
* Get the tuple descriptor for the row. Use the rel's tupdesc if rel is
4438+
* provided, otherwise look to the scan node's ScanTupleSlot.
4439+
*/
44304440
if (rel)
44314441
tupdesc = RelationGetDescr(rel);
44324442
else
@@ -4447,6 +4457,7 @@ make_tuple_from_result_row(PGresult *res,
44474457
* Set up and install callback to report where conversion error occurs.
44484458
*/
44494459
errpos.cur_attno = 0;
4460+
errpos.rel = rel;
44504461
errpos.fsstate = fsstate;
44514462
errcallback.callback = conversion_error_callback;
44524463
errcallback.arg = (void *) &errpos;
@@ -4547,65 +4558,92 @@ make_tuple_from_result_row(PGresult *res,
45474558
*
45484559
* Note that this function mustn't do any catalog lookups, since we are in
45494560
* an already-failed transaction. Fortunately, we can get the needed info
4550-
* from the query's rangetable instead.
4561+
* from the relation or the query's rangetable instead.
45514562
*/
45524563
static void
45534564
conversion_error_callback(void *arg)
45544565
{
45554566
ConversionLocation *errpos = (ConversionLocation *) arg;
4567+
Relation rel = errpos->rel;
45564568
ForeignScanState *fsstate = errpos->fsstate;
4557-
ForeignScan *fsplan = (ForeignScan *) fsstate->ss.ps.plan;
4558-
int varno = 0;
4559-
AttrNumber colno = 0;
45604569
const char *attname = NULL;
45614570
const char *relname = NULL;
45624571
bool is_wholerow = false;
45634572

4564-
if (fsplan->scan.scanrelid > 0)
4565-
{
4566-
/* error occurred in a scan against a foreign table */
4567-
varno = fsplan->scan.scanrelid;
4568-
colno = errpos->cur_attno;
4569-
}
4570-
else
4573+
/*
4574+
* If we're in a scan node, always use aliases from the rangetable, for
4575+
* consistency between the simple-relation and remote-join cases. Look at
4576+
* the relation's tupdesc only if we're not in a scan node.
4577+
*/
4578+
if (fsstate)
45714579
{
4572-
/* error occurred in a scan against a foreign join */
4573-
TargetEntry *tle;
4574-
4575-
Assert(IsA(fsplan, ForeignScan));
4576-
tle = (TargetEntry *) list_nth(fsplan->fdw_scan_tlist,
4577-
errpos->cur_attno - 1);
4578-
Assert(IsA(tle, TargetEntry));
4580+
/* ForeignScan case */
4581+
ForeignScan *fsplan = (ForeignScan *) fsstate->ss.ps.plan;
4582+
int varno = 0;
4583+
AttrNumber colno = 0;
45794584

4580-
/*
4581-
* Target list can have Vars and expressions. For Vars, we can get
4582-
* some information, however for expressions we can't. Thus for
4583-
* expressions, just show generic context message.
4584-
*/
4585-
if (IsA(tle->expr, Var))
4585+
if (fsplan->scan.scanrelid > 0)
4586+
{
4587+
/* error occurred in a scan against a foreign table */
4588+
varno = fsplan->scan.scanrelid;
4589+
colno = errpos->cur_attno;
4590+
}
4591+
else
45864592
{
4587-
Var *var = (Var *) tle->expr;
4593+
/* error occurred in a scan against a foreign join */
4594+
TargetEntry *tle;
4595+
4596+
Assert(IsA(fsplan, ForeignScan));
4597+
tle = (TargetEntry *) list_nth(fsplan->fdw_scan_tlist,
4598+
errpos->cur_attno - 1);
4599+
Assert(IsA(tle, TargetEntry));
4600+
4601+
/*
4602+
* Target list can have Vars and expressions. For Vars, we can
4603+
* get some information, however for expressions we can't. Thus
4604+
* for expressions, just show generic context message.
4605+
*/
4606+
if (IsA(tle->expr, Var))
4607+
{
4608+
Var *var = (Var *) tle->expr;
4609+
4610+
varno = var->varno;
4611+
colno = var->varattno;
4612+
}
4613+
}
45884614

4589-
varno = var->varno;
4590-
colno = var->varattno;
4615+
if (varno > 0)
4616+
{
4617+
EState *estate = fsstate->ss.ps.state;
4618+
RangeTblEntry *rte = rt_fetch(varno, estate->es_range_table);
4619+
4620+
relname = rte->eref->aliasname;
4621+
4622+
if (colno == 0)
4623+
is_wholerow = true;
4624+
else if (colno > 0 && colno <= list_length(rte->eref->colnames))
4625+
attname = strVal(list_nth(rte->eref->colnames, colno - 1));
4626+
else if (colno == SelfItemPointerAttributeNumber)
4627+
attname = "ctid";
4628+
else if (colno == ObjectIdAttributeNumber)
4629+
attname = "oid";
45914630
}
45924631
}
4593-
4594-
if (varno > 0)
4632+
else if (rel)
45954633
{
4596-
EState *estate = fsstate->ss.ps.state;
4597-
RangeTblEntry *rte = rt_fetch(varno, estate->es_range_table);
4634+
/* Non-ForeignScan case (we should always have a rel here) */
4635+
TupleDesc tupdesc = RelationGetDescr(rel);
45984636

4599-
relname = rte->eref->aliasname;
4637+
relname = RelationGetRelationName(rel);
4638+
if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts)
4639+
{
4640+
Form_pg_attribute attr = TupleDescAttr(tupdesc,
4641+
errpos->cur_attno - 1);
46004642

4601-
if (colno == 0)
4602-
is_wholerow = true;
4603-
else if (colno > 0 && colno <= list_length(rte->eref->colnames))
4604-
attname = strVal(list_nth(rte->eref->colnames, colno - 1));
4605-
else if (colno == SelfItemPointerAttributeNumber)
4643+
attname = NameStr(attr->attname);
4644+
}
4645+
else if (errpos->cur_attno == SelfItemPointerAttributeNumber)
46064646
attname = "ctid";
4607-
else if (colno == ObjectIdAttributeNumber)
4608-
attname = "oid";
46094647
}
46104648

46114649
if (relname && is_wholerow)

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
707707
WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
708708
SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
709709
WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
710+
ANALYZE ft1; -- ERROR
710711
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
711712

712713
-- ===================================================================

0 commit comments

Comments
 (0)