Commit bcb8d49b authored by Yang Guo's avatar Yang Guo Committed by Commit Bot

[snapshot] add checksum to startup snapshot

We already had checksumming for code cache data. We now extend
checksumming to the startup snapshot to catch data corruption early.

The performance impact for deserialization is a regression of 1-2%,
which should be acceptable.

Sample output for the included test with --profile-deserialization:

[Verifying snapshot checksum took 0.023 ms]
[Deserializing isolate (134348 bytes) took 1.891 ms]
[Verifying snapshot checksum took 0.024 ms]
[Deserializing isolate (134348 bytes) took 1.654 ms]
[Deserializing context #0 (47208 bytes) took 0.331 ms]
Deserialization will reserve:
    208168 bytes per isolate
    123368 bytes per context #0
Snapshot blob consists of:
    134492 bytes in 6 chunks for startup
    115272 bytes for builtins
     47152 bytes in 31 chunks for context #0
[Verifying snapshot checksum took 0.048 ms]
[Verifying snapshot checksum took 0.043 ms]

R=peria@chromium.org, petermarshall@chromium.org

Bug: chromium:881417
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: Ibc57520d459c86be8972f731aa35045b5e3751d7
Reviewed-on: https://chromium-review.googlesource.com/1241874Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Commit-Queue: Yang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56217}
parent e4396813
......@@ -835,6 +835,7 @@ StartupData SnapshotCreator::CreateBlob(
}
data->created_ = true;
DCHECK(i::Snapshot::VerifyChecksum(&result));
return result;
}
......
......@@ -34,13 +34,16 @@ void BuiltinSerializer::SerializeBuiltinsAndHandlers() {
SerializeBuiltin(code);
}
// Pad with kNop since GetInt() might read too far.
Pad();
// Append the offset table. During deserialization, the offset table is
// extracted by BuiltinSnapshotData.
const byte* data = reinterpret_cast<const byte*>(&code_offsets_[0]);
int data_length = static_cast<int>(sizeof(code_offsets_));
// Pad with kNop since GetInt() might read too far.
Pad(data_length);
// Append the offset table. During deserialization, the offset table is
// extracted by BuiltinSnapshotData.
sink_.PutRaw(data, data_length, "BuiltinOffsets");
}
......
......@@ -336,44 +336,6 @@ MaybeHandle<SharedFunctionInfo> CodeSerializer::Deserialize(
return scope.CloseAndEscape(result);
}
class Checksum {
public:
explicit Checksum(Vector<const byte> payload) {
#ifdef MEMORY_SANITIZER
// Computing the checksum includes padding bytes for objects like strings.
// Mark every object as initialized in the code serializer.
MSAN_MEMORY_IS_INITIALIZED(payload.start(), payload.length());
#endif // MEMORY_SANITIZER
// Fletcher's checksum. Modified to reduce 64-bit sums to 32-bit.
uintptr_t a = 1;
uintptr_t b = 0;
const uintptr_t* cur = reinterpret_cast<const uintptr_t*>(payload.start());
DCHECK(IsAligned(payload.length(), kIntptrSize));
const uintptr_t* end = cur + payload.length() / kIntptrSize;
while (cur < end) {
// Unsigned overflow expected and intended.
a += *cur++;
b += a;
}
#if V8_HOST_ARCH_64_BIT
a ^= a >> 32;
b ^= b >> 32;
#endif // V8_HOST_ARCH_64_BIT
a_ = static_cast<uint32_t>(a);
b_ = static_cast<uint32_t>(b);
}
bool Check(uint32_t a, uint32_t b) const { return a == a_ && b == b_; }
uint32_t a() const { return a_; }
uint32_t b() const { return b_; }
private:
uint32_t a_;
uint32_t b_;
DISALLOW_COPY_AND_ASSIGN(Checksum);
};
SerializedCodeData::SerializedCodeData(const std::vector<byte>* payload,
const CodeSerializer* cs) {
......@@ -390,10 +352,14 @@ SerializedCodeData::SerializedCodeData(const std::vector<byte>* payload,
uint32_t padded_payload_offset = POINTER_SIZE_ALIGN(payload_offset);
uint32_t size =
padded_payload_offset + static_cast<uint32_t>(payload->size());
DCHECK(IsAligned(size, kPointerAlignment));
// Allocate backing store and create result data.
AllocateData(size);
// Zero out pre-payload data. Part of that is only used for padding.
memset(data_, 0, padded_payload_offset);
// Set header values.
SetMagicNumber(cs->isolate());
SetHeaderValue(kVersionHashOffset, Version::Hash());
......@@ -418,16 +384,13 @@ SerializedCodeData::SerializedCodeData(const std::vector<byte>* payload,
CopyBytes(data_ + kHeaderSize + reservation_size,
reinterpret_cast<const byte*>(stub_keys->data()), stub_keys_size);
// Zero out any padding before the payload.
memset(data_ + payload_offset, 0, padded_payload_offset - payload_offset);
// Copy serialized data.
CopyBytes(data_ + padded_payload_offset, payload->data(),
static_cast<size_t>(payload->size()));
Checksum checksum(DataWithoutHeader());
SetHeaderValue(kChecksum1Offset, checksum.a());
SetHeaderValue(kChecksum2Offset, checksum.b());
Checksum checksum(ChecksummedContent());
SetHeaderValue(kChecksumPartAOffset, checksum.a());
SetHeaderValue(kChecksumPartBOffset, checksum.b());
}
SerializedCodeData::SanityCheckResult SerializedCodeData::SanityCheck(
......@@ -440,8 +403,8 @@ SerializedCodeData::SanityCheckResult SerializedCodeData::SanityCheck(
uint32_t cpu_features = GetHeaderValue(kCpuFeaturesOffset);
uint32_t flags_hash = GetHeaderValue(kFlagHashOffset);
uint32_t payload_length = GetHeaderValue(kPayloadLengthOffset);
uint32_t c1 = GetHeaderValue(kChecksum1Offset);
uint32_t c2 = GetHeaderValue(kChecksum2Offset);
uint32_t c1 = GetHeaderValue(kChecksumPartAOffset);
uint32_t c2 = GetHeaderValue(kChecksumPartBOffset);
if (version_hash != Version::Hash()) return VERSION_MISMATCH;
if (source_hash != expected_source_hash) return SOURCE_MISMATCH;
if (cpu_features != static_cast<uint32_t>(CpuFeatures::SupportedFeatures())) {
......@@ -454,7 +417,7 @@ SerializedCodeData::SanityCheckResult SerializedCodeData::SanityCheck(
GetHeaderValue(kNumReservationsOffset) * kInt32Size +
GetHeaderValue(kNumCodeStubKeysOffset) * kInt32Size);
if (payload_length > max_payload_length) return LENGTH_MISMATCH;
if (!Checksum(DataWithoutHeader()).Check(c1, c2)) return CHECKSUM_MISMATCH;
if (!Checksum(ChecksummedContent()).Check(c1, c2)) return CHECKSUM_MISMATCH;
return CHECK_SUCCESS;
}
......
......@@ -110,8 +110,8 @@ class SerializedCodeData : public SerializedData {
// [6] number of code stub keys
// [7] number of reservation size entries
// [8] payload length
// [9] payload checksum part 1
// [10] payload checksum part 2
// [9] payload checksum part A
// [10] payload checksum part B
// ... reservations
// ... code stub keys
// ... serialized payload
......@@ -124,9 +124,12 @@ class SerializedCodeData : public SerializedData {
kNumReservationsOffset + kUInt32Size;
static const uint32_t kPayloadLengthOffset =
kNumCodeStubKeysOffset + kUInt32Size;
static const uint32_t kChecksum1Offset = kPayloadLengthOffset + kUInt32Size;
static const uint32_t kChecksum2Offset = kChecksum1Offset + kUInt32Size;
static const uint32_t kUnalignedHeaderSize = kChecksum2Offset + kUInt32Size;
static const uint32_t kChecksumPartAOffset =
kPayloadLengthOffset + kUInt32Size;
static const uint32_t kChecksumPartBOffset =
kChecksumPartAOffset + kUInt32Size;
static const uint32_t kUnalignedHeaderSize =
kChecksumPartBOffset + kUInt32Size;
static const uint32_t kHeaderSize = POINTER_SIZE_ALIGN(kUnalignedHeaderSize);
// Used when consuming.
......@@ -155,7 +158,7 @@ class SerializedCodeData : public SerializedData {
SerializedCodeData(const byte* data, int size)
: SerializedData(const_cast<byte*>(data), size) {}
Vector<const byte> DataWithoutHeader() const {
Vector<const byte> ChecksummedContent() const {
return Vector<const byte>(data_ + kHeaderSize, size_ - kHeaderSize);
}
......
......@@ -350,6 +350,45 @@ class SerializedData {
DISALLOW_COPY_AND_ASSIGN(SerializedData);
};
class Checksum {
public:
explicit Checksum(Vector<const byte> payload) {
#ifdef MEMORY_SANITIZER
// Computing the checksum includes padding bytes for objects like strings.
// Mark every object as initialized in the code serializer.
MSAN_MEMORY_IS_INITIALIZED(payload.start(), payload.length());
#endif // MEMORY_SANITIZER
// Fletcher's checksum. Modified to reduce 64-bit sums to 32-bit.
uintptr_t a = 1;
uintptr_t b = 0;
const uintptr_t* cur = reinterpret_cast<const uintptr_t*>(payload.start());
DCHECK(IsAligned(payload.length(), kIntptrSize));
const uintptr_t* end = cur + payload.length() / kIntptrSize;
while (cur < end) {
// Unsigned overflow expected and intended.
a += *cur++;
b += a;
}
#if V8_HOST_ARCH_64_BIT
a ^= a >> 32;
b ^= b >> 32;
#endif // V8_HOST_ARCH_64_BIT
a_ = static_cast<uint32_t>(a);
b_ = static_cast<uint32_t>(b);
}
bool Check(uint32_t a, uint32_t b) const { return a == a_ && b == b_; }
uint32_t a() const { return a_; }
uint32_t b() const { return b_; }
private:
uint32_t a_;
uint32_t b_;
DISALLOW_COPY_AND_ASSIGN(Checksum);
};
} // namespace internal
} // namespace v8
......
......@@ -327,14 +327,14 @@ void Serializer<AllocatorT>::PutNextChunk(int space) {
}
template <class AllocatorT>
void Serializer<AllocatorT>::Pad() {
void Serializer<AllocatorT>::Pad(int padding_offset) {
// The non-branching GetInt will read up to 3 bytes too far, so we need
// to pad the snapshot to make sure we don't read over the end.
for (unsigned i = 0; i < sizeof(int32_t) - 1; i++) {
sink_.Put(kNop, "Padding");
}
// Pad up to pointer size for checksum.
while (!IsAligned(sink_.Position(), kPointerAlignment)) {
while (!IsAligned(sink_.Position() + padding_offset, kPointerAlignment)) {
sink_.Put(kNop, "Padding");
}
}
......
......@@ -210,7 +210,8 @@ class Serializer : public SerializerDeserializer {
}
// GetInt reads 4 bytes at once, requiring padding at the end.
void Pad();
// Use padding_offset to specify the space you want to use after padding.
void Pad(int padding_offset = 0);
// We may not need the code address map for logging for every instance
// of the serializer. Initialize it on demand.
......
......@@ -44,6 +44,7 @@ bool Snapshot::Initialize(Isolate* isolate) {
const v8::StartupData* blob = isolate->snapshot_blob();
CheckVersion(blob);
CHECK(VerifyChecksum(blob));
Vector<const byte> startup_data = ExtractStartupData(blob);
SnapshotData startup_snapshot_data(startup_data);
Vector<const byte> builtin_data = ExtractBuiltinData(blob);
......@@ -202,15 +203,22 @@ v8::StartupData Snapshot::CreateSnapshotBlob(
uint32_t num_contexts = static_cast<uint32_t>(context_snapshots.size());
uint32_t startup_snapshot_offset = StartupSnapshotOffset(num_contexts);
uint32_t total_length = startup_snapshot_offset;
DCHECK(IsAligned(total_length, kPointerAlignment));
total_length += static_cast<uint32_t>(startup_snapshot->RawData().length());
DCHECK(IsAligned(total_length, kPointerAlignment));
total_length += static_cast<uint32_t>(builtin_snapshot->RawData().length());
DCHECK(IsAligned(total_length, kPointerAlignment));
for (const auto context_snapshot : context_snapshots) {
total_length += static_cast<uint32_t>(context_snapshot->RawData().length());
DCHECK(IsAligned(total_length, kPointerAlignment));
}
ProfileDeserialization(startup_snapshot, builtin_snapshot, context_snapshots);
char* data = new char[total_length];
// Zero out pre-payload data. Part of that is only used for padding.
memset(data, 0, StartupSnapshotOffset(num_contexts));
SetHeaderValue(data, kNumberOfContextsOffset, num_contexts);
SetHeaderValue(data, kRehashabilityOffset, can_be_rehashed ? 1 : 0);
......@@ -260,8 +268,13 @@ v8::StartupData Snapshot::CreateSnapshotBlob(
payload_offset += payload_length;
}
v8::StartupData result = {data, static_cast<int>(total_length)};
DCHECK_EQ(total_length, payload_offset);
v8::StartupData result = {data, static_cast<int>(total_length)};
Checksum checksum(ChecksummedContent(&result));
SetHeaderValue(data, kChecksumPartAOffset, checksum.a());
SetHeaderValue(data, kChecksumPartBOffset, checksum.b());
return result;
}
......@@ -481,6 +494,19 @@ uint32_t Snapshot::ExtractNumContexts(const v8::StartupData* data) {
return num_contexts;
}
bool Snapshot::VerifyChecksum(const v8::StartupData* data) {
base::ElapsedTimer timer;
if (FLAG_profile_deserialization) timer.Start();
uint32_t expected_a = GetHeaderValue(data, kChecksumPartAOffset);
uint32_t expected_b = GetHeaderValue(data, kChecksumPartBOffset);
Checksum checksum(ChecksummedContent(data));
if (FLAG_profile_deserialization) {
double ms = timer.Elapsed().InMillisecondsF();
PrintF("[Verifying snapshot checksum took %0.3f ms]\n", ms);
}
return checksum.Check(expected_a, expected_b);
}
void EmbeddedData::PrintStatistics() const {
DCHECK(FLAG_serialization_statistics);
......@@ -614,12 +640,18 @@ SnapshotData::SnapshotData(const Serializer<AllocatorT>* serializer) {
// Calculate sizes.
uint32_t reservation_size =
static_cast<uint32_t>(reservations.size()) * kUInt32Size;
uint32_t payload_offset = kHeaderSize + reservation_size;
uint32_t padded_payload_offset = POINTER_SIZE_ALIGN(payload_offset);
uint32_t size =
kHeaderSize + reservation_size + static_cast<uint32_t>(payload->size());
padded_payload_offset + static_cast<uint32_t>(payload->size());
DCHECK(IsAligned(size, kPointerAlignment));
// Allocate backing store and create result data.
AllocateData(size);
// Zero out pre-payload data. Part of that is only used for padding.
memset(data_, 0, padded_payload_offset);
// Set header values.
SetMagicNumber(serializer->isolate());
SetHeaderValue(kNumReservationsOffset, static_cast<int>(reservations.size()));
......@@ -630,7 +662,7 @@ SnapshotData::SnapshotData(const Serializer<AllocatorT>* serializer) {
reservation_size);
// Copy serialized data.
CopyBytes(data_ + kHeaderSize + reservation_size, payload->data(),
CopyBytes(data_ + padded_payload_offset, payload->data(),
static_cast<size_t>(payload->size()));
}
......@@ -649,7 +681,9 @@ std::vector<SerializedData::Reservation> SnapshotData::Reservations() const {
Vector<const byte> SnapshotData::Payload() const {
uint32_t reservations_size =
GetHeaderValue(kNumReservationsOffset) * kUInt32Size;
const byte* payload = data_ + kHeaderSize + reservations_size;
uint32_t padded_payload_offset =
POINTER_SIZE_ALIGN(kHeaderSize + reservations_size);
const byte* payload = data_ + padded_payload_offset;
uint32_t length = GetHeaderValue(kPayloadLengthOffset);
DCHECK_EQ(data_ + size_, payload + length);
return Vector<const byte>(payload, length);
......@@ -659,26 +693,21 @@ BuiltinSnapshotData::BuiltinSnapshotData(const BuiltinSerializer* serializer)
: SnapshotData(serializer) {}
Vector<const byte> BuiltinSnapshotData::Payload() const {
uint32_t reservations_size =
GetHeaderValue(kNumReservationsOffset) * kUInt32Size;
const byte* payload = data_ + kHeaderSize + reservations_size;
Vector<const byte> payload = SnapshotData::Payload();
const int builtin_offsets_size = Builtins::builtin_count * kUInt32Size;
uint32_t payload_length = GetHeaderValue(kPayloadLengthOffset);
DCHECK_EQ(data_ + size_, payload + payload_length);
DCHECK_GT(payload_length, builtin_offsets_size);
return Vector<const byte>(payload, payload_length - builtin_offsets_size);
DCHECK_EQ(data_ + size_, payload.start() + payload.size());
DCHECK_GT(payload.size(), builtin_offsets_size);
return Vector<const byte>(payload.start(),
payload.size() - builtin_offsets_size);
}
Vector<const uint32_t> BuiltinSnapshotData::BuiltinOffsets() const {
uint32_t reservations_size =
GetHeaderValue(kNumReservationsOffset) * kUInt32Size;
const byte* payload = data_ + kHeaderSize + reservations_size;
Vector<const byte> payload = SnapshotData::Payload();
const int builtin_offsets_size = Builtins::builtin_count * kUInt32Size;
uint32_t payload_length = GetHeaderValue(kPayloadLengthOffset);
DCHECK_EQ(data_ + size_, payload + payload_length);
DCHECK_GT(payload_length, builtin_offsets_size);
DCHECK_EQ(data_ + size_, payload.start() + payload.size());
DCHECK_GT(payload.size(), builtin_offsets_size);
const uint32_t* data = reinterpret_cast<const uint32_t*>(
payload + payload_length - builtin_offsets_size);
payload.start() + payload.size() - builtin_offsets_size);
return Vector<const uint32_t>(data, Builtins::builtin_count);
}
......
......@@ -183,6 +183,8 @@ class Snapshot : public AllStatic {
// To be implemented by the snapshot source.
static const v8::StartupData* DefaultSnapshotBlob();
static bool VerifyChecksum(const v8::StartupData* data);
// ---------------- Serialization ----------------
static v8::StartupData CreateSnapshotBlob(
......@@ -218,10 +220,12 @@ class Snapshot : public AllStatic {
// Snapshot blob layout:
// [0] number of contexts N
// [1] rehashability
// [2] (128 bytes) version string
// [3] offset to builtins
// [4] offset to context 0
// [5] offset to context 1
// [2] checksum part A
// [3] checksum part B
// [4] (128 bytes) version string
// [5] offset to builtins
// [6] offset to context 0
// [7] offset to context 1
// ...
// ... offset to context N - 1
// ... startup snapshot data
......@@ -233,16 +237,28 @@ class Snapshot : public AllStatic {
// TODO(yangguo): generalize rehashing, and remove this flag.
static const uint32_t kRehashabilityOffset =
kNumberOfContextsOffset + kUInt32Size;
static const uint32_t kVersionStringOffset =
static const uint32_t kChecksumPartAOffset =
kRehashabilityOffset + kUInt32Size;
static const uint32_t kChecksumPartBOffset =
kChecksumPartAOffset + kUInt32Size;
static const uint32_t kVersionStringOffset =
kChecksumPartBOffset + kUInt32Size;
static const uint32_t kVersionStringLength = 64;
static const uint32_t kBuiltinOffsetOffset =
kVersionStringOffset + kVersionStringLength;
static const uint32_t kFirstContextOffsetOffset =
kBuiltinOffsetOffset + kUInt32Size;
static Vector<const byte> ChecksummedContent(const v8::StartupData* data) {
const uint32_t kChecksumStart = kVersionStringOffset;
return Vector<const byte>(
reinterpret_cast<const byte*>(data->data + kChecksumStart),
data->raw_size - kChecksumStart);
}
static uint32_t StartupSnapshotOffset(int num_contexts) {
return kFirstContextOffsetOffset + num_contexts * kInt32Size;
return POINTER_SIZE_ALIGN(kFirstContextOffsetOffset +
num_contexts * kInt32Size);
}
static uint32_t ContextSnapshotOffsetOffset(int index) {
......
......@@ -785,6 +785,17 @@ TEST(CustomSnapshotDataBlob1) {
delete[] data1.data; // We can dispose of the snapshot blob now.
}
TEST(SnapshotChecksum) {
DisableAlwaysOpt();
const char* source1 = "function f() { return 42; }";
v8::StartupData data1 = CreateSnapshotDataBlob(source1);
CHECK(i::Snapshot::VerifyChecksum(&data1));
const_cast<char*>(data1.data)[142] = data1.data[142] ^ 4; // Flip a bit.
CHECK(!i::Snapshot::VerifyChecksum(&data1));
delete[] data1.data; // We can dispose of the snapshot blob now.
}
struct InternalFieldData {
uint32_t data;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment