Coverage Report

Created: 2026-10-15 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/rdpgfx/client/test/TestFuzzChannelRdpgfx.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * libFuzzer harness for the rdpgfx (graphics pipeline) dynamic channel client
4
 *
5
 * The built-in rdpgfx DVC plugin is loaded through its DVCPluginEntry with a
6
 * stub dynamic channel manager, a channel is opened (sending the client caps)
7
 * and server PDUs are delivered through the channel's OnDataReceived callback.
8
 *
9
 * Input: [mode:1] followed by records [length:2 LE][data].
10
 * If bit 0 of mode is clear each record is wrapped into an uncompressed ZGFX
11
 * segment so the fuzzer can focus on graphics PDUs, otherwise the record is
12
 * passed as-is and also goes through ZGFX decompression.
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
22
#include <freerdp/addin.h>
23
#include <freerdp/client.h>
24
#include <freerdp/client/channels.h>
25
#include <freerdp/channels/rdpgfx.h>
26
#include <freerdp/codec/zgfx.h>
27
#include <freerdp/dvc.h>
28
29
2.96k
#define FUZZ_MAX_RECORDS 64
30
31
typedef struct
32
{
33
  IDRDYNVC_ENTRY_POINTS iface;
34
  rdpContext* context;
35
  IWTSPlugin* plugin;
36
} fuzz_entry_points;
37
38
typedef struct
39
{
40
  IWTSVirtualChannelManager iface;
41
  IWTSListener listener;
42
  IWTSListenerCallback* listenerCallback;
43
} fuzz_channel_manager;
44
45
static UINT fuzz_register_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints,
46
                                 WINPR_ATTR_UNUSED const char* name, IWTSPlugin* pPlugin)
47
2.65k
{
48
2.65k
  fuzz_entry_points* ep = (fuzz_entry_points*)pEntryPoints;
49
2.65k
  if (ep->plugin)
50
0
    return CHANNEL_RC_ALREADY_INITIALIZED;
51
2.65k
  ep->plugin = pPlugin;
52
2.65k
  return CHANNEL_RC_OK;
53
2.65k
}
54
55
static IWTSPlugin* fuzz_get_plugin(WINPR_ATTR_UNUSED IDRDYNVC_ENTRY_POINTS* pEntryPoints,
56
                                   WINPR_ATTR_UNUSED const char* name)
