Skip to content

Commit 1e868bb

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 a1d9aac commit 1e868bb

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
@@ -275,9 +275,9 @@ static Datum plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
275275
bool *isnull);
276276
static void _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam);
277277
static Datum plperl_array_to_datum(SV *src, Oid typid, int32 typmod);
278-
static void array_to_datum_internal(AV *av, ArrayBuildState *astate,
278+
static void array_to_datum_internal(AV *av, ArrayBuildState **astatep,
279279
int *ndims, int *dims, int cur_depth,
280-
Oid arraytypid, Oid elemtypid, int32 typmod,
280+
Oid elemtypid, int32 typmod,
281281
FmgrInfo *finfo, Oid typioparam);
282282
static Datum plperl_hash_to_datum(SV *src, TupleDesc td);
283283

@@ -1163,11 +1163,16 @@ get_perl_array_ref(SV *sv)
11631163

11641164
/*
11651165
* helper function for plperl_array_to_datum, recurses for multi-D arrays
1166+
*
1167+
* The ArrayBuildState is created only when we first find a scalar element;
1168+
* if we didn't do it like that, we'd need some other convention for knowing
1169+
* whether we'd already found any scalars (and thus the number of dimensions
1170+
* is frozen).
11661171
*/
11671172
static void
1168-
array_to_datum_internal(AV *av, ArrayBuildState *astate,
1173+
array_to_datum_internal(AV *av, ArrayBuildState **astatep,
11691174
int *ndims, int *dims, int cur_depth,
1170-
Oid arraytypid, Oid elemtypid, int32 typmod,
1175+
Oid elemtypid, int32 typmod,
11711176
FmgrInfo *finfo, Oid typioparam)
11721177
{
11731178
dTHX;
@@ -1187,28 +1192,34 @@ array_to_datum_internal(AV *av, ArrayBuildState *astate,
11871192
{
11881193
AV *nav = (AV *) SvRV(sav);
11891194

1190-
/* dimensionality checks */
1191-
if (cur_depth + 1 > MAXDIM)
1192-
ereport(ERROR,
1193-
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1194-
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
1195-
cur_depth + 1, MAXDIM)));
1196-
11971195
/* set size when at first element in this level, else compare */
11981196
if (i == 0 && *ndims == cur_depth)
11991197
{
1198+
/* array after some scalars at same level? */
1199+
if (*astatep != NULL)
1200+
ereport(ERROR,
1201+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1202+
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
1203+
/* too many dimensions? */
1204+
if (cur_depth + 1 > MAXDIM)
1205+
ereport(ERROR,
1206+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1207+
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
1208+
cur_depth + 1, MAXDIM)));
1209+
/* OK, add a dimension */
12001210
dims[*ndims] = av_len(nav) + 1;
12011211
(*ndims)++;
12021212
}
1203-
else if (av_len(nav) + 1 != dims[cur_depth])
1213+
else if (cur_depth >= *ndims ||
1214+
av_len(nav) + 1 != dims[cur_depth])
12041215
ereport(ERROR,
12051216
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
12061217
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
12071218

12081219
/* recurse to fetch elements of this sub-array */
1209-
array_to_datum_internal(nav, astate,
1220+
array_to_datum_internal(nav, astatep,
12101221
ndims, dims, cur_depth + 1,
1211-
arraytypid, elemtypid, typmod,
1222+
elemtypid, typmod,
12121223
finfo, typioparam);
12131224
}
12141225
else
@@ -1230,7 +1241,13 @@ array_to_datum_internal(AV *av, ArrayBuildState *astate,
12301241
typioparam,
12311242
&isnull);
12321243

1233-
(void) accumArrayResult(astate, dat, isnull,
1244+
/* Create ArrayBuildState if we didn't already */
1245+
if (*astatep == NULL)
1246+
*astatep = initArrayResult(elemtypid,
1247+
CurrentMemoryContext, true);
1248+
1249+
/* ... and save the element value in it */
1250+
(void) accumArrayResult(*astatep, dat, isnull,
12341251
elemtypid, CurrentMemoryContext);
12351252
}
12361253
}
@@ -1243,7 +1260,8 @@ static Datum
12431260
plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
12441261
{
12451262
dTHX;
1246-
ArrayBuildState *astate;
1263+
AV *nav = (AV *) SvRV(src);
1264+
ArrayBuildState *astate = NULL;
12471265
Oid elemtypid;
12481266
FmgrInfo finfo;
12491267
Oid typioparam;
@@ -1259,21 +1277,19 @@ plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
12591277
errmsg("cannot convert Perl array to non-array type %s",
12601278
format_type_be(typid))));
12611279

1262-
astate = initArrayResult(elemtypid, CurrentMemoryContext, true);
1263-
12641280
_sv_to_datum_finfo(elemtypid, &finfo, &typioparam);
12651281

12661282
memset(dims, 0, sizeof(dims));
1267-
dims[0] = av_len((AV *) SvRV(src)) + 1;
1283+
dims[0] = av_len(nav) + 1;
12681284

1269-
array_to_datum_internal((AV *) SvRV(src), astate,
1285+
array_to_datum_internal(nav, &astate,
12701286
&ndims, dims, 1,
1271-
typid, elemtypid, typmod,
1287+
elemtypid, typmod,
12721288
&finfo, typioparam);
12731289

12741290
/* ensure we get zero-D array for no inputs, as per PG convention */
1275-
if (dims[0] <= 0)
1276-
ndims = 0;
1291+
if (astate == NULL)
1292+
return PointerGetDatum(construct_empty_array(elemtypid));
12771293

12781294
for (i = 0; i < ndims; i++)
12791295
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)