Coverage Report

Created: 2024-05-20 06:11

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