Ensure that Skia persistent cache filenames do not exceed the maximum allowed length (flutter/engine#28315)

This commit is contained in:
Jason Simmons 2021-09-02 11:56:02 -07:00 committed by GitHub
parent 71d6a2814f
commit 34f7c9e249
11 changed files with 203 additions and 48 deletions

View File

@ -162,6 +162,9 @@ FILE: ../../../flutter/fml/file.h
FILE: ../../../flutter/fml/file_unittest.cc
FILE: ../../../flutter/fml/hash_combine.h
FILE: ../../../flutter/fml/hash_combine_unittests.cc
FILE: ../../../flutter/fml/hex_codec.cc
FILE: ../../../flutter/fml/hex_codec.h
FILE: ../../../flutter/fml/hex_codec_unittest.cc
FILE: ../../../flutter/fml/icu_util.cc
FILE: ../../../flutter/fml/icu_util.h
FILE: ../../../flutter/fml/log_level.h

View File

@ -22,6 +22,7 @@ source_set("graphics") {
"//flutter/assets",
"//flutter/fml",
"//flutter/shell/version:version",
"//third_party/boringssl",
"//third_party/rapidjson",
"//third_party/skia",
]

View File

@ -11,12 +11,14 @@
#include "flutter/fml/base32.h"
#include "flutter/fml/file.h"
#include "flutter/fml/hex_codec.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/paths.h"
#include "flutter/fml/trace_event.h"
#include "flutter/shell/version/version.h"
#include "openssl/sha.h"
#include "rapidjson/document.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/utils/SkBase64.h"
@ -30,21 +32,17 @@ std::shared_ptr<AssetManager> PersistentCache::asset_manager_;
std::mutex PersistentCache::instance_mutex_;
std::unique_ptr<PersistentCache> PersistentCache::gPersistentCache;
std::string PersistentCache::SkKeyToFilePath(const SkData& data) {
if (data.data() == nullptr || data.size() == 0) {
std::string PersistentCache::SkKeyToFilePath(const SkData& key) {
if (key.data() == nullptr || key.size() == 0) {
return "";
}
std::string_view view(reinterpret_cast<const char*>(data.data()),
data.size());
uint8_t sha_digest[SHA_DIGEST_LENGTH];
SHA1(static_cast<const uint8_t*>(key.data()), key.size(), sha_digest);
auto encode_result = fml::Base32Encode(view);
if (!encode_result.first) {
return "";
}
return encode_result.second;
std::string_view view(reinterpret_cast<const char*>(sha_digest),
SHA_DIGEST_LENGTH);
return fml::HexEncode(view);
}
bool PersistentCache::gIsReadOnly = false;
@ -205,7 +203,7 @@ size_t PersistentCache::PrecompileKnownSkSLs(GrDirectContext* context) const {
size_t precompiled_count = 0;
for (const auto& sksl : known_sksls) {
TRACE_EVENT0("flutter", "PrecompilingSkSL");
if (context->precompileShader(*sksl.first, *sksl.second)) {
if (context->precompileShader(*sksl.key, *sksl.value)) {
precompiled_count++;
}
}
@ -221,10 +219,9 @@ std::vector<PersistentCache::SkSLCache> PersistentCache::LoadSkSLs() const {
std::vector<PersistentCache::SkSLCache> result;
fml::FileVisitor visitor = [&result](const fml::UniqueFD& directory,
const std::string& filename) {
sk_sp<SkData> key = ParseBase32(filename);
sk_sp<SkData> data = LoadFile(directory, filename);
if (key != nullptr && data != nullptr) {
result.push_back({key, data});
SkSLCache cache = LoadFile(directory, filename, true);
if (cache.key != nullptr && cache.value != nullptr) {
result.push_back(cache);
} else {
FML_LOG(ERROR) << "Failed to load: " << filename;
}
@ -291,17 +288,38 @@ bool PersistentCache::IsValid() const {
return cache_directory_ && cache_directory_->is_valid();
}
sk_sp<SkData> PersistentCache::LoadFile(const fml::UniqueFD& dir,
const std::string& file_name) {
PersistentCache::SkSLCache PersistentCache::LoadFile(
const fml::UniqueFD& dir,
const std::string& file_name,
bool need_key) {
SkSLCache result;
auto file = fml::OpenFileReadOnly(dir, file_name.c_str());
if (!file.is_valid()) {
return nullptr;
return result;
}
auto mapping = std::make_unique<fml::FileMapping>(file);
if (mapping->GetSize() == 0) {
return nullptr;
if (mapping->GetSize() < sizeof(CacheObjectHeader)) {
return result;
}
return SkData::MakeWithCopy(mapping->GetMapping(), mapping->GetSize());
const CacheObjectHeader* header =
reinterpret_cast<const CacheObjectHeader*>(mapping->GetMapping());
if (header->signature != CacheObjectHeader::kSignature ||
header->version != CacheObjectHeader::kVersion1) {
FML_LOG(INFO) << "Persistent cache header is corrupt: " << file_name;
return result;
}
if (mapping->GetSize() < sizeof(CacheObjectHeader) + header->key_size) {
FML_LOG(INFO) << "Persistent cache size is corrupt: " << file_name;
return result;
}
if (need_key) {
result.key = SkData::MakeWithCopy(
mapping->GetMapping() + sizeof(CacheObjectHeader), header->key_size);
}
size_t value_offset = sizeof(CacheObjectHeader) + header->key_size;
result.value = SkData::MakeWithCopy(mapping->GetMapping() + value_offset,
mapping->GetSize() - value_offset);
return result;
}
// |GrContextOptions::PersistentCache|
@ -314,7 +332,8 @@ sk_sp<SkData> PersistentCache::load(const SkData& key) {
if (file_name.size() == 0) {
return nullptr;
}
auto result = PersistentCache::LoadFile(*cache_directory_, file_name);
auto result =
PersistentCache::LoadFile(*cache_directory_, file_name, false).value;
if (result != nullptr) {
TRACE_EVENT0("flutter", "PersistentCacheLoadHit");
}
@ -349,6 +368,26 @@ static void PersistentCacheStore(fml::RefPtr<fml::TaskRunner> worker,
}
}
std::unique_ptr<fml::MallocMapping> PersistentCache::BuildCacheObject(
const SkData& key,
const SkData& data) {
size_t total_size = sizeof(CacheObjectHeader) + key.size() + data.size();
uint8_t* mapping_buf = reinterpret_cast<uint8_t*>(malloc(total_size));
if (!mapping_buf) {
return nullptr;
}
auto mapping = std::make_unique<fml::MallocMapping>(mapping_buf, total_size);
CacheObjectHeader header(key.size());
memcpy(mapping_buf, &header, sizeof(CacheObjectHeader));
mapping_buf += sizeof(CacheObjectHeader);
memcpy(mapping_buf, key.data(), key.size());
mapping_buf += key.size();
memcpy(mapping_buf, data.data(), data.size());
return mapping;
}
// |GrContextOptions::PersistentCache|
void PersistentCache::store(const SkData& key, const SkData& data) {
stored_new_shaders_ = true;
@ -367,10 +406,8 @@ void PersistentCache::store(const SkData& key, const SkData& data) {
return;
}
auto mapping = std::make_unique<fml::DataMapping>(
std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
if (mapping == nullptr || mapping->GetSize() == 0) {
std::unique_ptr<fml::MallocMapping> mapping = BuildCacheObject(key, data);
if (!mapping) {
return;
}

View File

@ -45,7 +45,26 @@ class PersistentCache : public GrContextOptions::PersistentCache {
//
// This is used to specify persistent cache filenames and service protocol
// json keys.
static std::string SkKeyToFilePath(const SkData& data);
static std::string SkKeyToFilePath(const SkData& key);
// Allocate a MallocMapping containing the given key and value in the file
// format used by the cache.
static std::unique_ptr<fml::MallocMapping> BuildCacheObject(
const SkData& key,
const SkData& data);
// Header written into the files used to store cached Skia objects.
struct CacheObjectHeader {
// A prefix used to identify the cache object file format.
static const uint32_t kSignature = 0xA869593F;
static const uint32_t kVersion1 = 1;
CacheObjectHeader(uint32_t p_key_size) : key_size(p_key_size) {}
uint32_t signature = kSignature;
uint32_t version = kVersion1;
uint32_t key_size;
};
~PersistentCache() override;
@ -69,7 +88,10 @@ class PersistentCache : public GrContextOptions::PersistentCache {
// |GrContextOptions::PersistentCache|
sk_sp<SkData> load(const SkData& key) override;
using SkSLCache = std::pair<sk_sp<SkData>, sk_sp<SkData>>;
struct SkSLCache {
sk_sp<SkData> key;
sk_sp<SkData> value;
};
/// Load all the SkSL shader caches in the right directory.
std::vector<SkSLCache> LoadSkSLs() const;
@ -135,8 +157,9 @@ class PersistentCache : public GrContextOptions::PersistentCache {
bool stored_new_shaders_ = false;
bool is_dumping_skp_ = false;
static sk_sp<SkData> LoadFile(const fml::UniqueFD& dir,
const std::string& filen_ame);
static SkSLCache LoadFile(const fml::UniqueFD& dir,
const std::string& file_name,
bool need_key);
bool IsValid() const;

View File

@ -24,6 +24,8 @@ source_set("fml") {
"file.cc",
"file.h",
"hash_combine.h",
"hex_codec.cc",
"hex_codec.h",
"icu_util.cc",
"icu_util.h",
"log_level.h",
@ -269,6 +271,7 @@ if (enable_unittests) {
"command_line_unittest.cc",
"file_unittest.cc",
"hash_combine_unittests.cc",
"hex_codec_unittest.cc",
"logging_unittests.cc",
"mapping_unittests.cc",
"memory/ref_counted_unittest.cc",

View File

@ -0,0 +1,24 @@
// Copyright 2013 The Flutter 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 "flutter/fml/base32.h"
#include <string>
namespace fml {
static constexpr char kEncoding[] = "0123456789abcdef";
std::string HexEncode(std::string_view input) {
std::string result;
result.reserve(input.size() * 2);
for (char c : input) {
uint8_t b = static_cast<uint8_t>(c);
result.push_back(kEncoding[b >> 4]);
result.push_back(kEncoding[b & 0xF]);
}
return result;
}
} // namespace fml

View File

@ -0,0 +1,16 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FML_HEX_CODEC_H_
#define FLUTTER_FML_HEX_CODEC_H_
#include <string_view>
namespace fml {
std::string HexEncode(std::string_view input);
} // namespace fml
#endif // FLUTTER_FML_HEX_CODEC_H_

View File

@ -0,0 +1,31 @@
// Copyright 2013 The Flutter 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 "flutter/fml/hex_codec.h"
#include <iostream>
#include "gtest/gtest.h"
TEST(HexCodecTest, CanEncode) {
{
auto result = fml::HexEncode("hello");
ASSERT_EQ(result, "68656c6c6f");
}
{
auto result = fml::HexEncode("");
ASSERT_EQ(result, "");
}
{
auto result = fml::HexEncode("1");
ASSERT_EQ(result, "31");
}
{
auto result = fml::HexEncode(std::string_view("\xFF\xFE\x00\x01", 4));
ASSERT_EQ(result, "fffe0001");
}
}

View File

@ -253,14 +253,14 @@ TEST_F(PersistentCacheTest, CanLoadSkSLsFromAsset) {
// Make sure that the 2 shaders are sorted by their keys. Their keys should
// be "A" and "B" (decoded from "II" and "IE").
if (shaders[0].first->bytes()[0] == 'B') {
if (shaders[0].key->bytes()[0] == 'B') {
std::swap(shaders[0], shaders[1]);
}
CheckTextSkData(shaders[0].first, "A");
CheckTextSkData(shaders[1].first, "B");
CheckTextSkData(shaders[0].second, "x");
CheckTextSkData(shaders[1].second, "y");
CheckTextSkData(shaders[0].key, "A");
CheckTextSkData(shaders[1].key, "B");
CheckTextSkData(shaders[0].value, "x");
CheckTextSkData(shaders[1].value, "y");
}
// Cleanup.

View File

@ -11,6 +11,7 @@
#include "flutter/assets/directory_asset_bundle.h"
#include "flutter/common/graphics/persistent_cache.h"
#include "flutter/fml/base32.h"
#include "flutter/fml/file.h"
#include "flutter/fml/icu_util.h"
#include "flutter/fml/log_settings.h"
@ -1679,14 +1680,19 @@ bool Shell::OnServiceProtocolGetSkSLs(
std::vector<PersistentCache::SkSLCache> sksls = persistent_cache->LoadSkSLs();
for (const auto& sksl : sksls) {
size_t b64_size =
SkBase64::Encode(sksl.second->data(), sksl.second->size(), nullptr);
SkBase64::Encode(sksl.value->data(), sksl.value->size(), nullptr);
sk_sp<SkData> b64_data = SkData::MakeUninitialized(b64_size + 1);
char* b64_char = static_cast<char*>(b64_data->writable_data());
SkBase64::Encode(sksl.second->data(), sksl.second->size(), b64_char);
SkBase64::Encode(sksl.value->data(), sksl.value->size(), b64_char);
b64_char[b64_size] = 0; // make it null terminated for printing
rapidjson::Value shader_value(b64_char, response->GetAllocator());
rapidjson::Value shader_key(PersistentCache::SkKeyToFilePath(*sksl.first),
response->GetAllocator());
std::string_view key_view(reinterpret_cast<const char*>(sksl.key->data()),
sksl.key->size());
auto encode_result = fml::Base32Encode(key_view);
if (!encode_result.first) {
continue;
}
rapidjson::Value shader_key(encode_result.second, response->GetAllocator());
shaders_json.AddMember(shader_key, shader_value, response->GetAllocator());
}
response->AddMember("SkSLs", shaders_json, response->GetAllocator());

View File

@ -12,6 +12,7 @@
#include <vector>
#include "assets/directory_asset_bundle.h"
#include "common/graphics/persistent_cache.h"
#include "flutter/common/graphics/persistent_cache.h"
#include "flutter/flow/layers/layer_tree.h"
#include "flutter/flow/layers/picture_layer.h"
@ -2012,14 +2013,24 @@ TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) {
PersistentCache::kSkSLSubdirName};
auto sksl_dir = fml::CreateDirectory(base_dir.fd(), components,
fml::FilePermission::kReadWrite);
const std::string x = "x";
const std::string y = "y";
auto x_data = std::make_unique<fml::DataMapping>(
std::vector<uint8_t>{x.begin(), x.end()});
auto y_data = std::make_unique<fml::DataMapping>(
std::vector<uint8_t>{y.begin(), y.end()});
ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "IE", *x_data));
ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "II", *y_data));
const std::string x_key_str = "A";
const std::string x_value_str = "x";
sk_sp<SkData> x_key =
SkData::MakeWithCopy(x_key_str.data(), x_key_str.size());
sk_sp<SkData> x_value =
SkData::MakeWithCopy(x_value_str.data(), x_value_str.size());
auto x_data = PersistentCache::BuildCacheObject(*x_key, *x_value);
const std::string y_key_str = "B";
const std::string y_value_str = "y";
sk_sp<SkData> y_key =
SkData::MakeWithCopy(y_key_str.data(), y_key_str.size());
sk_sp<SkData> y_value =
SkData::MakeWithCopy(y_value_str.data(), y_value_str.size());
auto y_data = PersistentCache::BuildCacheObject(*y_key, *y_value);
ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "x_cache", *x_data));
ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "y_cache", *y_data));
Settings settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);