Coverage Report

Created: 2026-05-30 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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.95k
#define TEST_SSPI_INTERFACE SSPI_INTERFACE_WINPR
16
3.91k
#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.17k
{
63
1.17k
  if (!client)
64
0
    return;
65
66
1.17k
  free(client->outputBuffer[0].pvBuffer);
67
1.17k
  client->outputBuffer[0].pvBuffer = nullptr;
68
69
1.17k
  free(client->identity.User);
70
1.17k
  free(client->identity.Domain);
71
1.17k
  free(client->identity.Password);
72
73
1.17k
  if (client->table)
74
1.17k
  {
75
1.17k
    if (SecIsValidHandle(&client->credentials))
76
1.17k
      (void)client->table->FreeCredentialsHandle(&client->credentials);
77
1.17k
    if (client->pPackageInfo)
78
1.17k
      (void)client->table->FreeContextBuffer(client->pPackageInfo);
79
1.17k
    if (client->haveContext && SecIsValidHandle(&client->context))
80
1.17k
      (void)client->table->DeleteSecurityContext(&client->context);
81
1.17k
  }
82
1.17k
}
83
84
static BOOL fuzz_ntlm_client_init(FUZZ_NTLM_CLIENT* client)
85
1.17k
{
86
1.17k
  SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
87
88
1.17k
  WINPR_ASSERT(client);
89
90
1.17k
  ZeroMemory(client, sizeof(*client));
91
1.17k
  SecInvalidateHandle(&client->context);
92
1.17k
  SecInvalidateHandle(&client->credentials);
93
94
1.17k
  client->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE);
95
1.17k
  if (!client->table)
96
0
    return FALSE;
97
98
1.17k
  if (sspi_SetAuthIdentity(&client->identity, TEST_NTLM_USER, TEST_NTLM_DOMAIN,
99
1.17k
                           TEST_NTLM_PASSWORD) < 0)
100
0
    return FALSE;
101
102
1.17k
  status = client->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &client->pPackageInfo);
103
1.17k
  if (status != SEC_E_OK)
104
0
    return FALSE;
105
106
1.17k
  client->cbMaxToken = client->pPackageInfo->cbMaxToken;
107
1.17k
  status = client->table->AcquireCredentialsHandle(
108
1.17k
      nullptr, NTLM_PACKAGE_NAME, SECPKG_CRED_OUTBOUND, nullptr, &client->identity, nullptr,
109
1.17k
      nullptr, &client->credentials, &client->expiration);
110
1.17k
  if (status != SEC_E_OK)
111
0
    return FALSE;
112
113
1.17k
  client->fContextReq = ISC_REQ_MUTUAL_AUTH | ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY;
114
1.17k
  return TRUE;
115
1.17k
}
116
117
static SECURITY_STATUS fuzz_ntlm_client_step(FUZZ_NTLM_CLIENT* client)
118
1.83k
{
119
1.83k
  SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
120
121
1.83k
  WINPR_ASSERT(client);
122
123
1.83k
  free(client->outputBuffer[0].pvBuffer);
124
1.83k
  client->outputBuffer[0].pvBuffer = nullptr;
125
126
1.83k
  client->outputBufferDesc.ulVersion = SECBUFFER_VERSION;
127
1.83k
  client->outputBufferDesc.cBuffers = ARRAYSIZE(client->outputBuffer);
128
1.83k
  client->outputBufferDesc.pBuffers = client->outputBuffer;
129
1.83k
  client->outputBuffer[0].BufferType = SECBUFFER_TOKEN;
130
1.83k
  client->outputBuffer[0].cbBuffer = client->cbMaxToken;
131
1.83k
  client->outputBuffer[0].pvBuffer = calloc(1, client->outputBuffer[0].cbBuffer);
132
1.83k
  if (!client->outputBuffer[0].pvBuffer)
133
0
    return SEC_E_INSUFFICIENT_MEMORY;
134
135
1.83k
  if (client->haveInputBuffer)
136
663
  {
137
663
    client->inputBufferDesc.ulVersion = SECBUFFER_VERSION;
138
663
    client->inputBufferDesc.cBuffers = ARRAYSIZE(client->inputBuffer);
139
663
    client->inputBufferDesc.pBuffers = client->inputBuffer;
140
663
    client->inputBuffer[0].BufferType = SECBUFFER_TOKEN;
141
663
  }
142
143
1.83k
  status = client->table->InitializeSecurityContext(
144
1.83k
      &client->credentials, client->haveContext ? &client->context : nullptr, nullptr,
145
1.83k
      client->fContextReq, 0, SECURITY_NATIVE_DREP,
146
1.83k
      client->haveInputBuffer ? &client->inputBufferDesc : nullptr, 0, &client->context,
147
1.83k
      &client->outputBufferDesc, &client->pfContextAttr, &client->expiration);
148
149
1.83k
  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.83k
  if (!IsSecurityStatusError(status))
161
1.34k
    client->haveContext = TRUE;
162
163
1.83k
  return status;
164
1.83k
}
165
166
static void fuzz_ntlm_server_uninit(FUZZ_NTLM_SERVER* server)
167
779
{
168
779
  if (!server)
169
0
    return;
170
171
779
  free(server->outputBuffer[0].pvBuffer);
172
779
  server->outputBuffer[0].pvBuffer = nullptr;
173
174
779
  if (server->table)
175
779
  {
176
779
    if (SecIsValidHandle(&server->credentials))
177
779
      (void)server->table->FreeCredentialsHandle(&server->credentials);
178
779
    if (server->pPackageInfo)
179
779
      (void)server->table->FreeContextBuffer(server->pPackageInfo);
180
779
    if (SecIsValidHandle(&server->context))
181
779
      (void)server->table->DeleteSecurityContext(&server->context);
182
779
  }
183
779
}
184
185
static BOOL fuzz_ntlm_server_init(FUZZ_NTLM_SERVER* server)
186
779
{
187
779
  SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
188
189
779
  WINPR_ASSERT(server);
190
191
779
  ZeroMemory(server, sizeof(*server));
192
779
  SecInvalidateHandle(&server->context);
193
779
  SecInvalidateHandle(&server->credentials);
194
195
779
  server->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE);
