Coverage Report

Created: 2026-03-04 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/libfreerdp/core/childsession.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Named pipe transport
4
 *
5
 * Copyright 2023-2024 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 "tcp.h"
21
22
#include <winpr/library.h>
23
#include <winpr/assert.h>
24
#include <winpr/print.h>
25
#include <winpr/sysinfo.h>
26
27
#include <freerdp/utils/ringbuffer.h>
28
29
#include "childsession.h"
30
31
#define TAG FREERDP_TAG("childsession")
32
33
typedef struct
34
{
35
  OVERLAPPED readOverlapped;
36
  HANDLE hFile;
37
  BOOL opInProgress;
38
  BOOL lastOpClosed;
39
  RingBuffer readBuffer;
40
  BOOL blocking;
41
  BYTE tmpReadBuffer[4096];
42
43
  HANDLE readEvent;
44
} WINPR_BIO_NAMED;
45
46
static int transport_bio_named_uninit(BIO* bio);
47
48
static int transport_bio_named_write(BIO* bio, const char* buf, int size)
49
0
{
50
0
  WINPR_ASSERT(bio);
51
0
  WINPR_ASSERT(buf);
52
53
0
  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
54
55
0
  if (!buf)
56
0
    return 0;
57
58
0
  BIO_clear_flags(bio, BIO_FLAGS_WRITE);
59
0
  DWORD written = 0;
60
61
0
  const UINT64 start = GetTickCount64();
62
0
  BOOL ret =
63
0
      WriteFile(ptr->hFile, buf, WINPR_ASSERTING_INT_CAST(uint32_t, size), &written, nullptr);
64
  // winpr_HexDump(TAG, WLOG_DEBUG, buf, size);
65
66
0
  if (!ret)
67
0
  {
68
0
    WLog_VRB(TAG, "error or deferred");
69
0
    return 0;
70
0
  }
71
72
0
  WLog_VRB(TAG, "(%d)=%d written=%" PRIu32 " duration=%" PRIu64, size, ret, written,
73
0
           GetTickCount64() - start);
74
75
0
  if (written == 0)
76
0
  {
77
0
    WLog_VRB(TAG, "closed on write");
78
0
    return 0;
79
0
  }
80
81
0
  WINPR_ASSERT(written <= INT32_MAX);
82
0
  return (int)written;
83
0
}
84
85
static BOOL treatReadResult(WINPR_BIO_NAMED* ptr, DWORD readBytes)
86
0
{
87
0
  WLog_VRB(TAG, "treatReadResult(readBytes=%" PRIu32 ")", readBytes);
88
0
  ptr->opInProgress = FALSE;
89
0
  if (readBytes == 0)
90
0
  {
91
0
    WLog_VRB(TAG, "readBytes == 0");
92
0
    return TRUE;
93
0
  }
94
95
0
  if (!ringbuffer_write(&ptr->readBuffer, ptr->tmpReadBuffer, readBytes))
96
0
  {
97
0
    WLog_VRB(TAG, "ringbuffer_write()");
98
0
    return FALSE;
99
0
  }
100
101
0
  return SetEvent(ptr->readEvent);
102
0
}
103
104
static BOOL doReadOp(WINPR_BIO_NAMED* ptr)
105
0
{
106
0
  DWORD readBytes = 0;
107
108
0
  if (!ResetEvent(ptr->readEvent))
109
0
    return FALSE;
110
111
0
  ptr->opInProgress = TRUE;
112
0
  if (!ReadFile(ptr->hFile, ptr->tmpReadBuffer, sizeof(ptr->tmpReadBuffer), &readBytes,
113
0
                &ptr->readOverlapped))
114
0
  {
115
0
    DWORD error = GetLastError();
116
0
    switch (error)
117
0
    {
118
0
      case ERROR_NO_DATA:
119
0
        WLog_VRB(TAG, "No Data, unexpected");
120
0
        return TRUE;
121
0
      case ERROR_IO_PENDING:
122
0
        WLog_VRB(TAG, "ERROR_IO_PENDING");
123
0
        return TRUE;
124
0
      case ERROR_BROKEN_PIPE:
125
0
        WLog_VRB(TAG, "broken pipe");
126
0
        ptr->lastOpClosed = TRUE;
127
0
        return TRUE;
128
0
      default:
129
0
        return FALSE;
130
0
    }
131
0
  }
132
133
0
  return treatReadResult(ptr, readBytes);
134
0
}
135
136
static int transport_bio_named_read(BIO* bio, char* buf, int size)
137
0
{
138
0
  WINPR_ASSERT(bio);
139
0
  WINPR_ASSERT(buf);
140
141
0
  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
142
0
  if (!buf)
143
0
    return 0;
144
145
0
  BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ);
