Coverage Report

Created: 2026-10-28 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/client/generic_dynvc.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Dynamic channel
4
 *
5
 * Copyright 2022 David Fort <contact@hardening-consulting.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <freerdp/config.h>
21
#include <freerdp/log.h>
22
#include <freerdp/client/channels.h>
23
24
#define TAG FREERDP_TAG("genericdynvc")
25
26
static char magic[32] = "**GENERIC_DYNVC_PLUGIN_PRIVATE*";
27
static const size_t maxPrivateOffset = 0x100000; /* 1MB should not be reached. */
28
29
typedef struct
30
{
31
  char magic[32];
32
  size_t pluginSize;
33
  DWORD flags;
34
  BOOL connected;
35
} GENERIC_DYNVC_PLUGIN_PRIVATE;
36
37
WINPR_ATTR_NODISCARD
38
static GENERIC_DYNVC_PLUGIN_PRIVATE* getFromGeneric(GENERIC_DYNVC_PLUGIN* plugin)
39
10.9k
{
40
10.9k
  if (!plugin)
41
0
    return nullptr;
42
43
  /* We do not know the exact size used to allocate this struct (+ some channel custom data
44
   * afterward) but we know that the private segment starts with a certain magic.
45
   *
46
   * So search for the magic from the end of GENERIC_DYNVC_PLUGIN until found.
47
   */
48
10.9k
  char* ptr = (char*)(&plugin[1]);
49
50
2.24G
  for (size_t count = 0; count <= maxPrivateOffset; count++, ptr++)
51
2.24G
  {
52
    /* Only bytes matching the first element of the magic pattern get the full memcmp. A
53
     * byte that matches it alone is skipped like any other. */
54
2.24G
    if ((*ptr == magic[0]) && (memcmp(ptr, magic, sizeof(magic)) == 0))
55
10.9k
      return WINPR_PACKED_ALIGN_CAST(GENERIC_DYNVC_PLUGIN_PRIVATE*, ptr);
56
2.24G
  }
57
58
0
  WLog_ERR(TAG,
59
0
           "Failed to find GENERIC_DYNVC_PLUGIN_PRIVATE offset in the first %" PRIuz
60
0
           " bytes after GENERIC_DYNVC_PLUGIN, giving up.",
61
0
           maxPrivateOffset);
62
0
  WINPR_ASSERT(FALSE);
63
0
  return nullptr;
64
10.9k
}
65
66
WINPR_ATTR_NODISCARD
67
static BOOL generic_connected_allow_multiple(GENERIC_DYNVC_PLUGIN* plugin)
68
2.73k
{
69
2.73k
  GENERIC_DYNVC_PLUGIN_PRIVATE* priv = getFromGeneric(plugin);
70
2.73k
  if (!priv)
71
0
    return FALSE;
72
2.73k
  return (priv->flags & GENERIC_CHANNEL_ENTRY_OPTION_MULTI_INSTANCE) != 0;
73
2.73k
}
74
75
static BOOL generic_update_connected_state(GENERIC_DYNVC_PLUGIN* plugin, BOOL val)
76
5.46k
{
77
5.46k
  GENERIC_DYNVC_PLUGIN_PRIVATE* priv = getFromGeneric(plugin);
78
5.46k
  if (!priv)
79
0
    return FALSE;
80
5.46k
  priv->connected = val;
81
5.46k
  return TRUE;
82
5.46k
}
83
84
WINPR_ATTR_NODISCARD
85
static BOOL generic_get_connected_state(GENERIC_DYNVC_PLUGIN* plugin)
86
2.73k
{
87
2.73k
  GENERIC_DYNVC_PLUGIN_PRIVATE* priv = getFromGeneric(plugin);
88
2.73k
  if (!priv)
89
0
    return FALSE;
90
2.73k
  return priv->connected;
91
2.73k
}
92
93
static UINT generic_on_close(IWTSVirtualChannelCallback* pChannelCallback)
94
2.73k
{
95
2.73k
  GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
96
2.73k
  WINPR_ASSERT(callback);
97
98
2.73k
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)callback->plugin;
99
2.73k
  WINPR_ASSERT(plugin);
