Skip to content

Commit cd4ca9d

Browse files
authored
Fix int overflow in Arrow text body buffer computation (#9807)
The PG text values can be up to 1GB in size, so the computation for extending the Arrow text body buffer could overflow. Switch it to use the Size type.
1 parent ee80bfa commit cd4ca9d

4 files changed

Lines changed: 134 additions & 5 deletions

File tree

tsl/src/nodes/vector_agg/exec.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ typedef struct
7272

7373
uint64 *restrict validity;
7474

75-
int allocated_body_bytes;
75+
Size allocated_body_bytes;
7676
uint8 *restrict body_buffer;
7777

7878
uint32 *restrict offset_buffer;
@@ -171,9 +171,19 @@ columnar_result_set_row(ColumnarResult *columnar_result, DecompressBatchState co
171171
}
172172
case DT_ArrowText:
173173
{
174-
const int result_bytes = VARSIZE_ANY_EXHDR(datum);
175-
const int required_body_bytes =
174+
const Size result_bytes = VARSIZE_ANY_EXHDR(datum);
175+
const Size required_body_bytes =
176176
pad_to_multiple(64, columnar_result->current_offset + result_bytes);
177+
if (required_body_bytes > MaxAllocSize)
178+
{
179+
/*
180+
* The body buffer is allocated through palloc, so it cannot
181+
* exceed MaxAllocSize.
182+
*/
183+
ereport(ERROR,
184+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
185+
errmsg("vectorized text result exceeds %zu bytes", (Size) MaxAllocSize)));
186+
}
177187
if (required_body_bytes > columnar_result->allocated_body_bytes)
178188
{
179189
/*
@@ -183,8 +193,9 @@ columnar_result_set_row(ColumnarResult *columnar_result, DecompressBatchState co
183193
* tuned manually on a few real data sets until this balance
184194
* looked somewhat acceptable.
185195
*/
186-
const int new_body_bytes =
187-
required_body_bytes * Min(10, Max(1.2, 1.2 * nrows / ((float) row + 1))) + 1;
196+
const double desired_growth_factor = 1.2 * nrows / ((double) row + 1);
197+
const double growth_factor = Min(10, Max(1.2, desired_growth_factor));
198+
Size new_body_bytes = Min(MaxAllocSize, (Size) required_body_bytes * growth_factor);
188199
Assert(new_body_bytes >= required_body_bytes);
189200
columnar_result->body_buffer =
190201
repalloc(columnar_result->body_buffer, new_body_bytes);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
-- This file and its contents are licensed under the Timescale License.
2+
-- Please see the included NOTICE for copyright information and
3+
-- LICENSE-TIMESCALE for a copy of the license.
4+
-- Tests for body buffer overflow in columnar_result_set_row with
5+
-- DT_ArrowText type. When vectorized text functions produce large
6+
-- results, the body buffer growth computation could overflow int32,
7+
-- and the total buffer size can exceed MaxAllocSize.
8+
set max_parallel_workers_per_gather to 0;
9+
create table t_textoverflow (time timestamptz, a text);
10+
select table_name from create_hypertable('t_textoverflow', 'time',
11+
chunk_time_interval => interval '1 year');
12+
table_name
13+
----------------
14+
t_textoverflow
15+
16+
insert into t_textoverflow
17+
select '2020-01-01'::timestamptz + i * interval '1 hour',
18+
repeat('x', 100000)
19+
from generate_series(1, 10) i;
20+
alter table t_textoverflow set (
21+
timescaledb.compress,
22+
timescaledb.compress_orderby = 'time'
23+
);
24+
select count(compress_chunk(c)) from show_chunks('t_textoverflow') c;
25+
count
26+
-------
27+
1
28+
29+
set timescaledb.debug_require_vector_agg to 'require';
30+
select sum(length(a || a)) from t_textoverflow;
31+
sum
32+
---------
33+
2000000
34+
35+
reset timescaledb.debug_require_vector_agg;
36+
drop table t_textoverflow cascade;
37+
-- When the actual data doesn't fit into MaxAllocSize, produce an error message
38+
-- instead of erroring out in palloc.
39+
create table t_text_overflow (time timestamptz not null, text_col text not null);
40+
select table_name from create_hypertable('t_text_overflow', 'time',
41+
chunk_time_interval => interval '1 year');
42+
table_name
43+
-----------------
44+
t_text_overflow
45+
46+
insert into t_text_overflow
47+
select '2020-01-01'::timestamptz + i * interval '1 hour', 'a'
48+
from generate_series(1, 1000) i;
49+
alter table t_text_overflow set (
50+
timescaledb.compress,
51+
timescaledb.compress_orderby = 'time'
52+
);
53+
select count(compress_chunk(c)) from show_chunks('t_text_overflow') c;
54+
count
55+
-------
56+
1
57+
58+
set timescaledb.debug_require_vector_agg to 'require';
59+
select count(text_col || repeat('x', 2000000)) from t_text_overflow;
60+
ERROR: vectorized text result exceeds 1073741823 bytes

tsl/test/sql/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug)
193193
vector_agg_filter.sql
194194
vector_agg_grouping.sql
195195
vector_agg_text.sql
196+
vector_agg_text_overflow.sql
196197
vector_agg_memory.sql
197198
vector_agg_modify_hypertable.sql
198199
vector_agg_segmentby.sql
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
-- This file and its contents are licensed under the Timescale License.
2+
-- Please see the included NOTICE for copyright information and
3+
-- LICENSE-TIMESCALE for a copy of the license.
4+
5+
-- Tests for body buffer overflow in columnar_result_set_row with
6+
-- DT_ArrowText type. When vectorized text functions produce large
7+
-- results, the body buffer growth computation could overflow int32,
8+
-- and the total buffer size can exceed MaxAllocSize.
9+
10+
set max_parallel_workers_per_gather to 0;
11+
12+
create table t_textoverflow (time timestamptz, a text);
13+
14+
select table_name from create_hypertable('t_textoverflow', 'time',
15+
chunk_time_interval => interval '1 year');
16+
17+
insert into t_textoverflow
18+
select '2020-01-01'::timestamptz + i * interval '1 hour',
19+
repeat('x', 100000)
20+
from generate_series(1, 10) i;
21+
22+
alter table t_textoverflow set (
23+
timescaledb.compress,
24+
timescaledb.compress_orderby = 'time'
25+
);
26+
select count(compress_chunk(c)) from show_chunks('t_textoverflow') c;
27+
28+
set timescaledb.debug_require_vector_agg to 'require';
29+
select sum(length(a || a)) from t_textoverflow;
30+
reset timescaledb.debug_require_vector_agg;
31+
32+
drop table t_textoverflow cascade;
33+
34+
-- When the actual data doesn't fit into MaxAllocSize, produce an error message
35+
-- instead of erroring out in palloc.
36+
create table t_text_overflow (time timestamptz not null, text_col text not null);
37+
38+
select table_name from create_hypertable('t_text_overflow', 'time',
39+
chunk_time_interval => interval '1 year');
40+
41+
insert into t_text_overflow
42+
select '2020-01-01'::timestamptz + i * interval '1 hour', 'a'
43+
from generate_series(1, 1000) i;
44+
45+
alter table t_text_overflow set (
46+
timescaledb.compress,
47+
timescaledb.compress_orderby = 'time'
48+
);
49+
select count(compress_chunk(c)) from show_chunks('t_text_overflow') c;
50+
51+
set timescaledb.debug_require_vector_agg to 'require';
52+
select count(text_col || repeat('x', 2000000)) from t_text_overflow;
53+
reset timescaledb.debug_require_vector_agg;
54+
55+
drop table t_text_overflow cascade;
56+
57+
reset max_parallel_workers_per_gather;

0 commit comments

Comments
 (0)