blob: 6c4de477f2d014f33c81da4b44c239af2c771591 [file] [log] [blame]
// 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 <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_memcpy_wrappers.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/numerics/checked_math.h"
#include "core/fxcrt/span.h"
#include "public/cpp/fpdf_scopers.h"
#include "public/fpdf_dataavail.h"
#include "public/fpdf_ext.h"
#include "public/fpdf_text.h"
namespace {
class FuzzerTestLoader {
public:
explicit FuzzerTestLoader(pdfium::span<const char> span) : span_(span) {}
static int GetBlock(void* param,
unsigned long pos,
unsigned char* pBuf,
unsigned long size) {
FuzzerTestLoader* pLoader = static_cast<FuzzerTestLoader*>(param);
pdfium::CheckedNumeric<size_t> end = pos;
end += size;
CHECK_LE(end.ValueOrDie(), pLoader->span_.size());
FXSYS_memcpy(pBuf, &pLoader->span_[pos], size);
return 1;
}
private:
const pdfium::span<const char> 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<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) {}
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) {}
PDFiumFuzzerHelper::RenderingOptions GetRenderingOptionsFromData(
pdfium::span<const char> span) {
// Assume size_t may be 32 bits, so split `span` in 2 and hash them separately
// to get enough bits.
auto span_parts = span.split_at(span.size() / 2);
std::string_view view1(span_parts.first.begin(), span_parts.first.end());
std::string_view view2(span_parts.second.begin(), span_parts.second.end());
const size_t data_hash1 = std::hash<std::string_view>()(view1);
const size_t data_hash2 = std::hash<std::string_view>()(view2);
// The largest flag value is 0x7FFF, so just take 15 bits from `data_hash1` at
// a time.
static constexpr int kFlagMask = 0x7fff;
const int render_flags = data_hash1 & kFlagMask;
const int form_flags = (data_hash1 >> 16) & kFlagMask;
// Separately, use `data_hash2` for the bitmap format. The valid values are
// [1, 5]. Avoid using the bytes at the start or end of `span`, as the header
// and footer in PDFs usually have fixed data.
const int bitmap_format = (data_hash2 % 5) + 1;
return {
.render_flags = render_flags,
.form_flags = form_flags,
.bitmap_format = bitmap_format,
};
}
int GetBytesPerPixelForBitmapFormat(int bitmap_format) {
switch (bitmap_format) {
case FPDFBitmap_Gray:
return 1;
case FPDFBitmap_BGR:
return 3;
case FPDFBitmap_BGRx:
case FPDFBitmap_BGRA:
case FPDFBitmap_BGRA_Premul:
return 4;
default:
NOTREACHED();
}
}
bool CheckImageSize(int width, int height, int bitmap_format) {
static constexpr uint32_t kMemLimitBytes = 1024 * 1024 * 1024; // 1 GB.
FX_SAFE_UINT32 mem = width;
mem *= height;
mem *= GetBytesPerPixelForBitmapFormat(bitmap_format);
return mem.IsValid() && mem.ValueOrDie() <= kMemLimitBytes;
}
} // namespace
PDFiumFuzzerHelper::PDFiumFuzzerHelper() = default;
PDFiumFuzzerHelper::~PDFiumFuzzerHelper() = default;
bool PDFiumFuzzerHelper::OnFormFillEnvLoaded(FPDF_DOCUMENT doc) {
return true;
}
void PDFiumFuzzerHelper::RenderPdf(const char* data, size_t len) {
// SAFETY: trusted arguments from fuzzer,
auto span = UNSAFE_BUFFERS(pdfium::span(data, len));
RenderingOptions options = GetRenderingOptionsFromData(span);
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(span);
FPDF_FILEACCESS file_access;
memset(&file_access, '\0', sizeof(file_access));
file_access.m_FileLen = static_cast<unsigned long>(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;
}
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, options);
}
OnRenderFinished(doc.get());
FORM_DoDocumentAAction(form.get(), FPDFDOC_AACTION_WC);
}
bool PDFiumFuzzerHelper::RenderPage(FPDF_DOCUMENT doc,
FPDF_FORMHANDLE form,
int page_index,
const RenderingOptions& options) {
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());
int width = static_cast<int>(FPDF_GetPageWidthF(page.get()));
int height = static_cast<int>(FPDF_GetPageHeightF(page.get()));
if (!CheckImageSize(width, height, options.bitmap_format)) {
return false;
}
ScopedFPDFBitmap bitmap(FPDFBitmap_CreateEx(
width, height, options.bitmap_format, nullptr, /*stride=*/0));
if (bitmap) {
if (!FPDFBitmap_FillRect(bitmap.get(), 0, 0, width, height, 0xFFFFFFFF)) {
return false;
}
FPDF_RenderPageBitmap(bitmap.get(), page.get(), 0, 0, width, height, 0,
options.render_flags);
FPDF_FFLDraw(form, bitmap.get(), page.get(), 0, 0, width, height, 0,
options.form_flags);
}
FORM_DoPageAAction(page.get(), form, FPDFPAGE_AACTION_CLOSE);
FORM_OnBeforeClosePage(page.get(), form);
return !!bitmap;
}