Coverage Report

Created: 2026-07-16 07:14

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