blob: c86fbf35d95863976dcfb21ac755f9653bd0135e [file] [log] [blame] [edit]
// Copyright 2021 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuzzer/FuzzedDataProvider.h>
#include <string>
#include <vector>
#include "core/fxcrt/containers/adapters.h"
#include "public/fpdf_formfill.h"
#include "testing/fuzzers/pdf_fuzzer_templates.h"
#include "testing/fuzzers/pdfium_fuzzer_helper.h"
class PDFiumXFAFuzzer : public PDFiumFuzzerHelper {
public:
PDFiumXFAFuzzer() = default;
~PDFiumXFAFuzzer() override = default;
int GetFormCallbackVersion() const override { return 2; }
void SetFdp(FuzzedDataProvider* fdp) { fdp_ = fdp; }
// Return false if XFA doesn't load as otherwise we're duplicating the work
// done by the non-xfa fuzzer.
bool OnFormFillEnvLoaded(FPDF_DOCUMENT doc) override {
int form_type = FPDF_GetFormType(doc);
if (form_type != FORMTYPE_XFA_FULL && form_type != FORMTYPE_XFA_FOREGROUND)
return false;
return FPDF_LoadXFA(doc);
}
void FormActionHandler(FPDF_FORMHANDLE form,
FPDF_DOCUMENT doc,
FPDF_PAGE page) override {
if (!fdp_) {
return;
}
char local_buf[50];
int number_of_calls = fdp_->ConsumeIntegralInRange<int>(0, 250);
for (int i = 0; i < number_of_calls; i++) {
UserInteraction selector = fdp_->ConsumeEnum<UserInteraction>();
switch (selector) {
case kOnLButtonUp: {
FORM_OnLButtonUp(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegralInRange<int>(-100, 1000),
fdp_->ConsumeIntegralInRange<int>(-100, 1000));
break;
}
case kOnRButtonUp: {
FORM_OnRButtonUp(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegralInRange<int>(-100, 1000),
fdp_->ConsumeIntegralInRange<int>(-100, 1000));
break;
}
case kOnLButtonDown: {
FORM_OnLButtonDown(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegralInRange<int>(-100, 1000),
fdp_->ConsumeIntegralInRange<int>(-100, 1000));
break;
}
case kOnRButtonDown: {
FORM_OnRButtonDown(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegralInRange<int>(-100, 1000),
fdp_->ConsumeIntegralInRange<int>(-100, 1000));
break;
}
case kOnChar: {
FORM_OnChar(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kOnKeyDown: {
FORM_OnKeyDown(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kOnKeyUp: {
FORM_OnKeyUp(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kOnLButtonDoubleClick: {
FORM_OnLButtonDoubleClick(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kOnMouseMove: {
FORM_OnMouseMove(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kOnMouseWheel: {
const FS_POINTF point = {fdp_->ConsumeFloatingPoint<float>(),
fdp_->ConsumeFloatingPoint<float>()};
FORM_OnMouseWheel(form, page, fdp_->ConsumeIntegral<int>(), &point,
fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kOnFocus: {
FORM_OnFocus(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kUndo: {
if (FORM_CanUndo(form, page)) {
FORM_Undo(form, page);
}
break;
}
case kSelectAllText: {
FORM_SelectAllText(form, page);
break;
}
case kRedo: {
if (FORM_CanRedo(form, page)) {
FORM_Redo(form, page);
}
break;
}
case kAnnot: {
FPDF_ANNOTATION annot = nullptr;
int page_index = -2;
FORM_GetFocusedAnnot(form, &page_index, &annot);
if (annot) {
FORM_SetFocusedAnnot(form, annot);
}
break;
}
case kSetIndexSelected: {
FORM_SetIndexSelected(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeBool());
break;
}
case kIsIndexSelected: {
FORM_IsIndexSelected(form, page, fdp_->ConsumeIntegral<int>());
break;
}
case kHasFormFieldAtPoint: {
FPDFPage_HasFormFieldAtPoint(form, page, fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kFormFieldZOrderAtPoint: {
FPDFPage_FormFieldZOrderAtPoint(form, page,
fdp_->ConsumeIntegral<int>(),
fdp_->ConsumeIntegral<int>());
break;
}
case kGetSelectedText: {
FORM_GetSelectedText(form, page, local_buf, sizeof(local_buf));
break;
}
case kGetFocusedText: {
FORM_GetFocusedText(form, page, local_buf, sizeof(local_buf));
break;
}
}
}
}
private:
enum UserInteraction {
kOnLButtonUp = 0,
kOnRButtonUp,
kOnLButtonDown,
kOnRButtonDown,
kOnChar,
kOnKeyDown,
kOnKeyUp,
kOnLButtonDoubleClick,
kOnMouseMove,
kOnMouseWheel,
kOnFocus,
kUndo,
kSelectAllText,
kRedo,
kAnnot,
kSetIndexSelected,
kIsIndexSelected,
kHasFormFieldAtPoint,
kFormFieldZOrderAtPoint,
kGetSelectedText,
kGetFocusedText,
kMaxValue = kGetFocusedText
};
FuzzedDataProvider* fdp_ = nullptr;
};
// Possible names of an XFA FormCalc script function
std::string GenXfaFormCalcScriptFuncName(FuzzedDataProvider* data_provider) {
static const char* const kXfaScriptFuncs[] = {
"Abs", "Apr", "At", "Avg", "Ceil",
"Choose", "Concat", "Count", "Cterm", "Date",
"Date2Num", "DateFmt", "Decode", "Encode", "Eval",
"Exists", "Floor", "Format", "FV", "Get",
"HasValue", "If", "Ipmt", "IsoDate2Num", "IsoTime2Num",
"Left", "Len", "LocalDateFmt", "LocalTimeFmt", "Lower",
"Ltrim", "Max", "Min", "Mod", "NPV",
"Num2Date", "Num2GMTime", "Num2Time", "Oneof", "Parse",
"Pmt", "Post", "PPmt", "Put", "PV",
"Rate", "Ref", "Replace", "Right", "Round",
"Rtrim", "Space", "Str", "Stuff", "Substr",
"Sum", "Term", "Time", "Time2Num", "TimeFmt",
"Translate", "UnitType", "UnitValue", "Upper", "Uuid",
"Within", "WordNum",
};
size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
0, std::size(kXfaScriptFuncs) - 1);
return kXfaScriptFuncs[elem_selector];
}
std::string MaybeQuote(FuzzedDataProvider* data_provider, std::string body) {
if (data_provider->ConsumeIntegralInRange<uint32_t>(0, 100) < 20) {
return "\"" + body + "\"";
}
return body;
}
// Possible arguments to a XFA script function
std::string GenXfaScriptParam(FuzzedDataProvider* data_provider) {
static const char* const kXfaFuncParams[] = {
"$",
"-0",
"04/13/2019",
".05",
"-1",
"1",
" 1 | 0",
"10 * 10 * 10 * 9 * 123",
"1024",
"10 * a + 9",
"1.2131",
"[1,2,3]",
"%123",
"[1,2,3][0]",
"123124",
"123342123",
"13:13:13",
"13:13:13 GMT",
"19960315T20:20:20",
"1 and 1",
"1 and 2",
"2",
"20000201",
"2009-06-01T13:45:30",
"2009-06-15T01:45:30",
"2009-06-15T13:45:30-07:00",
"2009-06-15T13:45:30.5275000",
" 2 < 3 + 1",
"2 + 3 + 9",
"3",
"3 * 1",
"3 -9",
"5 < 5",
"-99",
"99",
"9999999",
"99999999999",
"A",
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"\xc3\x81\xc3\x82\xc3\x83\xc3\x84\xc3\x85\xc3\x86",
"<a><b></b></a>",
"&Acirc;",
"&AElig;&Aacute;&Acirc;&Aacute;",
"Amount[*]",
"~!@#$%^&amp;*()_+",
"&amp;|",
"&apos",
"apr",
"april",
"B",
"<br>",
"C",
"de_DE",
"es_ES",
"feb",
"febuary",
"HH:MM:SS",
"<html>",
"html",
"HTML",
"jan",
"january",
"json",
"lkdjfglsdkfgj",
"mar",
"march",
"name[0]",
"name1",
"name2",
"name3",
"name4",
"name[*].numAmount",
"&quot;",
"Space",
"Str",
"url",
"xhtml",
"xml",
"XML&quot;",
};
size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
0, std::size(kXfaFuncParams) - 1);
return MaybeQuote(data_provider, kXfaFuncParams[elem_selector]);
}
// Possible XFA tags
std::string GenXfaTag(FuzzedDataProvider* data_provider) {
static const char* const kXfaElemTags[] = {
"accessibleContent",
"acrobat",
"acrobat",
"acrobat7",
"ADBE_JSConsole",
"ADBE_JSDebugger",
"addSilentPrint",
"addViewerPreferences",
"adjustData",
"adobeExtensionLevel",
"agent",
"alwaysEmbed",
"amd",
"appearanceFilter",
"arc",
"area",
"assist",
"attributes",
"autoSave",
"barcode",
"base",
"batchOutput",
"behaviorOverride",
"bind",
"bindItems",
"bookend",
"boolean",
"border",
"break",
"breakAfter",
"breakBefore",
"button",
"cache",
"calculate",
"calendarSymbols",
"caption",
"certificate",
"certificates",
"change",
"checkButton",
"choiceList",
"color",
"comb",
"command",
"common",
"compress",
"compression",
"compressLogicalStructure",
"compressObjectStream",
"config",
"config",
"conformance",
"connect",
"connectionSet",
"connectString",
"contentArea",
"contentCopy",
"copies",
"corner",
"creator",
"currencySymbol",
"currencySymbols",
"currentPage",
"data",
"dataGroup",
"dataModel",
"dataValue",
"dataWindow",
"date",
"datePattern",
"datePatterns",
"dateTime",
"dateTimeEdit",
"dateTimeSymbols",
"day",
"dayNames",
"debug",
"decimal",
"defaultTypeface",
"defaultUi",
"delete",
"delta",
"deltas",
"desc",
"destination",
"digestMethod",
"digestMethods",
"documentAssembly",
"draw",
"driver",
"dSigData",
"duplexOption",
"dynamicRender",
"edge",
"effectiveInputPolicy",
"effectiveOutputPolicy",
"embed",
"encoding",
"encodings",
"encrypt",
"encryption",
"encryptionLevel",
"encryptionMethod",
"encryptionMethods",
"enforce",
"equate",
"equateRange",
"era",
"eraNames",
"event",
"eventPseudoModel",
"exclGroup",
"exclude",
"excludeNS",
"exData",
"execute",
"exObject",
"extras",
"field",
"fill",
"filter",
"flipLabel",
"float",
"font",
"fontInfo",
"form",
"format",
"formFieldFilling",
"groupParent",
"handler",
"hostPseudoModel",
"hyphenation",
"ifEmpty",
"image",
"imageEdit",
"includeXDPContent",
"incrementalLoad",
"incrementalMerge",
"insert",
"instanceManager",
"integer",
"interactive",
"issuers",
"items",
"jog",
"keep",
"keyUsage",
"labelPrinter",
"layout",
"layoutPseudoModel",
"level",
"line",
"linear",
"linearized",
"list",
"locale",
"localeSet",
"lockDocument",
"log",
"logPseudoModel",
"manifest",
"map",
"margin",
"mdp",
"medium",
"mediumInfo",
"meridiem",
"meridiemNames",
"message",
"messaging",
"mode",
"modifyAnnots",
"month",
"monthNames",
"msgId",
"nameAttr",
"neverEmbed",
"numberOfCopies",
"numberPattern",
"numberPatterns",
"numberSymbol",
"numberSymbols",
"numericEdit",
"object",
"occur",
"oid",
"oids",
"openAction",
"operation",
"output",
"outputBin",
"outputXSL",
"overflow",
"overprint",
"packet",
"packets",
"pageArea",
"pageOffset",
"pageRange",
"pageSet",
"pagination",
"paginationOverride",
"para",
"part",
"password",
"passwordEdit",
"pattern",
"pcl",
"pdf",
"pdfa",
"permissions",
"pickTrayByPDFSize",
"picture",
"plaintextMetadata",
"presence",
"present",
"present",
"print",
"printerName",
"printHighQuality",
"printScaling",
"producer",
"proto",
"ps",
"psMap",
"query",
"radial",
"range",
"reason",
"reasons",
"record",
"recordSet",
"rectangle",
"ref",
"relevant",
"rename",
"renderPolicy",
"rootElement",
"runScripts",
"script",
"scriptModel",
"select",
"setProperty",
"severity",
"signature",
"signatureProperties",
"signaturePseudoModel",
"signData",
"signing",
"silentPrint",
"soapAction",
"soapAddress",
"solid",
"source",
"sourceSet",
"speak",
"staple",
"startNode",
"startPage",
"stipple",
"subform",
"subform",
"subformSet",
"subjectDN",
"subjectDNs",
"submit",
"submitFormat",
"submitUrl",
"subsetBelow",
"suppressBanner",
"tagged",
"template",
"template",
"templateCache",
"#text",
"text",
"textedit",
"textEdit",
"threshold",
"time",
"timePattern",
"timePatterns",
"timeStamp",
"to",
"toolTip",
"trace",
"transform",
"traversal",
"traverse",
"treeList",
"type",
"typeface",
"typefaces",
"ui",
"update",
"uri",
"user",
"validate",
"validate",
"validateApprovalSignatures",
"validationMessaging",
"value",
"variables",
"version",
"versionControl",
"viewerPreferences",
"webClient",
"whitespace",
"window",
"wsdlAddress",
"wsdlConnection",
"xdc",
"xdp",
"xfa",
"#xHTML",
"#xml",
"xmlConnection",
"xsdConnection",
"xsl",
"zpl",
};
size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
0, std::size(kXfaElemTags) - 1);
return kXfaElemTags[elem_selector];
}
// Possible XFA attributes values
std::string GenXfaTagValue(FuzzedDataProvider* data_provider) {
static const char* const kXfaTagVals[] = {
"0", "0pt", "-1",
"123", "1pt", "203.2mm",
"22.1404mm", "255", "256",
"321", "5431.21mm", "6.35mm",
"8in", "8pt", "application/x-javascript",
"bold", "bold", "change",
"click", "consumeData", "docReady",
"en_US", "form1", "initialize",
"italic", "middle", "name2",
"name3", "name4", "name5",
"onEnter", "Page1", "RadioList[0]",
"subform_1", "tb", "Verdana",
};
size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
0, std::size(kXfaTagVals) - 1);
return MaybeQuote(data_provider, kXfaTagVals[elem_selector]);
}
// possible XFA attributes
std::string GenXfaTagName(FuzzedDataProvider* data_provider) {
static const char* const kXfaTagNames[] = {
"activity", "baselineShift",
"contentType", "h",
"id", "layout",
"layout", "leftInset",
"locale", "long",
"marginLeft", "marginRight",
"marginRight", "mergeMode",
"name", "ref",
"scriptTest", "short",
"size", "spaceAbove",
"spaceBelow", "startNew",
"stock", "textIndent",
"timeStamp", "typeface",
"uuid", "vAlign",
"value", "w",
"weight", "x",
"y",
};
size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
0, std::size(kXfaTagNames) - 1);
return kXfaTagNames[elem_selector];
}
// Will create a simple XFA FormCalc script that calls a single function.
std::string GenXfaFormCalcScript(FuzzedDataProvider* data_provider) {
std::string xfa_string = GenXfaFormCalcScriptFuncName(data_provider);
xfa_string += "(";
// Generate parameters
size_t num_params = data_provider->ConsumeIntegralInRange<size_t>(0, 3);
for (size_t i = 0; i < num_params; i++) {
if (i != 0) {
xfa_string += ",";
}
xfa_string += GenXfaScriptParam(data_provider);
}
xfa_string += ")";
return xfa_string;
}
// XFA Javascript logic
std::string GenXfaName(FuzzedDataProvider* data_provider) {
return "name" + std::to_string(data_provider->ConsumeIntegralInRange(0, 25));
}
std::string GetXfaJSPrimitiveType(FuzzedDataProvider* data_provider) {
return GenXfaScriptParam(data_provider);
}
std::string GenXfaJSRValue(FuzzedDataProvider* data_provider) {
if (data_provider->ConsumeBool()) {
return GenXfaScriptParam(data_provider);
}
std::string xfa_string;
if (data_provider->ConsumeBool()) {
xfa_string += "xfa.form.";
}
// Handle the possibility of nested names
size_t num_nests = data_provider->ConsumeIntegralInRange<size_t>(1, 3);
for (size_t i = 0; i < num_nests; i++) {
if (i != 0) {
xfa_string += ".";
}
xfa_string += GenXfaName(data_provider);
}
return MaybeQuote(data_provider, xfa_string);
}
std::string GenXfaJSAssignment(FuzzedDataProvider* data_provider) {
return GenXfaName(data_provider) + " = " + GenXfaJSRValue(data_provider);
}
std::string GenXfaJSMethodCall(FuzzedDataProvider* data_provider) {
static const char* const kXfaJSFuncs[] = {
"addItem",
"boundItem",
"clearItems",
"deleteItem",
"execCalculate",
"execEvent",
"execInitialize",
"execValidate",
"getDisplayItem",
"getItemState",
"getSaveItem",
"exec.form.formNodes",
"exec.form.recalculate",
"setItemState",
"xfa.container.getDelta",
"xfa.container.getDeltas",
"xfa.event.emit",
"xfa.event.reset",
"xfa.form.execCalculat",
"xfa.form.execInitialize",
"xfa.form.execValidate",
"xfa.form.remerge",
"xfa.host.beep",
"xfa.host.documentCountInBatch",
"xfa.host.documentInBatch",
"xfa.host.exportData",
"xfa.host.getFocus",
"xfa.host.gotoURL",
"xfa.host.importData",
"xfa.host.messageBox",
"xfa.host.openList",
"xfa.host.pageDown",
"xfa.host.pageUp",
"xfa.host.print",
"xfa.host.resetData",
"xfa.host.setFocus",
"xfa.host.response",
"xfa.resolveNode",
};
std::string xfa_string = data_provider->PickValueInArray(kXfaJSFuncs);
xfa_string += "(";
// Get the params
size_t param_count = data_provider->ConsumeIntegralInRange<size_t>(0, 3);
for (size_t i = 0; i < param_count; i++) {
if (i != 0) {
xfa_string += ",";
}
xfa_string += GenXfaJSRValue(data_provider);
}
xfa_string += ")";
return xfa_string;
}
// This is a simple generator of xfa-based javascript. The function creates
// simple javascript statements that are related to XFA logic and the goal is
// not to create fully-fleged javascript programs but rather use simple
// statements to ensure XFA code is covered.
enum XFAJSStatement {
kAssignment = 0,
kJSMethodCall,
kJSObjectCall,
kMaxValue = kJSObjectCall
};
std::string GenXfaJSScript(FuzzedDataProvider* data_provider) {
std::string xfa_string;
size_t num_stmts = data_provider->ConsumeIntegralInRange<size_t>(1, 10);
for (size_t i = 0; i < num_stmts; i++) {
XFAJSStatement stmt = data_provider->ConsumeEnum<XFAJSStatement>();
switch (stmt) {
case kAssignment:
xfa_string += GenXfaJSAssignment(data_provider);
break;
case kJSMethodCall:
xfa_string += GenXfaJSMethodCall(data_provider);
break;
case kJSObjectCall:
xfa_string += GenXfaName(data_provider);
xfa_string += ".";
xfa_string += GenXfaJSMethodCall(data_provider);
break;
}
xfa_string += ";\n";
}
return xfa_string;
}
std::string GenXfacript(FuzzedDataProvider* data_provider) {
// Determine if this should be a FormCalc script or Javascript, 50/50 chance
// for each.
if (data_provider->ConsumeBool()) {
return GenXfaFormCalcScript(data_provider);
}
return GenXfaJSScript(data_provider);
}
// Will create a single XFA attributes, with both lhs and rhs.
std::string getXfaElemAttributes(FuzzedDataProvider* data_provider) {
// Generate a set of tags, and a set of values for the tags.
return GenXfaTagName(data_provider) + "=" + GenXfaTagValue(data_provider);
}
// Creates an XFA structure wrapped in <xdp tags.
std::string GenXfaTree(FuzzedDataProvider* data_provider) {
std::string xfa_string = "<xdp xmlns=\"http://ns.adobe.com/xdp/\">";
// One stack iteration
int stack_iterations = data_provider->ConsumeIntegralInRange(1, 3);
for (int si = 0; si < stack_iterations; si++) {
int elem_count = data_provider->ConsumeIntegralInRange(1, 6);
std::vector<std::string> xml_stack;
xml_stack.reserve(elem_count);
for (int i = 0; i < elem_count; i++) {
std::string tag = GenXfaTag(data_provider);
xfa_string += "<" + tag;
// in 30% of cases, add attributes
if (data_provider->ConsumeIntegralInRange(1, 100) > 70) {
size_t attribute_count = data_provider->ConsumeIntegralInRange(1, 5);
for (; 0 < attribute_count; attribute_count--) {
xfa_string += " " + getXfaElemAttributes(data_provider);
}
}
xfa_string += ">";
// If needed, add a body to the tag
if (tag == "script") {
xfa_string += GenXfacript(data_provider);
}
// Push the tag to the stack so we can close it when done
xml_stack.push_back(tag);
}
for (const std::string& tag : pdfium::Reversed(xml_stack)) {
xfa_string += "</" + tag + ">";
}
}
xfa_string += "</xdp>";
return xfa_string;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedDataProvider data_provider(data, size);
std::string xfa_string = GenXfaTree(&data_provider);
// Add 1 for newline before endstream.
std::string xfa_stream_len = std::to_string(xfa_string.size() + 1);
// Compose the fuzzer
std::string xfa_final_str = std::string(kSimplePdfTemplate);
xfa_final_str.replace(xfa_final_str.find("$1"), 2, xfa_stream_len);
xfa_final_str.replace(xfa_final_str.find("$2"), 2, xfa_string);
#ifdef PDFIUM_FUZZER_DUMP
for (size_t i = 0; i < xfa_final_str.size(); i++) {
putc(xfa_final_str[i], stdout);
}
#endif
PDFiumXFAFuzzer fuzzer;
fuzzer.SetFdp(&data_provider);
fuzzer.RenderPdf(xfa_final_str.c_str(), xfa_final_str.size());
return 0;
}