Skip to content

Commit 9921f67

Browse files
committed
Merge remote-tracking branch 'upstream/main' into limited-api-direct-replacements
2 parents f308f1c + 2f5384f commit 9921f67

9 files changed

Lines changed: 133 additions & 15 deletions

File tree

.github/CODEOWNERS

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ compose.yaml @assignUser @jonkeane @kou @raulcd
5555

5656
## C++
5757
/cpp/src/arrow/ @pitrou
58-
/cpp/src/arrow/acero @westonpace
5958
/cpp/src/arrow/adapters/orc @wgtmac
60-
/cpp/src/arrow/engine @westonpace
6159
/cpp/src/arrow/flight/ @lidavidm
6260
/cpp/src/gandiva @dmitry-chirkov-dremio @lriggs @akravchukdremio @xxlaykxx
6361
/cpp/src/parquet @wgtmac @pitrou

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ repos:
298298
?^ci/scripts/cpp_test\.sh$|
299299
?^ci/scripts/download_tz_database\.sh$|
300300
?^ci/scripts/install_azurite\.sh$|
301+
?^ci/scripts/install_bison\.sh$|
301302
?^ci/scripts/install_ccache\.sh$|
302303
?^ci/scripts/install_chromedriver\.sh$|
303304
?^ci/scripts/install_cmake\.sh$|

ci/scripts/install_bison.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ wget -q "${url}" -O - | tar -xzf - --directory /tmp/bison --strip-components=1
3434

3535
pushd /tmp/bison
3636
./configure --prefix="${prefix}"
37-
make -j$(nproc)
37+
make -j"$(nproc)"
3838
make install
3939
popd
4040

cpp/src/gandiva/precompiled/string_ops.cc

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,12 @@ gdv_int32 utf8_length_ignore_invalid(const char* data, gdv_int32 data_len) {
208208
}
209209
for (int j = 1; j < char_len; ++j) {
210210
if ((data[i + j] & 0xC0) != 0x80) { // bytes following head-byte of glyph
211-
char_len += 1;
211+
// Only the bytes up to the mismatch belong to this (invalid) glyph, so
212+
// advance past them and let the outer loop re-parse the rest. Keeping
213+
// char_len at its declared width would swallow valid characters that
214+
// fall inside the truncated sequence's window.
215+
char_len = j;
216+
break;
212217
}
213218
}
214219
++count;
@@ -2472,6 +2477,21 @@ const char* byte_substr_binary_int32_int32(gdv_int64 context, const char* text,
24722477
return "";
24732478
}
24742479

2480+
int32_t startPos = 0;
2481+
if (offset >= 0) {
2482+
startPos = offset - 1;
2483+
} else if (text_len + offset >= 0) {
2484+
startPos = text_len + offset;
2485+
}
2486+
2487+
// an offset past the end of the text leaves nothing to copy; without this the
2488+
// truncation below yields a negative *out_len that memcpy reads as a huge size.
2489+
// check before allocating so a past-end offset needs no output buffer at all
2490+
if (startPos >= text_len) {
2491+
*out_len = 0;
2492+
return "";
2493+
}
2494+
24752495
char* ret =
24762496
reinterpret_cast<gdv_binary>(gdv_fn_context_arena_malloc(context, text_len));
24772497

@@ -2481,15 +2501,11 @@ const char* byte_substr_binary_int32_int32(gdv_int64 context, const char* text,
24812501
return "";
24822502
}
24832503

