Skip to content

Commit 900a8d5

Browse files
committed
Tighten array dimensionality checks in Perl -> SQL array conversion.
plperl_array_to_datum() wasn't sufficiently careful about checking that nested lists represent a rectangular array structure; it would accept inputs such as "[1, []]". This is a bit related to the PL/Python bug fixed in commit 81eaaf6, but it doesn't seem to provide any direct route to a memory stomp. Instead the likely failure mode is for makeMdArrayResult to be passed fewer Datums than the claimed array dimensionality requires, possibly leading to a wild pointer dereference and SIGSEGV. Per report from Alexander Lakhin. It's been broken for a long time, so back-patch to all supported branches. Discussion: https://postgr.es/m/5ebae5e4-d401-fadf-8585-ac3eaf53219c@gmail.com
1 parent ff9203f commit 900a8d5

File tree

3 files changed

+119
-23
lines changed

3 files changed

+119
-23
lines changed

src/pl/plperl/expected/plperl_array.out

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,49 @@ select plperl_arrays_inout_l('{{1}, {2}, {3}}');
215215
{{1},{2},{3}}
216216
(1 row)
217217

218+
-- check output of multi-dimensional arrays
219+
CREATE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
220+
return [['a'], ['b'], ['c']];
221+
$$ LANGUAGE plperl;
222+
select plperl_md_array_out();
223+
plperl_md_array_out
224+
---------------------
225+
{{a},{b},{c}}
226+
(1 row)
227+
228+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
229+
return [[], []];
230+
$$ LANGUAGE plperl;
231+
select plperl_md_array_out();
232+
plperl_md_array_out
233+
---------------------
234+
{}
235+
(1 row)
236+
237+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
238+
return [[], [1]];
239+
$$ LANGUAGE plperl;
240+
select plperl_md_array_out(); -- fail
241+
ERROR: multidimensional arrays must have array expressions with matching dimensions
242+
CONTEXT: PL/Perl function "plperl_md_array_out"
243+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
244+
return [[], 1];
245+
$$ LANGUAGE plperl;
246+
select plperl_md_array_out(); -- fail
247+
ERROR: multidimensional arrays must have array expressions with matching dimensions
248+
CONTEXT: PL/Perl function "plperl_md_array_out"
249+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
250+
return [1, []];
251+
$$ LANGUAGE plperl;
252+
select plperl_md_array_out(); -- fail
253+
ERROR: multidimensional arrays must have array expressions with matching dimensions
254+
CONTEXT: PL/Perl function "plperl_md_array_out"
255+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
256+
return [[1], [[]]];
257+
$$ LANGUAGE plperl;
258+
select plperl_md_array_out(); -- fail
259+
ERROR: multidimensional arrays must have array expressions with matching dimensions
260+
CONTEXT: PL/Perl function "plperl_md_array_out"
218261
-- make sure setof works
219262
create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$
220263
my $arr = shift;