146
147
0
  if (ptr->blocking)
148
0
  {
149
0
    while (!ringbuffer_used(&ptr->readBuffer))
150
0
    {
151
0
      if (ptr->lastOpClosed)
152
0
        return 0;
153
154
0
      if (ptr->opInProgress)
155
0
      {
156
0
        DWORD status = WaitForSingleObjectEx(ptr->readEvent, 500, TRUE);
157
0
        switch (status)
158
0
        {
159
0
          case WAIT_TIMEOUT:
160
0
          case WAIT_IO_COMPLETION:
161
0
            continue;
162
0
          case WAIT_OBJECT_0:
163
0
            break;
164
0
          default:
165
0
            return -1;
166
0
        }
167
168
0
        DWORD readBytes = 0;
169
0
        if (!GetOverlappedResult(ptr->hFile, &ptr->readOverlapped, &readBytes, FALSE))
170
0
        {
171
0
          WLog_ERR(TAG, "GetOverlappedResult blocking(lastError=%" PRIu32 ")",
172
0
                   GetLastError());
173
0
          return -1;
174
0
        }
175
176
0
        if (!treatReadResult(ptr, readBytes))
177
0
        {
178
0
          WLog_ERR(TAG, "treatReadResult blocking");
179
0
          return -1;
180
0
        }
181
0
      }
182
0
    }
183
0
  }
184
0
  else
185
0
  {
186
0
    if (ptr->opInProgress)
187
0
    {
188
0
      DWORD status = WaitForSingleObject(ptr->readEvent, 0);
189
0
      switch (status)
190
0
      {
191
0
        case WAIT_OBJECT_0:
192
0
          break;
193
0
        case WAIT_TIMEOUT:
194
0
          BIO_set_flags(bio, (BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ));
195
0
          return -1;
196
0
        default:
197
0
          WLog_ERR(TAG, "error WaitForSingleObject(readEvent)=0x%" PRIx32 "", status);
198
0
          return -1;
199
0
      }
200
201
0
      DWORD readBytes = 0;
202
0
      if (!GetOverlappedResult(ptr->hFile, &ptr->readOverlapped, &readBytes, FALSE))
203
0
      {
204
0
        WLog_ERR(TAG, "GetOverlappedResult non blocking(lastError=%" PRIu32 ")",
205
0
                 GetLastError());
206
0
        return -1;
207
0
      }
208
209
0
      if (!treatReadResult(ptr, readBytes))
210
0
      {
211
0
        WLog_ERR(TAG, "error treatReadResult non blocking");
212
0
        return -1;
213
0
      }
214
0
    }
215
0
  }
216
217
0
  SSIZE_T ret = -1;
218
0
  if (size >= 0)
219
0
  {
220
0
    size_t rsize = ringbuffer_used(&ptr->readBuffer);
221
0
    if (rsize <= SSIZE_MAX)
222
0
      ret = MIN(size, (SSIZE_T)rsize);
223
0
  }
224
0
  if ((size >= 0) && ret)
225
0
  {
226
0
    DataChunk chunks[2] = WINPR_C_ARRAY_INIT;
227
0
    const int nchunks =
228
0
        ringbuffer_peek(&ptr->readBuffer, chunks, WINPR_ASSERTING_INT_CAST(size_t, ret));
229
0
    for (int i = 0; i < nchunks; i++)
230
0
    {
231
0
      memcpy(buf, chunks[i].data, chunks[i].size);
232
0
      buf += chunks[i].size;
233
0
    }
234
235
0
    ringbuffer_commit_read_bytes(&ptr->readBuffer, WINPR_ASSERTING_INT_CAST(size_t, ret));
236
237
0
    WLog_VRB(TAG, "(%d)=%" PRIdz " nchunks=%d", size, ret, nchunks);
238
0
  }
