Coverage Report

Created: 2026-06-15 06:57

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