|  | // 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 "public/fpdf_formfill.h" | 
|  | #include "testing/fuzzers/pdf_fuzzer_templates.h" | 
|  | #include "testing/fuzzers/pdfium_fuzzer_helper.h" | 
|  |  | 
|  | class PDFiumXDPFuzzer : public PDFiumFuzzerHelper { | 
|  | public: | 
|  | PDFiumXDPFuzzer() = default; | 
|  | ~PDFiumXDPFuzzer() override = default; | 
|  |  | 
|  | int GetFormCallbackVersion() const override { return 2; } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | }; | 
|  |  | 
|  | struct Tag { | 
|  | const char* tag_name; | 
|  | const char* tag_start; | 
|  | const char* tag_end; | 
|  | }; | 
|  |  | 
|  | const Tag kTagData[]{ | 
|  | {.tag_name = "config", | 
|  | .tag_start = | 
|  | R""(<xfa:config xmlns:xfa="http://www.xfa.org/schema/xci/3.1/">)"", | 
|  | .tag_end = "</xfa:config>"}, | 
|  | {.tag_name = "template", | 
|  | .tag_start = | 
|  | R""(<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">)"", | 
|  | .tag_end = "</template>"}, | 
|  | {.tag_name = "sourceSet", | 
|  | .tag_start = | 
|  | R""(<sourceSet xmlns="http://www.xfa.org/schema/xfa-source-set/2.7/">)"", | 
|  | .tag_end = "</sourceSet>"}, | 
|  | {.tag_name = "localeSet", | 
|  | .tag_start = | 
|  | R""(<localeSet xmlns="http://www.xfa.org/schema/xfa-locale-set/2.7/">)"", | 
|  | .tag_end = "</localeSet>"}, | 
|  | {.tag_name = "dataSet", | 
|  | .tag_start = | 
|  | R""(<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">)"", | 
|  | .tag_end = "</xfa:datasets>"}, | 
|  | {.tag_name = "connectionSet", | 
|  | .tag_start = | 
|  | R""(<connectionSet xmlns="http://www.xfa.org/schema/xfa-connection-set/2.8/">)"", | 
|  | .tag_end = "</connectionSet>"}, | 
|  | {.tag_name = "xdc", | 
|  | .tag_start = | 
|  | R""(<xsl:xdc xmlns:xdc="http://www.xfa.org/schema/xdc/1.0/">)"", | 
|  | .tag_end = "</xsl:xdc>"}, | 
|  | {.tag_name = "signature", | 
|  | .tag_start = R""(<signature xmlns="http://www.w3.org/2000/09/xmldsig#">)"", | 
|  | .tag_end = "</signature>"}, | 
|  | {.tag_name = "stylesheet", | 
|  | .tag_start = | 
|  | R""(<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" id="identifier">)"", | 
|  | .tag_end = "</stylesheet>"}, | 
|  | {.tag_name = "xfdf", | 
|  | .tag_start = | 
|  | R""(<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">)"", | 
|  | .tag_end = "</xfdf>"}, | 
|  | {.tag_name = "xmpmeta", | 
|  | .tag_start = | 
|  | R""(<xmpmeta xmlns="http://ns.adobe.com/xmpmeta/" xml:space="preserve">)"", | 
|  | .tag_end = "</xmpmeta>"}}; | 
|  |  | 
|  | std::string CreateObject(int obj_num, const std::string& body) { | 
|  | std::string obj_template = R""($1 0 obj | 
|  | $2 | 
|  | endobj | 
|  | )""; | 
|  |  | 
|  | obj_template.replace(obj_template.find("$1"), 2, std::to_string(obj_num)); | 
|  | obj_template.replace(obj_template.find("$2"), 2, body); | 
|  | return obj_template; | 
|  | } | 
|  |  | 
|  | std::string CreateStreamObject(int obj_num, const std::string& body) { | 
|  | std::string obj_template = R""($1 0 obj | 
|  | <</Length $2>> | 
|  | stream | 
|  | $3 | 
|  | endstream | 
|  | endobj | 
|  | )""; | 
|  |  | 
|  | obj_template.replace(obj_template.find("$1"), 2, std::to_string(obj_num)); | 
|  | obj_template.replace(obj_template.find("$2"), 2, | 
|  | std::to_string(body.size() + 1)); | 
|  | obj_template.replace(obj_template.find("$3"), 2, body); | 
|  |  | 
|  | return obj_template; | 
|  | } | 
|  |  | 
|  | std::string GenXrefEntry(size_t offset) { | 
|  | return std::string(10 - std::to_string(offset).size(), '0') + | 
|  | std::to_string(offset) + " 00000 n\n"; | 
|  | } | 
|  |  | 
|  | std::string GenTagBody(const Tag& tag, FuzzedDataProvider* data_provider) { | 
|  | std::string tag_content = data_provider->ConsumeRandomLengthString(); | 
|  | return tag.tag_start + tag_content + tag.tag_end; | 
|  | } | 
|  |  | 
|  | std::string GenXDPPdfFile(FuzzedDataProvider* data_provider) { | 
|  | std::vector<std::string> pdf_objects; | 
|  | std::string pdf_header = | 
|  | std::string(reinterpret_cast<const char*>(kSimplePdfHeader), | 
|  | sizeof(kSimplePdfHeader)); | 
|  |  | 
|  | pdf_objects.push_back(CreateObject(1, kCatalog)); | 
|  |  | 
|  | std::string xfa_obj = kSimpleXfaObjWrapper; | 
|  | Tag tag1 = data_provider->PickValueInArray(kTagData); | 
|  | Tag tag2 = data_provider->PickValueInArray(kTagData); | 
|  | Tag tag3 = data_provider->PickValueInArray(kTagData); | 
|  | xfa_obj.replace(xfa_obj.find("$1"), 2, tag1.tag_name); | 
|  | xfa_obj.replace(xfa_obj.find("$2"), 2, tag2.tag_name); | 
|  | xfa_obj.replace(xfa_obj.find("$3"), 2, tag3.tag_name); | 
|  | pdf_objects.push_back(CreateObject(2, xfa_obj)); | 
|  | pdf_objects.push_back(CreateObject(3, kSimplePagesObj)); | 
|  | pdf_objects.push_back(CreateObject(4, kSimplePageObj)); | 
|  |  | 
|  | // preamble | 
|  | pdf_objects.push_back(CreateStreamObject(5, kSimplePreamble)); | 
|  |  | 
|  | // The three XFA tags | 
|  | pdf_objects.push_back(CreateStreamObject(6, GenTagBody(tag1, data_provider))); | 
|  | pdf_objects.push_back(CreateStreamObject(7, GenTagBody(tag2, data_provider))); | 
|  | pdf_objects.push_back(CreateStreamObject(8, GenTagBody(tag3, data_provider))); | 
|  |  | 
|  | // postamble | 
|  | pdf_objects.push_back(CreateStreamObject(9, kSimplePostamble)); | 
|  |  | 
|  | // Create the xref table | 
|  | std::string xref = R""(xref | 
|  | 0 10 | 
|  | 0000000000 65535 f | 
|  | )""; | 
|  |  | 
|  | // Add xref entries | 
|  | size_t curr_offset = pdf_header.size(); | 
|  | for (const auto& ostr : pdf_objects) { | 
|  | xref += GenXrefEntry(curr_offset); | 
|  | curr_offset += ostr.size(); | 
|  | } | 
|  |  | 
|  | std::string footer = R""(trailer | 
|  | <</Root 1 0 R /Size 10>> | 
|  | startxref | 
|  | $1 | 
|  | %%EOF)""; | 
|  | footer.replace(footer.find("$1"), 2, std::to_string(curr_offset)); | 
|  |  | 
|  | std::string pdf_core; | 
|  | for (const auto& ostr : pdf_objects) { | 
|  | pdf_core += ostr; | 
|  | } | 
|  |  | 
|  | // Return the full PDF | 
|  | return pdf_header + pdf_core + xref + footer; | 
|  | } | 
|  |  | 
|  | bool IsValidForFuzzing(const uint8_t* data, size_t size) { | 
|  | if (size > 2048) { | 
|  | return false; | 
|  | } | 
|  | const char* ptr = reinterpret_cast<const char*>(data); | 
|  | for (size_t i = 0; i < size; i++) { | 
|  | if (!std::isspace(ptr[i]) && !std::isprint(ptr[i])) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { | 
|  | if (!IsValidForFuzzing(data, size)) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | FuzzedDataProvider data_provider(data, size); | 
|  | std::string xfa_final_str = GenXDPPdfFile(&data_provider); | 
|  |  | 
|  | #ifdef PDFIUM_FUZZER_DUMP | 
|  | for (size_t i = 0; i < xfa_final_str.size(); i++) { | 
|  | putc(xfa_final_str[i], stdout); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | PDFiumXDPFuzzer fuzzer; | 
|  | fuzzer.RenderPdf(xfa_final_str.c_str(), xfa_final_str.size()); | 
|  | return 0; | 
|  | } |