blob: 7e2985316887d81e65130fc8a2375ed8fd5fdb6f [file] [log] [blame] [edit]
// 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 "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;
}