239
240
0
  if (!ringbuffer_used(&ptr->readBuffer))
241
0
  {
242
0
    if (!ptr->opInProgress && !doReadOp(ptr))
243
0
    {
244
0
      WLog_ERR(TAG, "error rearming read");
245
0
      return -1;
246
0
    }
247
0
  }
248
249
0
  if (ret <= 0)
250
0
    BIO_set_flags(bio, (BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ));
251
252
0
  WINPR_ASSERT(ret <= INT32_MAX);
253
0
  return (int)ret;
254
0
}
255
256
static int transport_bio_named_puts(BIO* bio, const char* str)
257
0
{
258
0
  WINPR_ASSERT(bio);
259
0
  WINPR_ASSERT(str);
260
261
0
  const size_t max = (INT_MAX > SIZE_MAX) ? SIZE_MAX : INT_MAX;
262
0
  const size_t len = strnlen(str, max);
263
0
  if (len >= max)
264
0
    return -1;
265
0
  return transport_bio_named_write(bio, str, WINPR_ASSERTING_INT_CAST(int, len));
266
0
}
267
268
static int transport_bio_named_gets(BIO* bio, char* str, int size)
269
0
{
270
0
  WINPR_ASSERT(bio);
271
0
  WINPR_ASSERT(str);
272
273
0
  return transport_bio_named_read(bio, str, size);
274
0
}
275
276
static long transport_bio_named_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
277
0
{
278
0
  WINPR_ASSERT(bio);
279
280
0
  int status = -1;
281
0
  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
282
283
0
  switch (cmd)
284
0
  {
285
0
    case BIO_C_SET_SOCKET:
286
0
    case BIO_C_GET_SOCKET:
287
0
      return -1;
288
0
    case BIO_C_GET_EVENT:
289
0
      if (!BIO_get_init(bio) || !arg2)
290
0
        return 0;
291
292
0
      *((HANDLE*)arg2) = ptr->readEvent;
293
0
      return 1;
294
0
    case BIO_C_SET_HANDLE:
295
0
      BIO_set_init(bio, 1);
296
0
      if (!BIO_get_init(bio) || !arg2)
297
0
        return 0;
298
299
0
      ptr->hFile = (HANDLE)arg2;
300
0
      ptr->blocking = TRUE;
301
0
      if (!doReadOp(ptr))
302
0
        return -1;
303
0
      return 1;
304
0
    case BIO_C_SET_NONBLOCK:
305
0
    {
306
0
      WLog_DBG(TAG, "BIO_C_SET_NONBLOCK");
307
0
      ptr->blocking = FALSE;
308
0
      return 1;
309
0
    }
310
0
    case BIO_C_WAIT_READ:
311
0
    {
312
0
      WLog_DBG(TAG, "BIO_C_WAIT_READ");
313
0
      return 1;
314
0
    }
315
316
0
    case BIO_C_WAIT_WRITE:
317
0
    {
318
0
      WLog_DBG(TAG, "BIO_C_WAIT_WRITE");
319
0
      return 1;
320
0
    }
321
322
0
    default:
323
0
      break;
324
0
  }
325
326
0
  switch (cmd)
327
0
  {
328
0
    case BIO_CTRL_GET_CLOSE:
329
0
      status = BIO_get_shutdown(bio);
330
0
      break;
331
332
0
    case BIO_CTRL_SET_CLOSE:
333
0
      BIO_set_shutdown(bio, (int)arg1);
334
0
      status = 1;
335
0
      break;
336
337
0
    case BIO_CTRL_DUP:
338
0
      status = 1;
339
0
      break;
340
341
0
    case BIO_CTRL_FLUSH:
342
0
      status = 1;
343
0
      break;
344
345
0
    default:
346
0
      status = 0;
347
0
      break;
348
0
  }
349
350
0
  return status;
351
0
}
352
353
static void BIO_NAMED_free(WINPR_BIO_NAMED* ptr)
354
0
{
355
0
  if (!ptr)
356
0
    return;
357
358
0
  if (ptr->hFile)
359
0
  {
360
0
    (void)CloseHandle(ptr->hFile);
361
0
    ptr->hFile = nullptr;
362
0
  }
363
364
0
  if (ptr->readEvent)
365
0
  {
366
0
    (void)CloseHandle(ptr->readEvent);
367
0
    ptr->readEvent = nullptr;
368
0
  }
369
370
0
  ringbuffer_destroy(&ptr->readBuffer);
371
0
  free(ptr);
372
0
}
373
374
static int transport_bio_named_uninit(BIO* bio)
375
0
{
376
0
  WINPR_ASSERT(bio);
377
0
  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
378
379
0
  BIO_NAMED_free(ptr);
380
381
0
  BIO_set_init(bio, 0);
382
0
  BIO_set_flags(bio, 0);
383
0
  return 1;
384
0
}
385
386
static int transport_bio_named_new(BIO* bio)
387
0
{
388
0
  WINPR_ASSERT(bio);
389
390
0
  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)calloc(1, sizeof(WINPR_BIO_NAMED));
