Coverage Report

Created: 2026-10-15 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/drdynvc/client/test/TestFuzzDrdynvc.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * libFuzzer harness for the drdynvc client channel (drdynvc_main.c)
4
 *
5
 * The built-in drdynvc channel is loaded through its VirtualChannelEntryEx with
6
 * stubbed virtual channel entry points, connected, and fed server PDUs through
7
 * the channel's CHANNEL_EVENT_DATA_RECEIVED handler. A listener named
8
 * FUZZ_LISTENER_NAME accepts dynamic channels and echoes received data back, so
9
 * channel creation, (compressed) data, fragmentation and close paths are reached.
10
 *
11
 * Input: a sequence of records [flags:1][totalLength:2 LE][length:2 LE][data]
12
 * where flags carries CHANNEL_FLAG_FIRST / CHANNEL_FLAG_LAST.
13
 */
14
15
#include <stddef.h>
16
#include <stdint.h>
17
18
#include <winpr/crt.h>
19
#include <winpr/stream.h>
20
#include <winpr/wlog.h>
21
#include <winpr/wtsapi.h>
22
23
#include <freerdp/addin.h>
24
#include <freerdp/client.h>
25
#include <freerdp/client/channels.h>
26
#include <freerdp/channels/drdynvc.h>
27
#include <freerdp/dvc.h>
28
#include <freerdp/svc.h>
29
30
#include "../drdynvc_main.h"
31
32
975
#define FUZZ_LISTENER_NAME "fuzz"
33
25.4k
#define FUZZ_OPEN_HANDLE 1
34
25.4k
#define FUZZ_MAX_RECORDS 256
35
36
typedef struct
37
{
38
  PCHANNEL_INIT_EVENT_EX_FN initEvent;
39
  PCHANNEL_OPEN_EVENT_EX_FN openEvent;
40
  LPVOID userParam;
41
} fuzz_vc;
42
43
typedef struct
44
{
45
  IWTSVirtualChannelCallback iface;
46
  IWTSVirtualChannel* channel;
47
} fuzz_dvc_callback;
48
49
static fuzz_vc g_vc = WINPR_C_ARRAY_INIT;
50
static int g_initHandle = 0;
51
52
static UINT VCAPITYPE fuzz_init_ex(LPVOID lpUserParam, WINPR_ATTR_UNUSED LPVOID clientContext,
53
                                   WINPR_ATTR_UNUSED LPVOID pInitHandle,
54
                                   WINPR_ATTR_UNUSED PCHANNEL_DEF pChannel,
55
                                   WINPR_ATTR_UNUSED INT channelCount,
56
                                   WINPR_ATTR_UNUSED ULONG versionRequested,
57
                                   PCHANNEL_INIT_EVENT_EX_FN pChannelInitEventProcEx)
58
975
{
59
975
  g_vc.userParam = lpUserParam;
60
975
  g_vc.initEvent = pChannelInitEventProcEx;
61
975
  return CHANNEL_RC_OK;
62
975
}
63
64
static UINT VCAPITYPE fuzz_open_ex(WINPR_ATTR_UNUSED LPVOID pInitHandle, LPDWORD pOpenHandle,
65
                                   WINPR_ATTR_UNUSED PCHAR pChannelName,
66
                                   PCHANNEL_OPEN_EVENT_EX_FN pChannelOpenEventProcEx)
67
975
{
68
975
  *pOpenHandle = FUZZ_OPEN_HANDLE;
69
975
  g_vc.openEvent = pChannelOpenEventProcEx;
70
975
  return CHANNEL_RC_OK;
71
975
}
72
73
static UINT VCAPITYPE fuzz_close_ex(WINPR_ATTR_UNUSED LPVOID pInitHandle,
74
                                    WINPR_ATTR_UNUSED DWORD openHandle)
75
975
{
76
975
  return CHANNEL_RC_OK;
77
975
}
78
79
static UINT VCAPITYPE fuzz_write_ex(WINPR_ATTR_UNUSED LPVOID pInitHandle, DWORD openHandle,
80
                                    WINPR_ATTR_UNUSED LPVOID pData,
81
                                    WINPR_ATTR_UNUSED ULONG dataLength, LPVOID pUserData)
