/src/kea/fuzz/fuzz_http_endpoint_kea_dhcp4.cc
Line | Count | Source |
1 | | // Copyright (C) 2024-2025 Internet Systems Consortium, Inc. ("ISC") |
2 | | // |
3 | | // This Source Code Form is subject to the terms of the Mozilla Public |
4 | | // License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
6 | | |
7 | | #include <config.h> |
8 | | |
9 | | #include <cassert> |
10 | | #include <cstdlib> |
11 | | #include <iostream> |
12 | | |
13 | | #include <fuzz.h> |
14 | | |
15 | | #include <asiolink/io_service.h> |
16 | | #include <asiolink/interval_timer.h> |
17 | | #include <cc/data.h> |
18 | | #include <config/cmd_http_listener.h> |
19 | | #include <dhcp4/ctrl_dhcp4_srv.h> |
20 | | #include <dhcpsrv/cfgmgr.h> |
21 | | #include <http/listener.h> |
22 | | #include <http/post_request_json.h> |
23 | | #include <http/response.h> |
24 | | #include <http/response_json.h> |
25 | | #include <http/tests/response_test.h> |
26 | | #include <http/testutils/test_http_client.h> |
27 | | #include <process/d_controller.h> |
28 | | #include <util/filesystem.h> |
29 | | #include <util/multi_threading_mgr.h> |
30 | | |
31 | | using namespace isc::asiolink; |
32 | | using namespace isc::config; |
33 | | using namespace isc::data; |
34 | | using namespace isc::dhcp; |
35 | | using namespace isc::process; |
36 | | using namespace isc::http; |
37 | | using namespace isc::http::test; |
38 | | using namespace isc::util; |
39 | | using namespace isc::util::file; |
40 | | using namespace std; |
41 | | |
42 | | namespace { |
43 | | |
44 | | static pid_t const PID(getpid()); |
45 | | static string const PID_STR(to_string(PID)); |
46 | | static string const ADDRESS("127.0.0.1"); |
47 | | static string const KEA_DHCP4_CONF(KEA_FUZZ_DIR() + "/kea-dhcp4-" + PID_STR + ".conf"); |
48 | | static string const KEA_DHCP4_CSV(KEA_FUZZ_DIR() + "/kea-dhcp4-" + PID_STR + ".csv"); |
49 | | |
50 | | static int PORT; |
51 | | static string PORT_STR; |
52 | | |
53 | | /// @brief Represents HTTP POST request with JSON body. |
54 | | /// |
55 | | /// In addition to the requirements specified by the @ref PostHttpRequest |
56 | | /// this class requires that the "Content-Type" is "application/json". |
57 | | /// |
58 | | /// This class provides methods to parse and retrieve JSON data structures. |
59 | | struct PostHttpRequestBytes : PostHttpRequest { |
60 | | /// @brief Constructor for inbound HTTP request. |
61 | 0 | explicit PostHttpRequestBytes() : PostHttpRequest() { |
62 | 0 | requireHeaderValue("Content-Type", "application/json"); |
63 | 0 | } |
64 | | |
65 | | /// @brief Constructor for outbound HTTP request. |
66 | | /// |
67 | | /// This constructor adds "Content-Type" header with the value of |
68 | | /// "application/json" to the context. |
69 | | /// |
70 | | /// @param method HTTP method, e.g. POST. |
71 | | /// @param uri URI. |
72 | | /// @param version HTTP version. |
73 | | /// @param host_header Host header to be included in the request. The default |
74 | | /// is the empty Host header. |
75 | | /// @param basic_auth Basic HTTP authentication credential. The default |
76 | | /// is no authentication. |
77 | | explicit PostHttpRequestBytes(const Method& method, |
78 | | const string& uri, |
79 | | const HttpVersion& version, |
80 | | const HostHttpHeader& host_header = HostHttpHeader(), |
81 | | const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr()) |
82 | 231 | : PostHttpRequest(method, uri, version, host_header, basic_auth) { |
83 | 231 | requireHeaderValue("Content-Type", "application/json"); |
84 | 231 | context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json")); |
85 | 231 | } |
86 | | |
87 | | /// @brief Sets JSON body for an outbound message. |
88 | | /// |
89 | | /// @param body JSON structure to be used as a body. |
90 | 231 | void setBodyAsBytes(vector<uint8_t> const& input) { |
91 | 231 | context_->body_ = string(input.begin(), input.end()); |
92 | 231 | } |
93 | | }; |
94 | | |
95 | | using PostHttpRequestBytesPtr = boost::shared_ptr<PostHttpRequestBytes>; |
96 | | |
97 | | } // namespace |
98 | | |
99 | | extern "C" { |
100 | | |
101 | | int |
102 | 6 | LLVMFuzzerInitialize() { |
103 | 6 | static bool initialized(DoInitialization()); |
104 | 6 | assert(initialized); |
105 | | |
106 | 6 | setenv("KEA_DHCP4_FUZZING_ROTATE_PORT", "true", 0); |
107 | | |
108 | | // The main focus is on fuzzing the raw HTTP endpoint without the authorization header. |
109 | | // So bypass the enforcement. |
110 | 6 | PathChecker::enableEnforcement(false); |
111 | | |
112 | 6 | return 0; |
113 | 6 | } |
114 | | |
115 | | int |
116 | 2 | LLVMFuzzerTearDown() { |
117 | 2 | try { |
118 | 2 | remove(KEA_DHCP4_CONF.c_str()); |
119 | 2 | } catch (...) { |
120 | 0 | } |
121 | 2 | try { |
122 | 0 | remove(KEA_DHCP4_CSV.c_str()); |
123 | 0 | } catch (...) { |
124 | 0 | } |
125 | 0 | return 0; |
126 | 0 | } |
127 | | |
128 | | int |
129 | 231 | LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) { |
130 | 231 | CfgMgr::instance().clear(); |
131 | 231 | ControlledDhcpv4Srv server; |
132 | | |
133 | 231 | PORT = ControlledDhcpv4Srv::getInstance()->getServerPort(); |
134 | 231 | PORT_STR = to_string(PORT); |
135 | | |
136 | 231 | writeToFile(KEA_DHCP4_CONF, R"( |
137 | 231 | { |
138 | 231 | "Dhcp4": { |
139 | 231 | "control-sockets": [ |
140 | 231 | { |
141 | 231 | "socket-address": ")" + ADDRESS + R"(", |
142 | 231 | "socket-port": )" + PORT_STR + R"(, |
143 | 231 | "socket-type": "http" |
144 | 231 | } |
145 | 231 | ], |
146 | 231 | "lease-database": { |
147 | 231 | "name": ")" + KEA_DHCP4_CSV + R"(", |
148 | 231 | "persist": false, |
149 | 231 | "type": "memfile" |
150 | 231 | } |
151 | 231 | } |
152 | 231 | } |
153 | 231 | )"); |
154 | | |
155 | 231 | server.init(KEA_DHCP4_CONF); |
156 | | |
157 | 231 | HttpClient client(ControlledDhcpv4Srv::getInstance()->getIOService(), false); |
158 | 231 | stringstream ss; |
159 | 231 | ss << "http://" << ADDRESS << ":" << PORT_STR; |
160 | 231 | Url url(ss.str()); |
161 | | |
162 | | // Initiate request to the server. |
163 | 231 | PostHttpRequestBytesPtr request(new PostHttpRequestBytes( |
164 | 231 | HttpRequest::Method::HTTP_POST, "/", HttpVersion(1, 1))); |
165 | | |
166 | | // Body is a map with a specified parameter included. |
167 | 231 | vector<uint8_t> const body(data, data + size); |
168 | 231 | request->setBodyAsBytes(body); |
169 | 231 | request->finalize(); |
170 | 231 | HttpResponseJsonPtr response(new HttpResponseJson()); |
171 | 231 | client.asyncSendRequest( |
172 | 231 | url, TlsContextPtr(), request, response, |
173 | 231 | [](boost::system::error_code const&, |
174 | 231 | HttpResponsePtr const&, |
175 | 231 | string const&) { |
176 | 231 | }); |
177 | | |
178 | 231 | ControlledDhcpv4Srv::getInstance()->getIOService()->poll(); |
179 | | |
180 | | // Make sure that the received responses are different. We check that by |
181 | | // comparing value of the sequence parameters. |
182 | 231 | if (getenv("DEBUG")) { |
183 | 0 | if (response) { |
184 | 0 | cout << response->getBody() << endl; |
185 | 0 | } else { |
186 | 0 | cout << "no response" << endl; |
187 | 0 | } |
188 | 0 | } |
189 | 231 | client.stop(); |
190 | 231 | ControlledDhcpv4Srv::getInstance()->getIOService()->poll(); |
191 | 231 | MultiThreadingMgr::instance().setMode(false); |
192 | | |
193 | 231 | return 0; |
194 | 231 | } |
195 | | |
196 | | } // extern "C" |