57
2.65k
{
58
2.65k
  return nullptr;
59
2.65k
}
60
61
static const ADDIN_ARGV* fuzz_get_plugin_data(WINPR_ATTR_UNUSED IDRDYNVC_ENTRY_POINTS* pEntryPoints)
62
0
{
63
0
  return nullptr;
64
0
}
65
66
static rdpSettings* fuzz_get_rdp_settings(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
67
2.65k
{
68
2.65k
  return ((fuzz_entry_points*)pEntryPoints)->context->settings;
69
2.65k
}
70
71
static rdpContext* fuzz_get_rdp_context(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
72
2.65k
{
73
2.65k
  return ((fuzz_entry_points*)pEntryPoints)->context;
74
2.65k
}
75
76
static UINT fuzz_create_listener(IWTSVirtualChannelManager* pChannelMgr,
77
                                 WINPR_ATTR_UNUSED const char* pszChannelName,
78
                                 WINPR_ATTR_UNUSED ULONG ulFlags,
79
                                 IWTSListenerCallback* pListenerCallback, IWTSListener** ppListener)
80
2.65k
{
81
2.65k
  fuzz_channel_manager* mgr = (fuzz_channel_manager*)pChannelMgr;
82
2.65k
  mgr->listenerCallback = pListenerCallback;
83
2.65k
  if (ppListener)
84
2.65k
    *ppListener = &mgr->listener;
85
2.65k
  return CHANNEL_RC_OK;
86
2.65k
}
87
88
static UINT fuzz_destroy_listener(IWTSVirtualChannelManager* pChannelMgr,
89
                                  WINPR_ATTR_UNUSED IWTSListener* pListener)
90
2.65k
{
91
2.65k
  ((fuzz_channel_manager*)pChannelMgr)->listenerCallback = nullptr;
92
2.65k
  return CHANNEL_RC_OK;
93
2.65k
}
94
95
static UINT32 fuzz_get_channel_id(WINPR_ATTR_UNUSED IWTSVirtualChannel* channel)
96
0
{
97
0
  return 1;
98
0
}
99
100
static const char* fuzz_get_channel_name(WINPR_ATTR_UNUSED IWTSVirtualChannel* channel)
101
0
{
102
0
  return RDPGFX_DVC_CHANNEL_NAME;
103
0
}
104
105
static IWTSVirtualChannel* fuzz_find_channel_by_id(WINPR_ATTR_UNUSED IWTSVirtualChannelManager* mgr,
106
                                                   WINPR_ATTR_UNUSED UINT32 ChannelId)
107
0
{
108
0
  return nullptr;
109
0
}
110
111
static UINT fuzz_channel_write(WINPR_ATTR_UNUSED IWTSVirtualChannel* pChannel,
112
                               WINPR_ATTR_UNUSED ULONG cbSize,
113
                               WINPR_ATTR_UNUSED const BYTE* pBuffer,
114
                               WINPR_ATTR_UNUSED void* pReserved)
115
58.1k
{
116
58.1k
  return CHANNEL_RC_OK;
117
58.1k
}
118
119
static UINT fuzz_channel_close(WINPR_ATTR_UNUSED IWTSVirtualChannel* pChannel)
120
0
{
121
0
  return CHANNEL_RC_OK;
122
0
}
123
124
static void fuzz_feed(IWTSVirtualChannelCallback* callback, const uint8_t* data, size_t size)
125
2.65k
{
126
2.65k
  wStream buffer = WINPR_C_ARRAY_INIT;
127
2.65k
  wStream* s = Stream_StaticConstInit(&buffer, data, size);
128
129
2.65k
  if (Stream_GetRemainingLength(s) < 1)
130
0
    return;
131
2.65k
  const BOOL raw = (Stream_Get_UINT8(s) & 0x01) != 0;
132
133
2.65k
  wStream* pdu = Stream_New(nullptr, UINT16_MAX + 2);
134
2.65k
  if (!pdu)
135
0
    return;
136
137
2.96k
  for (size_t index = 0; index < FUZZ_MAX_RECORDS; index++)
138
2.96k
  {
139
2.96k
    if (Stream_GetRemainingLength(s) < 2)
140
117
      break;
141
142
2.85k
    size_t length = Stream_Get_UINT16(s);
143
2.85k
    if (length > Stream_GetRemainingLength(s))
144
2.55k
      length = Stream_GetRemainingLength(s);
145
146
2.85k
    Stream_ResetPosition(pdu);
147
2.85k
    if (!raw)
148
1.49k
    {
149
1.49k
      Stream_Write_UINT8(pdu, ZGFX_SEGMENTED_SINGLE);
150
1.49k
      Stream_Write_UINT8(pdu, ZGFX_PACKET_COMPR_TYPE_RDP8);
151
1.49k
    }
152
2.85k
    Stream_Copy(s, pdu, length);
153
2.85k
    Stream_SealLength(pdu);
154
2.85k
    Stream_ResetPosition(pdu);
155
156
2.85k
    if (callback->OnDataReceived(callback, pdu) != CHANNEL_RC_OK)
157
2.54k
      break;
158
2.85k
  }
159
160
2.65k
  Stream_Free(pdu, TRUE);
161
2.65k
}
162
163
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
164
2.65k
{
165
2.65k
  if (size > (1u << 20))
166
0
    return 0;
167
168
2.65k
  wLog* root = WLog_GetRoot();
169
2.65k
  (void)WLog_SetLogLevel(root, WLOG_TRACE);
170
2.65k
  (void)WLog_SetLogAppenderType(root, WLOG_APPENDER_CALLBACK);
171
172
2.65k
  RDP_CLIENT_ENTRY_POINTS entry = WINPR_C_ARRAY_INIT;
173
2.65k
  entry.Version = RDP_CLIENT_INTERFACE_VERSION;
174
2.65k
  entry.Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
175
2.65k
  entry.ContextSize = sizeof(rdpContext);
176
177
2.65k
  rdpContext* context = freerdp_client_context_new(&entry);
178
2.65k
  if (!context)
179
0
    return 0;
180
181
2.65k
  PDVC_PLUGIN_ENTRY pluginEntry = WINPR_FUNC_PTR_CAST(
182
2.65k
      freerdp_channels_load_static_addin_entry(RDPGFX_CHANNEL_NAME, nullptr, nullptr,
183
2.65k
                                               FREERDP_ADDIN_CHANNEL_DYNAMIC),
184
2.65k
      PDVC_PLUGIN_ENTRY);
185
186
2.65k
  fuzz_entry_points ep = WINPR_C_ARRAY_INIT;
187
2.65k
  ep.iface.RegisterPlugin = fuzz_register_plugin;
188
2.65k
  ep.iface.GetPlugin = fuzz_get_plugin;
189
2.65k
  ep.iface.GetPluginData = fuzz_get_plugin_data;
190
2.65k
  ep.iface.GetRdpSettings = fuzz_get_rdp_settings;
191
2.65k
  ep.iface.GetRdpContext = fuzz_get_rdp_context;
192
2.65k
  ep.context = context;
193
194
2.65k
  if (!pluginEntry || (pluginEntry(&ep.iface) != CHANNEL_RC_OK) || !ep.plugin)
195
0
    goto fail;
196
197
2.65k
  {
198
2.65k
    IWTSPlugin* plugin = ep.plugin;
199
200
2.65k
    fuzz_channel_manager mgr = WINPR_C_ARRAY_INIT;
201
2.65k
    mgr.iface.CreateListener = fuzz_create_listener;
202
2.65k
    mgr.iface.DestroyListener = fuzz_destroy_listener;
203
2.65k
    mgr.iface.GetChannelId = fuzz_get_channel_id;
204
2.65k
    mgr.iface.GetChannelName = fuzz_get_channel_name;
205
2.65k
    mgr.iface.FindChannelById = fuzz_find_channel_by_id;
206
207
2.65k
    IWTSVirtualChannel channel = WINPR_C_ARRAY_INIT;
208
2.65k
    channel.Write = fuzz_channel_write;
209
2.65k
    channel.Close = fuzz_channel_close;
210
211
2.65k
    if ((plugin->Initialize(plugin, &mgr.iface) == CHANNEL_RC_OK) && mgr.listenerCallback)
212
2.65k
    {
213
2.65k
      BOOL accept = TRUE;
214
2.65k
      IWTSVirtualChannelCallback* callback = nullptr;
215
216
2.65k
      if ((mgr.listenerCallback->OnNewChannelConnection(mgr.listenerCallback, &channel,
217
2.65k
                                                        nullptr, &accept,
218
2.65k
                                                        &callback) == CHANNEL_RC_OK) &&
219
2.65k
          callback)
220
2.65k
      {
221
2.65k
        if (callback->OnOpen(callback) == CHANNEL_RC_OK)
222
2.65k
          fuzz_feed(callback, data, size);
223
        /* frees the channel callback */
224
2.65k
        (void)callback->OnClose(callback);
225
2.65k
      }
226
2.65k
    }
227
228
    /* frees the plugin */
229
2.65k
    (void)plugin->Terminated(plugin);
230
2.65k
  }
231
232
2.65k
fail:
233
2.65k
  freerdp_client_context_free(context);
234
2.65k
  return 0;
235
2.65k
}