v8/test/unittests/heap/bitmap-unittest.cc
Ulan Degenbaev 8e8a06fac9 [heap] Fix an out-of-bounds access in the marking bitmap
Deserializer can trigger OOB read in the marking bitmap inside the
RegisterDeserializedObjectsForBlackAllocation function. This happens
for example if an internalized string is deserialized as the last object
on a page and is the turned into a thin-string leaving a one-word filler
at the end of the page. In such a case IsBlack(filler) will try to fetch
a cell outside the marking bitmap.

The fix is to increase the size of the marking bitmap by one cell, so
that it is always safe to query markbits of any object on a page.

Bug: chromium:978156
Change-Id: If3c74e4f97d2caeb3c3f37a4147f38dea5f0e5a8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2152838
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67223}
2020-04-20 09:07:57 +00:00

163 lines
5.2 KiB
C++

// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/spaces.h"
#include "test/unittests/heap/bitmap-test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
const uint32_t kBlackCell = 0xAAAAAAAA;
const uint32_t kWhiteCell = 0x00000000;
const uint32_t kBlackByte = 0xAA;
const uint32_t kWhiteByte = 0x00;
template <typename T>
using BitmapTest = TestWithBitmap<T>;
TYPED_TEST_SUITE(BitmapTest, BitmapTypes);
using NonAtomicBitmapTest =
TestWithBitmap<ConcurrentBitmap<AccessMode::NON_ATOMIC>>;
TEST_F(NonAtomicBitmapTest, IsZeroInitialized) {
// We require all tests to start from a zero-initialized bitmap. Manually
// verify this invariant here.
for (size_t i = 0; i < Bitmap::kSize; i++) {
EXPECT_EQ(raw_bitmap()[i], kWhiteByte);
}
}
TEST_F(NonAtomicBitmapTest, Cells) {
auto bm = bitmap();
bm->cells()[1] = kBlackCell;
uint8_t* raw = raw_bitmap();
int second_cell_base = Bitmap::kBytesPerCell;
for (size_t i = 0; i < Bitmap::kBytesPerCell; i++) {
EXPECT_EQ(raw[second_cell_base + i], kBlackByte);
}
}
TEST_F(NonAtomicBitmapTest, CellsCount) {
size_t last_cell_index = bitmap()->CellsCount() - 1;
bitmap()->cells()[last_cell_index] = kBlackCell;
// Manually verify on raw memory.
uint8_t* raw = raw_bitmap();
for (size_t i = 0; i < Bitmap::kSize; i++) {
// Last cell should be set.
if (i >= (Bitmap::kSize - Bitmap::kBytesPerCell)) {
EXPECT_EQ(raw[i], kBlackByte);
} else {
EXPECT_EQ(raw[i], kWhiteByte);
}
}
}
TEST_F(NonAtomicBitmapTest, IsClean) {
auto bm = bitmap();
EXPECT_TRUE(bm->IsClean());
bm->cells()[0] = kBlackCell;
EXPECT_FALSE(bm->IsClean());
}
TYPED_TEST(BitmapTest, Clear) {
auto bm = this->bitmap();
for (size_t i = 0; i < Bitmap::kSize; i++) {
this->raw_bitmap()[i] = 0xFFu;
}
bm->Clear();
for (size_t i = 0; i < Bitmap::kSize; i++) {
EXPECT_EQ(this->raw_bitmap()[i], 0);
}
}
TYPED_TEST(BitmapTest, MarkAllBits) {
auto bm = this->bitmap();
bm->MarkAllBits();
for (size_t i = 0; i < Bitmap::kSize; i++) {
EXPECT_EQ(this->raw_bitmap()[i], 0xFF);
}
}
TYPED_TEST(BitmapTest, ClearRange1) {
auto bm = this->bitmap();
bm->cells()[0] = kBlackCell;
bm->cells()[1] = kBlackCell;
bm->cells()[2] = kBlackCell;
bm->ClearRange(0, Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2);
EXPECT_EQ(bm->cells()[0], kWhiteCell);
EXPECT_EQ(bm->cells()[1], 0xAAAA0000);
EXPECT_EQ(bm->cells()[2], kBlackCell);
}
TYPED_TEST(BitmapTest, ClearRange2) {
auto bm = this->bitmap();
bm->cells()[0] = kBlackCell;
bm->cells()[1] = kBlackCell;
bm->cells()[2] = kBlackCell;
bm->ClearRange(Bitmap::kBitsPerCell,
Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2);
EXPECT_EQ(bm->cells()[0], kBlackCell);
EXPECT_EQ(bm->cells()[1], 0xAAAA0000);
EXPECT_EQ(bm->cells()[2], kBlackCell);
}
TYPED_TEST(BitmapTest, SetAndClearRange) {
auto bm = this->bitmap();
for (int i = 0; i < 3; i++) {
bm->SetRange(i, Bitmap::kBitsPerCell + i);
CHECK_EQ(bm->cells()[0], 0xFFFFFFFFu << i);
CHECK_EQ(bm->cells()[1], (1u << i) - 1);
bm->ClearRange(i, Bitmap::kBitsPerCell + i);
CHECK_EQ(bm->cells()[0], 0x0u);
CHECK_EQ(bm->cells()[1], 0x0u);
}
}
// AllBitsSetInRange() and AllBitsClearInRange() are only used when verifying
// the heap on the main thread so they don't have atomic implementations.
TEST_F(NonAtomicBitmapTest, ClearMultipleRanges) {
auto bm = this->bitmap();
bm->SetRange(0, Bitmap::kBitsPerCell * 3);
CHECK(bm->AllBitsSetInRange(0, Bitmap::kBitsPerCell));
bm->ClearRange(Bitmap::kBitsPerCell / 2, Bitmap::kBitsPerCell);
bm->ClearRange(Bitmap::kBitsPerCell,
Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2);
bm->ClearRange(Bitmap::kBitsPerCell * 2 + 8, Bitmap::kBitsPerCell * 2 + 16);
bm->ClearRange(Bitmap::kBitsPerCell * 2 + 24, Bitmap::kBitsPerCell * 3);
CHECK_EQ(bm->cells()[0], 0xFFFFu);
CHECK(bm->AllBitsSetInRange(0, Bitmap::kBitsPerCell / 2));
CHECK(
bm->AllBitsClearInRange(Bitmap::kBitsPerCell / 2, Bitmap::kBitsPerCell));
CHECK_EQ(bm->cells()[1], 0xFFFF0000u);
CHECK(bm->AllBitsClearInRange(
Bitmap::kBitsPerCell, Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2));
CHECK(bm->AllBitsSetInRange(Bitmap::kBitsPerCell + Bitmap::kBitsPerCell / 2,
Bitmap::kBitsPerCell * 2));
CHECK_EQ(bm->cells()[2], 0xFF00FFu);
CHECK(bm->AllBitsSetInRange(
Bitmap::kBitsPerCell * 2,
Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 4));
CHECK(bm->AllBitsClearInRange(
Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 4,
Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 2));
CHECK(bm->AllBitsSetInRange(
Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 2,
Bitmap::kBitsPerCell * 2 + Bitmap::kBitsPerCell / 2 +
Bitmap::kBitsPerCell / 4));
CHECK(bm->AllBitsClearInRange(Bitmap::kBitsPerCell * 2 +
Bitmap::kBitsPerCell / 2 +
Bitmap::kBitsPerCell / 4,
Bitmap::kBitsPerCell * 3));
}
} // namespace internal
} // namespace v8