Skip to content

Commit cb1dc29

Browse files
author
anand76
committed
Fix a buffer overrun problem in BlockBasedTable::MultiGet (facebook#6014)
Summary: The calculation in BlockBasedTable::MultiGet for the required buffer length for reading in compressed blocks is incorrect. It needs to take the 5-byte block trailer into account. Pull Request resolved: facebook#6014 Test Plan: Add a unit test DBBasicTest.MultiGetBufferOverrun that fails in asan_check before the fix, and passes after. Differential Revision: D18412753 Pulled By: anand1976 fbshipit-source-id: 754dfb66be1d5f161a7efdf87be872198c7e3b72
1 parent 98e5189 commit cb1dc29

File tree

5 files changed

+57
-8
lines changed

5 files changed

+57
-8
lines changed

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
## Unreleased
33
### Bug Fixes
44
* Fix a assertion failure in MultiGe4t() when BlockBasedTableOptions::no_block_cache is true and there is no compressed block cache
5+
* Fix a buffer overrun problem in BlockBasedTable::MultiGet() when compression is enabled and no compressed block cache is configured.
56

67
## 6.5.1 (10/16/2019)
78
### Bug Fixes

db/db_basic_test.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "port/stack_trace.h"
1212
#include "rocksdb/perf_context.h"
1313
#include "rocksdb/utilities/debug.h"
14+
#include "table/block_based/block_based_table_reader.h"
1415
#include "table/block_based/block_builder.h"
1516
#include "test_util/fault_injection_test_env.h"
1617
#if !defined(ROCKSDB_LITE)
@@ -1541,6 +1542,45 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
15411542
}
15421543
#endif // !ROCKSDB_LITE
15431544

