Coverage Report

Created: 2026-05-11 06:55

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.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
}