196
779
  if (!server->table)
197
0
    return FALSE;
198
199
779
  status = server->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &server->pPackageInfo);
200
779
  if (status != SEC_E_OK)
201
0
    return FALSE;
202
203
779
  server->cbMaxToken = server->pPackageInfo->cbMaxToken;
204
779
  status = server->table->AcquireCredentialsHandle(
205
779
      nullptr, NTLM_PACKAGE_NAME, SECPKG_CRED_INBOUND, nullptr, nullptr, nullptr, nullptr,
206
779
      &server->credentials, &server->expiration);
207
779
  if (status != SEC_E_OK)
208
0
    return FALSE;
209
210
779
  server->fContextReq = ASC_REQ_MUTUAL_AUTH | ASC_REQ_CONFIDENTIALITY | ASC_REQ_CONNECTION |
211
779
                        ASC_REQ_USE_SESSION_KEY | ASC_REQ_REPLAY_DETECT |
212
779
                        ASC_REQ_SEQUENCE_DETECT | ASC_REQ_EXTENDED_ERROR;
213
779
  return TRUE;
214
779
}
215
216
static SECURITY_STATUS fuzz_ntlm_server_step(FUZZ_NTLM_SERVER* server)
217
1.29k
{
218
1.29k
  SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
219
220
1.29k
  WINPR_ASSERT(server);
221
222
1.29k
  free(server->outputBuffer[0].pvBuffer);
223
1.29k
  server->outputBuffer[0].pvBuffer = nullptr;
224
225
1.29k
  server->inputBufferDesc.ulVersion = SECBUFFER_VERSION;
226
1.29k
  server->inputBufferDesc.cBuffers = ARRAYSIZE(server->inputBuffer);
227
1.29k
  server->inputBufferDesc.pBuffers = server->inputBuffer;
228
1.29k
  server->inputBuffer[0].BufferType = SECBUFFER_TOKEN;
229
230
1.29k
  server->outputBufferDesc.ulVersion = SECBUFFER_VERSION;
231
1.29k
  server->outputBufferDesc.cBuffers = ARRAYSIZE(server->outputBuffer);
232
1.29k
  server->outputBufferDesc.pBuffers = server->outputBuffer;
233
1.29k
  server->outputBuffer[0].BufferType = SECBUFFER_TOKEN;
234
1.29k
  server->outputBuffer[0].cbBuffer = server->cbMaxToken;
235
1.29k
  server->outputBuffer[0].pvBuffer = calloc(1, server->outputBuffer[0].cbBuffer);
236
1.29k
  if (!server->outputBuffer[0].pvBuffer)
237
0
    return SEC_E_INSUFFICIENT_MEMORY;
238
239
1.29k
  status = server->table->AcceptSecurityContext(
240
1.29k
      &server->credentials, server->haveContext ? &server->context : nullptr,
241
1.29k
      &server->inputBufferDesc, server->fContextReq, SECURITY_NATIVE_DREP, &server->context,
242
1.29k
      &server->outputBufferDesc, &server->pfContextAttr, &server->expiration);
243
244
1.29k
  if (!IsSecurityStatusError(status))
245
577
    server->haveContext = TRUE;
246
247
1.29k
  if (status == SEC_I_CONTINUE_NEEDED)
248
577
  {
249
577
    SecPkgContext_AuthNtlmHash hash = WINPR_C_ARRAY_INIT;
250
577
    hash.Version = 2;
251
577
    CopyMemory(hash.NtlmHash, TEST_NTLM_V2_HASH, sizeof(TEST_NTLM_V2_HASH));
252
577
    (void)server->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_HASH,
253
577
                                              &hash, sizeof(hash));
254
577
  }
