Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,26 @@
set(MODULE_NAME "TestFreeRDPUtils")
set(MODULE_PREFIX "TEST_FREERDP_UTILS")
disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
set(${MODULE_PREFIX}_TESTS TestRingBuffer.c TestPodArrays.c TestEncodedTypes.c)
create_test_sourcelist(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_DRIVER} ${${MODULE_PREFIX}_TESTS})
add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
target_link_libraries(${MODULE_NAME} PRIVATE freerdp winpr)
if(NOT WIN32)
target_link_libraries(${MODULE_NAME} PRIVATE m)
endif()
set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
foreach(test ${${MODULE_PREFIX}_TESTS})
get_filename_component(TestName ${test} NAME_WE)
add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
endforeach()
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")

View File

@@ -0,0 +1,218 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2024 Thincast Technologies GmbH
* Copyright 2024 Armin Novak <anovak@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <winpr/crypto.h>
#include <freerdp/utils/encoded_types.h>
#ifndef MIN
#define MIN(x, y) ((x) < (y)) ? (x) : (y)
#endif
#ifndef MAX
#define MAX(x, y) ((x) > (y)) ? (x) : (y)
#endif
static BOOL test_signed_integer_read_write_equal(INT32 value)
{
INT32 rvalue = 0;
BYTE buffer[32] = WINPR_C_ARRAY_INIT;
wStream sbuffer = WINPR_C_ARRAY_INIT;
wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
WINPR_ASSERT(s);
if (!freerdp_write_four_byte_signed_integer(s, value))
{
(void)fprintf(stderr, "[%s(%" PRId32 ")] failed to write to stream\n", __func__, value);
return FALSE;
}
Stream_ResetPosition(s);
if (!freerdp_read_four_byte_signed_integer(s, &rvalue))
{
(void)fprintf(stderr, "[%s(%" PRId32 ")] failed to read from stream\n", __func__, value);
return FALSE;
}
if (value != rvalue)
{
(void)fprintf(stderr, "[%s(%" PRId32 ")] read invalid value %" PRId32 " from stream\n",
__func__, value, rvalue);
return FALSE;
}
return TRUE;
}
static BOOL test_signed_integer_write_oor(INT32 value)
{
BYTE buffer[32] = WINPR_C_ARRAY_INIT;
wStream sbuffer = WINPR_C_ARRAY_INIT;
wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
WINPR_ASSERT(s);
if (freerdp_write_four_byte_signed_integer(s, value))
{
(void)fprintf(stderr,
"[%s(%" PRId32 ")] out of range value not detected and written to stream\n",
__func__, value);
return FALSE;
}
return TRUE;
}
static BOOL test_signed_integers(void)
{
const INT32 outofrange[] = { FREERDP_FOUR_BYTE_SIGNED_INT_MAX + 1,
FREERDP_FOUR_BYTE_SIGNED_INT_MIN - 1, INT32_MAX, INT32_MIN };
const INT32 limits[] = { 1, 0, -1, FREERDP_FOUR_BYTE_SIGNED_INT_MAX,
FREERDP_FOUR_BYTE_SIGNED_INT_MIN };
for (size_t x = 0; x < ARRAYSIZE(limits); x++)
{
if (!test_signed_integer_read_write_equal(limits[x]))
return FALSE;
}
for (size_t x = 0; x < ARRAYSIZE(outofrange); x++)
{
if (!test_signed_integer_write_oor(outofrange[x]))
return FALSE;
}
for (size_t x = 0; x < 100000; x++)
{
INT32 val = 0;
if (winpr_RAND(&val, sizeof(val)) < 0)
return FALSE;
val = MAX(val, 0);
val = MIN(val, FREERDP_FOUR_BYTE_SIGNED_INT_MAX);
const INT32 nval = -val;
if (!test_signed_integer_read_write_equal(val))
return FALSE;
if (!test_signed_integer_read_write_equal(nval))
return FALSE;
}
return TRUE;
}
static BOOL test_float_read_write_equal(double value)
{
BYTE exp = 0;
double rvalue = FP_NAN;
BYTE buffer[32] = WINPR_C_ARRAY_INIT;
wStream sbuffer = WINPR_C_ARRAY_INIT;
wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
WINPR_ASSERT(s);
if (!freerdp_write_four_byte_float(s, value))
{
(void)fprintf(stderr, "[%s(%lf)] failed to write to stream\n", __func__, value);
return FALSE;
}
Stream_ResetPosition(s);
if (!freerdp_read_four_byte_float_exp(s, &rvalue, &exp))
{
(void)fprintf(stderr, "[%s(%lf)] failed to read from stream\n", __func__, value);
return FALSE;
}
const double diff = fabs(value - rvalue);
const double expdiff = diff * pow(10, exp);
if (expdiff >= 1.0)
{
(void)fprintf(stderr, "[%s(%lf)] read invalid value %lf from stream\n", __func__, value,
rvalue);
return FALSE;
}
return TRUE;
}
static BOOL test_floag_write_oor(double value)
{
BYTE buffer[32] = WINPR_C_ARRAY_INIT;
wStream sbuffer = WINPR_C_ARRAY_INIT;
wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
WINPR_ASSERT(s);
if (freerdp_write_four_byte_float(s, value))
{
(void)fprintf(stderr, "[%s(%lf)] out of range value not detected and written to stream\n",
__func__, value);
return FALSE;
}
return TRUE;
}
static double get(void)
{
double val = NAN;
do
{
if (winpr_RAND(&val, sizeof(val)) < 0)
{
// NOLINTNEXTLINE(concurrency-mt-unsafe)
exit(-1);
}
} while ((val < 0.0) || (val > FREERDP_FOUR_BYTE_FLOAT_MAX) || isnan(val) || isinf(val));
return val;
}
static BOOL test_floats(void)
{
const double outofrange[] = { FREERDP_FOUR_BYTE_FLOAT_MAX + 1, FREERDP_FOUR_BYTE_FLOAT_MIN - 1,
DBL_MAX, -DBL_MAX };
const double limits[] = { 100045.26129238126, 1, 0, -1, FREERDP_FOUR_BYTE_FLOAT_MAX,
FREERDP_FOUR_BYTE_FLOAT_MIN };
for (size_t x = 0; x < ARRAYSIZE(limits); x++)
{
if (!test_float_read_write_equal(limits[x]))
return FALSE;
}
for (size_t x = 0; x < ARRAYSIZE(outofrange); x++)
{
if (!test_floag_write_oor(outofrange[x]))
return FALSE;
}
for (size_t x = 0; x < 100000; x++)
{
double val = get();
const double nval = -val;
if (!test_float_read_write_equal(val))
return FALSE;
if (!test_float_read_write_equal(nval))
return FALSE;
}
return TRUE;
}
int TestEncodedTypes(int argc, char* argv[])
{
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
if (!test_signed_integers())
return -1;
if (!test_floats())
return -1;
return 0;
}

