/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 | } |