xref: /aosp_15_r20/external/cronet/testing/libfuzzer/fuzzers/url_parse_proto_fuzzer.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2017 The Chromium 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 <assert.h>
6 #include <stdlib.h>
7 
8 #include <iostream>
9 
10 // Includes copied from url_parse_fuzzer.cc
11 #include "base/at_exit.h"
12 #include "base/i18n/icu_util.h"
13 #include "url/gurl.h"
14 
15 // Includes *not* copied from url_parse_fuzzer.cc
16 // Contains DEFINE_BINARY_PROTO_FUZZER, a macro we use to define our target
17 // function.
18 #include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
19 // Header information about the Protocol Buffer Url class.
20 #include "testing/libfuzzer/proto/url.pb.h"
21 
22 // The code using TestCase is copied from url_parse_fuzzer.cc
23 struct TestCase {
TestCaseTestCase24   TestCase() {
25     CHECK(base::i18n::InitializeICU());
26   }
27   // used by ICU integration.
28   base::AtExitManager at_exit_manager;
29 };
30 
31 TestCase* test_case = new TestCase();
32 
33 // Silence logging from the protobuf library.
34 protobuf_mutator::protobuf::LogSilencer log_silencer;
35 
Slash_to_string(int slash)36 std::string Slash_to_string(int slash) {
37   if (slash == url_proto::Url::NONE)
38     return "";
39   if (slash == url_proto::Url::FORWARD)
40     return "/";
41   if (slash == url_proto::Url::BACKWARD) {
42     return "\\";
43   }
44   assert(false && "Received unexpected value for slash");
45   // Silence compiler warning about not returning in non-void function.
46   return "";
47 }
48 
49 // Converts a URL in Protocol Buffer format to a url in string format.
50 // Since protobuf is a relatively simple format, fuzzing targets that do not
51 // accept protobufs (such as this one) will require code to convert from
52 // protobuf to the accepted format (string in this case).
protobuf_to_string(const url_proto::Url & url)53 std::string protobuf_to_string(const url_proto::Url& url) {
54   // Build url_string piece by piece from url and then return it.
55   std::string url_string = std::string("");
56 
57   if (url.has_scheme()) {  // Get the scheme if Url has it.
58     // Append the scheme to the url. This may be empty. Then append a colon
59     // which is mandatory if there is a scheme.
60     url_string += url.scheme() + ":";
61   }
62 
63   // Just append the slashes without doing validation, since it would be too
64   // complex. libFuzzer will hopefully figure out good values.
65   for (const int slash : url.slashes())
66     url_string += Slash_to_string(slash);
67 
68   // Get host. This is simple since hosts are simply strings according to our
69   // definition.
70   if (url.has_host()) {
71     // Get userinfo if libFuzzer set it. Ensure that user is seperated
72     // from the password by ":" (if a password is included) and that userinfo is
73     // separated from the host by "@".
74     if (url.has_userinfo()) {
75       url_string += url.userinfo().user();
76       if (url.userinfo().has_password()) {
77         url_string += ":";
78         url_string += url.userinfo().password();
79       }
80       url_string += "@";
81     }
82     url_string += url.host();
83 
84     // As explained in url.proto, if libFuzzer included a port in url ensure
85     // that it is preceded by the host and then ":".
86     if (url.has_port())
87       // Convert url.port() from an unsigned 32 bit int before appending it.
88       url_string += ":" + std::to_string(url.port());
89   }
90 
91   // Append the path segments to the url, with each segment separated by
92   // the path_separator.
93   bool first_segment = true;
94   std::string path_separator = Slash_to_string(url.path_separator());
95   for (const std::string& path_segment : url.path()) {
96     // There does not need to be a path, but if there is a path and a host,
97     // ensure the path begins with "/".
98     if (url.has_host() && first_segment) {
99       url_string += "/" + path_segment;
100       first_segment = false;
101     } else
102       url_string += path_separator + path_segment;
103   }
104 
105   // Queries must be started by "?". If libFuzzer included a query in url,
106   // ensure that it is preceded by "?". Also Seperate query components with
107   // ampersands as is the convention.
108   bool first_component = true;
109   for (const std::string& query_component : url.query()) {
110     if (first_component) {
111       url_string += "?" + query_component;
112       first_component = false;
113     } else
114       url_string += "&" + query_component;
115   }
116 
117   // Fragments must be started by "#". If libFuzzer included a fragment
118   // in url, ensure that it is preceded by "#".
119   if (url.has_fragment())
120     url_string += "#" + url.fragment();
121 
122   return url_string;
123 }
124 
125 // The target function. This is the equivalent of LLVMFuzzerTestOneInput in
126 // typical libFuzzer based fuzzers. It is passed our Url protobuf object that
127 // was mutated by libFuzzer, converts it to a string and then feeds it to url()
128 // for fuzzing.
DEFINE_BINARY_PROTO_FUZZER(const url_proto::Url & url_protobuf)129 DEFINE_BINARY_PROTO_FUZZER(const url_proto::Url& url_protobuf) {
130   std::string url_string = protobuf_to_string(url_protobuf);
131 
132   // Allow native input to be retrieved easily.
133   // Note that there will be a trailing newline that is not part of url_string.
134   if (getenv("LPM_DUMP_NATIVE_INPUT"))
135     std::cout << url_string << std::endl;
136 
137   GURL url(url_string);
138 }
139