100
101
  /* We're wrapping the originally set callback to update the channel state.
102
   * call the originally intended OnClose from the plugin
103
   */
104
2.73k
  WINPR_ASSERT(plugin->channel_callbacks);
105
2.73k
  const UINT rc =
106
2.73k
      IFCALLRESULT(CHANNEL_RC_OK, plugin->channel_callbacks->OnClose, pChannelCallback);
107
2.73k
  generic_update_connected_state(plugin, FALSE);
108
109
2.73k
  return rc;
110
2.73k
}
111
112
static UINT generic_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
113
                                              IWTSVirtualChannel* pChannel,
114
                                              WINPR_ATTR_UNUSED BYTE* Data,
115
                                              WINPR_ATTR_UNUSED BOOL* pbAccept,
116
                                              IWTSVirtualChannelCallback** ppCallback)
117
2.73k
{
118
2.73k
  GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
119
120
2.73k
  if (!listener_callback || !listener_callback->plugin)
121
0
    return ERROR_INTERNAL_ERROR;
122
123
2.73k
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)listener_callback->plugin;
124
2.73k
  WLog_Print(plugin->log, WLOG_TRACE, "...");
125
126
2.73k
  if (!generic_connected_allow_multiple(plugin) && generic_get_connected_state(plugin))
127
0
  {
128
0
    WLog_Print(plugin->log, WLOG_ERROR, "Channel already connected, terminating.");
129
0
    return ERROR_DEVICE_ALREADY_ATTACHED;
130
0
  }
131
132
2.73k
  GENERIC_CHANNEL_CALLBACK* callback =
133
2.73k
      (GENERIC_CHANNEL_CALLBACK*)calloc(1, plugin->channelCallbackSize);
134
2.73k
  if (!callback)
135
0
  {
136
0
    WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
137
0
    return CHANNEL_RC_NO_MEMORY;
138
0
  }
139
140
  /* implant configured channel callbacks */
141
2.73k
  callback->iface = *plugin->channel_callbacks;
142
2.73k
  callback->iface.OnClose = generic_on_close;
143
144
2.73k
  callback->plugin = listener_callback->plugin;
145
2.73k
  callback->channel_mgr = listener_callback->channel_mgr;
146
2.73k
  callback->channel = pChannel;
147
148
2.73k
  listener_callback->channel_callback = callback;
149
2.73k
  listener_callback->channel = pChannel;
150
151
2.73k
  *ppCallback = &callback->iface;
152
2.73k
  generic_update_connected_state(plugin, TRUE);
153
2.73k
  return CHANNEL_RC_OK;
154
2.73k
}
155
156
static UINT generic_dynvc_plugin_initialize(IWTSPlugin* pPlugin,
157
                                            IWTSVirtualChannelManager* pChannelMgr)
