// Copyright 2014 The Chromium 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 "content/common/font_warmup_win.h" #include #include #include "base/debug/alias.h" #include "base/files/file_path.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/numerics/safe_conversions.h" #include "base/numerics/safe_math.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/lock.h" #include "base/sys_byteorder.h" #include "base/win/iat_patch_function.h" #include "base/win/windows_version.h" #include "content/public/common/dwrite_font_platform_win.h" #include "skia/ext/fontmgr_default_win.h" #include "skia/ext/refptr.h" #include "third_party/WebKit/public/web/win/WebFontRendering.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/ports/SkFontMgr.h" #include "third_party/skia/include/ports/SkTypeface_win.h" #include "ui/gfx/hud_font.h" namespace content { namespace { SkFontMgr* g_warmup_fontmgr = nullptr; base::win::IATPatchFunction g_iat_patch_open_sc_manager; base::win::IATPatchFunction g_iat_patch_close_service_handle; base::win::IATPatchFunction g_iat_patch_open_service; base::win::IATPatchFunction g_iat_patch_start_service; base::win::IATPatchFunction g_iat_patch_nt_connect_port; // These are from ntddk.h #if !defined(STATUS_ACCESS_DENIED) #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) #endif typedef LONG NTSTATUS; const uintptr_t kFakeSCMHandle = 0xdead0001; const uintptr_t kFakeServiceHandle = 0xdead0002; SC_HANDLE WINAPI OpenSCManagerWPatch(const wchar_t* machine_name, const wchar_t* database_name, DWORD access_mask) { ::SetLastError(0); return reinterpret_cast(kFakeSCMHandle); } SC_HANDLE WINAPI OpenServiceWPatch(SC_HANDLE sc_manager, const wchar_t* service_name, DWORD access_mask) { ::SetLastError(0); return reinterpret_cast(kFakeServiceHandle); } BOOL WINAPI CloseServiceHandlePatch(SC_HANDLE service_handle) { if (service_handle != reinterpret_cast(kFakeServiceHandle) && service_handle != reinterpret_cast(kFakeSCMHandle)) CHECK(false); ::SetLastError(0); return TRUE; } BOOL WINAPI StartServiceWPatch(SC_HANDLE service, DWORD args, const wchar_t** arg_vectors) { if (service != reinterpret_cast(kFakeServiceHandle)) CHECK(false); ::SetLastError(ERROR_ACCESS_DENIED); return FALSE; } NTSTATUS WINAPI NtALpcConnectPortPatch(HANDLE* port_handle, void* port_name, void* object_attribs, void* port_attribs, DWORD flags, void* server_sid, void* message, DWORD* buffer_length, void* out_message_attributes, void* in_message_attributes, void* time_out) { return STATUS_ACCESS_DENIED; } // Directwrite connects to the font cache service to retrieve information about // fonts installed on the system etc. This works well outside the sandbox and // within the sandbox as long as the lpc connection maintained by the current // process with the font cache service remains valid. It appears that there // are cases when this connection is dropped after which directwrite is unable // to connect to the font cache service which causes problems with characters // disappearing. // Directwrite has fallback code to enumerate fonts if it is unable to connect // to the font cache service. We need to intercept the following APIs to // ensure that it does not connect to the font cache service. // NtALpcConnectPort // OpenSCManagerW // OpenServiceW // StartServiceW // CloseServiceHandle. // These are all IAT patched. void PatchServiceManagerCalls() { static bool is_patched = false; if (is_patched) return; const char* service_provider_dll = (base::win::GetVersion() >= base::win::VERSION_WIN8 ? "api-ms-win-service-management-l1-1-0.dll" : "advapi32.dll"); is_patched = true; DWORD patched = g_iat_patch_open_sc_manager.Patch(L"dwrite.dll", service_provider_dll, "OpenSCManagerW", OpenSCManagerWPatch); DCHECK(patched == 0); patched = g_iat_patch_close_service_handle.Patch( L"dwrite.dll", service_provider_dll, "CloseServiceHandle", CloseServiceHandlePatch); DCHECK(patched == 0); patched = g_iat_patch_open_service.Patch(L"dwrite.dll", service_provider_dll, "OpenServiceW", OpenServiceWPatch); DCHECK(patched == 0); patched = g_iat_patch_start_service.Patch( L"dwrite.dll", service_provider_dll, "StartServiceW", StartServiceWPatch); DCHECK(patched == 0); patched = g_iat_patch_nt_connect_port.Patch( L"dwrite.dll", "ntdll.dll", "NtAlpcConnectPort", NtALpcConnectPortPatch); DCHECK(patched == 0); } // Windows-only DirectWrite support. These warm up the DirectWrite paths // before sandbox lock down to allow Skia access to the Font Manager service. void CreateDirectWriteFactory(IDWriteFactory** factory) { typedef decltype(DWriteCreateFactory)* DWriteCreateFactoryProc; HMODULE dwrite_dll = LoadLibraryW(L"dwrite.dll"); // TODO(scottmg): Temporary code to track crash in http://crbug.com/387867. if (!dwrite_dll) { DWORD load_library_get_last_error = GetLastError(); base::debug::Alias(&dwrite_dll); base::debug::Alias(&load_library_get_last_error); CHECK(false); } PatchServiceManagerCalls(); DWriteCreateFactoryProc dwrite_create_factory_proc = reinterpret_cast( GetProcAddress(dwrite_dll, "DWriteCreateFactory")); // TODO(scottmg): Temporary code to track crash in http://crbug.com/387867. if (!dwrite_create_factory_proc) { DWORD get_proc_address_get_last_error = GetLastError(); base::debug::Alias(&dwrite_create_factory_proc); base::debug::Alias(&get_proc_address_get_last_error); CHECK(false); } CHECK(SUCCEEDED(dwrite_create_factory_proc( DWRITE_FACTORY_TYPE_ISOLATED, __uuidof(IDWriteFactory), reinterpret_cast(factory)))); } HRESULT STDMETHODCALLTYPE StubFontCollection(IDWriteFactory* factory, IDWriteFontCollection** col, BOOL checkUpdates) { // We always return pre-created font collection from here. IDWriteFontCollection* custom_collection = GetCustomFontCollection(factory); DCHECK(custom_collection != nullptr); *col = custom_collection; return S_OK; } void PatchDWriteFactory(IDWriteFactory* factory) { const unsigned int kGetSystemFontCollectionVTableIndex = 3; PROC* vtable = *reinterpret_cast(factory); PROC* function_ptr = &vtable[kGetSystemFontCollectionVTableIndex]; void* stub_function = &StubFontCollection; base::win::ModifyCode(function_ptr, &stub_function, sizeof(PROC)); } // Class to fake out a DC or a Font object. Maintains a reference to a // SkTypeFace to emulate the simple operation of a DC and Font. class FakeGdiObject : public base::RefCountedThreadSafe { public: FakeGdiObject(uint32_t magic, void* handle) : handle_(handle), magic_(magic) {} void set_typeface(const skia::RefPtr& typeface) { typeface_ = typeface; } skia::RefPtr typeface() { return typeface_; } void* handle() { return handle_; } uint32_t magic() { return magic_; } private: friend class base::RefCountedThreadSafe; ~FakeGdiObject() {} void* handle_; uint32_t magic_; skia::RefPtr typeface_; DISALLOW_COPY_AND_ASSIGN(FakeGdiObject); }; // This class acts as a factory for creating new fake GDI objects. It also maps // the new instances of the FakeGdiObject class to an incrementing handle value // which is passed to the caller of the emulated GDI function for later // reference. We can't be sure that this won't be used in a multi-threaded // environment so we need to ensure a lock is taken before accessing the map of // issued objects. class FakeGdiObjectFactory { public: FakeGdiObjectFactory() : curr_handle_(0) {} // Find a corresponding fake GDI object and verify its magic value. // The returned value is either nullptr or the validated object. scoped_refptr Validate(void* obj, uint32_t magic) { if (obj) { base::AutoLock scoped_lock(objects_lock_); auto handle_entry = objects_.find(obj); if (handle_entry != objects_.end() && handle_entry->second->magic() == magic) { return handle_entry->second; } } return nullptr; } scoped_refptr Create(uint32_t magic) { base::AutoLock scoped_lock(objects_lock_); curr_handle_++; // We don't support wrapping the fake handle value. void* handle = reinterpret_cast(curr_handle_.ValueOrDie()); scoped_refptr object(new FakeGdiObject(magic, handle)); objects_[handle] = object; return object; } bool DeleteObject(void* obj, uint32_t magic) { base::AutoLock scoped_lock(objects_lock_); auto handle_entry = objects_.find(obj); if (handle_entry != objects_.end() && handle_entry->second->magic() == magic) { objects_.erase(handle_entry); return true; } return false; } size_t GetObjectCount() { base::AutoLock scoped_lock(objects_lock_); return objects_.size(); } void ResetObjectHandles() { base::AutoLock scoped_lock(objects_lock_); curr_handle_ = 0; objects_.clear(); } private: base::CheckedNumeric curr_handle_; std::map> objects_; base::Lock objects_lock_; DISALLOW_COPY_AND_ASSIGN(FakeGdiObjectFactory); }; base::LazyInstance::Leaky g_fake_gdi_object_factory = LAZY_INSTANCE_INITIALIZER; // Magic values for the fake GDI objects. const uint32_t kFakeDCMagic = 'fkdc'; const uint32_t kFakeFontMagic = 'fkft'; skia::RefPtr GetTypefaceFromLOGFONT(const LOGFONTW* log_font) { CHECK(g_warmup_fontmgr); int weight = log_font->lfWeight; if (weight == FW_DONTCARE) weight = SkFontStyle::kNormal_Weight; SkFontStyle style(weight, log_font->lfWidth, log_font->lfItalic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant); std::string family_name = base::WideToUTF8(log_font->lfFaceName); return skia::AdoptRef( g_warmup_fontmgr->matchFamilyStyle(family_name.c_str(), style)); } HDC WINAPI CreateCompatibleDCPatch(HDC dc_handle) { scoped_refptr ret = g_fake_gdi_object_factory.Get().Create(kFakeDCMagic); return static_cast(ret->handle()); } HFONT WINAPI CreateFontIndirectWPatch(const LOGFONTW* log_font) { if (!log_font) return nullptr; skia::RefPtr typeface = GetTypefaceFromLOGFONT(log_font); if (!typeface) return nullptr; scoped_refptr ret = g_fake_gdi_object_factory.Get().Create(kFakeFontMagic); ret->set_typeface(typeface); return static_cast(ret->handle()); } BOOL WINAPI DeleteDCPatch(HDC dc_handle) { return g_fake_gdi_object_factory.Get().DeleteObject(dc_handle, kFakeDCMagic); } BOOL WINAPI DeleteObjectPatch(HGDIOBJ object_handle) { return g_fake_gdi_object_factory.Get().DeleteObject(object_handle, kFakeFontMagic); } int WINAPI EnumFontFamiliesExWPatch(HDC dc_handle, LPLOGFONTW log_font, FONTENUMPROCW enum_callback, LPARAM callback_param, DWORD flags) { scoped_refptr dc_obj = g_fake_gdi_object_factory.Get().Validate(dc_handle, kFakeDCMagic); if (!dc_obj) return 1; if (!log_font || !enum_callback) return 1; skia::RefPtr typeface = GetTypefaceFromLOGFONT(log_font); if (!typeface) return 1; ENUMLOGFONTEXDVW enum_log_font = {}; enum_log_font.elfEnumLogfontEx.elfLogFont = *log_font; // TODO: Fill in the rest of the text metric structure. Not yet needed for // Flash support but might be in the future. NEWTEXTMETRICEXW text_metric = {}; text_metric.ntmTm.ntmFlags = NTM_PS_OPENTYPE; return enum_callback(&enum_log_font.elfEnumLogfontEx.elfLogFont, reinterpret_cast(&text_metric), TRUETYPE_FONTTYPE, callback_param); } DWORD WINAPI GetFontDataPatch(HDC dc_handle, DWORD table_tag, DWORD table_offset, LPVOID buffer, DWORD buffer_length) { scoped_refptr dc_obj = g_fake_gdi_object_factory.Get().Validate(dc_handle, kFakeDCMagic); if (!dc_obj) return GDI_ERROR; skia::RefPtr typeface = dc_obj->typeface(); if (!typeface) return GDI_ERROR; // |getTableData| handles |buffer| being nullptr. However if it is nullptr // then set the size to INT32_MAX otherwise |getTableData| will return the // minimum value between the table entry size and the size passed in. The // common Windows idiom is to pass 0 as |buffer_length| when passing nullptr, // which would in this case result in |getTableData| returning 0 which isn't // the correct answer for emulating GDI. |table_tag| must also have its // byte order swapped to counter the swap which occurs in the called method. size_t length = typeface->getTableData( base::ByteSwap(base::strict_cast(table_tag)), table_offset, buffer ? buffer_length : INT32_MAX, buffer); // We can't distinguish between an empty table and an error. if (length == 0) return GDI_ERROR; return base::checked_cast(length); } HGDIOBJ WINAPI SelectObjectPatch(HDC dc_handle, HGDIOBJ object_handle) { scoped_refptr dc_obj = g_fake_gdi_object_factory.Get().Validate(dc_handle, kFakeDCMagic); if (!dc_obj) return nullptr; scoped_refptr font_obj = g_fake_gdi_object_factory.Get().Validate(object_handle, kFakeFontMagic); if (!font_obj) return nullptr; // Construct a new fake font object to handle the old font if there's one. scoped_refptr new_font_obj; skia::RefPtr old_typeface = dc_obj->typeface(); if (old_typeface) { new_font_obj = g_fake_gdi_object_factory.Get().Create(kFakeFontMagic); new_font_obj->set_typeface(old_typeface); } dc_obj->set_typeface(font_obj->typeface()); if (new_font_obj) return static_cast(new_font_obj->handle()); return nullptr; } void DoSingleGdiPatch(base::win::IATPatchFunction& patch, const base::FilePath& path, const char* function_name, void* new_function) { patch.Patch(path.value().c_str(), "gdi32.dll", function_name, new_function); } class GdiFontPatchDataImpl : public content::GdiFontPatchData { public: GdiFontPatchDataImpl(const base::FilePath& path); private: base::win::IATPatchFunction create_compatible_dc_patch_; base::win::IATPatchFunction create_font_indirect_patch_; base::win::IATPatchFunction create_delete_dc_patch_; base::win::IATPatchFunction create_delete_object_patch_; base::win::IATPatchFunction create_enum_font_families_patch_; base::win::IATPatchFunction create_get_font_data_patch_; base::win::IATPatchFunction create_select_object_patch_; }; GdiFontPatchDataImpl::GdiFontPatchDataImpl(const base::FilePath& path) { DoSingleGdiPatch(create_compatible_dc_patch_, path, "CreateCompatibleDC", CreateCompatibleDCPatch); DoSingleGdiPatch(create_font_indirect_patch_, path, "CreateFontIndirectW", CreateFontIndirectWPatch); DoSingleGdiPatch(create_delete_dc_patch_, path, "DeleteDC", DeleteDCPatch); DoSingleGdiPatch(create_delete_object_patch_, path, "DeleteObject", DeleteObjectPatch); DoSingleGdiPatch(create_enum_font_families_patch_, path, "EnumFontFamiliesExW", EnumFontFamiliesExWPatch); DoSingleGdiPatch(create_get_font_data_patch_, path, "GetFontData", GetFontDataPatch); DoSingleGdiPatch(create_select_object_patch_, path, "SelectObject", SelectObjectPatch); } } // namespace void DoPreSandboxWarmupForTypeface(SkTypeface* typeface) { SkPaint paint_warmup; paint_warmup.setTypeface(typeface); wchar_t glyph = L'S'; paint_warmup.measureText(&glyph, 2); } SkFontMgr* GetPreSandboxWarmupFontMgr() { if (!g_warmup_fontmgr) { IDWriteFactory* factory; CreateDirectWriteFactory(&factory); GetCustomFontCollection(factory); PatchDWriteFactory(factory); blink::WebFontRendering::setDirectWriteFactory(factory); g_warmup_fontmgr = SkFontMgr_New_DirectWrite(factory); } return g_warmup_fontmgr; } GdiFontPatchData* PatchGdiFontEnumeration(const base::FilePath& path) { // We assume the fontmgr is already warmed up before calling this. DCHECK(g_warmup_fontmgr); return new GdiFontPatchDataImpl(path); } size_t GetEmulatedGdiHandleCountForTesting() { return g_fake_gdi_object_factory.Get().GetObjectCount(); } void ResetEmulatedGdiHandlesForTesting() { g_fake_gdi_object_factory.Get().ResetObjectHandles(); } void SetPreSandboxWarmupFontMgrForTesting(SkFontMgr* fontmgr) { g_warmup_fontmgr = fontmgr; } void WarmupDirectWrite() { // The objects used here are intentionally not freed as we want the Skia // code to use these objects after warmup. SetDefaultSkiaFactory(GetPreSandboxWarmupFontMgr()); // We need to warm up *some* font for DirectWrite. We also need to pass one // down for the CC HUD code, so use the same one here. Note that we don't use // a monospace as would be nice in an attempt to avoid a small startup time // regression, see http://crbug.com/463613. skia::RefPtr hud_typeface = skia::AdoptRef( GetPreSandboxWarmupFontMgr()->legacyCreateTypeface("Times New Roman", 0)); DoPreSandboxWarmupForTypeface(hud_typeface.get()); gfx::SetHudTypeface(hud_typeface); } } // namespace content