255
256
1.29k
  return status;
257
1.29k
}
258
259
static void fuzz_ntlm_set_input(SecBuffer* buffer, BOOL* haveInputBuffer, const uint8_t* data,
260
                                size_t size)
261
1.95k
{
262
1.95k
  WINPR_ASSERT(buffer);
263
1.95k
  WINPR_ASSERT(haveInputBuffer);
264
265
1.95k
  buffer[0].BufferType = SECBUFFER_TOKEN;
266
1.95k
  buffer[0].pvBuffer = (void*)data;
267
1.95k
  buffer[0].cbBuffer = (ULONG)size;
268
1.95k
  *haveInputBuffer = TRUE;
269
1.95k
}
270
271
static void fuzz_ntlm_negotiate(const uint8_t* data, size_t size)
272
266
{
273
266
  FUZZ_NTLM_SERVER server = WINPR_C_ARRAY_INIT;
274
275
266
  if (!fuzz_ntlm_server_init(&server))
276
0
    goto fail;
277
278
266
  fuzz_ntlm_set_input(server.inputBuffer, &server.haveInputBuffer, data, size);
279
266
  (void)fuzz_ntlm_server_step(&server);
280
281
266
fail:
282
266
  fuzz_ntlm_server_uninit(&server);
283
266
}
284
285
static void fuzz_ntlm_challenge(const uint8_t* data, size_t size)
286
663
{
287
663
  FUZZ_NTLM_CLIENT client = WINPR_C_ARRAY_INIT;
288
289
663
  if (!fuzz_ntlm_client_init(&client))
290
0
    goto fail;
291
292
663
  if (IsSecurityStatusError(fuzz_ntlm_client_step(&client)))
293
0
    goto fail;
294
295
663
  fuzz_ntlm_set_input(client.inputBuffer, &client.haveInputBuffer, data, size);
296
663
  (void)fuzz_ntlm_client_step(&client);
297
298
663
fail:
299
663
  fuzz_ntlm_client_uninit(&client);
300
663
}
301
302
static void fuzz_ntlm_authenticate(const uint8_t* data, size_t size)
303
513
{
304
513
  FUZZ_NTLM_CLIENT client = WINPR_C_ARRAY_INIT;
305
513
  FUZZ_NTLM_SERVER server = WINPR_C_ARRAY_INIT;
306
307
513
  if (!fuzz_ntlm_client_init(&client))
308
0
    goto fail;
309
513
  if (!fuzz_ntlm_server_init(&server))
310
0
    goto fail;
311
312
513
  if (fuzz_ntlm_client_step(&client) != SEC_I_CONTINUE_NEEDED)
313
0
    goto fail;
314
315
513
  fuzz_ntlm_set_input(server.inputBuffer, &server.haveInputBuffer,
316
513
                      client.outputBuffer[0].pvBuffer, client.outputBuffer[0].cbBuffer);
317
513
  if (fuzz_ntlm_server_step(&server) != SEC_I_CONTINUE_NEEDED)
318
0
    goto fail;
319
320
513
  fuzz_ntlm_set_input(server.inputBuffer, &server.haveInputBuffer, data, size);
321
513
  (void)fuzz_ntlm_server_step(&server);
322
323
513
fail:
324
513
  fuzz_ntlm_client_uninit(&client);
325
513
  fuzz_ntlm_server_uninit(&server);
326
513
}
327
328
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
329
1.44k
{
330
1.44k
  static BOOL loggingInitialized = FALSE;
331
332
1.44k
  if (!loggingInitialized)
333
1
  {
334
1
    (void)WLog_SetLogLevel(WLog_GetRoot(), WLOG_TRACE);
335
1
    loggingInitialized = TRUE;
336
1
  }
337
338
1.44k
  if (size < 2)
339
2
    return 0;
340
1.44k
  if (size > (1u << 20))
341
0
    return 0;
342
343
1.44k
  switch (data[0] % 3)
344
1.44k
  {
345
266
    case 0:
346
266
      fuzz_ntlm_negotiate(data + 1, size - 1);
347
266
      break;
348
663
    case 1:
349
663
      fuzz_ntlm_challenge(data + 1, size - 1);
350
663
      break;
351
513
    case 2:
352
513
      fuzz_ntlm_authenticate(data + 1, size - 1);
353
513
      break;
354
0
    default:
355
0
      break;
356
1.44k
  }
357
358
1.44k
  return 0;
359
1.44k
}