src/pl/plperl/plperl.c

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,9 @@ static Datum plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
276276
bool *isnull);
277277
static void _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam);
278278
static Datum plperl_array_to_datum(SV *src, Oid typid, int32 typmod);
279-
static void array_to_datum_internal(AV *av, ArrayBuildState *astate,
279+
static void array_to_datum_internal(AV *av, ArrayBuildState **astatep,
280280
int *ndims, int *dims, int cur_depth,
281-
Oid arraytypid, Oid elemtypid, int32 typmod,
281+
Oid elemtypid, int32 typmod,
282282
FmgrInfo *finfo, Oid typioparam);
283283
static Datum plperl_hash_to_datum(SV *src, TupleDesc td);
284284

@@ -1167,11 +1167,16 @@ get_perl_array_ref(SV *sv)
11671167

11681168
/*
11691169
* helper function for plperl_array_to_datum, recurses for multi-D arrays
1170+
*
1171+
* The ArrayBuildState is created only when we first find a scalar element;
1172+
* if we didn't do it like that, we'd need some other convention for knowing
1173+
* whether we'd already found any scalars (and thus the number of dimensions
1174+
* is frozen).
11701175
*/
11711176
static void
1172-
array_to_datum_internal(AV *av, ArrayBuildState *astate,
1177+
array_to_datum_internal(AV *av, ArrayBuildState **astatep,
11731178
int *ndims, int *dims, int cur_depth,
1174-
Oid arraytypid, Oid elemtypid, int32 typmod,
1179+
Oid elemtypid, int32 typmod,
11751180
FmgrInfo *finfo, Oid typioparam)
11761181
{
11771182
dTHX;
@@ -1191,28 +1196,34 @@ array_to_datum_internal(AV *av, ArrayBuildState *astate,
11911196
{
11921197
AV *nav = (AV *) SvRV(sav);
11931198

1194-
/* dimensionality checks */
1195-
if (cur_depth + 1 > MAXDIM)
1196-
ereport(ERROR,
1197-
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1198-
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
1199-
cur_depth + 1, MAXDIM)));
1200-
12011199
/* set size when at first element in this level, else compare */
12021200
if (i == 0 && *ndims == cur_depth)
12031201
{
1202+
/* array after some scalars at same level? */
1203+
if (*astatep != NULL)
1204+
ereport(ERROR,
1205+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1206+
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
1207+
/* too many dimensions? */
1208+
if (cur_depth + 1 > MAXDIM)
1209+
ereport(ERROR,
1210+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1211+
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
1212+
cur_depth + 1, MAXDIM)));
1213+
/* OK, add a dimension */
12041214
dims[*ndims] = av_len(nav) + 1;
12051215
(*ndims)++;
12061216
}
1207-
else if (av_len(nav) + 1 != dims[cur_depth])
1217+
else if (cur_depth >= *ndims ||
1218+
av_len(nav) + 1 != dims[cur_depth])
12081219
ereport(ERROR,
12091220
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
12101221
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
12111222

12121223
/* recurse to fetch elements of this sub-array */
1213-
array_to_datum_internal(nav, astate,
1224+
array_to_datum_internal(nav, astatep,
12141225
ndims, dims, cur_depth + 1,
1215-
arraytypid, elemtypid, typmod,
1226+
elemtypid, typmod,
12161227
finfo, typioparam);
12171228
}
12181229
else
@@ -1234,7 +1245,13 @@ array_to_datum_internal(AV *av, ArrayBuildState *astate,
12341245
typioparam,
12351246
&isnull);
12361247

1237-
(void) accumArrayResult(astate, dat, isnull,
1248+
/* Create ArrayBuildState if we didn't already */
1249+
if (*astatep == NULL)
1250+
*astatep = initArrayResult(elemtypid,
1251+
CurrentMemoryContext, true);
1252+
1253+
/* ... and save the element value in it */
1254+
(void) accumArrayResult(*astatep, dat, isnull,
12381255
elemtypid, CurrentMemoryContext);
12391256
}
12401257
}
@@ -1247,7 +1264,8 @@ static Datum
12471264
plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
12481265
{
12491266
dTHX;
1250-
ArrayBuildState *astate;
1267+
AV *nav = (AV *) SvRV(src);
1268+
ArrayBuildState *astate = NULL;
12511269
Oid elemtypid;
12521270
FmgrInfo finfo;
12531271
Oid typioparam;
@@ -1263,21 +1281,19 @@ plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
12631281
errmsg("cannot convert Perl array to non-array type %s",
12641282
format_type_be(typid))));
12651283

1266-
astate = initArrayResult(elemtypid, CurrentMemoryContext, true);
1267-
12681284
_sv_to_datum_finfo(elemtypid, &finfo, &typioparam);
12691285

12701286
memset(dims, 0, sizeof(dims));
1271-
dims[0] = av_len((AV *) SvRV(src)) + 1;
1287+
dims[0] = av_len(nav) + 1;
12721288

1273-
array_to_datum_internal((AV *) SvRV(src), astate,
1289+
array_to_datum_internal(nav, &astate,
12741290
&ndims, dims, 1,
1275-
typid, elemtypid, typmod,
1291+
elemtypid, typmod,
12761292
&finfo, typioparam);
12771293

12781294
/* ensure we get zero-D array for no inputs, as per PG convention */
1279-
if (dims[0] <= 0)
1280-
ndims = 0;
1295+
if (astate == NULL)
1296+
return PointerGetDatum(construct_empty_array(elemtypid));
12811297

12821298
for (i = 0; i < ndims; i++)
12831299
lbs[i] = 1;

src/pl/plperl/sql/plperl_array.sql

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,43 @@ $$ LANGUAGE plperl;
159159

160160
select plperl_arrays_inout_l('{{1}, {2}, {3}}');
161161

162+
-- check output of multi-dimensional arrays
163+
CREATE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
164+
return [['a'], ['b'], ['c']];
165+
$$ LANGUAGE plperl;
166+
167+
select plperl_md_array_out();
168+
169+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
170+
return [[], []];
171+
$$ LANGUAGE plperl;
172+
173+
select plperl_md_array_out();
174+
175+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
176+
return [[], [1]];
177+
$$ LANGUAGE plperl;
178+
179+
select plperl_md_array_out(); -- fail
180+
181+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
182+
return [[], 1];
183+
$$ LANGUAGE plperl;
184+
185+
select plperl_md_array_out(); -- fail
186+
187+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
188+
return [1, []];
189+
$$ LANGUAGE plperl;
190+
191+
select plperl_md_array_out(); -- fail
192+
193+
CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
194+
return [[1], [[]]];
195+
$$ LANGUAGE plperl;
196+
197+
select plperl_md_array_out(); -- fail
198+
162199
-- make sure setof works
163200
create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$
164201
my $arr = shift;

0 commit comments

Comments
 (0)