2484-
int32_t startPos = 0;
2485-
if (offset >= 0) {
2486-
startPos = offset - 1;
2487-
} else if (text_len + offset >= 0) {
2488-
startPos = text_len + offset;
2489-
}
2490-
2491-
// calculate end position from length and truncate to upper value bounds
2492-
if (startPos + length > text_len) {
2504+
// calculate end position from length and truncate to upper value bounds.
2505+
// startPos < text_len is guaranteed above, so text_len - startPos is positive;
2506+
// comparing against it avoids the startPos + length overflow when length is
2507+
// near INT32_MAX, which would otherwise leave *out_len huge for the memcpy.
2508+
if (length > text_len - startPos) {
24932509
*out_len = text_len - startPos;
24942510
} else {
24952511
*out_len = length;

cpp/src/gandiva/precompiled/string_ops_test.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
#include <gmock/gmock.h>
1919
#include <gtest/gtest.h>
2020

21+
#include <cstring>
2122
#include <limits>
23+
#include <memory>
2224

2325
#include "gandiva/execution_context.h"
2426
#include "gandiva/precompiled/types.h"
@@ -1608,6 +1610,60 @@ TEST(TestStringOps, TestRpadString) {
16081610
EXPECT_EQ(std::string(out_str + 5000, 2), "α");
16091611
}
16101612

1613+
TEST(TestStringOps, TestPadMalformedUtf8NoOverread) {
1614+
gandiva::ExecutionContext ctx;
1615+
uint64_t ctx_ptr = reinterpret_cast<gdv_int64>(&ctx);
1616+
gdv_int32 out_len = 0;
1617+
1618+
// A 4-byte utf8 lead byte followed by non-continuation bytes and no trailing
1619+
// space. utf8_length_ignore_invalid() used to extend the glyph length past
1620+
// the end of the buffer while scanning the continuation bytes. The input is
1621+
// held in an exactly-sized heap buffer so any over-read trips AddressSanitizer.
1622+
const char bytes[] = {'\xF0', 'a', 'a', 'a'};
1623+
const auto text_len = static_cast<gdv_int32>(sizeof(bytes));
1624+
std::unique_ptr<char[]> text(new char[text_len]);
1625+
std::memcpy(text.get(), bytes, text_len);
1626+
const std::string text_str(text.get(), text_len);
1627+
1628+
// The lone lead byte counts as one invalid glyph and the three 'a's as one
1629+
// each, so the length is 4 and padding to width 6 adds two fill characters.
1630+
const char* out_str =
1631+
lpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 6, " ", 1, &out_len);
1632+
EXPECT_EQ(out_len, 6);
1633+
EXPECT_EQ(std::string(out_str, out_len), " " + text_str);
1634+
1635+
out_str = rpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 6, " ", 1, &out_len);
1636+
EXPECT_EQ(out_len, 6);
1637+
EXPECT_EQ(std::string(out_str, out_len), text_str + " ");
1638+
}
1639+
1640+
TEST(TestStringOps, TestPadMalformedUtf8KeepsValidGlyph) {
1641+
gandiva::ExecutionContext ctx;
1642+
uint64_t ctx_ptr = reinterpret_cast<gdv_int64>(&ctx);
1643+
gdv_int32 out_len = 0;
1644+
1645+
// {0xF0, 'a', 0xE2, 0x82, 0xAC}: malformed 4-byte lead + ASCII 'a' + U+20AC €.
1646+
// 0xF0 alone counts as one invalid glyph, then 'a' and € follow on their own,
1647+
// so the count is 3. If the inner scan kept char_len at 4 it would advance the
1648+
// outer loop past 'a', 0xE2, 0x82 and only see the orphaned 0xAC, giving 2.
1649+
// The input sits in an exactly-sized heap buffer so any over-read trips ASAN.
1650+
const char bytes[] = {'\xF0', 'a', '\xE2', '\x82', '\xAC'};
1651+
const auto text_len = static_cast<gdv_int32>(sizeof(bytes));
1652+
std::unique_ptr<char[]> text(new char[text_len]);
1653+
std::memcpy(text.get(), bytes, text_len);
1654+
const std::string text_str(text.get(), text_len);
1655+
1656+
// 3 glyphs padded to width 5 adds two fill characters, out_len = 2 + 5 = 7.
1657+
const char* out_str =
1658+
lpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 5, " ", 1, &out_len);
1659+
EXPECT_EQ(out_len, 7);
1660+
EXPECT_EQ(std::string(out_str, out_len), " " + text_str);
1661+
1662+
out_str = rpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 5, " ", 1, &out_len);
1663+
EXPECT_EQ(out_len, 7);
1664+
EXPECT_EQ(std::string(out_str, out_len), text_str + " ");
1665+
}
1666+
16111667
TEST(TestStringOps, TestRtrim) {
16121668
gandiva::ExecutionContext ctx;
16131669
uint64_t ctx_ptr = reinterpret_cast<gdv_int64>(&ctx);
@@ -1903,6 +1959,20 @@ TEST(TestStringOps, TestByteSubstr) {
19031959
out_str = byte_substr_binary_int32_int32(ctx_ptr, "TestString", 10, -100, 10, &out_len);
19041960
EXPECT_EQ(std::string(out_str, out_len), "TestString");
19051961
EXPECT_FALSE(ctx.has_error());
1962+
1963+
// offset past the end of the text must yield an empty result, not a negative
1964+
// length that memcpy reads as an out-of-bounds copy
1965+
out_str = byte_substr_binary_int32_int32(ctx_ptr, "TestString", 10, 15, 10, &out_len);
1966+
EXPECT_EQ(out_len, 0);
1967+
EXPECT_EQ(std::string(out_str, out_len), "");
1968+
EXPECT_FALSE(ctx.has_error());
1969+
1970+
// a huge length must be truncated to the remaining bytes, not overflow
1971+
// startPos + length and copy far past the end of the text
1972+
out_str =
1973+
byte_substr_binary_int32_int32(ctx_ptr, "TestString", 10, 2, 2147483647, &out_len);
1974+
EXPECT_EQ(std::string(out_str, out_len), "estString");
1975+
EXPECT_FALSE(ctx.has_error());
19061976
}
19071977

19081978
TEST(TestStringOps, TestStrPos) {

cpp/src/gandiva/tests/date_time_test.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ int32_t DaysSince(time_t base_line, int32_t yy, int32_t mm, int32_t dd, int32_t
105105
given_ts.tm_min = min;
106106
given_ts.tm_sec = sec;
107107

108-
time_t ts = mktime(&given_ts);
108+
time_t ts = timegm(&given_ts);
109109
if (ts == static_cast<time_t>(-1)) {
110-
ARROW_LOG(FATAL) << "mktime() failed";
110+
ARROW_LOG(FATAL) << "timegm() failed";
111111
}
112112
// time_t is an arithmetic type on both POSIX and Windows, we can simply
113113
// subtract to get a duration in seconds.

docs/source/cpp/security.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ variants, but these typically fall into two categories:
120120
arguments (this is typical of the :ref:`array builders <cpp-api-array-builders>`
121121
and :ref:`buffer builders <cpp-api-buffer-builders>`).
122122

123+
Controlling and restricting memory allocation
124+
---------------------------------------------
125+
126+
By construction, many Arrow C++ APIs can allocate large amounts of memory, depending
127+
on their input parameters. Arrow C++ allows customizing the memory allocator for
128+
such large data requests through the :ref:`MemoryPool <cpp_memory_pool>` interface.
129+
130+
You can therefore implement a MemoryPool class enforcing the restrictions
131+
of your choice (for example to limit the total number of allocated bytes), and pass
132+
it to any Arrow C++ APIs you use.
133+
134+
.. note::
135+
Unlike memory used for Arrow data, smaller metadata structures (such as field
136+
names, etc.) instead rely on the C++ standard library allocators for convenience.
137+
They will therefore be invisible to the MemoryPool memory accounting.
138+
123139
Ingesting untrusted data
124140
========================
125141

@@ -144,6 +160,13 @@ from an untrusted source), you **must** follow these steps:
144160
2. If the API returned successfully, validate the returned Arrow data in full
145161
(see "Full validity" above)
146162

163+
Furthermore, both the IPC and the Parquet format allow for powerful forms of
164+
compression, and can therefore exhibit large expansion factors when reading.
165+
If you need to guard against potential denial-of-service attacks that would
166+
exhaust available memory, we recommend you enforce memory allocation limits
167+
using a dedicated MemoryPool implementation (see "Controlling and restricting
168+
memory allocation" above).
169+
147170
CSV reader
148171
----------
149172

docs/source/format/Columnar.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,8 @@ have two entries in each RecordBatch. For a RecordBatch of this schema with
13911391
buffer 12: col2 data
13921392
buffer 13: col2 data
13931393

1394+
.. _buffer-compression:
1395+
13941396
Compression
13951397
-----------
13961398

docs/source/format/Security.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ their own risks. For example, buffer offsets and sizes encoded in IPC messages
180180
may be out of bounds for the IPC stream; Flatbuffers-encoded metadata payloads
181181
may carry incorrect offsets pointing outside of the designated metadata area.
182182

183+
In addition, the IPC format provides optional :ref:`buffer compression <buffer-compression>`
184+
using general-purpose compression algorithms. It is therefore possible to craft an IPC
185+
stream or file that acts as a decompression bomb by consuming all available memory,
186+
opening a potential channel for denial-of-service attacks.
187+
183188
Advice for users
184189
----------------
185190

@@ -194,6 +199,9 @@ It is **extremely recommended** to run dedicated validation checks when decoding
194199
the IPC format, to make sure that the decoding can not induce unwanted behavior.
195200
Failing those checks should return a well-known error to the caller, not crash.
196201

202+
It is **recommended** to provide facilities for users to control the memory
203+
allocation behavior when reading an IPC file or stream (for example by making
204+
the allocator customizable).
197205

198206
Extension Types
199207
===============

0 commit comments

Comments
 (0)