82
1.42k
{
83
  /* the channel releases the written stream on CHANNEL_EVENT_WRITE_COMPLETE */
84
1.42k
  if (g_vc.openEvent)
85
1.42k
    g_vc.openEvent(g_vc.userParam, openHandle, CHANNEL_EVENT_WRITE_COMPLETE, pUserData, 0, 0,
86
1.42k
                   0);
87
0
  else
88
0
    Stream_Release((wStream*)pUserData);
89
1.42k
  return CHANNEL_RC_OK;
90
1.42k
}
91
92
static UINT fuzz_dvc_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
93
0
{
94
0
  fuzz_dvc_callback* callback = (fuzz_dvc_callback*)pChannelCallback;
95
0
  const size_t length = Stream_GetRemainingLength(data);
96
97
  /* echo back to exercise the client write and fragmentation path */
98
0
  if ((length == 0) || (length > UINT32_MAX) || !callback->channel)
99
0
    return CHANNEL_RC_OK;
100
0
  return callback->channel->Write(callback->channel, (ULONG)length, Stream_ConstPointer(data),
101
0
                                  nullptr);
102
0
}
103
104
static UINT fuzz_dvc_on_open(WINPR_ATTR_UNUSED IWTSVirtualChannelCallback* pChannelCallback)
105
0
{
106
0
  return CHANNEL_RC_OK;
107
0
}
108
109
static UINT fuzz_dvc_on_close(IWTSVirtualChannelCallback* pChannelCallback)
110
0
{
111
0
  free(pChannelCallback);
112
0
  return CHANNEL_RC_OK;
113
0
}
114
115
static UINT
116
fuzz_on_new_channel_connection(WINPR_ATTR_UNUSED IWTSListenerCallback* pListenerCallback,
117
                               IWTSVirtualChannel* pChannel, WINPR_ATTR_UNUSED BYTE* Data,
118
                               BOOL* pbAccept, IWTSVirtualChannelCallback** ppCallback)