View File

@@ -0,0 +1,132 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* TestPodArrays
*
* Copyright 2022 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/utils/pod_arrays.h>
// NOLINTNEXTLINE(readability-non-const-parameter)
static BOOL cb_compute_sum(UINT32* v, void* target)
{
UINT32* ret = (UINT32*)target;
*ret += *v;
return TRUE;
}
static BOOL cb_stop_at_5(UINT32* v, void* target)
{
UINT32* ret = (UINT32*)target;
*ret += 1;
return (*ret != 5);
}
static BOOL cb_set_to_1(UINT32* v, void* target)
{
*v = 1;
return TRUE;
}
static BOOL cb_reset_after_1(UINT32* v, void* target)
{
ArrayUINT32* a = (ArrayUINT32*)target;
array_uint32_reset(a);
return TRUE;
}
typedef struct
{
UINT32 v1;
UINT16 v2;
} BasicStruct;
static BOOL cb_basic_struct(BasicStruct* v, void* target)
{
return (v->v1 == 1) && (v->v2 == 2);
}
// NOLINTNEXTLINE(bugprone-suspicious-memory-comparison,cert-exp42-c,cert-flp37-c)
POD_ARRAYS_IMPL(BasicStruct, basicstruct)
int TestPodArrays(int argc, char* argv[])
{
int rc = -1;
UINT32 sum = 0;
UINT32 foreach_index = 0;
ArrayUINT32 uint32s = WINPR_C_ARRAY_INIT;
UINT32* ptr = nullptr;
const UINT32* cptr = nullptr;
ArrayBasicStruct basicStructs = WINPR_C_ARRAY_INIT;
BasicStruct basicStruct = { 1, 2 };
array_uint32_init(&uint32s);
array_basicstruct_init(&basicStructs);
for (UINT32 i = 0; i < 10; i++)
if (!array_uint32_append(&uint32s, i))
goto fail;
sum = 0;
if (!array_uint32_foreach(&uint32s, cb_compute_sum, &sum))
goto fail;
if (sum != 45)
goto fail;
foreach_index = 0;
if (array_uint32_foreach(&uint32s, cb_stop_at_5, &foreach_index))
goto fail;
if (foreach_index != 5)
goto fail;
if (array_uint32_get(&uint32s, 4) != 4)
goto fail;
array_uint32_set(&uint32s, 4, 5);
if (array_uint32_get(&uint32s, 4) != 5)
goto fail;
ptr = array_uint32_data(&uint32s);
if (*ptr != 0)
goto fail;
cptr = array_uint32_cdata(&uint32s);
if (*cptr != 0)
goto fail;
/* test modifying values of the array during the foreach */
if (!array_uint32_foreach(&uint32s, cb_set_to_1, nullptr) || array_uint32_get(&uint32s, 5) != 1)
goto fail;
/* this one is to test that we can modify the array itself during the foreach and that things
* go nicely */
if (!array_uint32_foreach(&uint32s, cb_reset_after_1, &uint32s) || array_uint32_size(&uint32s))
goto fail;
/* give a try with an array of BasicStructs */
if (!array_basicstruct_append(&basicStructs, basicStruct))
goto fail;
if (!array_basicstruct_foreach(&basicStructs, cb_basic_struct, nullptr))
goto fail;
rc = 0;
fail:
array_uint32_uninit(&uint32s);
array_basicstruct_uninit(&basicStructs);
return rc;
}

