From 632aa9128db19a8d7e57578c904a9db8a969b393 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Wed, 27 Jan 2021 16:46:49 -0800 Subject: [PATCH] Check fs-verity status in app process. - When serializing font map in system-server, record fs-verity status. - When loading font file lazily in app process, - check if fs-verity should be enabled based on serialized font map. - if fs-verity should be enabled: - verify fs-verity bit of the file. - verify font revision from the OTF metadata. - verify PostScript name from the OTF metadata. Bug: 176939176 Test: atest FrameworksCoreTests:TypefaceTest Change-Id: I679918442094d66a1dbc4b1480b8e6fe26528a06 --- libs/hwui/jni/Typeface.cpp | 101 +++++++++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 9 deletions(-) diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index 10c80774fd165..8f455fe4ab433 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -25,12 +25,17 @@ #include #include #include +#include #include #include #include #include +#ifdef __ANDROID__ +#include +#endif + using namespace android; using android::uirenderer::TraceUtils; @@ -155,12 +160,43 @@ static void Typeface_registerGenericFamily(JNIEnv *env, jobject, jstring familyN toTypeface(ptr)->fFontCollection); } -static sk_sp makeSkDataCached(const std::string& path) { +#ifdef __ANDROID__ + +static bool getVerity(const std::string& path) { + struct statx out = {}; + if (statx(AT_FDCWD, path.c_str(), 0 /* flags */, STATX_ALL, &out) != 0) { + ALOGE("statx failed for %s, errno = %d", path.c_str(), errno); + return false; + } + + // Validity check. + if ((out.stx_attributes_mask & STATX_ATTR_VERITY) == 0) { + // STATX_ATTR_VERITY not supported by kernel. + return false; + } + + return (out.stx_attributes & STATX_ATTR_VERITY) != 0; +} + +#else + +static bool getVerity(const std::string&) { + // verity check is not enabled on desktop. + return false; +} + +#endif // __ANDROID__ + +static sk_sp makeSkDataCached(const std::string& path, bool hasVerity) { // We don't clear cache as Typeface objects created by Typeface_readTypefaces() will be stored // in a static field and will not be garbage collected. static std::unordered_map> cache; static std::mutex mutex; ALOG_ASSERT(!path.empty()); + if (hasVerity && !getVerity(path)) { + LOG_ALWAYS_FATAL("verity bit was removed from %s", path.c_str()); + return nullptr; + } std::lock_guard lock{mutex}; sk_sp& entry = cache[path]; if (entry.get() == nullptr) { @@ -171,15 +207,34 @@ static sk_sp makeSkDataCached(const std::string& path) { static std::function()> readMinikinFontSkia( minikin::BufferReader* reader) { - std::string_view fontPath = reader->readString(); - int fontIndex = reader->read(); - const minikin::FontVariation* axesPtr; - uint32_t axesCount; - std::tie(axesPtr, axesCount) = reader->readArray(); - return [fontPath, fontIndex, axesPtr, axesCount]() -> std::shared_ptr { + const void* buffer = reader->data(); + size_t pos = reader->pos(); + // Advance reader's position. + reader->skipString(); // fontPath + reader->skip(); // fontIndex + reader->skipArray(); // axesPtr, axesCount + bool hasVerity = static_cast(reader->read()); + if (hasVerity) { + reader->skip(); // expectedFontRevision + reader->skipString(); // expectedPostScriptName + } + return [buffer, pos]() -> std::shared_ptr { + minikin::BufferReader fontReader(buffer, pos); + std::string_view fontPath = fontReader.readString(); std::string path(fontPath.data(), fontPath.size()); ATRACE_FORMAT("Loading font %s", path.c_str()); - sk_sp data = makeSkDataCached(path); + int fontIndex = fontReader.read(); + const minikin::FontVariation* axesPtr; + uint32_t axesCount; + std::tie(axesPtr, axesCount) = fontReader.readArray(); + bool hasVerity = static_cast(fontReader.read()); + uint32_t expectedFontRevision; + std::string_view expectedPostScriptName; + if (hasVerity) { + expectedFontRevision = fontReader.read(); + expectedPostScriptName = fontReader.readString(); + } + sk_sp data = makeSkDataCached(path, hasVerity); if (data.get() == nullptr) { // This may happen if: // 1. When the process failed to open the file (e.g. invalid path or permission). @@ -189,6 +244,20 @@ static std::function()> readMinikinFontSki } const void* fontPtr = data->data(); size_t fontSize = data->size(); + if (hasVerity) { + // Verify font metadata if verity is enabled. + minikin::FontFileParser parser(fontPtr, fontSize, fontIndex); + std::optional revision = parser.getFontRevision(); + if (!revision.has_value() || revision.value() != expectedFontRevision) { + LOG_ALWAYS_FATAL("Wrong font revision: %s", path.c_str()); + return nullptr; + } + std::optional psName = parser.getPostScriptName(); + if (!psName.has_value() || psName.value() != expectedPostScriptName) { + LOG_ALWAYS_FATAL("Wrong PostScript name: %s", path.c_str()); + return nullptr; + } + } std::vector axes(axesPtr, axesPtr + axesCount); std::shared_ptr minikinFont = fonts::createMinikinFontSkia(std::move(data), fontPath, fontPtr, fontSize, @@ -203,10 +272,24 @@ static std::function()> readMinikinFontSki static void writeMinikinFontSkia(minikin::BufferWriter* writer, const minikin::MinikinFont* typeface) { - writer->writeString(typeface->GetFontPath()); + const std::string& path = typeface->GetFontPath(); + writer->writeString(path); writer->write(typeface->GetFontIndex()); const std::vector& axes = typeface->GetAxes(); writer->writeArray(axes.data(), axes.size()); + bool hasVerity = getVerity(path); + writer->write(static_cast(hasVerity)); + if (hasVerity) { + // Write font metadata for verification only when verity is enabled. + minikin::FontFileParser parser(typeface->GetFontData(), typeface->GetFontSize(), + typeface->GetFontIndex()); + std::optional revision = parser.getFontRevision(); + LOG_ALWAYS_FATAL_IF(!revision.has_value()); + writer->write(revision.value()); + std::optional psName = parser.getPostScriptName(); + LOG_ALWAYS_FATAL_IF(!psName.has_value()); + writer->writeString(psName.value()); + } } static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) {