| // Copyright 2017 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 "testing/fuzzers/pdfium_fuzzer_helper.h" | |
| #include <assert.h> | |
| #include <limits.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #ifdef _WIN32 | |
| #include <Windows.h> | |
| #elif defined(__APPLE__) | |
| #include <mach-o/dyld.h> | |
| #else // Linux | |
| #include <unistd.h> | |
| #endif // _WIN32 | |
| #include <memory> | |
| #include <sstream> | |
| #include <string> | |
| #include <utility> | |
| #include "public/cpp/fpdf_scopers.h" | |
| #include "public/fpdf_dataavail.h" | |
| #include "public/fpdf_ext.h" | |
| #include "public/fpdf_text.h" | |
| #include "testing/test_loader.h" | |
| #ifdef PDF_ENABLE_V8 | |
| #include "testing/free_deleter.h" | |
| #include "testing/v8_initializer.h" | |
| #include "v8/include/v8-platform.h" | |
| #include "v8/include/v8.h" | |
| #endif | |
| namespace { | |
| int ExampleAppAlert(IPDF_JSPLATFORM*, | |
| FPDF_WIDESTRING, | |
| FPDF_WIDESTRING, | |
| int, | |
| int) { | |
| return 0; | |
| } | |
| int ExampleAppResponse(IPDF_JSPLATFORM*, | |
| FPDF_WIDESTRING question, | |
| FPDF_WIDESTRING title, | |
| FPDF_WIDESTRING default_value, | |
| FPDF_WIDESTRING label, | |
| FPDF_BOOL is_password, | |
| void* response, | |
| int length) { | |
| // UTF-16, always LE regardless of platform. | |
| uint8_t* ptr = static_cast<uint8_t*>(response); | |
| ptr[0] = 'N'; | |
| ptr[1] = 0; | |
| ptr[2] = 'o'; | |
| ptr[3] = 0; | |
| return 4; | |
| } | |
| void ExampleDocGotoPage(IPDF_JSPLATFORM*, int pageNumber) {} | |
| void ExampleDocMail(IPDF_JSPLATFORM*, | |
| void* mailData, | |
| int length, | |
| FPDF_BOOL UI, | |
| FPDF_WIDESTRING To, | |
| FPDF_WIDESTRING Subject, | |
| FPDF_WIDESTRING CC, | |
| FPDF_WIDESTRING BCC, | |
| FPDF_WIDESTRING Msg) {} | |
| void ExampleUnsupportedHandler(UNSUPPORT_INFO*, int type) {} | |
| FPDF_BOOL Is_Data_Avail(FX_FILEAVAIL* pThis, size_t offset, size_t size) { | |
| return true; | |
| } | |
| void Add_Segment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {} | |
| #ifdef PDF_ENABLE_V8 | |
| std::string ProgramPath() { | |
| std::string result; | |
| #ifdef _WIN32 | |
| char path[MAX_PATH]; | |
| DWORD len = GetModuleFileNameA(NULL, path, MAX_PATH); | |
| if (len != 0) | |
| result = std::string(path, len); | |
| #elif defined(__APPLE__) | |
| char path[PATH_MAX]; | |
| unsigned int len = PATH_MAX; | |
| if (!_NSGetExecutablePath(path, &len)) { | |
| std::unique_ptr<char, pdfium::FreeDeleter> resolved_path( | |
| realpath(path, nullptr)); | |
| if (resolved_path.get()) | |
| result = std::string(resolved_path.get()); | |
| } | |
| #else // Linux | |
| char path[PATH_MAX]; | |
| ssize_t len = readlink("/proc/self/exe", path, PATH_MAX); | |
| if (len > 0) | |
| result = std::string(path, len); | |
| #endif | |
| return result; | |
| } | |
| #endif // PDF_ENABLE_V8 | |
| std::pair<int, int> GetRenderingAndFormFlagFromData(const char* data, | |
| size_t len) { | |
| std::string data_str = std::string(data, len); | |
| std::size_t data_hash = std::hash<std::string>()(data_str); | |
| // The largest flag value is 0x4FFF, so just take 16 bits from |data_hash| at | |
| // a time. | |
| int render_flags = data_hash & 0xffff; | |
| int form_flags = (data_hash >> 16) & 0xffff; | |
| return std::make_pair(render_flags, form_flags); | |
| } | |
| } // namespace | |
| PDFiumFuzzerHelper::PDFiumFuzzerHelper() = default; | |
| PDFiumFuzzerHelper::~PDFiumFuzzerHelper() = default; | |
| bool PDFiumFuzzerHelper::OnFormFillEnvLoaded(FPDF_DOCUMENT doc) { | |
| return true; | |
| } | |
| void PDFiumFuzzerHelper::RenderPdf(const char* data, size_t len) { | |
| int render_flags; | |
| int form_flags; | |
| std::tie(render_flags, form_flags) = | |
| GetRenderingAndFormFlagFromData(data, len); | |
| IPDF_JSPLATFORM platform_callbacks; | |
| memset(&platform_callbacks, '\0', sizeof(platform_callbacks)); | |
| platform_callbacks.version = 3; | |
| platform_callbacks.app_alert = ExampleAppAlert; | |
| platform_callbacks.app_response = ExampleAppResponse; | |
| platform_callbacks.Doc_gotoPage = ExampleDocGotoPage; | |
| platform_callbacks.Doc_mail = ExampleDocMail; | |
| FPDF_FORMFILLINFO form_callbacks; | |
| memset(&form_callbacks, '\0', sizeof(form_callbacks)); | |
| form_callbacks.version = GetFormCallbackVersion(); | |
| form_callbacks.m_pJsPlatform = &platform_callbacks; | |
| TestLoader loader({data, len}); | |
| FPDF_FILEACCESS file_access; | |
| memset(&file_access, '\0', sizeof(file_access)); | |
| file_access.m_FileLen = static_cast<unsigned long>(len); | |
| file_access.m_GetBlock = TestLoader::GetBlock; | |
| file_access.m_Param = &loader; | |
| FX_FILEAVAIL file_avail; | |
| memset(&file_avail, '\0', sizeof(file_avail)); | |
| file_avail.version = 1; | |
| file_avail.IsDataAvail = Is_Data_Avail; | |
| FX_DOWNLOADHINTS hints; | |
| memset(&hints, '\0', sizeof(hints)); | |
| hints.version = 1; | |
| hints.AddSegment = Add_Segment; | |
| ScopedFPDFAvail pdf_avail(FPDFAvail_Create(&file_avail, &file_access)); | |
| int nRet = PDF_DATA_NOTAVAIL; | |
| bool bIsLinearized = false; | |
| ScopedFPDFDocument doc; | |
| if (FPDFAvail_IsLinearized(pdf_avail.get()) == PDF_LINEARIZED) { | |
| doc.reset(FPDFAvail_GetDocument(pdf_avail.get(), nullptr)); | |
| if (doc) { | |
| while (nRet == PDF_DATA_NOTAVAIL) | |
| nRet = FPDFAvail_IsDocAvail(pdf_avail.get(), &hints); | |
| if (nRet == PDF_DATA_ERROR) | |
| return; | |
| nRet = FPDFAvail_IsFormAvail(pdf_avail.get(), &hints); | |
| if (nRet == PDF_FORM_ERROR || nRet == PDF_FORM_NOTAVAIL) | |
| return; | |
| bIsLinearized = true; | |
| } | |
| } else { | |
| doc.reset(FPDF_LoadCustomDocument(&file_access, nullptr)); | |
| } | |
| if (!doc) | |
| return; | |
| (void)FPDF_GetDocPermissions(doc.get()); | |
| ScopedFPDFFormHandle form( | |
| FPDFDOC_InitFormFillEnvironment(doc.get(), &form_callbacks)); | |
| if (!OnFormFillEnvLoaded(doc.get())) | |
| return; | |
| FPDF_SetFormFieldHighlightColor(form.get(), FPDF_FORMFIELD_UNKNOWN, 0xFFE4DD); | |
| FPDF_SetFormFieldHighlightAlpha(form.get(), 100); | |
| FORM_DoDocumentJSAction(form.get()); | |
| FORM_DoDocumentOpenAction(form.get()); | |
| int page_count = FPDF_GetPageCount(doc.get()); | |
| for (int i = 0; i < page_count; ++i) { | |
| if (bIsLinearized) { | |
| nRet = PDF_DATA_NOTAVAIL; | |
| while (nRet == PDF_DATA_NOTAVAIL) | |
| nRet = FPDFAvail_IsPageAvail(pdf_avail.get(), i, &hints); | |
| if (nRet == PDF_DATA_ERROR) | |
| return; | |
| } | |
| RenderPage(doc.get(), form.get(), i, render_flags, form_flags); | |
| } | |
| FORM_DoDocumentAAction(form.get(), FPDFDOC_AACTION_WC); | |
| } | |
| bool PDFiumFuzzerHelper::RenderPage(FPDF_DOCUMENT doc, | |
| FPDF_FORMHANDLE form, | |
| int page_index, | |
| int render_flags, | |
| int form_flags) { | |
| ScopedFPDFPage page(FPDF_LoadPage(doc, page_index)); | |
| if (!page) | |
| return false; | |
| ScopedFPDFTextPage text_page(FPDFText_LoadPage(page.get())); | |
| FORM_OnAfterLoadPage(page.get(), form); | |
| FORM_DoPageAAction(page.get(), form, FPDFPAGE_AACTION_OPEN); | |
| const double scale = 1.0; | |
| int width = static_cast<int>(FPDF_GetPageWidth(page.get()) * scale); | |
| int height = static_cast<int>(FPDF_GetPageHeight(page.get()) * scale); | |
| ScopedFPDFBitmap bitmap(FPDFBitmap_Create(width, height, 0)); | |
| if (bitmap) { | |
| FPDFBitmap_FillRect(bitmap.get(), 0, 0, width, height, 0xFFFFFFFF); | |
| FPDF_RenderPageBitmap(bitmap.get(), page.get(), 0, 0, width, height, 0, | |
| render_flags); | |
| FPDF_FFLDraw(form, bitmap.get(), page.get(), 0, 0, width, height, 0, | |
| form_flags); | |
| } | |
| FORM_DoPageAAction(page.get(), form, FPDFPAGE_AACTION_CLOSE); | |
| FORM_OnBeforeClosePage(page.get(), form); | |
| return !!bitmap; | |
| } | |
| // Initialize the library once for all runs of the fuzzer. | |
| struct TestCase { | |
| TestCase() { | |
| #ifdef PDF_ENABLE_V8 | |
| #ifdef V8_USE_EXTERNAL_STARTUP_DATA | |
| platform = InitializeV8ForPDFiumWithStartupData( | |
| ProgramPath(), "", &natives_blob, &snapshot_blob); | |
| #else | |
| platform = InitializeV8ForPDFium(ProgramPath()); | |
| #endif // V8_USE_EXTERNAL_STARTUP_DATA | |
| #endif // PDF_ENABLE_V8 | |
| memset(&config, '\0', sizeof(config)); | |
| config.version = 2; | |
| config.m_pUserFontPaths = nullptr; | |
| config.m_pIsolate = nullptr; | |
| config.m_v8EmbedderSlot = 0; | |
| FPDF_InitLibraryWithConfig(&config); | |
| memset(&unsupport_info, '\0', sizeof(unsupport_info)); | |
| unsupport_info.version = 1; | |
| unsupport_info.FSDK_UnSupport_Handler = ExampleUnsupportedHandler; | |
| FSDK_SetUnSpObjProcessHandler(&unsupport_info); | |
| } | |
| #ifdef PDF_ENABLE_V8 | |
| std::unique_ptr<v8::Platform> platform; | |
| v8::StartupData natives_blob; | |
| v8::StartupData snapshot_blob; | |
| #endif | |
| FPDF_LIBRARY_CONFIG config; | |
| UNSUPPORT_INFO unsupport_info; | |
| }; | |
| // pdf_fuzzer_init.cc and pdfium_fuzzer_helper.cc are mutually exclusive and | |
| // should not be built together. They deliberately have the same global | |
| // variable. | |
| static TestCase* g_test_case = new TestCase(); |