View File

@@ -0,0 +1,227 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2014 Thincast Technologies GmbH
* Copyright 2014 Hardening <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <string.h>
#include <freerdp/utils/ringbuffer.h>
static BOOL test_overlaps(void)
{
RingBuffer rb;
DataChunk chunks[2];
BYTE bytes[200];
int nchunks = 0;
int counter = 0;
for (size_t i = 0; i < sizeof(bytes); i++)
bytes[i] = (BYTE)i;
ringbuffer_init(&rb, 5);
if (!ringbuffer_write(&rb, bytes, 4)) /* [0123.] */
goto error;
counter += 4;
ringbuffer_commit_read_bytes(&rb, 2); /* [..23.] */
if (!ringbuffer_write(&rb, &bytes[counter], 2)) /* [5.234] */
goto error;
counter += 2;
nchunks = ringbuffer_peek(&rb, chunks, 4);
if (nchunks != 2 || chunks[0].size != 3 || chunks[1].size != 1)
goto error;
for (int x = 0, j = 2; x < nchunks; x++)
{
for (size_t k = 0; k < chunks[x].size; k++, j++)
{
if (chunks[x].data[k] != (BYTE)j)
goto error;
}
}
ringbuffer_commit_read_bytes(&rb, 3); /* [5....] */
if (ringbuffer_used(&rb) != 1)
goto error;
if (!ringbuffer_write(&rb, &bytes[counter], 6)) /* [56789ab....] */
goto error;
ringbuffer_commit_read_bytes(&rb, 6); /* [......b....] */
nchunks = ringbuffer_peek(&rb, chunks, 10);
if (nchunks != 1 || chunks[0].size != 1 || (*chunks[0].data != 0xb))
goto error;
if (ringbuffer_capacity(&rb) != 5)
goto error;
ringbuffer_destroy(&rb);
return TRUE;
error:
ringbuffer_destroy(&rb);
return FALSE;
}
int TestRingBuffer(int argc, char* argv[])
{
RingBuffer ringBuffer;
int testNo = 0;
BYTE* tmpBuf = nullptr;
BYTE* rb_ptr = nullptr;
DataChunk chunks[2];
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
if (!ringbuffer_init(&ringBuffer, 10))
{
(void)fprintf(stderr, "unable to initialize ringbuffer\n");
return -1;
}
tmpBuf = (BYTE*)malloc(50);
if (!tmpBuf)
return -1;
for (int i = 0; i < 50; i++)
tmpBuf[i] = (char)i;
(void)fprintf(stderr, "%d: basic tests...", ++testNo);
if (!ringbuffer_write(&ringBuffer, tmpBuf, 5) || !ringbuffer_write(&ringBuffer, tmpBuf, 5) ||
!ringbuffer_write(&ringBuffer, tmpBuf, 5))
{
(void)fprintf(stderr, "error when writing bytes\n");
return -1;
}
if (ringbuffer_used(&ringBuffer) != 15)
{
(void)fprintf(stderr, "invalid used size got %" PRIuz " when I would expect 15\n",
ringbuffer_used(&ringBuffer));
return -1;
}
if (ringbuffer_peek(&ringBuffer, chunks, 10) != 1 || chunks[0].size != 10)
{
(void)fprintf(stderr, "error when reading bytes\n");
return -1;
}
ringbuffer_commit_read_bytes(&ringBuffer, chunks[0].size);
/* check retrieved bytes */
for (size_t i = 0; i < chunks[0].size; i++)
{
if (chunks[0].data[i] != i % 5)
{
(void)fprintf(stderr,
"invalid byte at %" PRIuz ", got %" PRIu8 " instead of %" PRIuz "\n", i,
chunks[0].data[i], i % 5U);
return -1;
}
}
if (ringbuffer_used(&ringBuffer) != 5)
{
(void)fprintf(stderr, "invalid used size after read got %" PRIuz " when I would expect 5\n",
ringbuffer_used(&ringBuffer));
return -1;
}
/* write some more bytes to have writePtr < readPtr and data split in 2 chunks */
if (!ringbuffer_write(&ringBuffer, tmpBuf, 6) ||
ringbuffer_peek(&ringBuffer, chunks, 11) != 2 || chunks[0].size != 10 ||
chunks[1].size != 1)
{
(void)fprintf(stderr, "invalid read of split data\n");
return -1;
}
ringbuffer_commit_read_bytes(&ringBuffer, 11);
(void)fprintf(stderr, "ok\n");
(void)fprintf(stderr, "%d: peek with nothing to read...", ++testNo);
if (ringbuffer_peek(&ringBuffer, chunks, 10))
{
(void)fprintf(stderr, "peek returns some chunks\n");
return -1;
}
(void)fprintf(stderr, "ok\n");
(void)fprintf(stderr, "%d: ensure_linear_write / read() shouldn't grow...", ++testNo);
for (int i = 0; i < 1000; i++)
{
rb_ptr = ringbuffer_ensure_linear_write(&ringBuffer, 50);
if (!rb_ptr)
{
(void)fprintf(stderr, "ringbuffer_ensure_linear_write() error\n");
return -1;
}
memcpy(rb_ptr, tmpBuf, 50);
if (!ringbuffer_commit_written_bytes(&ringBuffer, 50))
{
(void)fprintf(stderr, "ringbuffer_commit_written_bytes() error, i=%d\n", i);
return -1;
}
// ringbuffer_commit_read_bytes(&ringBuffer, 25);
}
for (int i = 0; i < 1000; i++)
ringbuffer_commit_read_bytes(&ringBuffer, 25);
for (int i = 0; i < 1000; i++)
ringbuffer_commit_read_bytes(&ringBuffer, 25);
if (ringbuffer_capacity(&ringBuffer) != 10)
{
(void)fprintf(stderr, "not the expected capacity, have %" PRIuz " and expects 10\n",
ringbuffer_capacity(&ringBuffer));
return -1;
}
(void)fprintf(stderr, "ok\n");
(void)fprintf(stderr, "%d: free size is correctly computed...", ++testNo);
for (int i = 0; i < 1000; i++)
{
ringbuffer_ensure_linear_write(&ringBuffer, 50);
if (!ringbuffer_commit_written_bytes(&ringBuffer, 50))
{
(void)fprintf(stderr, "ringbuffer_commit_written_bytes() error, i=%d\n", i);
return -1;
}
}
ringbuffer_commit_read_bytes(&ringBuffer, 50ULL * 1000ULL);
(void)fprintf(stderr, "ok\n");
ringbuffer_destroy(&ringBuffer);
(void)fprintf(stderr, "%d: specific overlaps test...", ++testNo);
if (!test_overlaps())
{
(void)fprintf(stderr, "ko\n");
return -1;
}
(void)fprintf(stderr, "ok\n");
ringbuffer_destroy(&ringBuffer);
free(tmpBuf);
return 0;
}