119
0
{
120
0
  fuzz_dvc_callback* callback = (fuzz_dvc_callback*)calloc(1, sizeof(fuzz_dvc_callback));
121
0
  if (!callback)
122
0
    return CHANNEL_RC_NO_MEMORY;
123
124
0
  callback->iface.OnDataReceived = fuzz_dvc_on_data_received;
125
0
  callback->iface.OnOpen = fuzz_dvc_on_open;
126
0
  callback->iface.OnClose = fuzz_dvc_on_close;
127
0
  callback->channel = pChannel;
128
129
0
  *pbAccept = TRUE;
130
0
  *ppCallback = &callback->iface;
131
0
  return CHANNEL_RC_OK;
132
0
}
133
134
static IWTSListenerCallback g_listener = { fuzz_on_new_channel_connection, nullptr };
135
136
static void fuzz_feed(const uint8_t* data, size_t size)
137
975
{
138
975
  wStream buffer = WINPR_C_ARRAY_INIT;
139
975
  wStream* s = Stream_StaticConstInit(&buffer, data, size);
140
141
25.4k
  for (size_t index = 0; index < FUZZ_MAX_RECORDS; index++)
142
25.4k
  {
143
25.4k
    if (Stream_GetRemainingLength(s) < 5)
144
965
      break;
145
146
24.4k
    const UINT8 flags = Stream_Get_UINT8(s);
147
24.4k
    const UINT16 totalLength = Stream_Get_UINT16(s);
148
24.4k
    UINT16 length = Stream_Get_UINT16(s);
149
24.4k
    if (length > Stream_GetRemainingLength(s))
150
675
      length = (UINT16)Stream_GetRemainingLength(s);
151
152
24.4k
    g_vc.openEvent(g_vc.userParam, FUZZ_OPEN_HANDLE, CHANNEL_EVENT_DATA_RECEIVED,
153
24.4k
                   Stream_Pointer(s), length, totalLength,
154
24.4k
                   flags & (CHANNEL_FLAG_FIRST | CHANNEL_FLAG_LAST));
155
24.4k
    Stream_Seek(s, length);
156
24.4k
  }
157
975
}
158
159
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
160
975
{
161
975
  if (size > (1u << 20))
162
0
    return 0;
163
164
975
  wLog* root = WLog_GetRoot();
165
975
  (void)WLog_SetLogLevel(root, WLOG_TRACE);
166
975
  (void)WLog_SetLogAppenderType(root, WLOG_APPENDER_CALLBACK);
167
168
975
  RDP_CLIENT_ENTRY_POINTS entry = WINPR_C_ARRAY_INIT;
169
975
  entry.Version = RDP_CLIENT_INTERFACE_VERSION;
170
975
  entry.Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
171
975
  entry.ContextSize = sizeof(rdpContext);
172
173
975
  rdpContext* context = freerdp_client_context_new(&entry);
174
975
  if (!context)
175
0
    return 0;
176
177
  /* process PDUs on the calling thread instead of the channel worker thread */
178
975
  if (!freerdp_settings_set_bool(context->settings, FreeRDP_SynchronousDynamicChannels, TRUE))
179
0
    goto fail;
180
181
975
  {
182
975
    PVIRTUALCHANNELENTRYEX entryEx =
183
975
        WINPR_FUNC_PTR_CAST(freerdp_channels_load_static_addin_entry(
184
975
                                DRDYNVC_SVC_CHANNEL_NAME, nullptr, nullptr,
185
975
                                FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX),
186
975
                            PVIRTUALCHANNELENTRYEX);
187
975
    if (!entryEx)
188
0
      goto fail;
189
190
975
    CHANNEL_ENTRY_POINTS_FREERDP_EX entryPoints = WINPR_C_ARRAY_INIT;
191
975
    entryPoints.cbSize = sizeof(entryPoints);
192
975
    entryPoints.protocolVersion = VIRTUAL_CHANNEL_VERSION_WIN2000;
193
975
    entryPoints.pVirtualChannelInitEx = fuzz_init_ex;
194
975
    entryPoints.pVirtualChannelOpenEx = fuzz_open_ex;
195
975
    entryPoints.pVirtualChannelCloseEx = fuzz_close_ex;
196
975
    entryPoints.pVirtualChannelWriteEx = fuzz_write_ex;
197
975
    entryPoints.MagicNumber = FREERDP_CHANNEL_MAGIC_NUMBER;
198
975
    entryPoints.context = context;
199
200
975
    const fuzz_vc empty = WINPR_C_ARRAY_INIT;
201
975
    g_vc = empty;
202
975
    if (!entryEx((PCHANNEL_ENTRY_POINTS_EX)&entryPoints, &g_initHandle) || !g_vc.initEvent)
203
0
      goto fail;
204
975
  }
205
206
975
  g_vc.initEvent(g_vc.userParam, &g_initHandle, CHANNEL_EVENT_INITIALIZED, nullptr, 0);
207
975
  g_vc.initEvent(g_vc.userParam, &g_initHandle, CHANNEL_EVENT_CONNECTED, nullptr, 0);
208
209
975
  {
210
975
    drdynvcPlugin* drdynvc = (drdynvcPlugin*)g_vc.userParam;
211
975
    IWTSVirtualChannelManager* mgr = drdynvc->channel_mgr;
212
975
    if (mgr && g_vc.openEvent &&
213
975
        (mgr->CreateListener(mgr, FUZZ_LISTENER_NAME, 0, &g_listener, nullptr) ==
214
975
         CHANNEL_RC_OK))
215
975
      fuzz_feed(data, size);
216
975
  }
217
218
975
  g_vc.initEvent(g_vc.userParam, &g_initHandle, CHANNEL_EVENT_DISCONNECTED, nullptr, 0);
219
  /* frees the plugin */
220
975
  g_vc.initEvent(g_vc.userParam, &g_initHandle, CHANNEL_EVENT_TERMINATED, nullptr, 0);
221
222
975
fail:
223
975
  freerdp_client_context_free(context);
224
975
  return 0;
225
975
}