| // Copyright 2021 The PDFium 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 <fuzzer/FuzzedDataProvider.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "public/fpdf_formfill.h" |
| #include "testing/fuzzers/pdf_fuzzer_templates.h" |
| #include "testing/fuzzers/pdfium_fuzzer_helper.h" |
| #include "third_party/base/containers/adapters.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; |
| } |
| default: { |
| 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>", |
| "Â", |
| "ÆÁÂÁ", |
| "Amount[*]", |
| "~!@#$%^&*()_+", |
| "&|", |
| "&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", |
| """, |
| "Space", |
| "Str", |
| "url", |
| "xhtml", |
| "xml", |
| "XML"", |
| }; |
| |
| 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::base::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; |
| } |