158
2.73k
{
159
2.73k
  UINT rc = 0;
160
2.73k
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
161
162
2.73k
  if (!plugin)
163
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
164
165
2.73k
  if (!pChannelMgr)
166
0
    return ERROR_INVALID_PARAMETER;
167
168
2.73k
  if (plugin->initialized)
169
0
  {
170
0
    WLog_ERR(TAG, "[%s] channel initialized twice, aborting", plugin->dynvc_name);
171
0
    return ERROR_INVALID_DATA;
172
0
  }
173
174
2.73k
  WLog_Print(plugin->log, WLOG_TRACE, "...");
175
2.73k
  GENERIC_LISTENER_CALLBACK* listener_callback =
176
2.73k
      (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
177
2.73k
  if (!listener_callback)
178
0
  {
179
0
    WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
180
0
    return CHANNEL_RC_NO_MEMORY;
181
0
  }
182
183
2.73k
  plugin->listener_callback = listener_callback;
184
2.73k
  listener_callback->iface.OnNewChannelConnection = generic_on_new_channel_connection;
185
2.73k
  listener_callback->plugin = pPlugin;
186
2.73k
  listener_callback->channel_mgr = pChannelMgr;
187
2.73k
  rc = pChannelMgr->CreateListener(pChannelMgr, plugin->dynvc_name, 0, &listener_callback->iface,
188
2.73k
                                   &plugin->listener);
189
190
2.73k
  plugin->listener->pInterface = plugin->iface.pInterface;
191
2.73k
  plugin->initialized = (rc == CHANNEL_RC_OK);
192
2.73k
  return rc;
193
2.73k
}
194
195
static UINT generic_plugin_terminated(IWTSPlugin* pPlugin)
196
2.73k
{
197
2.73k
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
198
2.73k
  UINT error = CHANNEL_RC_OK;
199
200
2.73k
  if (!plugin)
201
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
202
203
2.73k
  WLog_Print(plugin->log, WLOG_TRACE, "...");
204
205
  /* some channels (namely rdpei), look at initialized to see if they should continue to run */
206
2.73k
  plugin->initialized = FALSE;
207
208
2.73k
  if (plugin->terminatePluginFn)
209
2.73k
    plugin->terminatePluginFn(plugin);
210
211
2.73k
  if (plugin->listener_callback)
212
2.73k
  {
213
2.73k
    IWTSVirtualChannelManager* mgr = plugin->listener_callback->channel_mgr;
214
2.73k
    if (mgr)
215
2.73k
      IFCALL(mgr->DestroyListener, mgr, plugin->listener);
216
2.73k
  }
217
218
2.73k
  free(plugin->listener_callback);
219
2.73k
  free(plugin->dynvc_name);
220
2.73k
  free(plugin);
221
2.73k
  return error;
222
2.73k
}
223
224
static UINT generic_dynvc_plugin_attached(IWTSPlugin* pPlugin)
225
0
{
226
0
  GENERIC_DYNVC_PLUGIN* pluginn = (GENERIC_DYNVC_PLUGIN*)pPlugin;
227
0
  UINT error = CHANNEL_RC_OK;
228
229
0
  if (!pluginn)
230
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
231
232
0
  pluginn->attached = TRUE;
233
0
  return error;
234
0
}
235
236
static UINT generic_dynvc_plugin_detached(IWTSPlugin* pPlugin)
237
0
{
238
0
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
239
0
  UINT error = CHANNEL_RC_OK;
240
241
0
  if (!plugin)
242
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
243
244
0
  plugin->attached = FALSE;
245
0
  return error;
246
0
}
247
248
UINT freerdp_generic_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag,
249
                                    const char* name, size_t pluginSize, size_t channelCallbackSize,
250
                                    const IWTSVirtualChannelCallback* channel_callbacks,
251
                                    DYNVC_PLUGIN_INIT_FN initPluginFn,
252
                                    DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn)
253
2.73k
{
254
2.73k
  return freerdp_generic_DVCPluginEntryWithOptions(pEntryPoints, logTag, name, pluginSize,
255
2.73k
                                                   channelCallbackSize, channel_callbacks,
256
2.73k
                                                   initPluginFn, terminatePluginFn, 0);
257
2.73k
}
258
259
UINT freerdp_generic_DVCPluginEntryWithOptions(
260
    IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag, const char* name, size_t pluginSize,
261
    size_t channelCallbackSize, const IWTSVirtualChannelCallback* channel_callbacks,
262
    DYNVC_PLUGIN_INIT_FN initPluginFn, DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn, UINT32 flags)
