Coverage Report

Created: 2026-10-28 06:49

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