1545+
TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
1546+
Options options = CurrentOptions();
1547+
Random rnd(301);
1548+
BlockBasedTableOptions table_options;
1549+
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
1550+
table_options.block_size = 16 * 1024;
1551+
assert(table_options.block_size >
1552+
BlockBasedTable::kMultiGetReadStackBufSize);
1553+
options.table_factory.reset(new BlockBasedTableFactory(table_options));
1554+
Reopen(options);
1555+
1556+
std::string zero_str(128, '\0');
1557+
for (int i = 0; i < 100; ++i) {
1558+
// Make the value compressible. A purely random string doesn't compress
1559+
// and the resultant data block will not be compressed
1560+
std::string value(RandomString(&rnd, 128) + zero_str);
1561+
assert(Put(Key(i), value) == Status::OK());
1562+
}
1563+
Flush();
1564+
1565+
std::vector<std::string> key_data(10);
1566+
std::vector<Slice> keys;
1567+
// We cannot resize a PinnableSlice vector, so just set initial size to
1568+
// largest we think we will need
1569+
std::vector<PinnableSlice> values(10);
1570+
std::vector<Status> statuses;
1571+
ReadOptions ro;
1572+
1573+
// Warm up the cache first
1574+
key_data.emplace_back(Key(0));
1575+
keys.emplace_back(Slice(key_data.back()));
1576+
key_data.emplace_back(Key(50));
1577+
keys.emplace_back(Slice(key_data.back()));
1578+
statuses.resize(keys.size());
1579+
1580+
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
1581+
keys.data(), values.data(), statuses.data(), true);
1582+
}
1583+
15441584
class DBBasicTestWithParallelIO
15451585
: public DBTestBase,
15461586
public testing::WithParamInterface<std::tuple<bool,bool,bool,bool>> {

table/block_based/block_based_table_reader.cc

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ class PartitionIndexReader : public BlockBasedTable::IndexReaderCommon {
474474
return;
475475
}
476476
handle = biter.value().handle;
477-
uint64_t last_off = handle.offset() + handle.size() + kBlockTrailerSize;
477+
uint64_t last_off = handle.offset() + block_size(handle);
478478
uint64_t prefetch_len = last_off - prefetch_off;
479479
std::unique_ptr<FilePrefetchBuffer> prefetch_buffer;
480480
auto& file = rep->file;
@@ -2299,7 +2299,7 @@ void BlockBasedTable::RetrieveMultipleBlocks(
22992299
}
23002300

23012301
ReadRequest req;
2302-
req.len = handle.size() + kBlockTrailerSize;
2302+
req.len = block_size(handle);
23032303
if (scratch == nullptr) {
23042304
req.scratch = new char[req.len];
23052305
} else {
@@ -2326,11 +2326,11 @@ void BlockBasedTable::RetrieveMultipleBlocks(
23262326
ReadRequest& req = read_reqs[read_req_idx++];
23272327
Status s = req.status;
23282328
if (s.ok()) {
2329-
if (req.result.size() != handle.size() + kBlockTrailerSize) {
2329+
if (req.result.size() != req.len) {
23302330
s = Status::Corruption("truncated block read from " +
23312331
rep_->file->file_name() + " offset " +
23322332
ToString(handle.offset()) + ", expected " +
2333-
ToString(handle.size() + kBlockTrailerSize) +
2333+
ToString(req.len) +
23342334
" bytes, got " + ToString(req.result.size()));
23352335
}
23362336
}
@@ -2886,8 +2886,7 @@ void BlockBasedTableIterator<TBlockIter, TValue>::InitDataBlock() {
28862886
BlockBasedTable::kMinNumFileReadsToStartAutoReadahead) {
28872887
if (!rep->file->use_direct_io() &&
28882888
(data_block_handle.offset() +
2889-
static_cast<size_t>(data_block_handle.size()) +
2890-
kBlockTrailerSize >
2889+
static_cast<size_t>(block_size(data_block_handle)) >
28912890
readahead_limit_)) {
28922891
// Buffered I/O
28932892
// Discarding the return status of Prefetch calls intentionally, as
@@ -3385,7 +3384,6 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
33853384
autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE> block_handles;
33863385
autovector<CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE> results;
33873386
autovector<Status, MultiGetContext::MAX_BATCH_SIZE> statuses;
3388-
static const size_t kMultiGetReadStackBufSize = 8192;
33893387
char stack_buf[kMultiGetReadStackBufSize];
33903388
std::unique_ptr<char[]> block_buf;
33913389
{
@@ -3467,7 +3465,7 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
34673465
block_handles.emplace_back(BlockHandle::NullBlockHandle());
34683466
} else {
34693467
block_handles.emplace_back(handle);
3470-
total_len += handle.size();
3468+
total_len += block_size(handle);
34713469
}
34723470
}
34733471

table/block_based/block_based_table_reader.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,13 @@ class BlockBasedTable : public TableReader {
461461
void DumpKeyValue(const Slice& key, const Slice& value,
462462
WritableFile* out_file);
463463

464+
// A cumulative data block file read in MultiGet lower than this size will
465+
// use a stack buffer
466+
static constexpr size_t kMultiGetReadStackBufSize = 8192;
467+
464468
friend class PartitionedFilterBlockReader;
465469
friend class PartitionedFilterBlockTest;
470+
friend class DBBasicTest_MultiGetIOBufferOverrun_Test;
466471
};
467472

468473
// Maitaning state of a two-level iteration on a partitioned index structure.

table/format.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ Status ReadFooterFromFile(RandomAccessFileReader* file,
220220
// 1-byte type + 32-bit crc
221221
static const size_t kBlockTrailerSize = 5;
222222

223+
// Make block size calculation for IO less error prone
224+
inline uint64_t block_size(const BlockHandle& handle) {
225+
return handle.size() + kBlockTrailerSize;
226+
}
227+
223228
inline CompressionType get_block_compression_type(const char* block_data,
224229
size_t block_size) {
225230
return static_cast<CompressionType>(block_data[block_size]);

0 commit comments

Comments
 (0)