// Copyright 2017 The PDFium Authors // 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 #include #include #include #include #include #include #include #include #include #include "public/cpp/fpdf_scopers.h" #include "public/fpdf_dataavail.h" #include "public/fpdf_ext.h" #include "public/fpdf_text.h" #include "third_party/base/check_op.h" #include "third_party/base/containers/span.h" #include "third_party/base/numerics/checked_math.h" namespace { class FuzzerTestLoader { public: explicit FuzzerTestLoader(pdfium::span span) : m_Span(span) {} static int GetBlock(void* param, unsigned long pos, unsigned char* pBuf, unsigned long size) { FuzzerTestLoader* pLoader = static_cast(param); pdfium::base::CheckedNumeric end = pos; end += size; CHECK_LE(end.ValueOrDie(), pLoader->m_Span.size()); memcpy(pBuf, &pLoader->m_Span[pos], size); return 1; } private: const pdfium::span m_Span; }; 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(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) {} 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) {} std::pair GetRenderingAndFormFlagFromData(const char* data, size_t len) { std::string data_str = std::string(data, len); size_t data_hash = std::hash()(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; FuzzerTestLoader loader({data, len}); FPDF_FILEACCESS file_access; memset(&file_access, '\0', sizeof(file_access)); file_access.m_FileLen = static_cast(len); file_access.m_GetBlock = FuzzerTestLoader::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); } OnRenderFinished(doc.get()); 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); FormActionHandler(form, doc, page.get()); const double scale = 1.0; int width = static_cast(FPDF_GetPageWidthF(page.get()) * scale); int height = static_cast(FPDF_GetPageHeightF(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; }