391
0
  if (!ptr)
392
0
    return 0;
393
394
0
  if (!ringbuffer_init(&ptr->readBuffer, 0xfffff))
395
0
    goto error;
396
397
0
  ptr->readEvent = CreateEventA(nullptr, TRUE, FALSE, nullptr);
398
0
  if (!ptr->readEvent || ptr->readEvent == INVALID_HANDLE_VALUE)
399
0
    goto error;
400
401
0
  ptr->readOverlapped.hEvent = ptr->readEvent;
402
403
0
  BIO_set_data(bio, ptr);
404
0
  BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
405
0
  return 1;
406
407
0
error:
408
0
  BIO_NAMED_free(ptr);
409
0
  return 0;
410
0
}
411
412
static int transport_bio_named_free(BIO* bio)
413
0
{
414
0
  WINPR_BIO_NAMED* ptr = nullptr;
415
416
0
  if (!bio)
417
0
    return 0;
418
419
0
  transport_bio_named_uninit(bio);
420
421
0
  ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
422
0
  if (ptr)
423
0
    BIO_set_data(bio, nullptr);
424
425
0
  return 1;
426
0
}
427
428
static BIO_METHOD* BIO_s_namedpipe(void)
429
0
{
430
0
  static BIO_METHOD* bio_methods = nullptr;
431
432
0
  if (bio_methods == nullptr)
433
0
  {
434
0
    if (!(bio_methods = BIO_meth_new(BIO_TYPE_NAMEDPIPE, "NamedPipe")))
435
0
      return nullptr;
436
437
0
    BIO_meth_set_write(bio_methods, transport_bio_named_write);
438
0
    BIO_meth_set_read(bio_methods, transport_bio_named_read);
439
0
    BIO_meth_set_puts(bio_methods, transport_bio_named_puts);
440
0
    BIO_meth_set_gets(bio_methods, transport_bio_named_gets);
441
0
    BIO_meth_set_ctrl(bio_methods, transport_bio_named_ctrl);
442
0
    BIO_meth_set_create(bio_methods, transport_bio_named_new);
443
0
    BIO_meth_set_destroy(bio_methods, transport_bio_named_free);
444
0
  }
445
446
0
  return bio_methods;
447
0
}
448
449
typedef NTSTATUS (*WinStationCreateChildSessionTransportFn)(WCHAR* path, DWORD len);
450
static BOOL createChildSessionTransport(HANDLE* pFile)
451
0
{
452
0
  WINPR_ASSERT(pFile);
453
454
0
  HANDLE hModule = nullptr;
455
0
  BOOL ret = FALSE;
456
0
  *pFile = INVALID_HANDLE_VALUE;
457
458
0
  BOOL childEnabled = 0;
459
0
  if (!WTSIsChildSessionsEnabled(&childEnabled))
460
0
  {
461
0
    WLog_ERR(TAG, "error when calling WTSIsChildSessionsEnabled");
462
0
    goto out;
463
0
  }
464
465
0
  if (!childEnabled)
466
0
  {
467
0
    WLog_INFO(TAG, "child sessions aren't enabled");
468
0
    if (!WTSEnableChildSessions(TRUE))
469
0
    {
470
0
      WLog_ERR(TAG, "error when calling WTSEnableChildSessions");
471
0
      goto out;
472
0
    }
473
0
    WLog_INFO(TAG, "successfully enabled child sessions");
474
0
  }
475
476
0
  hModule = LoadLibraryA("winsta.dll");
477
0
  if (!hModule)
478
0
    return FALSE;
479
480
0
  {
481
0
    WCHAR pipePath[0x80] = WINPR_C_ARRAY_INIT;
482
0
    char pipePathA[0x80] = WINPR_C_ARRAY_INIT;
483
484
0
    {
485
0
      WinStationCreateChildSessionTransportFn createChildSessionFn =
486
0
          GetProcAddressAs(hModule, "WinStationCreateChildSessionTransport",
487
0
                           WinStationCreateChildSessionTransportFn);
488
0
      if (!createChildSessionFn)
489
0
      {
490
0
        WLog_ERR(TAG, "unable to retrieve WinStationCreateChildSessionTransport function");
491
0
        goto out;
492
0
      }
493
494
0
      {
495
0
        HRESULT hStatus = createChildSessionFn(pipePath, 0x80);
496
0
        if (!SUCCEEDED(hStatus))
497
0
        {
498
0
          WLog_ERR(TAG, "error 0x%08x when creating childSessionTransport",
499
0
                   WINPR_CXX_COMPAT_CAST(unsigned, hStatus));
500
0
          goto out;
501
0
        }
502
0
      }
503
0
    }
504
505
0
    {
506
0
      const BYTE startOfPath[] = { '\\', 0, '\\', 0, '.', 0, '\\', 0 };
507
0
      if (_wcsncmp(pipePath, (const WCHAR*)startOfPath, 4))
508
0
      {
509
        /* when compiled under 32 bits, the path may miss "\\.\" at the beginning of the
510
         * string so add it if it's not there
511
         */
512
0
        size_t len = _wcslen(pipePath);
513
0
        if (len > 0x80 - (4 + 1))
514
0
        {
515
0
          WLog_ERR(TAG, "pipePath is too long to be adjusted");
516
0
          goto out;
517
0
        }
518
519
0
        memmove(pipePath + 4, pipePath, (len + 1) * sizeof(WCHAR));
520
0
        memcpy(pipePath, startOfPath, 8);
521
0
      }
522
0
    }
523
524
0
    (void)ConvertWCharNToUtf8(pipePath, 0x80, pipePathA, sizeof(pipePathA));
525
0
    WLog_DBG(TAG, "child session is at '%s'", pipePathA);
526
527
0
    {
528
0
      HANDLE f = CreateFileW(pipePath, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
529
0
                             OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
530
0
      if (f == INVALID_HANDLE_VALUE)
531
0
      {
532
0
        WLog_ERR(TAG, "error when connecting to local named pipe");
533
0
        goto out;
534
0
      }
535
536
0
      *pFile = f;
537
0
    }
538
0
  }
539
540
0
  ret = TRUE;
541
542
0
out:
543
0
  FreeLibrary(hModule);
544
0
  return ret;
545
0
}
546
547
BIO* createChildSessionBio(void)
548
0
{
549
0
  HANDLE f = INVALID_HANDLE_VALUE;
550
0
  if (!createChildSessionTransport(&f))
551
0
    return nullptr;
552
553
0
  BIO* lowLevelBio = BIO_new(BIO_s_namedpipe());
554
0
  if (!lowLevelBio)
555
0
  {
556
0
    (void)CloseHandle(f);
557
0
    return nullptr;
558
0
  }
559
560
0
  BIO_set_handle(lowLevelBio, f);
561
0
  BIO* bufferedBio = BIO_new(BIO_s_buffered_socket());
562
563
0
  if (!bufferedBio)
564
0
  {
565
0
    BIO_free_all(lowLevelBio);
566
0
    return nullptr;
567
0
  }
568
569
0
  bufferedBio = BIO_push(bufferedBio, lowLevelBio);
570
571
0
  return bufferedBio;
572
0
}