// Copyright 2021 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/ios/ios_intermediate_dump_reader.h"
#include <memory>
#include <stack>
#include <vector>
#include "base/logging.h"
#include "util/file/filesystem.h"
#include "util/ios/ios_intermediate_dump_data.h"
#include "util/ios/ios_intermediate_dump_format.h"
#include "util/ios/ios_intermediate_dump_list.h"
#include "util/ios/ios_intermediate_dump_object.h"
#include "util/ios/ios_intermediate_dump_writer.h"
namespace crashpad {
namespace internal {
IOSIntermediateDumpReaderInitializeResult IOSIntermediateDumpReader::Initialize(
const IOSIntermediateDumpInterface& dump_interface) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
// Don't initialize empty files.
FileOffset size = dump_interface.Size();
if (size == 0) {
return IOSIntermediateDumpReaderInitializeResult::kFailure;
}
IOSIntermediateDumpReaderInitializeResult result =
IOSIntermediateDumpReaderInitializeResult::kSuccess;
if (!Parse(dump_interface.FileReader(), size)) {
LOG(ERROR) << "Intermediate dump parsing failed";
result = IOSIntermediateDumpReaderInitializeResult::kIncomplete;
}
INITIALIZATION_STATE_SET_VALID(initialized_);
return result;
}
const IOSIntermediateDumpMap* IOSIntermediateDumpReader::RootMap() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return &intermediate_dump_;
}
bool IOSIntermediateDumpReader::Parse(FileReaderInterface* reader,
FileOffset file_size) {
std::stack<IOSIntermediateDumpObject*> stack;
stack.push(&intermediate_dump_);
using Command = IOSIntermediateDumpWriter::CommandType;
using Type = IOSIntermediateDumpObject::Type;
Command command;
if (!reader->ReadExactly(&command, sizeof(Command)) ||
command != Command::kRootMapStart) {
LOG(ERROR) << "Unexpected start to root map.";
return false;
}
while (reader->ReadExactly(&command, sizeof(Command))) {
constexpr int kMaxStackDepth = 10;
if (stack.size() > kMaxStackDepth) {
LOG(ERROR) << "Unexpected depth of intermediate dump data.";
return false;
}
IOSIntermediateDumpObject* parent = stack.top();
switch (command) {
case Command::kMapStart: {
std::unique_ptr<IOSIntermediateDumpMap> new_map(
new IOSIntermediateDumpMap());
if (parent->GetType() == Type::kMap) {
const auto parent_map = static_cast<IOSIntermediateDumpMap*>(parent);
stack.push(new_map.get());
IntermediateDumpKey key;
if (!reader->ReadExactly(&key, sizeof(key)))
return false;
if (key == IntermediateDumpKey::kInvalid)
return false;
parent_map->map_[key] = std::move(new_map);
} else if (parent->GetType() == Type::kList) {
const auto parent_list =
static_cast<IOSIntermediateDumpList*>(parent);
stack.push(new_map.get());
parent_list->push_back(std::move(new_map));
} else {
LOG(ERROR) << "Unexpected parent (not a map or list).";
return false;
}
break;
}
case Command::kArrayStart: {
auto new_list = std::make_unique<IOSIntermediateDumpList>();
if (parent->GetType() != Type::kMap) {
LOG(ERROR) << "Attempting to push an array not in a map.";
return false;
}
IntermediateDumpKey key;
if (!reader->ReadExactly(&key, sizeof(key)))
return false;
if (key == IntermediateDumpKey::kInvalid)
return false;
auto parent_map = static_cast<IOSIntermediateDumpMap*>(parent);
stack.push(new_list.get());
parent_map->map_[key] = std::move(new_list);
break;
}
case Command::kMapEnd:
if (stack.size() < 2) {
LOG(ERROR) << "Attempting to pop off main map.";
return false;
}
if (parent->GetType() != Type::kMap) {
LOG(ERROR) << "Unexpected map end not in a map.";
return false;
}
stack.pop();
break;
case Command::kArrayEnd:
if (stack.size() < 2) {
LOG(ERROR) << "Attempting to pop off main map.";
return false;
}
if (parent->GetType() != Type::kList) {
LOG(ERROR) << "Unexpected list end not in a list.";
return false;
}
stack.pop();
break;
case Command::kProperty: {
if (parent->GetType() != Type::kMap) {
LOG(ERROR) << "Attempting to add a property not in a map.";
return false;
}
IntermediateDumpKey key;
if (!reader->ReadExactly(&key, sizeof(key)))
return false;
if (key == IntermediateDumpKey::kInvalid)
return false;
size_t value_length;
if (!reader->ReadExactly(&value_length, sizeof(value_length))) {
return false;
}
constexpr int kMaximumPropertyLength = 64 * 1024 * 1024; // 64MB.
if (value_length > kMaximumPropertyLength) {
LOG(ERROR) << "Attempting to read a property that's too big: "
<< value_length;
return false;
}
std::vector<uint8_t> data(value_length);
if (!reader->ReadExactly(data.data(), value_length)) {
return false;
}
auto parent_map = static_cast<IOSIntermediateDumpMap*>(parent);
if (parent_map->map_.find(key) != parent_map->map_.end()) {
LOG(ERROR) << "Inserting duplicate key";
}
parent_map->map_[key] =
std::make_unique<IOSIntermediateDumpData>(std::move(data));
break;
}
case Command::kRootMapEnd: {
if (stack.size() != 1) {
LOG(ERROR) << "Unexpected end of root map.";
return false;
}
if (reader->Seek(0, SEEK_CUR) != file_size) {
LOG(ERROR) << "Root map ended before end of file.";
return false;
}
return true;
}
default:
LOG(ERROR) << "Failed to parse serialized intermediate minidump.";
return false;
}
}
LOG(ERROR) << "Unexpected end of root map.";
return false;
}
} // namespace internal
} // namespace crashpad