263
2.73k
{
264
2.73k
  UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
265
266
2.73k
  WINPR_ASSERT(pEntryPoints);
267
2.73k
  WINPR_ASSERT(pEntryPoints->GetPlugin);
268
2.73k
  WINPR_ASSERT(logTag);
269
2.73k
  WINPR_ASSERT(name);
270
2.73k
  WINPR_ASSERT(pluginSize >= sizeof(GENERIC_DYNVC_PLUGIN));
271
2.73k
  WINPR_ASSERT(channelCallbackSize >= sizeof(GENERIC_CHANNEL_CALLBACK));
272
273
2.73k
  GENERIC_DYNVC_PLUGIN* plugin =
274
2.73k
      (GENERIC_DYNVC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, name);
275
2.73k
  if (plugin != nullptr)
276
0
    return CHANNEL_RC_ALREADY_INITIALIZED;
277
278
  /* Ensure 32 byte alignment.
279
   */
280
2.73k
  size_t size = pluginSize;
281
2.73k
  if ((size % 32) != 0)
282
2.73k
    size += 32 - size % 32;
283
2.73k
  size += sizeof(GENERIC_DYNVC_PLUGIN_PRIVATE);
284
2.73k
  plugin = (GENERIC_DYNVC_PLUGIN*)calloc(1, size);
285
2.73k
  if (!plugin)
286
0
  {
287
0
    WLog_ERR(TAG, "calloc failed!");
288
0
    return CHANNEL_RC_NO_MEMORY;
289
0
  }
290
291
2.73k
  BYTE* ptr = &(((BYTE*)plugin)[size - sizeof(GENERIC_DYNVC_PLUGIN_PRIVATE)]);
292
2.73k
  GENERIC_DYNVC_PLUGIN_PRIVATE* priv =
293
2.73k
      WINPR_PACKED_ALIGN_CAST(GENERIC_DYNVC_PLUGIN_PRIVATE*, ptr);
294
2.73k
  if (!priv)
295
0
    goto error;
296
297
2.73k
  WINPR_STATIC_ASSERT(sizeof(magic) == sizeof(priv->magic));
298
2.73k
  memcpy(priv->magic, magic, sizeof(priv->magic));
299
2.73k
  priv->pluginSize = pluginSize;
300
2.73k
  priv->flags = flags;
301
302
2.73k
  plugin->log = WLog_Get(logTag);
303
2.73k
  plugin->attached = TRUE;
304
2.73k
  plugin->channel_callbacks = channel_callbacks;
305
2.73k
  plugin->channelCallbackSize = channelCallbackSize;
306
2.73k
  plugin->iface.Initialize = generic_dynvc_plugin_initialize;
307
2.73k
  plugin->iface.Connected = nullptr;
308
2.73k
  plugin->iface.Disconnected = nullptr;
309
2.73k
  plugin->iface.Terminated = generic_plugin_terminated;
310
2.73k
  plugin->iface.Attached = generic_dynvc_plugin_attached;
311
2.73k
  plugin->iface.Detached = generic_dynvc_plugin_detached;
312
2.73k
  plugin->terminatePluginFn = terminatePluginFn;
313
314
2.73k
  if (initPluginFn)
315
2.73k
  {
316
2.73k
    rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints);
317
2.73k
    rdpContext* context = pEntryPoints->GetRdpContext(pEntryPoints);
318
319
2.73k
    error = initPluginFn(plugin, context, settings);
320
2.73k
    if (error != CHANNEL_RC_OK)
321
0
      goto error;
322
2.73k
  }
323
324
2.73k
  plugin->dynvc_name = _strdup(name);
325
2.73k
  if (!plugin->dynvc_name)
326
0
    goto error;
327
328
2.73k
  error = pEntryPoints->RegisterPlugin(pEntryPoints, name, &plugin->iface);
329
2.73k
  if (error == CHANNEL_RC_OK)
330
2.73k
    return error;
331
332
0
error:
333
0
  generic_plugin_terminated(&plugin->iface);
334
0
  return error;
335
2.73k
}