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