Skip to content

Commit 72f6339

Browse files
akuzmtimescale-automation
authored andcommitted
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. (cherry picked from commit cd4ca9d)
1 parent 03cd8d3 commit 72f6339

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
@@ -187,6 +187,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug)
187187
vector_agg_filter.sql
188188
vector_agg_grouping.sql
189189
vector_agg_text.sql
190+
vector_agg_text_overflow.sql
190191
vector_agg_memory.sql
191192
vector_agg_modify_hypertable.sql
192193
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)