/src/FreeRDP/winpr/libwinpr/sspi/test/TestFuzzNTLMMessage.c
Line | Count | Source |
1 | | /** |
2 | | * WinPR: Windows Portable Runtime |
3 | | * libFuzzer harness for the NTLM SSPI lifecycle |
4 | | */ |
5 | | |
6 | | #include <stddef.h> |
7 | | #include <stdint.h> |
8 | | |
9 | | #include <winpr/crt.h> |
10 | | #include <winpr/sspi.h> |
11 | | #include <winpr/wlog.h> |
12 | | |
13 | | #include "../sspi.h" |
14 | | |
15 | 1.71k | #define TEST_SSPI_INTERFACE SSPI_INTERFACE_WINPR |
16 | 3.43k | #define NTLM_PACKAGE_NAME NTLM_SSP_NAME |
17 | | |
18 | | static const char* TEST_NTLM_USER = "Username"; |
19 | | static const char* TEST_NTLM_DOMAIN = "Domain"; |
20 | | static const char* TEST_NTLM_PASSWORD = "P4ss123!"; |
21 | | static const BYTE TEST_NTLM_V2_HASH[16] = { 0x4c, 0x7f, 0x70, 0x6f, 0x7d, 0xde, 0x05, 0xa9, |
22 | | 0xd1, 0xa0, 0xf4, 0xe7, 0xff, 0xe3, 0xbf, 0xb8 }; |
23 | | |
24 | | typedef struct |
25 | | { |
26 | | CtxtHandle context; |
27 | | ULONG cbMaxToken; |
28 | | ULONG fContextReq; |
29 | | ULONG pfContextAttr; |
30 | | TimeStamp expiration; |
31 | | SecBuffer inputBuffer[1]; |
32 | | SecBuffer outputBuffer[1]; |
33 | | BOOL haveContext; |
34 | | BOOL haveInputBuffer; |
35 | | SecBufferDesc inputBufferDesc; |
36 | | SecBufferDesc outputBufferDesc; |
37 | | CredHandle credentials; |
38 | | SecPkgInfo* pPackageInfo; |
39 | | SecurityFunctionTable* table; |
40 | | SEC_WINNT_AUTH_IDENTITY identity; |
41 | | } FUZZ_NTLM_CLIENT; |
42 | | |
43 | | typedef struct |
44 | | { |
45 | | CtxtHandle context; |
46 | | ULONG cbMaxToken; |
47 | | ULONG fContextReq; |
48 | | ULONG pfContextAttr; |
49 | | TimeStamp expiration; |
50 | | SecBuffer inputBuffer[1]; |
51 | | SecBuffer outputBuffer[1]; |
52 | | BOOL haveContext; |
53 | | BOOL haveInputBuffer; |
54 | | SecBufferDesc inputBufferDesc; |
55 | | SecBufferDesc outputBufferDesc; |
56 | | CredHandle credentials; |
57 | | SecPkgInfo* pPackageInfo; |
58 | | SecurityFunctionTable* table; |
59 | | } FUZZ_NTLM_SERVER; |
60 | | |
61 | | static void fuzz_ntlm_client_uninit(FUZZ_NTLM_CLIENT* client) |
62 | 1.00k | { |
63 | 1.00k | if (!client) |
64 | 0 | return; |
65 | | |
66 | 1.00k | free(client->outputBuffer[0].pvBuffer); |
67 | 1.00k | client->outputBuffer[0].pvBuffer = nullptr; |
68 | | |
69 | 1.00k | free(client->identity.User); |
70 | 1.00k | free(client->identity.Domain); |
71 | 1.00k | free(client->identity.Password); |
72 | | |
73 | 1.00k | if (client->table) |
74 | 1.00k | { |
75 | 1.00k | if (SecIsValidHandle(&client->credentials)) |
76 | 1.00k | (void)client->table->FreeCredentialsHandle(&client->credentials); |
77 | 1.00k | if (client->pPackageInfo) |
78 | 1.00k | (void)client->table->FreeContextBuffer(client->pPackageInfo); |
79 | 1.00k | if (client->haveContext && SecIsValidHandle(&client->context)) |
80 | 1.00k | (void)client->table->DeleteSecurityContext(&client->context); |
81 | 1.00k | } |
82 | 1.00k | } |
83 | | |
84 | | static BOOL fuzz_ntlm_client_init(FUZZ_NTLM_CLIENT* client) |
85 | 1.00k | { |
86 | 1.00k | SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; |
87 | | |
88 | 1.00k | WINPR_ASSERT(client); |
89 | | |
90 | 1.00k | ZeroMemory(client, sizeof(*client)); |
91 | 1.00k | SecInvalidateHandle(&client->context); |
92 | 1.00k | SecInvalidateHandle(&client->credentials); |
93 | | |
94 | 1.00k | client->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE); |
95 | 1.00k | if (!client->table) |
96 | 0 | return FALSE; |
97 | | |
98 | 1.00k | if (sspi_SetAuthIdentity(&client->identity, TEST_NTLM_USER, TEST_NTLM_DOMAIN, |
99 | 1.00k | TEST_NTLM_PASSWORD) < 0) |
100 | 0 | return FALSE; |
101 | | |
102 | 1.00k | status = client->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &client->pPackageInfo); |
103 | 1.00k | if (status != SEC_E_OK) |
104 | 0 | return FALSE; |
105 | | |
106 | 1.00k | client->cbMaxToken = client->pPackageInfo->cbMaxToken; |
107 | 1.00k | status = client->table->AcquireCredentialsHandle( |
108 | 1.00k | nullptr, NTLM_PACKAGE_NAME, SECPKG_CRED_OUTBOUND, nullptr, &client->identity, nullptr, |
109 | 1.00k | nullptr, &client->credentials, &client->expiration); |
110 | 1.00k | if (status != SEC_E_OK) |
111 | 0 | return FALSE; |
112 | | |
113 | 1.00k | client->fContextReq = ISC_REQ_MUTUAL_AUTH | ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY; |
114 | 1.00k | return TRUE; |
115 | 1.00k | } |
116 | | |
117 | | static SECURITY_STATUS fuzz_ntlm_client_step(FUZZ_NTLM_CLIENT* client) |
118 | 1.52k | { |
119 | 1.52k | SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; |
120 | | |
121 | 1.52k | WINPR_ASSERT(client); |
122 | | |
123 | 1.52k | free(client->outputBuffer[0].pvBuffer); |
124 | 1.52k | client->outputBuffer[0].pvBuffer = nullptr; |
125 | | |
126 | 1.52k | client->outputBufferDesc.ulVersion = SECBUFFER_VERSION; |
127 | 1.52k | client->outputBufferDesc.cBuffers = ARRAYSIZE(client->outputBuffer); |
128 | 1.52k | client->outputBufferDesc.pBuffers = client->outputBuffer; |
129 | 1.52k | client->outputBuffer[0].BufferType = SECBUFFER_TOKEN; |
130 | 1.52k | client->outputBuffer[0].cbBuffer = client->cbMaxToken; |
131 | 1.52k | client->outputBuffer[0].pvBuffer = calloc(1, client->outputBuffer[0].cbBuffer); |
132 | 1.52k | if (!client->outputBuffer[0].pvBuffer) |
133 | 0 | return SEC_E_INSUFFICIENT_MEMORY; |
134 | | |
135 | 1.52k | if (client->haveInputBuffer) |
136 | 520 | { |
137 | 520 | client->inputBufferDesc.ulVersion = SECBUFFER_VERSION; |
138 | 520 | client->inputBufferDesc.cBuffers = ARRAYSIZE(client->inputBuffer); |
139 | 520 | client->inputBufferDesc.pBuffers = client->inputBuffer; |
140 | 520 | client->inputBuffer[0].BufferType = SECBUFFER_TOKEN; |
141 | 520 | } |
142 | | |
143 | 1.52k | status = client->table->InitializeSecurityContext( |
144 | 1.52k | &client->credentials, client->haveContext ? &client->context : nullptr, nullptr, |
145 | 1.52k | client->fContextReq, 0, SECURITY_NATIVE_DREP, |
146 | 1.52k | client->haveInputBuffer ? &client->inputBufferDesc : nullptr, 0, &client->context, |
147 | 1.52k | &client->outputBufferDesc, &client->pfContextAttr, &client->expiration); |
148 | | |
149 | 1.52k | if ((status == SEC_I_COMPLETE_AND_CONTINUE) || (status == SEC_I_COMPLETE_NEEDED)) |
150 | 0 | { |
151 | 0 | if (client->table->CompleteAuthToken) |
152 | 0 | (void)client->table->CompleteAuthToken(&client->context, &client->outputBufferDesc); |
153 | |
|
154 | 0 | if (status == SEC_I_COMPLETE_NEEDED) |
155 | 0 | status = SEC_E_OK; |
156 | 0 | else |
157 | 0 | status = SEC_I_CONTINUE_NEEDED; |
158 | 0 | } |
159 | | |
160 | 1.52k | if (!IsSecurityStatusError(status)) |
161 | 1.05k | client->haveContext = TRUE; |
162 | | |
163 | 1.52k | return status; |
164 | 1.52k | } |
165 | | |
166 | | static void fuzz_ntlm_server_uninit(FUZZ_NTLM_SERVER* server) |
167 | 715 | { |
168 | 715 | if (!server) |
169 | 0 | return; |
170 | | |
171 | 715 | free(server->outputBuffer[0].pvBuffer); |
172 | 715 | server->outputBuffer[0].pvBuffer = nullptr; |
173 | | |
174 | 715 | if (server->table) |
175 | 715 | { |
176 | 715 | if (SecIsValidHandle(&server->credentials)) |
177 | 715 | (void)server->table->FreeCredentialsHandle(&server->credentials); |
178 | 715 | if (server->pPackageInfo) |
179 | 715 | (void)server->table->FreeContextBuffer(server->pPackageInfo); |
180 | 715 | if (SecIsValidHandle(&server->context)) |
181 | 715 | (void)server->table->DeleteSecurityContext(&server->context); |
182 | 715 | } |
183 | 715 | } |
184 | | |
185 | | static BOOL fuzz_ntlm_server_init(FUZZ_NTLM_SERVER* server) |
186 | 715 | { |
187 | 715 | SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; |
188 | | |
189 | 715 | WINPR_ASSERT(server); |
190 | | |
191 | 715 | ZeroMemory(server, sizeof(*server)); |
192 | 715 | SecInvalidateHandle(&server->context); |
193 | 715 | SecInvalidateHandle(&server->credentials); |
194 | | |
195 | 715 | server->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE); |
196 | 715 | if (!server->table) |
197 | 0 | return FALSE; |
198 | | |
199 | 715 | status = server->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &server->pPackageInfo); |
200 | 715 | if (status != SEC_E_OK) |
201 | 0 | return FALSE; |
202 | | |
203 | 715 | server->cbMaxToken = server->pPackageInfo->cbMaxToken; |
204 | 715 | status = server->table->AcquireCredentialsHandle( |
205 | 715 | nullptr, NTLM_PACKAGE_NAME, SECPKG_CRED_INBOUND, nullptr, nullptr, nullptr, nullptr, |
206 | 715 | &server->credentials, &server->expiration); |
207 | 715 | if (status != SEC_E_OK) |
208 | 0 | return FALSE; |
209 | | |
210 | 715 | server->fContextReq = ASC_REQ_MUTUAL_AUTH | ASC_REQ_CONFIDENTIALITY | ASC_REQ_CONNECTION | |
211 | 715 | ASC_REQ_USE_SESSION_KEY | ASC_REQ_REPLAY_DETECT | |
212 | 715 | ASC_REQ_SEQUENCE_DETECT | ASC_REQ_EXTENDED_ERROR; |
213 | 715 | return TRUE; |
214 | 715 | } |
215 | | |
216 | | static SECURITY_STATUS fuzz_ntlm_server_step(FUZZ_NTLM_SERVER* server) |
217 | 1.19k | { |
218 | 1.19k | SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; |
219 | | |
220 | 1.19k | WINPR_ASSERT(server); |
221 | | |
222 | 1.19k | free(server->outputBuffer[0].pvBuffer); |
223 | 1.19k | server->outputBuffer[0].pvBuffer = nullptr; |
224 | | |
225 | 1.19k | server->inputBufferDesc.ulVersion = SECBUFFER_VERSION; |
226 | 1.19k | server->inputBufferDesc.cBuffers = ARRAYSIZE(server->inputBuffer); |
227 | 1.19k | server->inputBufferDesc.pBuffers = server->inputBuffer; |
228 | 1.19k | server->inputBuffer[0].BufferType = SECBUFFER_TOKEN; |
229 | | |
230 | 1.19k | server->outputBufferDesc.ulVersion = SECBUFFER_VERSION; |
231 | 1.19k | server->outputBufferDesc.cBuffers = ARRAYSIZE(server->outputBuffer); |
232 | 1.19k | server->outputBufferDesc.pBuffers = server->outputBuffer; |
233 | 1.19k | server->outputBuffer[0].BufferType = SECBUFFER_TOKEN; |
234 | 1.19k | server->outputBuffer[0].cbBuffer = server->cbMaxToken; |
235 | 1.19k | server->outputBuffer[0].pvBuffer = calloc(1, server->outputBuffer[0].cbBuffer); |
236 | 1.19k | if (!server->outputBuffer[0].pvBuffer) |
237 | 0 | return SEC_E_INSUFFICIENT_MEMORY; |
238 | | |
239 | 1.19k | status = server->table->AcceptSecurityContext( |
240 | 1.19k | &server->credentials, server->haveContext ? &server->context : nullptr, |
241 | 1.19k | &server->inputBufferDesc, server->fContextReq, SECURITY_NATIVE_DREP, &server->context, |
242 | 1.19k | &server->outputBufferDesc, &server->pfContextAttr, &server->expiration); |
243 | | |
244 | 1.19k | if (!IsSecurityStatusError(status)) |
245 | 529 | server->haveContext = TRUE; |
246 | | |
247 | 1.19k | if (status == SEC_I_CONTINUE_NEEDED) |
248 | 529 | { |
249 | 529 | SecPkgContext_AuthNtlmHash hash = WINPR_C_ARRAY_INIT; |
250 | 529 | hash.Version = 2; |
251 | 529 | CopyMemory(hash.NtlmHash, TEST_NTLM_V2_HASH, sizeof(TEST_NTLM_V2_HASH)); |
252 | 529 | (void)server->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_HASH, |
253 | 529 | &hash, sizeof(hash)); |
254 | 529 | } |
255 | | |
256 | 1.19k | return status; |
257 | 1.19k | } |
258 | | |
259 | | static void fuzz_ntlm_set_input(SecBuffer* buffer, BOOL* haveInputBuffer, const uint8_t* data, |
260 | | size_t size) |
261 | 1.71k | { |
262 | 1.71k | WINPR_ASSERT(buffer); |
263 | 1.71k | WINPR_ASSERT(haveInputBuffer); |
264 | | |
265 | 1.71k | buffer[0].BufferType = SECBUFFER_TOKEN; |
266 | 1.71k | buffer[0].pvBuffer = (void*)data; |
267 | 1.71k | buffer[0].cbBuffer = (ULONG)size; |
268 | 1.71k | *haveInputBuffer = TRUE; |
269 | 1.71k | } |
270 | | |
271 | | static void fuzz_ntlm_negotiate(const uint8_t* data, size_t size) |
272 | 234 | { |
273 | 234 | FUZZ_NTLM_SERVER server = WINPR_C_ARRAY_INIT; |
274 | | |
275 | 234 | if (!fuzz_ntlm_server_init(&server)) |
276 | 0 | goto fail; |
277 | | |
278 | 234 | fuzz_ntlm_set_input(server.inputBuffer, &server.haveInputBuffer, data, size); |
279 | 234 | (void)fuzz_ntlm_server_step(&server); |
280 | | |
281 | 234 | fail: |
282 | 234 | fuzz_ntlm_server_uninit(&server); |
283 | 234 | } |
284 | | |
285 | | static void fuzz_ntlm_challenge(const uint8_t* data, size_t size) |
286 | 520 | { |
287 | 520 | FUZZ_NTLM_CLIENT client = WINPR_C_ARRAY_INIT; |
288 | | |
289 | 520 | if (!fuzz_ntlm_client_init(&client)) |
290 | 0 | goto fail; |
291 | | |
292 | 520 | if (IsSecurityStatusError(fuzz_ntlm_client_step(&client))) |
293 | 0 | goto fail; |
294 | | |
295 | 520 | fuzz_ntlm_set_input(client.inputBuffer, &client.haveInputBuffer, data, size); |
296 | 520 | (void)fuzz_ntlm_client_step(&client); |
297 | | |
298 | 520 | fail: |
299 | 520 | fuzz_ntlm_client_uninit(&client); |
300 | 520 | } |
301 | | |
302 | | static void fuzz_ntlm_authenticate(const uint8_t* data, size_t size) |
303 | 481 | { |
304 | 481 | FUZZ_NTLM_CLIENT client = WINPR_C_ARRAY_INIT; |
305 | 481 | FUZZ_NTLM_SERVER server = WINPR_C_ARRAY_INIT; |
306 | | |
307 | 481 | if (!fuzz_ntlm_client_init(&client)) |
308 | 0 | goto fail; |
309 | 481 | if (!fuzz_ntlm_server_init(&server)) |
310 | 0 | goto fail; |
311 | | |
312 | 481 | if (fuzz_ntlm_client_step(&client) != SEC_I_CONTINUE_NEEDED) |
313 | 0 | goto fail; |
314 | | |
315 | 481 | fuzz_ntlm_set_input(server.inputBuffer, &server.haveInputBuffer, |
316 | 481 | client.outputBuffer[0].pvBuffer, client.outputBuffer[0].cbBuffer); |
317 | 481 | if (fuzz_ntlm_server_step(&server) != SEC_I_CONTINUE_NEEDED) |
318 | 0 | goto fail; |
319 | | |
320 | 481 | fuzz_ntlm_set_input(server.inputBuffer, &server.haveInputBuffer, data, size); |
321 | 481 | (void)fuzz_ntlm_server_step(&server); |
322 | | |
323 | 481 | fail: |
324 | 481 | fuzz_ntlm_client_uninit(&client); |
325 | 481 | fuzz_ntlm_server_uninit(&server); |
326 | 481 | } |
327 | | |
328 | | int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) |
329 | 1.23k | { |
330 | 1.23k | static BOOL loggingInitialized = FALSE; |
331 | | |
332 | 1.23k | if (!loggingInitialized) |
333 | 1 | { |
334 | 1 | (void)WLog_SetLogLevel(WLog_GetRoot(), WLOG_TRACE); |
335 | 1 | loggingInitialized = TRUE; |
336 | 1 | } |
337 | | |
338 | 1.23k | if (size < 2) |
339 | 2 | return 0; |
340 | 1.23k | if (size > (1u << 20)) |
341 | 0 | return 0; |
342 | | |
343 | 1.23k | switch (data[0] % 3) |
344 | 1.23k | { |
345 | 234 | case 0: |
346 | 234 | fuzz_ntlm_negotiate(data + 1, size - 1); |
347 | 234 | break; |
348 | 520 | case 1: |
349 | 520 | fuzz_ntlm_challenge(data + 1, size - 1); |
350 | 520 | break; |
351 | 481 | case 2: |
352 | 481 | fuzz_ntlm_authenticate(data + 1, size - 1); |
353 | 481 | break; |
354 | 0 | default: |
355 | 0 | break; |
356 | 1.23k | } |
357 | | |
358 | 1.23k | return 0; |
359 | 1.23k | } |