1 // Copyright 2021 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <fuzzer/FuzzedDataProvider.h>
6
7 #include <string>
8
9 #include "public/fpdf_formfill.h"
10 #include "testing/fuzzers/pdf_fuzzer_templates.h"
11 #include "testing/fuzzers/pdfium_fuzzer_helper.h"
12
13 class PDFiumXDPFuzzer : public PDFiumFuzzerHelper {
14 public:
15 PDFiumXDPFuzzer() = default;
16 ~PDFiumXDPFuzzer() override = default;
17
GetFormCallbackVersion() const18 int GetFormCallbackVersion() const override { return 2; }
19
OnFormFillEnvLoaded(FPDF_DOCUMENT doc)20 bool OnFormFillEnvLoaded(FPDF_DOCUMENT doc) override {
21 int form_type = FPDF_GetFormType(doc);
22 if (form_type != FORMTYPE_XFA_FULL && form_type != FORMTYPE_XFA_FOREGROUND)
23 return false;
24 return FPDF_LoadXFA(doc);
25 }
26 };
27
28 struct Tag {
29 const char* tag_name;
30 const char* tag_start;
31 const char* tag_end;
32 };
33
34 const Tag kTagData[]{
35 {.tag_name = "config",
36 .tag_start =
37 R""(<xfa:config xmlns:xfa="http://www.xfa.org/schema/xci/3.1/">)"",
38 .tag_end = "</xfa:config>"},
39 {.tag_name = "template",
40 .tag_start =
41 R""(<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">)"",
42 .tag_end = "</template>"},
43 {.tag_name = "sourceSet",
44 .tag_start =
45 R""(<sourceSet xmlns="http://www.xfa.org/schema/xfa-source-set/2.7/">)"",
46 .tag_end = "</sourceSet>"},
47 {.tag_name = "localeSet",
48 .tag_start =
49 R""(<localeSet xmlns="http://www.xfa.org/schema/xfa-locale-set/2.7/">)"",
50 .tag_end = "</localeSet>"},
51 {.tag_name = "dataSet",
52 .tag_start =
53 R""(<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">)"",
54 .tag_end = "</xfa:datasets>"},
55 {.tag_name = "connectionSet",
56 .tag_start =
57 R""(<connectionSet xmlns="http://www.xfa.org/schema/xfa-connection-set/2.8/">)"",
58 .tag_end = "</connectionSet>"},
59 {.tag_name = "xdc",
60 .tag_start =
61 R""(<xsl:xdc xmlns:xdc="http://www.xfa.org/schema/xdc/1.0/">)"",
62 .tag_end = "</xsl:xdc>"},
63 {.tag_name = "signature",
64 .tag_start = R""(<signature xmlns="http://www.w3.org/2000/09/xmldsig#">)"",
65 .tag_end = "</signature>"},
66 {.tag_name = "stylesheet",
67 .tag_start =
68 R""(<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" id="identifier">)"",
69 .tag_end = "</stylesheet>"},
70 {.tag_name = "xfdf",
71 .tag_start =
72 R""(<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">)"",
73 .tag_end = "</xfdf>"},
74 {.tag_name = "xmpmeta",
75 .tag_start =
76 R""(<xmpmeta xmlns="http://ns.adobe.com/xmpmeta/" xml:space="preserve">)"",
77 .tag_end = "</xmpmeta>"}};
78
CreateObject(int obj_num,const std::string & body)79 std::string CreateObject(int obj_num, const std::string& body) {
80 std::string obj_template = R""($1 0 obj
81 $2
82 endobj
83 )"";
84
85 obj_template.replace(obj_template.find("$1"), 2, std::to_string(obj_num));
86 obj_template.replace(obj_template.find("$2"), 2, body);
87 return obj_template;
88 }
89
CreateStreamObject(int obj_num,const std::string & body)90 std::string CreateStreamObject(int obj_num, const std::string& body) {
91 std::string obj_template = R""($1 0 obj
92 <</Length $2>>
93 stream
94 $3
95 endstream
96 endobj
97 )"";
98
99 obj_template.replace(obj_template.find("$1"), 2, std::to_string(obj_num));
100 obj_template.replace(obj_template.find("$2"), 2,
101 std::to_string(body.size() + 1));
102 obj_template.replace(obj_template.find("$3"), 2, body);
103
104 return obj_template;
105 }
106
GenXrefEntry(size_t offset)107 std::string GenXrefEntry(size_t offset) {
108 return std::string(10 - std::to_string(offset).size(), '0') +
109 std::to_string(offset) + " 00000 n\n";
110 }
111
GenTagBody(const Tag & tag,FuzzedDataProvider * data_provider)112 std::string GenTagBody(const Tag& tag, FuzzedDataProvider* data_provider) {
113 std::string tag_content = data_provider->ConsumeRandomLengthString();
114 return tag.tag_start + tag_content + tag.tag_end;
115 }
116
GenXDPPdfFile(FuzzedDataProvider * data_provider)117 std::string GenXDPPdfFile(FuzzedDataProvider* data_provider) {
118 std::vector<std::string> pdf_objects;
119 std::string pdf_header =
120 std::string(reinterpret_cast<const char*>(kSimplePdfHeader),
121 sizeof(kSimplePdfHeader));
122
123 pdf_objects.push_back(CreateObject(1, kCatalog));
124
125 std::string xfa_obj = kSimpleXfaObjWrapper;
126 Tag tag1 = data_provider->PickValueInArray(kTagData);
127 Tag tag2 = data_provider->PickValueInArray(kTagData);
128 Tag tag3 = data_provider->PickValueInArray(kTagData);
129 xfa_obj.replace(xfa_obj.find("$1"), 2, tag1.tag_name);
130 xfa_obj.replace(xfa_obj.find("$2"), 2, tag2.tag_name);
131 xfa_obj.replace(xfa_obj.find("$3"), 2, tag3.tag_name);
132 pdf_objects.push_back(CreateObject(2, xfa_obj));
133 pdf_objects.push_back(CreateObject(3, kSimplePagesObj));
134 pdf_objects.push_back(CreateObject(4, kSimplePageObj));
135
136 // preamble
137 pdf_objects.push_back(CreateStreamObject(5, kSimplePreamble));
138
139 // The three XFA tags
140 pdf_objects.push_back(CreateStreamObject(6, GenTagBody(tag1, data_provider)));
141 pdf_objects.push_back(CreateStreamObject(7, GenTagBody(tag2, data_provider)));
142 pdf_objects.push_back(CreateStreamObject(8, GenTagBody(tag3, data_provider)));
143
144 // postamble
145 pdf_objects.push_back(CreateStreamObject(9, kSimplePostamble));
146
147 // Create the xref table
148 std::string xref = R""(xref
149 0 10
150 0000000000 65535 f
151 )"";
152
153 // Add xref entries
154 size_t curr_offset = pdf_header.size();
155 for (const auto& ostr : pdf_objects) {
156 xref += GenXrefEntry(curr_offset);
157 curr_offset += ostr.size();
158 }
159
160 std::string footer = R""(trailer
161 <</Root 1 0 R /Size 10>>
162 startxref
163 $1
164 %%EOF)"";
165 footer.replace(footer.find("$1"), 2, std::to_string(curr_offset));
166
167 std::string pdf_core;
168 for (const auto& ostr : pdf_objects) {
169 pdf_core += ostr;
170 }
171
172 // Return the full PDF
173 return pdf_header + pdf_core + xref + footer;
174 }
175
IsValidForFuzzing(const uint8_t * data,size_t size)176 bool IsValidForFuzzing(const uint8_t* data, size_t size) {
177 if (size > 2048) {
178 return false;
179 }
180 const char* ptr = reinterpret_cast<const char*>(data);
181 for (size_t i = 0; i < size; i++) {
182 if (!std::isspace(ptr[i]) && !std::isprint(ptr[i])) {
183 return false;
184 }
185 }
186 return true;
187 }
188
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)189 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
190 if (!IsValidForFuzzing(data, size)) {
191 return 0;
192 }
193
194 FuzzedDataProvider data_provider(data, size);
195 std::string xfa_final_str = GenXDPPdfFile(&data_provider);
196
197 #ifdef PDFIUM_FUZZER_DUMP
198 for (size_t i = 0; i < xfa_final_str.size(); i++) {
199 putc(xfa_final_str[i], stdout);
200 }
201 #endif
202
203 PDFiumXDPFuzzer fuzzer;
204 fuzzer.RenderPdf(xfa_final_str.c_str(), xfa_final_str.size());
205 return 0;
206 }
207