From ee4c2fb6c7909521cc6ea42d5d7c22f103707c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Mon, 28 Sep 2026 10:58:58 -0400 Subject: [PATCH] Merge commit from fork A memory-protected module's object lookup arrives at the Module Manager's dispatch layer, which decides how much of the module's name buffer the privileged comparison may touch. It checked one byte. _txm_module_manager_object_name_compare reads a character from each name at the top of every iteration and tests the remaining search length at the bottom, so a declared length N permits reads at offsets 0 through N: N+1 bytes, the extra one being the terminator a length excludes. The extended service receives that length in its extra parameter array, validates the array correctly, and then never uses the length to bound the buffer it describes. The deprecated service carries no length at all; its manager wrapper calls the same implementation with the largest value a UINT can hold, so it removes the bound rather than lacking one. The comparison stops at the first differing character, so a walk past the end of the module's memory looks as though it needs the bytes there to match a name the module chose. It does not. A create service stores the name pointer a module supplies in the control block rather than copying the string, so a module can register an object whose name is the very buffer it then searches for. Both sides of the comparison are then the same address, every character matches by construction, and the walk continues until it meets a terminator in memory the module does not own and cannot see. That walk runs in privileged mode with _tx_thread_preempt_disable raised, and none of the 27 memory fault handlers lowers it again, so a fault on it costs more than the requesting thread. The fix validates the range the comparison may reach. The extended dispatcher now checks the name over name_length + 1 bytes, refusing a length whose range cannot be expressed, and the extra parameter array is checked first because the length comes out of it. The deprecated dispatcher refuses a memory-protected module outright, because no range can be derived from a pointer alone; a module without protection is unchanged, as it is for every other check in this layer. _txm_module_manager_object_name_compare is left as it is. Reading N+1 bytes for a declared length of N is the documented contract, and the defect is that the contract was never checked. The regression test drives both lookup dispatchers and measures two extents rather than sampling either side of a boundary, because the finding is the relationship between them. It anchors the name buffer to the end of one of the module's regions and walks it backwards to find the smallest room the dispatcher accepts, which is the extent validated; and it fills memory with a filler character, plants the only terminator at a chosen depth, and asks for an object named with exactly the bytes up to it, so that the deepest depth the lookup can be made to return from is the extent read. Against dev's copy of the two headers the test exits 1 with 408 failed expectations of 1130: a 31-character name with one byte of room is accepted and read 31 bytes past the region, and the aliased walk reaches every depth offered. With the fix it exits 0 with 1490 expectations and none failed. Assisted-by: Claude Code (Opus 5) --- .../inc/txm_module_manager_dispatch.h | 18 +- .../inc/txm_module_manager_util.h | 9 + test/tx/cmake/module_manager/CMakeLists.txt | 22 + ...dx_module_manager_object_name_range_test.c | 1608 +++++++++++++++++ 4 files changed, 1650 insertions(+), 7 deletions(-) create mode 100644 test/tx/module_manager/threadx_module_manager_object_name_range_test.c diff --git a/common_modules/module_manager/inc/txm_module_manager_dispatch.h b/common_modules/module_manager/inc/txm_module_manager_dispatch.h index 35e493d4..2233ec00 100644 --- a/common_modules/module_manager/inc/txm_module_manager_dispatch.h +++ b/common_modules/module_manager/inc/txm_module_manager_dispatch.h @@ -19,6 +19,8 @@ // Some portions generated by Claude Code (Opus 5). +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -3310,11 +3312,10 @@ ALIGN_TYPE return_value; if (module_instance -> txm_module_instance_property_flags & TXM_MODULE_MEMORY_PROTECTION) { - if (!TXM_MODULE_MANAGER_PARAM_CHECK_DEREFERENCE_STRING(module_instance, param_1)) - return(TXM_MODULE_INVALID_MEMORY); - - if (!TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, param_2, sizeof(VOID *))) - return(TXM_MODULE_INVALID_MEMORY); + /* This service carries no name length, so the manager searches with the largest + length a UINT can hold and no readable range can be proven for the name. A + memory-protected module must use the extended service, which declares one. */ + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txm_module_manager_object_pointer_get( @@ -3340,10 +3341,13 @@ ALIGN_TYPE return_value; if (module_instance -> txm_module_instance_property_flags & TXM_MODULE_MEMORY_PROTECTION) { - if (!TXM_MODULE_MANAGER_PARAM_CHECK_DEREFERENCE_STRING(module_instance, param_1)) + if (!TXM_MODULE_MANAGER_ENSURE_INSIDE_MODULE_DATA(module_instance, (ALIGN_TYPE)extra_parameters, sizeof(ALIGN_TYPE[2]))) return(TXM_MODULE_INVALID_MEMORY); - if (!TXM_MODULE_MANAGER_ENSURE_INSIDE_MODULE_DATA(module_instance, (ALIGN_TYPE)extra_parameters, sizeof(ALIGN_TYPE[2]))) + /* The name length has to be checked before it is trusted, so the extra parameter + array is checked first. The comparison reads the declared length plus the + terminator that follows it, and all of that must be inside the module. */ + if (!TXM_MODULE_MANAGER_PARAM_CHECK_DEREFERENCE_STRING_RANGE(module_instance, param_1, (UINT) extra_parameters[0])) return(TXM_MODULE_INVALID_MEMORY); if (!TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, extra_parameters[1], sizeof(VOID *))) diff --git a/common_modules/module_manager/inc/txm_module_manager_util.h b/common_modules/module_manager/inc/txm_module_manager_util.h index 22b9570e..92bb55dd 100644 --- a/common_modules/module_manager/inc/txm_module_manager_util.h +++ b/common_modules/module_manager/inc/txm_module_manager_util.h @@ -135,6 +135,15 @@ ((TXM_MODULE_MANAGER_ENSURE_INSIDE_MODULE(module_instance, string_ptr, 1)) || \ ((void *) (string_ptr) == TX_NULL)) +/* Strings we walk are checked over the whole range the walk may reach: the declared + length plus the terminating character that follows it, since a length excludes the + terminator and the comparison reads it. A length whose range cannot be expressed + is refused rather than truncated. */ +#define TXM_MODULE_MANAGER_PARAM_CHECK_DEREFERENCE_STRING_RANGE(module_instance, string_ptr, string_length) \ + (((((ALIGN_TYPE) (string_length)) < (~((ALIGN_TYPE) 0))) && \ + (TXM_MODULE_MANAGER_ENSURE_INSIDE_MODULE(module_instance, string_ptr, ((ALIGN_TYPE) (string_length)) + ((ALIGN_TYPE) 1)))) || \ + ((void *) (string_ptr) == TX_NULL)) + #define TXM_MODULE_MANAGER_UTIL_MAX_VALUE_OF_TYPE_UNSIGNED(type) ((1ULL << (sizeof(type) * 8)) - 1) #define TXM_MODULE_MANAGER_UTIL_MATH_ADD_ULONG(augend, addend, result) \ diff --git a/test/tx/cmake/module_manager/CMakeLists.txt b/test/tx/cmake/module_manager/CMakeLists.txt index 109ee72a..f9830d44 100644 --- a/test/tx/cmake/module_manager/CMakeLists.txt +++ b/test/tx/cmake/module_manager/CMakeLists.txt @@ -194,3 +194,25 @@ target_compile_options( add_test(${CMAKE_BUILD_TYPE}::threadx_module_manager_block_pool_parameters_test threadx_module_manager_block_pool_parameters_test) + +add_executable( + threadx_module_manager_object_name_range_test + ${SOURCE_DIR}/threadx_module_manager_object_name_range_test.c + ${module_manager_dir}/src/txm_module_manager_object_pointer_get.c + ${module_manager_dir}/src/txm_module_manager_object_pointer_get_extended.c + ${module_manager_dir}/src/txm_module_manager_util.c) + +target_include_directories( + threadx_module_manager_object_name_range_test + PRIVATE ${SOURCE_DIR} + ${REPO_ROOT}/common/inc + ${REPO_ROOT}/common_modules/inc + ${module_manager_dir}/inc + ${cortex_m4_module_dir}/inc) + +target_compile_options( + threadx_module_manager_object_name_range_test + PRIVATE -include ${SOURCE_DIR}/threadx_module_manager_host_test_port.h) + +add_test(${CMAKE_BUILD_TYPE}::threadx_module_manager_object_name_range_test + threadx_module_manager_object_name_range_test) diff --git a/test/tx/module_manager/threadx_module_manager_object_name_range_test.c b/test/tx/module_manager/threadx_module_manager_object_name_range_test.c new file mode 100644 index 00000000..7e6ee5e5 --- /dev/null +++ b/test/tx/module_manager/threadx_module_manager_object_name_range_test.c @@ -0,0 +1,1608 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Test */ +/** */ +/** Module Manager object name range validation */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +/* A memory-protected module asks the Module Manager to find a kernel object by name. + The dispatch layer decides how much of the module's name buffer the privileged + comparison may touch. It checked one byte. + + _txm_module_manager_object_name_compare reads a character from each name at the top + of every iteration and tests the remaining search length at the bottom, so a declared + length N permits reads at offsets 0 through N -- N+1 bytes, the extra one being the + terminator a length excludes. The extended service receives that length in its extra + parameter array, validates the array correctly, and then never uses the length to + bound the buffer it describes. The deprecated service carries no length at all: its + manager wrapper calls the same implementation with the largest value a UINT can hold, + so it removes the bound rather than lacking one. + + The comparison stops at the first differing character, so the walk past the end of + the module's memory is not free -- unless the module arranges for it to match. It can: + a create service stores the name pointer a module supplies in the control block + rather than copying the string, so a module can register an object whose name is the + very buffer it then searches for. Both sides of the comparison are then the same + address, every character matches by construction, and the walk continues until it + meets a terminator in memory the module does not own and cannot see. + + This test drives both lookup dispatchers directly and measures two things rather than + sampling either side of a boundary, because there are two dials here and the finding + is about the relationship between them: + + - the extent the dispatcher validates, found by anchoring the name buffer to the end + of one of the module's regions and walking it backwards a byte at a time until the + request is accepted; and + + - the extent the comparison reads, found by filling memory with a filler character, + planting the only terminator at a chosen depth from the buffer, and asking for an + object whose name is exactly the bytes up to it. The lookup returns that object + only if the comparison read that deep, so the deepest depth it can be made to + return is the read extent. + + The assertion is the relationship: the comparison never reads further than the + dispatcher validated, and the dispatcher validates the length the module declared plus + its terminator. Before the fix the read extent follows the declared length and the + validated extent stays at one byte, which is the defect stated as a measurement. + + Two mechanics come from the queue message range test, which was the first test in the + tree to drive a dispatcher, and both are the reason this one is possible. + + The dispatch table is a header of static functions, one per kernel service, each + wrapped in #ifndef TXM__CALL_NOT_USED. There are 96 of those guards. Defining + the 94 this test does not exercise compiles the header down to the two lookup services, + which keeps the link to the manager sources under test and a handful of kernel globals + instead of the hundred-odd kernel entry points the whole table reaches. + + The extent has to reach a check that honours it, so this test compiles against a + Cortex-M module port's headers, whose inline data check is the shape the memory + protected module ports share. The Cortex-A7 port's data check takes the pointer alone, + so a test built on it would pass equally with and without this fix. + + Unlike the queue test, this one cannot stub the service behind the dispatcher: the + comparison being measured is the service. It links the real lookup implementation and + the real comparison, and stands up the kernel created lists they walk. */ + +#include +#include + +#include "threadx_module_manager_host_test_port.h" + +#include "tx_thread.h" +#include "tx_timer.h" +#include "tx_queue.h" +#include "tx_event_flags.h" +#include "tx_semaphore.h" +#include "tx_mutex.h" +#include "tx_block_pool.h" +#include "tx_byte_pool.h" +#include "txm_module.h" +#include "txm_module_manager_util.h" + + +/* Refuse every service in the dispatch table except the two under test. The list is the + header's own guard names; a service added to the table appears here as a link error + naming the kernel entry point it reaches, which is the right way to find out. */ + +#define TXM_BLOCK_ALLOCATE_CALL_NOT_USED +#define TXM_BLOCK_POOL_CREATE_CALL_NOT_USED +#define TXM_BLOCK_POOL_DELETE_CALL_NOT_USED +#define TXM_BLOCK_POOL_INFO_GET_CALL_NOT_USED +#define TXM_BLOCK_POOL_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_BLOCK_POOL_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_BLOCK_POOL_PRIORITIZE_CALL_NOT_USED +#define TXM_BLOCK_RELEASE_CALL_NOT_USED +#define TXM_BYTE_ALLOCATE_CALL_NOT_USED +#define TXM_BYTE_POOL_CREATE_CALL_NOT_USED +#define TXM_BYTE_POOL_DELETE_CALL_NOT_USED +#define TXM_BYTE_POOL_INFO_GET_CALL_NOT_USED +#define TXM_BYTE_POOL_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_BYTE_POOL_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_BYTE_POOL_PRIORITIZE_CALL_NOT_USED +#define TXM_BYTE_RELEASE_CALL_NOT_USED +#define TXM_EVENT_FLAGS_CREATE_CALL_NOT_USED +#define TXM_EVENT_FLAGS_DELETE_CALL_NOT_USED +#define TXM_EVENT_FLAGS_GET_CALL_NOT_USED +#define TXM_EVENT_FLAGS_INFO_GET_CALL_NOT_USED +#define TXM_EVENT_FLAGS_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_EVENT_FLAGS_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_EVENT_FLAGS_SET_CALL_NOT_USED +#define TXM_EVENT_FLAGS_SET_NOTIFY_CALL_NOT_USED +#define TXM_MUTEX_CREATE_CALL_NOT_USED +#define TXM_MUTEX_DELETE_CALL_NOT_USED +#define TXM_MUTEX_GET_CALL_NOT_USED +#define TXM_MUTEX_INFO_GET_CALL_NOT_USED +#define TXM_MUTEX_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_MUTEX_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_MUTEX_PRIORITIZE_CALL_NOT_USED +#define TXM_MUTEX_PUT_CALL_NOT_USED +#define TXM_QUEUE_CREATE_CALL_NOT_USED +#define TXM_QUEUE_DELETE_CALL_NOT_USED +#define TXM_QUEUE_FLUSH_CALL_NOT_USED +#define TXM_QUEUE_FRONT_SEND_CALL_NOT_USED +#define TXM_QUEUE_INFO_GET_CALL_NOT_USED +#define TXM_QUEUE_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_QUEUE_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_QUEUE_PRIORITIZE_CALL_NOT_USED +#define TXM_QUEUE_RECEIVE_CALL_NOT_USED +#define TXM_QUEUE_SEND_CALL_NOT_USED +#define TXM_QUEUE_SEND_NOTIFY_CALL_NOT_USED +#define TXM_SEMAPHORE_CEILING_PUT_CALL_NOT_USED +#define TXM_SEMAPHORE_CREATE_CALL_NOT_USED +#define TXM_SEMAPHORE_DELETE_CALL_NOT_USED +#define TXM_SEMAPHORE_GET_CALL_NOT_USED +#define TXM_SEMAPHORE_INFO_GET_CALL_NOT_USED +#define TXM_SEMAPHORE_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_SEMAPHORE_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_SEMAPHORE_PRIORITIZE_CALL_NOT_USED +#define TXM_SEMAPHORE_PUT_CALL_NOT_USED +#define TXM_SEMAPHORE_PUT_NOTIFY_CALL_NOT_USED +#define TXM_THREAD_CREATE_CALL_NOT_USED +#define TXM_THREAD_DELETE_CALL_NOT_USED +#define TXM_THREAD_ENTRY_EXIT_NOTIFY_CALL_NOT_USED +#define TXM_THREAD_IDENTIFY_CALL_NOT_USED +#define TXM_THREAD_INFO_GET_CALL_NOT_USED +#define TXM_THREAD_INTERRUPT_CONTROL_CALL_NOT_USED +#define TXM_THREAD_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_THREAD_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_THREAD_PREEMPTION_CHANGE_CALL_NOT_USED +#define TXM_THREAD_PRIORITY_CHANGE_CALL_NOT_USED +#define TXM_THREAD_RELINQUISH_CALL_NOT_USED +#define TXM_THREAD_RESET_CALL_NOT_USED +#define TXM_THREAD_RESUME_CALL_NOT_USED +#define TXM_THREAD_SLEEP_CALL_NOT_USED +#define TXM_THREAD_STACK_ERROR_NOTIFY_CALL_NOT_USED +#define TXM_THREAD_SUSPEND_CALL_NOT_USED +#define TXM_THREAD_SYSTEM_SUSPEND_CALL_NOT_USED +#define TXM_THREAD_TERMINATE_CALL_NOT_USED +#define TXM_THREAD_TIME_SLICE_CHANGE_CALL_NOT_USED +#define TXM_THREAD_WAIT_ABORT_CALL_NOT_USED +#define TXM_TIME_GET_CALL_NOT_USED +#define TXM_TIME_SET_CALL_NOT_USED +#define TXM_TIMER_ACTIVATE_CALL_NOT_USED +#define TXM_TIMER_CHANGE_CALL_NOT_USED +#define TXM_TIMER_CREATE_CALL_NOT_USED +#define TXM_TIMER_DEACTIVATE_CALL_NOT_USED +#define TXM_TIMER_DELETE_CALL_NOT_USED +#define TXM_TIMER_INFO_GET_CALL_NOT_USED +#define TXM_TIMER_PERFORMANCE_INFO_GET_CALL_NOT_USED +#define TXM_TIMER_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED +#define TXM_TRACE_BUFFER_FULL_NOTIFY_CALL_NOT_USED +#define TXM_TRACE_DISABLE_CALL_NOT_USED +#define TXM_TRACE_ENABLE_CALL_NOT_USED +#define TXM_TRACE_EVENT_FILTER_CALL_NOT_USED +#define TXM_TRACE_EVENT_UNFILTER_CALL_NOT_USED +#define TXM_TRACE_INTERRUPT_CONTROL_CALL_NOT_USED +#define TXM_TRACE_ISR_ENTER_INSERT_CALL_NOT_USED +#define TXM_TRACE_ISR_EXIT_INSERT_CALL_NOT_USED +#define TXM_TRACE_USER_EVENT_INSERT_CALL_NOT_USED +#define TXM_MODULE_OBJECT_ALLOCATE_CALL_NOT_USED +#define TXM_MODULE_OBJECT_DEALLOCATE_CALL_NOT_USED + +#include "txm_module_manager_dispatch.h" + +/* The two under test have to have survived that, and the rest have to have gone. */ + +#if defined(TXM_MODULE_OBJECT_POINTER_GET_CALL_NOT_USED) || defined(TXM_MODULE_OBJECT_POINTER_GET_EXTENDED_CALL_NOT_USED) +#error "the lookup services under test must be compiled in" +#endif + +#ifndef TXM_QUEUE_FLUSH_CALL_NOT_USED +#error "the services this test does not exercise must be compiled out" +#endif + + +/* Define the stand-in interrupt lock the port shim counts through. */ + +unsigned int test_interrupt_disable_depth; +unsigned int test_interrupt_disable_max_depth; +unsigned int test_interrupt_restore_underflows; + + +/* Define the manager state the utility source under test links against. No case here + reaches the object pool: a lookup names no control block. */ + +TX_BYTE_POOL _txm_module_manager_object_pool; +UINT _txm_module_manager_object_pool_created; + + +/* Define the kernel state the lookup reaches for. It walks a created list per object + type, holds off preemption while it does, and asks the current thread which module it + belongs to before it will search the two pool types. */ + +TX_THREAD *_tx_thread_current_ptr; +volatile UINT _tx_thread_preempt_disable; + +TX_BLOCK_POOL *_tx_block_pool_created_ptr; +ULONG _tx_block_pool_created_count; +TX_BYTE_POOL *_tx_byte_pool_created_ptr; +ULONG _tx_byte_pool_created_count; +TX_EVENT_FLAGS_GROUP *_tx_event_flags_created_ptr; +ULONG _tx_event_flags_created_count; +TX_MUTEX *_tx_mutex_created_ptr; +ULONG _tx_mutex_created_count; +TX_QUEUE *_tx_queue_created_ptr; +ULONG _tx_queue_created_count; +TX_SEMAPHORE *_tx_semaphore_created_ptr; +ULONG _tx_semaphore_created_count; +TX_THREAD *_tx_thread_created_ptr; +ULONG _tx_thread_created_count; +TX_TIMER *_tx_timer_created_ptr; +ULONG _tx_timer_created_count; + + +/* Count the lookup's final preemption check, and record what it left behind. + + The lookup calls this once, on its way out, after restoring the preemption count it + raised on the way in. Counting it is how a case tells a request the dispatch layer + refused from one it let through: a refusal returns before the lookup is entered at + all. Recording the preemption count as the lookup leaves it is how the run as a whole + asserts that the hold is balanced. */ + +static ULONG test_lookup_exits; +static ULONG test_preempt_disable_on_exit; + +VOID _tx_thread_system_preempt_check(VOID) +{ + test_lookup_exits++; + test_preempt_disable_on_exit = (ULONG) _tx_thread_preempt_disable; +} + + +/* Stand in for the port primitive a manager source under test refers to but that no case + here exercises. */ + +VOID _txm_module_manager_alignment_adjust(TXM_MODULE_PREAMBLE *module_preamble, ULONG *code_size, + ULONG *code_alignment, ULONG *data_size, ULONG *data_alignment) +{ + (VOID) module_preamble; + (VOID) code_size; + (VOID) code_alignment; + (VOID) data_size; + (VOID) data_alignment; +} + + +static UINT test_failures; +static ULONG test_checks; + + +/* Record the outcome of one expectation. */ +static VOID test_expect(const char *description, ULONG actual, ULONG expected) +{ + test_checks++; + + if (actual != expected) + { + printf("FAIL: %s (expected %lu, got %lu)\n", description, (unsigned long) expected, (unsigned long) actual); + test_failures++; + } +} + + +/* Model the module's memory as one arena, so that the three regions a name buffer may + legitimately live in have known addresses and known gaps between them. The gaps are + memory the module may not reach: they are what a buffer anchored to the end of a + region overruns into, and they are inside the arena so that the overrun a case + describes is memory this test owns. */ + +#define TEST_REGION_BYTES ((ULONG) 256) +#define TEST_GAP_BYTES ((ULONG) 128) + +static union +{ + ALIGN_TYPE test_arena_alignment; + UCHAR test_arena_bytes[(((ULONG) 3) * TEST_REGION_BYTES) + (((ULONG) 4) * TEST_GAP_BYTES)]; +} test_arena; + +#define TEST_DATA_START (&test_arena.test_arena_bytes[TEST_GAP_BYTES]) +#define TEST_CODE_START (&test_arena.test_arena_bytes[(((ULONG) 2) * TEST_GAP_BYTES) + TEST_REGION_BYTES]) +#define TEST_SHARED_START (&test_arena.test_arena_bytes[(((ULONG) 3) * TEST_GAP_BYTES) + (((ULONG) 2) * TEST_REGION_BYTES)]) + + +/* The character memory is filled with. Any non-zero value will do: what matters is that + a case can plant the only terminator in memory where it wants one, and that the bytes + before it match a name the module chose. */ + +#define TEST_FILLER ((UCHAR) 0x5A) + + +/* The module's outgoing parameters live at the front of its data region, because the + extra parameter array must be inside module data and the result slot must be + writable by the module. Every name buffer is anchored to the end of a region, so the + two never meet. */ + +#define TEST_EXTRA_PARAMETERS ((ALIGN_TYPE *) TEST_DATA_START) +#define TEST_RESULT_SLOT ((VOID **) (TEST_DATA_START + (((ULONG) 2) * sizeof(ALIGN_TYPE)))) + + +/* Define the requesting modules. One has memory protection, and is the one every range + case below drives; the other does not, and is how the cases show that the dispatch + layer's checks are not reached for it at all. */ + +static TXM_MODULE_INSTANCE test_protected_module; +static TXM_MODULE_INSTANCE test_unprotected_module; + + +/* Place a module's regions on the arena. */ +static VOID test_module_setup(TXM_MODULE_INSTANCE *module_instance, ULONG property_flags) +{ + memset((VOID *) module_instance, 0, sizeof(TXM_MODULE_INSTANCE)); + + module_instance -> txm_module_instance_property_flags = property_flags; + + module_instance -> txm_module_instance_data_start = (VOID *) TEST_DATA_START; + module_instance -> txm_module_instance_data_end = (VOID *) (TEST_DATA_START + (TEST_REGION_BYTES - ((ULONG) 1))); + + module_instance -> txm_module_instance_code_start = (VOID *) TEST_CODE_START; + module_instance -> txm_module_instance_code_end = (VOID *) (TEST_CODE_START + (TEST_REGION_BYTES - ((ULONG) 1))); + + module_instance -> txm_module_instance_shared_memory_address = (ULONG) TEST_SHARED_START; + module_instance -> txm_module_instance_shared_memory_length = TEST_REGION_BYTES; +} + + +/* Define the thread the lookup asks which module is calling, and the objects the created + lists are built from. The objects are outside the arena, where a kernel object belongs. */ + +static TX_THREAD test_current_thread; +static TX_SEMAPHORE test_semaphore; +static TX_SEMAPHORE test_decoy_semaphore; + + +/* Define three control blocks of each type for the cases that search every list the + lookup knows: the match, and two ahead of it so that a list is walked rather than + looked at. */ + +#define TEST_OBJECTS_PER_TYPE ((UINT) 3) + +static TX_THREAD test_thread_objects[TEST_OBJECTS_PER_TYPE]; +static TX_TIMER test_timer_objects[TEST_OBJECTS_PER_TYPE]; +static TX_QUEUE test_queue_objects[TEST_OBJECTS_PER_TYPE]; +static TX_EVENT_FLAGS_GROUP test_event_flags_objects[TEST_OBJECTS_PER_TYPE]; +static TX_SEMAPHORE test_semaphore_objects[TEST_OBJECTS_PER_TYPE]; +static TX_MUTEX test_mutex_objects[TEST_OBJECTS_PER_TYPE]; +static TX_BLOCK_POOL test_block_pool_objects[TEST_OBJECTS_PER_TYPE]; +static TX_BYTE_POOL test_byte_pool_objects[TEST_OBJECTS_PER_TYPE]; + + +/* Define the name a probing case gives an object. It is outside the arena too, because + an object's name is not the module's memory even when the module supplied the pointer; + the aliasing cases are the ones that make it so deliberately. */ + +static CHAR test_probe_name[TEST_REGION_BYTES + TEST_GAP_BYTES]; + + +/* Put one object on the created list for its type, newest last, the way the kernel's + create service links a new object in ahead of the head of the ring. The name is stored + as the pointer the caller gave, because that is what a create service does with it and + it is the reason the aliasing cases below are possible at all. + + Every list is built the same way and the eight blocks differ only in the type they + name, which is how the lookup itself is written. */ +static VOID test_object_create(UINT object_type, VOID *object_ptr, CHAR *name) +{ + switch (object_type) + { + + case TXM_THREAD_OBJECT: + { + TX_THREAD *thread_ptr = (TX_THREAD *) object_ptr; + TX_THREAD *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_THREAD)); + thread_ptr -> tx_thread_id = TX_THREAD_ID; + thread_ptr -> tx_thread_name = name; + + if (_tx_thread_created_ptr == TX_NULL) + { + thread_ptr -> tx_thread_created_next = thread_ptr; + _tx_thread_created_ptr = thread_ptr; + } + else + { + tail_ptr = _tx_thread_created_ptr; + while (tail_ptr -> tx_thread_created_next != _tx_thread_created_ptr) + { + tail_ptr = tail_ptr -> tx_thread_created_next; + } + tail_ptr -> tx_thread_created_next = thread_ptr; + thread_ptr -> tx_thread_created_next = _tx_thread_created_ptr; + } + _tx_thread_created_count++; + break; + } + + case TXM_TIMER_OBJECT: + { + TX_TIMER *timer_ptr = (TX_TIMER *) object_ptr; + TX_TIMER *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_TIMER)); + timer_ptr -> tx_timer_id = TX_TIMER_ID; + timer_ptr -> tx_timer_name = name; + + if (_tx_timer_created_ptr == TX_NULL) + { + timer_ptr -> tx_timer_created_next = timer_ptr; + _tx_timer_created_ptr = timer_ptr; + } + else + { + tail_ptr = _tx_timer_created_ptr; + while (tail_ptr -> tx_timer_created_next != _tx_timer_created_ptr) + { + tail_ptr = tail_ptr -> tx_timer_created_next; + } + tail_ptr -> tx_timer_created_next = timer_ptr; + timer_ptr -> tx_timer_created_next = _tx_timer_created_ptr; + } + _tx_timer_created_count++; + break; + } + + case TXM_QUEUE_OBJECT: + { + TX_QUEUE *queue_ptr = (TX_QUEUE *) object_ptr; + TX_QUEUE *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_QUEUE)); + queue_ptr -> tx_queue_id = TX_QUEUE_ID; + queue_ptr -> tx_queue_name = name; + + if (_tx_queue_created_ptr == TX_NULL) + { + queue_ptr -> tx_queue_created_next = queue_ptr; + _tx_queue_created_ptr = queue_ptr; + } + else + { + tail_ptr = _tx_queue_created_ptr; + while (tail_ptr -> tx_queue_created_next != _tx_queue_created_ptr) + { + tail_ptr = tail_ptr -> tx_queue_created_next; + } + tail_ptr -> tx_queue_created_next = queue_ptr; + queue_ptr -> tx_queue_created_next = _tx_queue_created_ptr; + } + _tx_queue_created_count++; + break; + } + + case TXM_EVENT_FLAGS_OBJECT: + { + TX_EVENT_FLAGS_GROUP *event_flags_ptr = (TX_EVENT_FLAGS_GROUP *) object_ptr; + TX_EVENT_FLAGS_GROUP *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_EVENT_FLAGS_GROUP)); + event_flags_ptr -> tx_event_flags_group_id = TX_EVENT_FLAGS_ID; + event_flags_ptr -> tx_event_flags_group_name = name; + + if (_tx_event_flags_created_ptr == TX_NULL) + { + event_flags_ptr -> tx_event_flags_group_created_next = event_flags_ptr; + _tx_event_flags_created_ptr = event_flags_ptr; + } + else + { + tail_ptr = _tx_event_flags_created_ptr; + while (tail_ptr -> tx_event_flags_group_created_next != _tx_event_flags_created_ptr) + { + tail_ptr = tail_ptr -> tx_event_flags_group_created_next; + } + tail_ptr -> tx_event_flags_group_created_next = event_flags_ptr; + event_flags_ptr -> tx_event_flags_group_created_next = _tx_event_flags_created_ptr; + } + _tx_event_flags_created_count++; + break; + } + + case TXM_SEMAPHORE_OBJECT: + { + TX_SEMAPHORE *semaphore_ptr = (TX_SEMAPHORE *) object_ptr; + TX_SEMAPHORE *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_SEMAPHORE)); + semaphore_ptr -> tx_semaphore_id = TX_SEMAPHORE_ID; + semaphore_ptr -> tx_semaphore_name = name; + + if (_tx_semaphore_created_ptr == TX_NULL) + { + semaphore_ptr -> tx_semaphore_created_next = semaphore_ptr; + _tx_semaphore_created_ptr = semaphore_ptr; + } + else + { + tail_ptr = _tx_semaphore_created_ptr; + while (tail_ptr -> tx_semaphore_created_next != _tx_semaphore_created_ptr) + { + tail_ptr = tail_ptr -> tx_semaphore_created_next; + } + tail_ptr -> tx_semaphore_created_next = semaphore_ptr; + semaphore_ptr -> tx_semaphore_created_next = _tx_semaphore_created_ptr; + } + _tx_semaphore_created_count++; + break; + } + + case TXM_MUTEX_OBJECT: + { + TX_MUTEX *mutex_ptr = (TX_MUTEX *) object_ptr; + TX_MUTEX *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_MUTEX)); + mutex_ptr -> tx_mutex_id = TX_MUTEX_ID; + mutex_ptr -> tx_mutex_name = name; + + if (_tx_mutex_created_ptr == TX_NULL) + { + mutex_ptr -> tx_mutex_created_next = mutex_ptr; + _tx_mutex_created_ptr = mutex_ptr; + } + else + { + tail_ptr = _tx_mutex_created_ptr; + while (tail_ptr -> tx_mutex_created_next != _tx_mutex_created_ptr) + { + tail_ptr = tail_ptr -> tx_mutex_created_next; + } + tail_ptr -> tx_mutex_created_next = mutex_ptr; + mutex_ptr -> tx_mutex_created_next = _tx_mutex_created_ptr; + } + _tx_mutex_created_count++; + break; + } + + case TXM_BLOCK_POOL_OBJECT: + { + TX_BLOCK_POOL *block_pool_ptr = (TX_BLOCK_POOL *) object_ptr; + TX_BLOCK_POOL *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_BLOCK_POOL)); + block_pool_ptr -> tx_block_pool_id = TX_BLOCK_POOL_ID; + block_pool_ptr -> tx_block_pool_name = name; + + if (_tx_block_pool_created_ptr == TX_NULL) + { + block_pool_ptr -> tx_block_pool_created_next = block_pool_ptr; + _tx_block_pool_created_ptr = block_pool_ptr; + } + else + { + tail_ptr = _tx_block_pool_created_ptr; + while (tail_ptr -> tx_block_pool_created_next != _tx_block_pool_created_ptr) + { + tail_ptr = tail_ptr -> tx_block_pool_created_next; + } + tail_ptr -> tx_block_pool_created_next = block_pool_ptr; + block_pool_ptr -> tx_block_pool_created_next = _tx_block_pool_created_ptr; + } + _tx_block_pool_created_count++; + break; + } + + case TXM_BYTE_POOL_OBJECT: + default: + { + TX_BYTE_POOL *byte_pool_ptr = (TX_BYTE_POOL *) object_ptr; + TX_BYTE_POOL *tail_ptr; + + memset(object_ptr, 0, sizeof(TX_BYTE_POOL)); + byte_pool_ptr -> tx_byte_pool_id = TX_BYTE_POOL_ID; + byte_pool_ptr -> tx_byte_pool_name = name; + + if (_tx_byte_pool_created_ptr == TX_NULL) + { + byte_pool_ptr -> tx_byte_pool_created_next = byte_pool_ptr; + _tx_byte_pool_created_ptr = byte_pool_ptr; + } + else + { + tail_ptr = _tx_byte_pool_created_ptr; + while (tail_ptr -> tx_byte_pool_created_next != _tx_byte_pool_created_ptr) + { + tail_ptr = tail_ptr -> tx_byte_pool_created_next; + } + tail_ptr -> tx_byte_pool_created_next = byte_pool_ptr; + byte_pool_ptr -> tx_byte_pool_created_next = _tx_byte_pool_created_ptr; + } + _tx_byte_pool_created_count++; + break; + } + } +} + + +/* Empty every created list. */ +static VOID test_lists_clear(VOID) +{ + _tx_block_pool_created_ptr = TX_NULL; + _tx_block_pool_created_count = ((ULONG) 0); + _tx_byte_pool_created_ptr = TX_NULL; + _tx_byte_pool_created_count = ((ULONG) 0); + _tx_event_flags_created_ptr = TX_NULL; + _tx_event_flags_created_count = ((ULONG) 0); + _tx_mutex_created_ptr = TX_NULL; + _tx_mutex_created_count = ((ULONG) 0); + _tx_queue_created_ptr = TX_NULL; + _tx_queue_created_count = ((ULONG) 0); + _tx_semaphore_created_ptr = TX_NULL; + _tx_semaphore_created_count = ((ULONG) 0); + _tx_thread_created_ptr = TX_NULL; + _tx_thread_created_count = ((ULONG) 0); + _tx_timer_created_ptr = TX_NULL; + _tx_timer_created_count = ((ULONG) 0); +} + + +/* Fill the arena, then restore the module's outgoing parameters, which live in it. */ +static VOID test_arena_fill(VOID) +{ + memset((VOID *) &test_arena, (int) TEST_FILLER, sizeof(test_arena)); + + TEST_EXTRA_PARAMETERS[0] = ((ALIGN_TYPE) 0); + TEST_EXTRA_PARAMETERS[1] = (ALIGN_TYPE) TEST_RESULT_SLOT; + *TEST_RESULT_SLOT = TX_NULL; +} + + +/* What one request came back with. */ + +#define TEST_RESULT_POISON ((VOID *) (ALIGN_TYPE) 0xD0D0D0D0) + +static ALIGN_TYPE test_return_value; +static VOID *test_result; +static ULONG test_reached_lookup; + + +/* Make one request of the extended dispatcher, the way a module makes it: the declared + length and the result slot go in the extra parameter array, which is in the module's + own data. */ +static VOID test_lookup_extended_result_slot(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type, + UCHAR *name, UINT name_length, ALIGN_TYPE *extra_parameters, + VOID **result_slot) +{ + *TEST_RESULT_SLOT = TEST_RESULT_POISON; + test_lookup_exits = ((ULONG) 0); + + /* The lookup asks the running thread which module is calling before it will search + the two pool types, so the request has to come from one. */ + test_current_thread.tx_thread_module_instance_ptr = (VOID *) module_instance; + _tx_thread_current_ptr = &test_current_thread; + + extra_parameters[0] = (ALIGN_TYPE) name_length; + extra_parameters[1] = (ALIGN_TYPE) result_slot; + + test_return_value = _txm_module_manager_txm_module_object_pointer_get_extended_dispatch(module_instance, object_type, + (ALIGN_TYPE) name, extra_parameters); + test_result = *TEST_RESULT_SLOT; + test_reached_lookup = test_lookup_exits; +} + + +/* Make one request without writing to the extra parameter array first, for the cases + whose whole point is an array the module could not have written to either. */ +static VOID test_lookup_extended_raw(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type, + UCHAR *name, ALIGN_TYPE *extra_parameters) +{ + *TEST_RESULT_SLOT = TEST_RESULT_POISON; + test_lookup_exits = ((ULONG) 0); + + test_current_thread.tx_thread_module_instance_ptr = (VOID *) module_instance; + _tx_thread_current_ptr = &test_current_thread; + + test_return_value = _txm_module_manager_txm_module_object_pointer_get_extended_dispatch(module_instance, object_type, + (ALIGN_TYPE) name, extra_parameters); + test_result = *TEST_RESULT_SLOT; + test_reached_lookup = test_lookup_exits; +} + + +/* The same request with the result slot the module normally uses. */ +static VOID test_lookup_extended(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type, + UCHAR *name, UINT name_length, ALIGN_TYPE *extra_parameters) +{ + test_lookup_extended_result_slot(module_instance, object_type, name, name_length, extra_parameters, TEST_RESULT_SLOT); +} + + +/* Make one request of the deprecated dispatcher, which carries no length. */ +static VOID test_lookup_deprecated(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type, UCHAR *name) +{ + *TEST_RESULT_SLOT = TEST_RESULT_POISON; + test_lookup_exits = ((ULONG) 0); + + test_current_thread.tx_thread_module_instance_ptr = (VOID *) module_instance; + _tx_thread_current_ptr = &test_current_thread; + + test_return_value = _txm_module_manager_txm_module_object_pointer_get_dispatch(module_instance, object_type, + (ALIGN_TYPE) name, (ALIGN_TYPE) TEST_RESULT_SLOT); + test_result = *TEST_RESULT_SLOT; + test_reached_lookup = test_lookup_exits; +} + + +/* Report whether the dispatch layer let the last request through to the lookup, checking + as it goes that a refusal is a whole refusal: TXM_MODULE_INVALID_MEMORY, the lookup not + entered, and the module's result slot not written. */ +static UINT test_accepted(VOID) +{ + +UINT accepted; + + + if (test_reached_lookup == ((ULONG) 0)) + { + accepted = TX_FALSE; + + test_expect("a refused request returns TXM_MODULE_INVALID_MEMORY", + (ULONG) test_return_value, (ULONG) TXM_MODULE_INVALID_MEMORY); + test_expect("...and writes nothing to the module's result slot", + (ULONG) (test_result == TEST_RESULT_POISON), (ULONG) TX_TRUE); + } + else + { + accepted = TX_TRUE; + + test_expect("an accepted request enters the lookup once", test_reached_lookup, ((ULONG) 1)); + test_expect("...and the lookup gives back the preemption it took", + test_preempt_disable_on_exit, ((ULONG) 0)); + } + + return(accepted); +} + + +/* Measure the extent the dispatcher validates. + + The name buffer is anchored to the end of one of the module's regions and walked + backwards into it a byte at a time, so that the room left between the buffer and the + end of the region grows by one on each step. The smallest amount of room the + dispatcher accepts is the extent it validated. TEST_NOT_ACCEPTED means it accepted + nothing within the region. */ + +#define TEST_NOT_ACCEPTED ((ULONG) 0xFFFFFFFF) + +static ULONG test_measure_validated_extent(TXM_MODULE_INSTANCE *module_instance, UCHAR *region_start, UINT name_length) +{ + +ULONG room; +ULONG walk_limit; +ULONG extent; +UCHAR *region_end_plus_one; + + + region_end_plus_one = region_start + TEST_REGION_BYTES; + + /* Walking past the length plus its terminator is enough to establish that nothing + validates more than that. */ + walk_limit = ((ULONG) name_length) + ((ULONG) 4); + + extent = TEST_NOT_ACCEPTED; + + for (room = ((ULONG) 0); room <= walk_limit; room++) + { + test_arena_fill(); + test_lists_clear(); + + test_lookup_extended(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, + region_end_plus_one - room, name_length, TEST_EXTRA_PARAMETERS); + + if (test_accepted() == TX_TRUE) + { + extent = room; + break; + } + } + + return(extent); +} + + +/* Measure the extent the comparison reads. + + Memory is filled with a filler character and the only terminator in it is planted at a + chosen depth from the name buffer. The object the module asks for is named with + exactly the bytes up to that terminator, so the lookup returns it if and only if the + comparison read that deep. Scanning the depth and keeping the deepest one that comes + back is therefore the read extent, measured rather than inferred. + + TEST_NOT_READ means no depth came back, which is what a refused request looks like. */ + +#define TEST_NOT_READ ((ULONG) 0xFFFFFFFF) + +static ULONG test_measure_read_depth(TXM_MODULE_INSTANCE *module_instance, UCHAR *region_start, + ULONG room, UINT name_length, ULONG max_depth) +{ + +ULONG depth; +ULONG deepest; +UCHAR *name; + + + deepest = TEST_NOT_READ; + + for (depth = ((ULONG) 0); depth <= max_depth; depth++) + { + test_arena_fill(); + + name = (region_start + TEST_REGION_BYTES) - room; + name[depth] = ((UCHAR) 0); + + memcpy((VOID *) test_probe_name, (VOID *) name, (size_t) depth); + test_probe_name[depth] = ((CHAR) 0); + + test_lists_clear(); + test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore, test_probe_name); + + test_lookup_extended(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, name_length, TEST_EXTRA_PARAMETERS); + + if ((test_reached_lookup != ((ULONG) 0)) && + (test_return_value == ((ALIGN_TYPE) TX_SUCCESS)) && + (test_result == (VOID *) &test_semaphore)) + { + deepest = depth; + } + } + + return(deepest); +} + + +/* Measure how far the comparison walks when the object's name is the module's own search + buffer. + + A create service stores the name pointer a module hands it rather than copying the + string, so a module can register an object whose name is the buffer it then searches + for. Both sides of the comparison are then the same address and every character + matches whatever it is, so the walk is bounded by nothing the module knows or supplied: + it runs to the first terminator in memory. This returns the deepest depth at which a + terminator is still reached, which is that walk length. */ +static ULONG test_measure_alias_walk(TXM_MODULE_INSTANCE *module_instance, UCHAR *region_start, + ULONG room, UINT name_length, ULONG max_depth, UINT use_deprecated) +{ + +ULONG depth; +ULONG deepest; +UCHAR *name; + + + deepest = TEST_NOT_READ; + + for (depth = ((ULONG) 0); depth <= max_depth; depth++) + { + test_arena_fill(); + + name = (region_start + TEST_REGION_BYTES) - room; + name[depth] = ((UCHAR) 0); + + test_lists_clear(); + test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore, (CHAR *) name); + + if (use_deprecated == TX_TRUE) + { + test_lookup_deprecated(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name); + } + else + { + test_lookup_extended(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, name_length, TEST_EXTRA_PARAMETERS); + } + + if ((test_reached_lookup != ((ULONG) 0)) && + (test_return_value == ((ALIGN_TYPE) TX_SUCCESS)) && + (test_result == (VOID *) &test_semaphore)) + { + deepest = depth; + } + } + + return(deepest); +} + + +/* The declared lengths the cases run through: none, one character, and lengths either + side of the room a case can leave in a region. */ + +static const UINT test_name_lengths[] = +{ + ((UINT) 0), + ((UINT) 1), + ((UINT) 2), + ((UINT) 3), + ((UINT) 7), + ((UINT) 16), + ((UINT) 31) +}; + +#define TEST_NAME_LENGTH_COUNT (sizeof(test_name_lengths) / sizeof(test_name_lengths[0])) + + +/* The three regions a name buffer may legitimately be in. A search name is read, never + written, so all three are permitted -- a module searching for a literal searches for + one out of its own read-only image. */ + +#define TEST_REGION_COUNT ((UINT) 3) + +static const char *test_region_names[TEST_REGION_COUNT] = +{ + "the module's data", + "a shared region", + "the module's read-only image" +}; + +static UCHAR *test_region_start(UINT region) +{ + +UCHAR *start; + + + if (region == ((UINT) 0)) + { + start = TEST_DATA_START; + } + else if (region == ((UINT) 1)) + { + start = TEST_SHARED_START; + } + else + { + start = TEST_CODE_START; + } + + return(start); +} + + +/* The eight object types the lookup knows, and one it does not. */ + +#define TEST_OBJECT_TYPE_COUNT ((UINT) 8) + +static const ALIGN_TYPE test_object_types[TEST_OBJECT_TYPE_COUNT] = +{ + (ALIGN_TYPE) TXM_THREAD_OBJECT, + (ALIGN_TYPE) TXM_TIMER_OBJECT, + (ALIGN_TYPE) TXM_QUEUE_OBJECT, + (ALIGN_TYPE) TXM_EVENT_FLAGS_OBJECT, + (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, + (ALIGN_TYPE) TXM_MUTEX_OBJECT, + (ALIGN_TYPE) TXM_BLOCK_POOL_OBJECT, + (ALIGN_TYPE) TXM_BYTE_POOL_OBJECT +}; + +static const char *test_object_type_names[TEST_OBJECT_TYPE_COUNT] = +{ + "thread", + "timer", + "queue", + "event flags group", + "semaphore", + "mutex", + "block pool", + "byte pool" +}; + +#define TEST_INVALID_OBJECT_TYPE ((ALIGN_TYPE) 0x7EU) + + +/* One of the two control blocks held for each type, in the order the type table lists + them. */ +static VOID *test_object_block(UINT type_index, UINT which) +{ + +VOID *object_ptr; + + + switch (type_index) + { + case ((UINT) 0): object_ptr = (VOID *) &test_thread_objects[which]; break; + case ((UINT) 1): object_ptr = (VOID *) &test_timer_objects[which]; break; + case ((UINT) 2): object_ptr = (VOID *) &test_queue_objects[which]; break; + case ((UINT) 3): object_ptr = (VOID *) &test_event_flags_objects[which]; break; + case ((UINT) 4): object_ptr = (VOID *) &test_semaphore_objects[which]; break; + case ((UINT) 5): object_ptr = (VOID *) &test_mutex_objects[which]; break; + case ((UINT) 6): object_ptr = (VOID *) &test_block_pool_objects[which]; break; + default: object_ptr = (VOID *) &test_byte_pool_objects[which]; break; + } + + return(object_ptr); +} + + +int main(void) +{ + +UINT length_index; +UINT region; +UINT type_index; +UINT name_length; +UCHAR *region_start; +UCHAR *name; +ULONG room; +ULONG depth; +ULONG expected_extent; +char description[192]; + + + memset((VOID *) &test_current_thread, 0, sizeof(TX_THREAD)); + test_current_thread.tx_thread_id = TX_THREAD_ID; + + test_module_setup(&test_protected_module, (ULONG) TXM_MODULE_MEMORY_PROTECTION); + test_module_setup(&test_unprotected_module, ((ULONG) 0)); + + printf("Module Manager object name range validation test\n"); + + /**********************************************************************/ + /* The extent the extended dispatcher validates. */ + /**********************************************************************/ + + /* A declared length excludes the terminator and the comparison reads it, so the range + the module has to have made readable is the length plus one byte, in whichever of + the three regions a search name may live in. */ + + for (length_index = ((UINT) 0); length_index < ((UINT) TEST_NAME_LENGTH_COUNT); length_index++) + { + name_length = test_name_lengths[length_index]; + expected_extent = ((ULONG) name_length) + ((ULONG) 1); + + for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++) + { + snprintf(description, sizeof(description), + "a declared length of %u is checked over %lu bytes of a name in %s", + name_length, (unsigned long) expected_extent, test_region_names[region]); + test_expect(description, + test_measure_validated_extent(&test_protected_module, test_region_start(region), name_length), + expected_extent); + } + } + + /**********************************************************************/ + /* The extent the comparison reads, against the extent validated. */ + /**********************************************************************/ + + /* The two dials are the declared length and the object's name, and the finding is the + relationship between them: the comparison reads through the declared length, so a + request must not be accepted unless that whole range is inside the module. For every + length and every amount of room, the request is accepted exactly when the room + covers the length and its terminator, and when it is accepted the deepest byte the + comparison can be made to read is the last byte of the declared name -- which the + room then guarantees is inside the module. */ + + for (length_index = ((UINT) 0); length_index < ((UINT) TEST_NAME_LENGTH_COUNT); length_index++) + { + name_length = test_name_lengths[length_index]; + + for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++) + { + region_start = test_region_start(region); + + for (room = ((ULONG) 1); room <= (((ULONG) name_length) + ((ULONG) 2)); room++) + { + depth = test_measure_read_depth(&test_protected_module, region_start, room, + name_length, ((ULONG) name_length) + ((ULONG) 2)); + + if (room >= (((ULONG) name_length) + ((ULONG) 1))) + { + snprintf(description, sizeof(description), + "a %u-character name with %lu bytes of room in %s is read to its declared end", + name_length, (unsigned long) room, test_region_names[region]); + test_expect(description, depth, (ULONG) name_length); + + snprintf(description, sizeof(description), + "...and every byte it read was inside the module (%u characters, %lu bytes of room)", + name_length, (unsigned long) room); + test_expect(description, (ULONG) (depth < room), (ULONG) TX_TRUE); + } + else + { + snprintf(description, sizeof(description), + "a %u-character name with only %lu bytes of room in %s is not read at all", + name_length, (unsigned long) room, test_region_names[region]); + test_expect(description, depth, TEST_NOT_READ); + } + } + } + } + + /**********************************************************************/ + /* The name a module registered for an object can be the name it */ + /* searches for. */ + /**********************************************************************/ + + /* A create service stores the name pointer rather than the string, so a module can + give an object the address of the buffer it then searches for. Both sides of the + comparison are the same address, so it matches whatever is there and the walk is + bounded by nothing the module supplied. That is the case the one-byte check could + not survive, and it is why the length has to bound the buffer rather than the + comparison having to discover the end of it. + + With the range checked, the walk cannot start: the only placements accepted are the + ones whose whole declared range is inside the module, and there the walk ends inside + it too. */ + + for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++) + { + region_start = test_region_start(region); + + snprintf(description, sizeof(description), + "an aliased name with one byte of room in %s reaches no depth at all", test_region_names[region]); + test_expect(description, + test_measure_alias_walk(&test_protected_module, region_start, ((ULONG) 1), + ((UINT) 31), ((ULONG) 31), TX_FALSE), + TEST_NOT_READ); + + snprintf(description, sizeof(description), + "an aliased name with room for its whole declared range in %s walks no further than that range", + test_region_names[region]); + test_expect(description, + test_measure_alias_walk(&test_protected_module, region_start, ((ULONG) 32), + ((UINT) 31), ((ULONG) 63), TX_FALSE), + ((ULONG) 31)); + } + + /**********************************************************************/ + /* The deprecated service, which carries no length. */ + /**********************************************************************/ + + /* Its manager wrapper searches with the largest length a UINT can hold, so there is no + range to check and none can be derived from a pointer. A memory-protected module is + refused it; the extended service is what such a module uses. */ + + for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++) + { + region_start = test_region_start(region); + + for (room = ((ULONG) 1); room <= ((ULONG) 64); room++) + { + test_arena_fill(); + test_lists_clear(); + + test_lookup_deprecated(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, + (region_start + TEST_REGION_BYTES) - room); + + if (room == ((ULONG) 1)) + { + snprintf(description, sizeof(description), + "a memory-protected module's lookup without a length in %s is refused", + test_region_names[region]); + test_expect(description, (ULONG) test_accepted(), (ULONG) TX_FALSE); + } + else + { + test_expect("a memory-protected module's lookup without a length is refused at every placement", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + } + } + + snprintf(description, sizeof(description), + "...including an aliased name in %s, which is the walk it cannot bound", test_region_names[region]); + test_expect(description, + test_measure_alias_walk(&test_protected_module, region_start, ((ULONG) 1), + ((UINT) 0), ((ULONG) 63), TX_TRUE), + TEST_NOT_READ); + } + + /* A module without memory protection still has it, and there it shows what the absent + length means: with the object's name aliased to the search buffer the walk runs to + whatever terminator memory happens to hold, however far away, because no length + stops it. The extended service on the same module stops exactly where the module + said to. */ + + test_expect("an unprotected module's lookup without a length walks to any depth memory puts a terminator at", + test_measure_alias_walk(&test_unprotected_module, TEST_DATA_START, ((ULONG) 1), + ((UINT) 0), ((ULONG) 100), TX_TRUE), + ((ULONG) 100)); + + test_expect("an unprotected module's extended lookup walks exactly as far as the length it declared", + test_measure_alias_walk(&test_unprotected_module, TEST_DATA_START, ((ULONG) 1), + ((UINT) 40), ((ULONG) 100), TX_FALSE), + ((ULONG) 40)); + + /**********************************************************************/ + /* A null name, and a null result slot. */ + /**********************************************************************/ + + /* The range check accepts a null pointer outright, as the one-byte check it replaces + did and as the buffer checks do, because passing null is how a module declines an + optional argument. Neither of these is optional, and it is the lookup that refuses + them, before it dereferences either. These cases assert that sequence rather than + the macro, so that neither side can change quietly. */ + + test_arena_fill(); + test_lists_clear(); + + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TX_NULL, ((UINT) 31), TEST_EXTRA_PARAMETERS); + test_expect("the extended dispatcher passes a null name through", (ULONG) test_return_value, (ULONG) TX_PTR_ERROR); + test_expect("...and the lookup refuses it before dereferencing it", test_reached_lookup, ((ULONG) 0)); + + test_arena_fill(); + test_lists_clear(); + TEST_EXTRA_PARAMETERS[0] = ((ALIGN_TYPE) 31); + TEST_EXTRA_PARAMETERS[1] = ((ALIGN_TYPE) 0); + test_return_value = _txm_module_manager_txm_module_object_pointer_get_extended_dispatch(&test_protected_module, + (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, + (ALIGN_TYPE) TEST_DATA_START, + TEST_EXTRA_PARAMETERS); + test_expect("the extended dispatcher passes a null result slot through", (ULONG) test_return_value, (ULONG) TX_PTR_ERROR); + + /* The deprecated service refuses a memory-protected module before it gets that far, + which is a change from the one-byte check and is recorded here as one. */ + + test_arena_fill(); + test_lists_clear(); + test_lookup_deprecated(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TX_NULL); + test_expect("a memory-protected module's null name is refused by the deprecated dispatcher", + (ULONG) test_return_value, (ULONG) TXM_MODULE_INVALID_MEMORY); + + test_arena_fill(); + test_lists_clear(); + test_lookup_deprecated(&test_unprotected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TX_NULL); + test_expect("an unprotected module's null name still reaches the lookup", (ULONG) test_return_value, (ULONG) TX_PTR_ERROR); + + /**********************************************************************/ + /* The extra parameter array itself. */ + /**********************************************************************/ + + /* The length is read out of that array, so checking the name's range depends on the + array having been checked first. Reordering the two checks is what the fix required, + and a request that fails both still fails the same way. */ + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), (ALIGN_TYPE *) TEST_CODE_START); + test_expect("an extra parameter array in the module's read-only image is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, + (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 1), + ((UINT) 31), (ALIGN_TYPE *) TEST_CODE_START); + test_expect("an unreadable name and an unreadable array are refused the same way", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), (ALIGN_TYPE *) ((TEST_DATA_START + TEST_REGION_BYTES) - sizeof(ALIGN_TYPE))); + test_expect("an extra parameter array with room for one word of two is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + /* A shared region registered to the module is writable by it, so an array there is + as good as one in its own data, and so is a result slot. */ + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), (ALIGN_TYPE *) TEST_SHARED_START, (VOID **) TEST_SHARED_START); + test_expect("an extra parameter array and result slot in a shared region are accepted", + (ULONG) test_accepted(), (ULONG) TX_TRUE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), (ALIGN_TYPE *) ((TEST_SHARED_START + TEST_REGION_BYTES) - sizeof(ALIGN_TYPE))); + test_expect("an extra parameter array running off the end of a shared region is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), (ALIGN_TYPE *) &test_arena.test_arena_bytes[0]); + test_expect("an extra parameter array below the module's data is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended_raw(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + (ALIGN_TYPE *) (~((ALIGN_TYPE) 0) - ((ALIGN_TYPE) 3))); + test_expect("an extra parameter array whose two words wrap the address space is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + /* The module's read-only image is not, and a result slot the module cannot write is + refused whatever else is right about the request. */ + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), TEST_EXTRA_PARAMETERS, (VOID **) TEST_CODE_START); + test_expect("a result slot in the module's read-only image is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), TEST_EXTRA_PARAMETERS, (VOID **) test_probe_name); + test_expect("a result slot outside every region the module owns is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 1), TEST_EXTRA_PARAMETERS, (VOID **) (~((ALIGN_TYPE) 0) - ((ALIGN_TYPE) 1))); + test_expect("a result slot whose word wraps the address space is refused", + (ULONG) test_accepted(), (ULONG) TX_FALSE); + + /**********************************************************************/ + /* Lengths that cannot describe a range. */ + /**********************************************************************/ + + /* The length plus its terminator has to be expressible, and the range has to fit + under the top of memory. Neither is a range a module can be given. */ + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + (UINT) TXM_MODULE_MANAGER_UTIL_MAX_VALUE_OF_TYPE_UNSIGNED(UINT), TEST_EXTRA_PARAMETERS); + test_expect("the largest length a UINT can hold is refused", (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) TXM_MODULE_MANAGER_UTIL_MAX_VALUE_OF_TYPE_UNSIGNED(UINT)) - ((UINT) 1), TEST_EXTRA_PARAMETERS); + test_expect("a length one below it is refused too", (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, + (UCHAR *) (~((ALIGN_TYPE) 0) - ((ALIGN_TYPE) 3)), ((UINT) 31), TEST_EXTRA_PARAMETERS); + test_expect("a name whose range wraps the address space is refused", (ULONG) test_accepted(), (ULONG) TX_FALSE); + + /**********************************************************************/ + /* The range check is the dispatcher's, not the object type's. */ + /**********************************************************************/ + + /* It runs before the lookup and does not depend on what is being looked up, so the + boundary is in the same place for all eight types the lookup knows and for one it + does not. What the lookup then does with the type is its own business, and the two + pool types it refuses a memory-protected module outright. */ + + for (type_index = ((UINT) 0); type_index < TEST_OBJECT_TYPE_COUNT; type_index++) + { + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, test_object_types[type_index], + (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 4), ((UINT) 4), TEST_EXTRA_PARAMETERS); + + snprintf(description, sizeof(description), "a %s name one byte short of its range is refused", + test_object_type_names[type_index]); + test_expect(description, (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, test_object_types[type_index], + (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 5), ((UINT) 4), TEST_EXTRA_PARAMETERS); + + snprintf(description, sizeof(description), "a %s name with its whole range is accepted", + test_object_type_names[type_index]); + test_expect(description, (ULONG) test_accepted(), (ULONG) TX_TRUE); + + if ((test_object_types[type_index] == ((ALIGN_TYPE) TXM_BLOCK_POOL_OBJECT)) || + (test_object_types[type_index] == ((ALIGN_TYPE) TXM_BYTE_POOL_OBJECT))) + { + snprintf(description, sizeof(description), "...and the lookup refuses a memory-protected module a %s", + test_object_type_names[type_index]); + test_expect(description, (ULONG) test_return_value, (ULONG) TXM_MODULE_INVALID); + } + else + { + snprintf(description, sizeof(description), "...and the lookup searches for the %s and finds none", + test_object_type_names[type_index]); + test_expect(description, (ULONG) test_return_value, (ULONG) TX_NO_INSTANCE); + } + } + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, TEST_INVALID_OBJECT_TYPE, + (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 4), ((UINT) 4), TEST_EXTRA_PARAMETERS); + test_expect("an unknown object type is refused for its range first", (ULONG) test_accepted(), (ULONG) TX_FALSE); + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_protected_module, TEST_INVALID_OBJECT_TYPE, + (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 5), ((UINT) 4), TEST_EXTRA_PARAMETERS); + test_expect("...and once its range is right, by the lookup for being unknown", + (ULONG) test_return_value, (ULONG) TX_OPTION_ERROR); + + /**********************************************************************/ + /* Every created list the lookup walks. */ + /**********************************************************************/ + + /* The comparison the range check protects runs once per object on the list for the + requested type, and there are eight such lists. These cases put two objects on each + of them and search for the second, so that every list is walked, every comparison + is reached, and the name pointer the lookup dereferences out of each kind of control + block is the one a create service stored there. + + The two pool types are refused to a memory-protected module by the lookup itself, so + these run as the module without protection, which is the caller that can reach them. */ + + for (type_index = ((UINT) 0); type_index < TEST_OBJECT_TYPE_COUNT; type_index++) + { + test_arena_fill(); + test_lists_clear(); + + name = (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 8); + memcpy((VOID *) name, "target", (size_t) 7); + memcpy((VOID *) test_probe_name, "target", (size_t) 7); + + test_object_create((UINT) test_object_types[type_index], test_object_block(type_index, ((UINT) 0)), "other"); + test_object_create((UINT) test_object_types[type_index], test_object_block(type_index, ((UINT) 1)), "another"); + test_object_create((UINT) test_object_types[type_index], test_object_block(type_index, ((UINT) 2)), test_probe_name); + + test_lookup_extended(&test_unprotected_module, test_object_types[type_index], name, ((UINT) 6), TEST_EXTRA_PARAMETERS); + + snprintf(description, sizeof(description), "the %s list is walked past its earlier members to the match", + test_object_type_names[type_index]); + test_expect(description, (ULONG) test_return_value, (ULONG) TX_SUCCESS); + + snprintf(description, sizeof(description), "...and the %s that comes back is the one whose name matched", + test_object_type_names[type_index]); + test_expect(description, + (ULONG) (test_result == test_object_block(type_index, ((UINT) 2))), (ULONG) TX_TRUE); + + /* A name on no list walks the whole of it and comes back empty, which is the + traversal running to the created count rather than to a match. */ + + memcpy((VOID *) name, "absent", (size_t) 7); + + test_lookup_extended(&test_unprotected_module, test_object_types[type_index], name, ((UINT) 6), TEST_EXTRA_PARAMETERS); + + snprintf(description, sizeof(description), "a name on no %s finds none of them", test_object_type_names[type_index]); + test_expect(description, (ULONG) test_return_value, (ULONG) TX_NO_INSTANCE); + } + + /* The two pool lists are also reachable from application code, which is not a module + at all. That caller does not come through the dispatch layer, so these two cases + call the lookup the dispatchers call. */ + + test_arena_fill(); + test_lists_clear(); + memcpy((VOID *) test_probe_name, "target", (size_t) 7); + test_object_create((UINT) TXM_BLOCK_POOL_OBJECT, (VOID *) &test_block_pool_objects[0], test_probe_name); + test_object_create((UINT) TXM_BYTE_POOL_OBJECT, (VOID *) &test_byte_pool_objects[0], test_probe_name); + test_current_thread.tx_thread_module_instance_ptr = TX_NULL; + _tx_thread_current_ptr = &test_current_thread; + + test_result = TEST_RESULT_POISON; + test_expect("a caller that is not a module may look a block pool up by name", + (ULONG) _txm_module_manager_object_pointer_get_extended((UINT) TXM_BLOCK_POOL_OBJECT, test_probe_name, + ((UINT) 6), &test_result), + (ULONG) TX_SUCCESS); + test_expect("...and gets the block pool it named", + (ULONG) (test_result == (VOID *) &test_block_pool_objects[0]), (ULONG) TX_TRUE); + + test_result = TEST_RESULT_POISON; + test_expect("a caller that is not a module may look a byte pool up by name", + (ULONG) _txm_module_manager_object_pointer_get_extended((UINT) TXM_BYTE_POOL_OBJECT, test_probe_name, + ((UINT) 6), &test_result), + (ULONG) TX_SUCCESS); + test_expect("...and gets the byte pool it named", + (ULONG) (test_result == (VOID *) &test_byte_pool_objects[0]), (ULONG) TX_TRUE); + + /* An object with no name at all is on the list like any other, and the comparison has + to refuse it without dereferencing the null. */ + + test_arena_fill(); + test_lists_clear(); + memcpy((VOID *) test_probe_name, "target", (size_t) 7); + test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore_objects[0], TX_NULL); + test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore_objects[1], test_probe_name); + + name = (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 8); + memcpy((VOID *) name, "target", (size_t) 7); + + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, ((UINT) 6), TEST_EXTRA_PARAMETERS); + test_expect("an unnamed object on the list is passed over", (ULONG) test_return_value, (ULONG) TX_SUCCESS); + test_expect("...and the named one behind it is found", + (ULONG) (test_result == (VOID *) &test_semaphore_objects[1]), (ULONG) TX_TRUE); + + /**********************************************************************/ + /* The object the module was looking for is still found. */ + /**********************************************************************/ + + /* A name that fits, matching an object whose name is a string of that length, comes + back -- and the first match on the list is the one that comes back, which is the + property the probing cases above rely on. */ + + test_arena_fill(); + test_lists_clear(); + + name = (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 8); + memcpy((VOID *) name, "object", (size_t) 7); + memcpy((VOID *) test_probe_name, "object", (size_t) 7); + + test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_decoy_semaphore, "objec"); + test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore, test_probe_name); + + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, ((UINT) 6), TEST_EXTRA_PARAMETERS); + test_expect("a name that fits finds the object it names", (ULONG) test_return_value, (ULONG) TX_SUCCESS); + test_expect("...and it is the matching object, not the one whose name is a prefix of it", + (ULONG) (test_result == (VOID *) &test_semaphore), (ULONG) TX_TRUE); + + /* The same search declared one character short stops before the end of the name and + finds nothing, which is the comparison obeying the length rather than the string. */ + + test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, ((UINT) 5), TEST_EXTRA_PARAMETERS); + test_expect("the same search one character short finds nothing", (ULONG) test_return_value, (ULONG) TX_NO_INSTANCE); + test_expect("...and leaves the module's result slot null", (ULONG) (test_result == TX_NULL), (ULONG) TX_TRUE); + + /**********************************************************************/ + /* A module without memory protection is unaffected. */ + /**********************************************************************/ + + /* Every check the fix touches is inside the dispatchers' memory protection block. A + module loaded without protection reaches the lookup with no range check of any kind, + before the fix and after it, and these cases are here so that a later change cannot + quietly start refusing its requests. */ + + for (length_index = ((UINT) 0); length_index < ((UINT) TEST_NAME_LENGTH_COUNT); length_index++) + { + name_length = test_name_lengths[length_index]; + + snprintf(description, sizeof(description), + "an unprotected module's %u-character name is accepted with one byte of room", name_length); + test_expect(description, + test_measure_validated_extent(&test_unprotected_module, TEST_DATA_START, name_length), + ((ULONG) 0)); + + snprintf(description, sizeof(description), + "an unprotected module's %u-character name is read to its declared end wherever it sits", name_length); + test_expect(description, + test_measure_read_depth(&test_unprotected_module, TEST_DATA_START, ((ULONG) 1), + name_length, ((ULONG) name_length) + ((ULONG) 2)), + (ULONG) name_length); + } + + test_arena_fill(); + test_lists_clear(); + test_lookup_extended(&test_unprotected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START, + ((UINT) 31), (ALIGN_TYPE *) test_probe_name); + test_expect("an unprotected module's extra parameter array is accepted outside every region it owns", + (ULONG) test_accepted(), (ULONG) TX_TRUE); + + /**********************************************************************/ + /* What the whole run has to have held. */ + /**********************************************************************/ + + /* The lookup raises the preemption hold and lowers it again around every search, and + the port shim counts the interrupt lock it takes to do so. A search that returned + without giving either back is a fault path this test cannot take, and one that never + took them is a search that did not happen. */ + + test_expect("the interrupt lock is balanced", (ULONG) test_interrupt_disable_depth, ((ULONG) 0)); + test_expect("the interrupt lock never underflowed", (ULONG) test_interrupt_restore_underflows, ((ULONG) 0)); + test_expect("the interrupt lock was never nested", (ULONG) test_interrupt_disable_max_depth, ((ULONG) 1)); + test_expect("the preemption hold is balanced", (ULONG) _tx_thread_preempt_disable, ((ULONG) 0)); + + test_expect("every case ran", (ULONG) (test_checks > ((ULONG) 1000)), (ULONG) TX_TRUE); + + if (test_failures == 0U) + { + printf("SUCCESS! %lu expectations\n", (unsigned long) test_checks); + return(0); + } + + printf("ERROR: %u expectation(s) failed of %lu\n", test_failures, (unsigned long) test_checks); + return(1); +}