/*************************************************************************** * 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 queue message range validation */ /** */ /**************************************************************************/ /**************************************************************************/ /* A memory-protected module's queue request arrives at the Module Manager's dispatch layer, which decides how much of the module's buffer the privileged queue copy is allowed to touch and hands that extent to the port's range check. A queue's tx_queue_message_size is a count of ULONGs, and the copy moves that many words, so the extent is message_size * sizeof(ULONG) bytes. Front-send passed the word count itself, which is a quarter of that on every target ThreadX supports, so a buffer ending message_size bytes into the module's permitted memory was accepted and then read message_size * sizeof(ULONG) bytes -- 3 * message_size bytes past the end of what the module may reach. Send and receive have always multiplied. This test drives the three queue dispatchers directly and measures the extent each one validates, rather than checking a handful of cases either side of it. For a given message size it walks the number of bytes left between the buffer and the end of the module's region and finds the smallest number the dispatcher accepts. That number is the extent, and it must be message_size * sizeof(ULONG) for all three services and for every region a queue buffer may legitimately live in. Two things make a dispatcher testable on the host at all. The dispatch table is a header of static functions, one per kernel service, each wrapped in #ifndef TXM__CALL_NOT_USED so that an integrator can refuse a service to modules. There are 96 of those guards. Defining the 93 this test does not exercise compiles the header down to the three queue services under test, which is what keeps the link to a handful of stubs instead of the hundred-odd kernel entry points the whole table reaches. It also exercises that conditional compilation, which nothing else in the tree does. The extent has to reach a check that honours it. The manager's portable checks do, and so does the inline data check of the Cortex-M module ports, which is the shape the memory-protected module ports share: an address range is inside the module's data, or inside a shared memory region registered to it, or it is not accepted. This test therefore compiles against a Cortex-M module port's headers. The Cortex-A7 port's data check is a function that takes the pointer alone and translates one address, so on that port no extent -- right or wrong -- reaches anything, and a test built on it would pass equally before and after this fix. That check is being corrected separately; the defect this test covers is in the portable dispatch layer and was live on every other memory-protected module port. The three regions the cases below use are the three a queue buffer can be in. A read source may be in the module's writable data, in a shared region, or in the module's read-only image, since a module sending a constant message sends it out of its own .rodata. A receive destination may not be in the read-only image, and the case that asks for that must be refused however much room is left. */ #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 three 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_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_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 #define TXM_MODULE_OBJECT_POINTER_GET_CALL_NOT_USED #define TXM_MODULE_OBJECT_POINTER_GET_EXTENDED_CALL_NOT_USED #include "txm_module_manager_dispatch.h" /* The three under test have to have survived that, and the rest have to have gone. */ #if defined(TXM_QUEUE_FRONT_SEND_CALL_NOT_USED) || defined(TXM_QUEUE_SEND_CALL_NOT_USED) || defined(TXM_QUEUE_RECEIVE_CALL_NOT_USED) #error "the queue 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. The object pool plays no part in a queue request: the object check these dispatchers run asks only that the queue lie outside the module. */ TX_BYTE_POOL _txm_module_manager_object_pool; UINT _txm_module_manager_object_pool_created; /* The kernel's created lists. A created object lives on the list for its type, and the manager reads those lists, so a test that stands in for the kernel has to provide them. All stay empty here, because this test creates no kernel object; it exercises the range a queue request hands the port's buffer check. */ 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; /* Stand in for the module port's load-time alignment adjustment, which the utility source declares and no case here reaches. */ 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 queue 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) 64) 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)]) /* Define the queue the requests name. It is outside the arena, which is what the object check requires of a kernel object a module uses. */ static TX_QUEUE test_queue; /* 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 buffer 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; } /* Record what the privileged queue services were handed. Whether the service was reached at all is half of every expectation below: a refusal that still calls the service has refused nothing, and it is the service that reads or writes the caller's buffer. */ static ULONG test_service_calls; static ALIGN_TYPE test_service_queue_ptr; static ALIGN_TYPE test_service_buffer_ptr; static ULONG test_service_wait_option; /* Stand in for the three privileged queue services. They keep the two checks the real ones make before they touch the caller's buffer: a queue pointer that is null or carries the wrong ID is refused with TX_QUEUE_ERROR, and a null buffer pointer is refused with TX_PTR_ERROR. The second one matters here. The dispatch layer's buffer checks accept a null buffer pointer -- passing one is how a module asks for an optional output it does not want -- and for these three services nothing is optional, so it is the service's own null check that keeps the extent question from arising at all. A case below drives that. */ static UINT test_service_record(TX_QUEUE *queue_ptr, VOID *buffer_ptr, ULONG wait_option) { UINT status; test_service_calls++; test_service_queue_ptr = (ALIGN_TYPE) queue_ptr; test_service_buffer_ptr = (ALIGN_TYPE) buffer_ptr; test_service_wait_option = wait_option; if (queue_ptr == TX_NULL) { status = TX_QUEUE_ERROR; } else if (queue_ptr -> tx_queue_id != TX_QUEUE_ID) { status = TX_QUEUE_ERROR; } else if (buffer_ptr == TX_NULL) { status = TX_PTR_ERROR; } else { status = TX_SUCCESS; } return(status); } UINT _txe_queue_front_send(TX_QUEUE *queue_ptr, VOID *source_ptr, ULONG wait_option) { return(test_service_record(queue_ptr, source_ptr, wait_option)); } UINT _txe_queue_send(TX_QUEUE *queue_ptr, VOID *source_ptr, ULONG wait_option) { return(test_service_record(queue_ptr, source_ptr, wait_option)); } UINT _txe_queue_receive(TX_QUEUE *queue_ptr, VOID *destination_ptr, ULONG wait_option) { return(test_service_record(queue_ptr, destination_ptr, wait_option)); } /* Name the three services under test. */ #define TEST_SERVICE_FRONT_SEND ((UINT) 0) #define TEST_SERVICE_SEND ((UINT) 1) #define TEST_SERVICE_RECEIVE ((UINT) 2) #define TEST_SERVICE_COUNT ((UINT) 3) static const char *test_service_names[TEST_SERVICE_COUNT] = { "front-send", "send", "receive" }; #define TEST_WAIT_OPTION ((ALIGN_TYPE) 27) /* Make one request of one dispatcher, resetting the record of the service behind it first so that a caller can tell a refusal from an acceptance. */ static ALIGN_TYPE test_dispatch(UINT service, TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE queue_ptr, ALIGN_TYPE buffer_ptr) { ALIGN_TYPE return_value; test_service_calls = ((ULONG) 0); test_service_queue_ptr = ((ALIGN_TYPE) 0); test_service_buffer_ptr = ((ALIGN_TYPE) 0); test_service_wait_option = ((ULONG) 0); if (service == TEST_SERVICE_FRONT_SEND) { return_value = _txm_module_manager_tx_queue_front_send_dispatch(module_instance, queue_ptr, buffer_ptr, TEST_WAIT_OPTION); } else if (service == TEST_SERVICE_SEND) { return_value = _txm_module_manager_tx_queue_send_dispatch(module_instance, queue_ptr, buffer_ptr, TEST_WAIT_OPTION); } else { return_value = _txm_module_manager_tx_queue_receive_dispatch(module_instance, queue_ptr, buffer_ptr, TEST_WAIT_OPTION); } return(return_value); } /* Report whether one request was accepted, checking as it goes that acceptance and refusal are each consistent: a refused request returns TXM_MODULE_INVALID_MEMORY and does not reach the service, and an accepted one reaches it exactly once and is given the caller's own arguments. */ static UINT test_accepted(UINT service, TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE queue_ptr, ALIGN_TYPE buffer_ptr) { ALIGN_TYPE return_value; UINT accepted; return_value = test_dispatch(service, module_instance, queue_ptr, buffer_ptr); if (test_service_calls == ((ULONG) 0)) { accepted = TX_FALSE; test_expect("a refused request returns TXM_MODULE_INVALID_MEMORY", (ULONG) return_value, (ULONG) TXM_MODULE_INVALID_MEMORY); } else { accepted = TX_TRUE; test_expect("an accepted request reaches the service once", test_service_calls, ((ULONG) 1)); test_expect("...with the queue the caller named", (ULONG) test_service_queue_ptr, (ULONG) queue_ptr); test_expect("...the buffer the caller supplied", (ULONG) test_service_buffer_ptr, (ULONG) buffer_ptr); test_expect("...and the wait option the caller chose", test_service_wait_option, (ULONG) TEST_WAIT_OPTION); } return(accepted); } /* Measure the extent a dispatcher validates. The 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, which is the right answer for a service that may not use that region at all. */ #define TEST_NOT_ACCEPTED ((ULONG) 0xFFFFFFFF) static ULONG test_measure_extent(UINT service, TXM_MODULE_INSTANCE *module_instance, ULONG message_size, UCHAR *region_start) { ULONG room; ULONG walk_limit; ULONG extent; UCHAR *region_end_plus_one; test_queue.tx_queue_message_size = message_size; region_end_plus_one = region_start + TEST_REGION_BYTES; /* Walking past the extent send and receive validate is enough to establish that nothing validates more than they do. */ walk_limit = (message_size * ((ULONG) sizeof(ULONG))) + ((ULONG) sizeof(ULONG)); extent = TEST_NOT_ACCEPTED; for (room = ((ULONG) 0); room <= walk_limit; room++) { if (test_accepted(service, module_instance, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) (region_end_plus_one - room)) == TX_TRUE) { extent = room; break; } } return(extent); } /* Define a buffer outside every region the module owns, for the cases about a module that has no memory protection and is therefore range-checked nowhere. */ static ULONG test_outside_buffer[TX_QUEUE_MESSAGE_MAX_SIZE]; /* The message sizes the cases run through. One word is the smallest a queue can carry and the case where the wrong extent is smallest; the configured maximum is the case where it is largest. TX_QUEUE_MESSAGE_MAX_SIZE is what an integrator sets, so the largest case follows the build rather than a number written here. */ static const ULONG test_message_sizes[] = { ((ULONG) 1), ((ULONG) 2), ((ULONG) 3), ((ULONG) TX_QUEUE_MESSAGE_MAX_SIZE) }; #define TEST_MESSAGE_SIZE_COUNT (sizeof(test_message_sizes) / sizeof(test_message_sizes[0])) int main(void) { UINT service; UINT size_index; ULONG message_size; ULONG expected_extent; ALIGN_TYPE return_value; UCHAR *buffer_ptr; TX_QUEUE saved_queue; char description[160]; memset((VOID *) &test_arena, 0, sizeof(test_arena)); memset((VOID *) &test_queue, 0, sizeof(test_queue)); test_queue.tx_queue_id = TX_QUEUE_ID; /* Put the queue on the kernel's created list. The dispatchers authenticate the object a module names against that list before they validate anything about the buffer, so a queue that is not on it is refused before the range check this test is about is ever reached. */ test_queue.tx_queue_created_next = &test_queue; _tx_queue_created_ptr = &test_queue; _tx_queue_created_count = ((ULONG) 1); test_module_setup(&test_protected_module, (ULONG) TXM_MODULE_MEMORY_PROTECTION); test_module_setup(&test_unprotected_module, ((ULONG) 0)); printf("Module Manager queue message range validation test\n"); /**********************************************************************/ /* The extent each service validates. */ /**********************************************************************/ /* A queue's message size is a count of ULONGs and the copy moves that many words, so the extent every one of the three services must validate is the message size times the width of a word -- in the module's data, in a shared region registered to it, and, for the two services that read the buffer, in the module's read-only image. */ for (size_index = ((UINT) 0); size_index < ((UINT) TEST_MESSAGE_SIZE_COUNT); size_index++) { message_size = test_message_sizes[size_index]; expected_extent = message_size * ((ULONG) sizeof(ULONG)); for (service = ((UINT) 0); service < TEST_SERVICE_COUNT; service++) { snprintf(description, sizeof(description), "%s validates %lu * sizeof(ULONG) bytes of a buffer in the module's data", test_service_names[service], (unsigned long) message_size); test_expect(description, test_measure_extent(service, &test_protected_module, message_size, TEST_DATA_START), expected_extent); snprintf(description, sizeof(description), "%s validates %lu * sizeof(ULONG) bytes of a buffer in a shared region", test_service_names[service], (unsigned long) message_size); test_expect(description, test_measure_extent(service, &test_protected_module, message_size, TEST_SHARED_START), expected_extent); } /* A module sending a constant message sends it out of its own read-only image, which a read source may be in and a receive destination may not. */ snprintf(description, sizeof(description), "front-send validates %lu * sizeof(ULONG) bytes of a source in the module's read-only image", (unsigned long) message_size); test_expect(description, test_measure_extent(TEST_SERVICE_FRONT_SEND, &test_protected_module, message_size, TEST_CODE_START), expected_extent); snprintf(description, sizeof(description), "send validates %lu * sizeof(ULONG) bytes of a source in the module's read-only image", (unsigned long) message_size); test_expect(description, test_measure_extent(TEST_SERVICE_SEND, &test_protected_module, message_size, TEST_CODE_START), expected_extent); snprintf(description, sizeof(description), "receive refuses a destination in the module's read-only image at any extent (%lu words)", (unsigned long) message_size); test_expect(description, test_measure_extent(TEST_SERVICE_RECEIVE, &test_protected_module, message_size, TEST_CODE_START), TEST_NOT_ACCEPTED); } /**********************************************************************/ /* The word count is not the byte count. */ /**********************************************************************/ /* The same statement as the measurements above, made once at the anchor point the defect turned on: a buffer with exactly as many bytes left as the message has words. Front-send accepted that and then read four times as far. */ for (size_index = ((UINT) 0); size_index < ((UINT) TEST_MESSAGE_SIZE_COUNT); size_index++) { message_size = test_message_sizes[size_index]; test_queue.tx_queue_message_size = message_size; for (service = ((UINT) 0); service < TEST_SERVICE_COUNT; service++) { buffer_ptr = (TEST_DATA_START + TEST_REGION_BYTES) - message_size; snprintf(description, sizeof(description), "%s refuses a %lu-word message given room for the word count in bytes", test_service_names[service], (unsigned long) message_size); test_expect(description, (ULONG) test_accepted(service, &test_protected_module, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) buffer_ptr), (ULONG) TX_FALSE); buffer_ptr = (TEST_DATA_START + TEST_REGION_BYTES) - ((message_size * ((ULONG) sizeof(ULONG))) - ((ULONG) 1)); snprintf(description, sizeof(description), "%s refuses a buffer one byte short of the whole message", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_protected_module, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) buffer_ptr), (ULONG) TX_FALSE); buffer_ptr = (TEST_DATA_START + TEST_REGION_BYTES) - (message_size * ((ULONG) sizeof(ULONG))); snprintf(description, sizeof(description), "%s accepts a buffer that reaches exactly the end of the region", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_protected_module, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) buffer_ptr), (ULONG) TX_TRUE); } } /**********************************************************************/ /* A refusal changes nothing. */ /**********************************************************************/ test_queue.tx_queue_message_size = (ULONG) TX_QUEUE_MESSAGE_MAX_SIZE; memcpy((VOID *) &saved_queue, (VOID *) &test_queue, sizeof(TX_QUEUE)); buffer_ptr = (TEST_DATA_START + TEST_REGION_BYTES) - test_queue.tx_queue_message_size; for (service = ((UINT) 0); service < TEST_SERVICE_COUNT; service++) { return_value = test_dispatch(service, &test_protected_module, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) buffer_ptr); snprintf(description, sizeof(description), "%s refuses with TXM_MODULE_INVALID_MEMORY", test_service_names[service]); test_expect(description, (ULONG) return_value, (ULONG) TXM_MODULE_INVALID_MEMORY); snprintf(description, sizeof(description), "...%s does not reach the queue service", test_service_names[service]); test_expect(description, test_service_calls, ((ULONG) 0)); snprintf(description, sizeof(description), "...and %s leaves the queue control block untouched", test_service_names[service]); test_expect(description, (ULONG) (memcmp((VOID *) &saved_queue, (VOID *) &test_queue, sizeof(TX_QUEUE)) == 0), (ULONG) TX_TRUE); } /**********************************************************************/ /* The queue itself still has to be a kernel object outside the module.*/ /**********************************************************************/ /* The message size is read out of the queue, so the extent check depends on the object check having run first. A buffer with all the room in the world does not excuse a queue pointer aimed into the module or at nothing at all. */ buffer_ptr = TEST_DATA_START; for (service = ((UINT) 0); service < TEST_SERVICE_COUNT; service++) { snprintf(description, sizeof(description), "%s refuses a queue inside the module's data", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_protected_module, (ALIGN_TYPE) TEST_DATA_START, (ALIGN_TYPE) buffer_ptr), (ULONG) TX_FALSE); snprintf(description, sizeof(description), "%s refuses a queue inside the module's read-only image", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_protected_module, (ALIGN_TYPE) TEST_CODE_START, (ALIGN_TYPE) buffer_ptr), (ULONG) TX_FALSE); snprintf(description, sizeof(description), "%s refuses a null queue", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_protected_module, ((ALIGN_TYPE) 0), (ALIGN_TYPE) buffer_ptr), (ULONG) TX_FALSE); } /**********************************************************************/ /* The null buffer the dispatch layer lets through. */ /**********************************************************************/ /* The dispatch layer's buffer checks accept a null pointer whatever extent they are given, so correcting the extent cannot be what refuses one. For these three services the buffer is not optional and the service refuses it itself, which is why the extent question never arises for a null buffer. */ test_queue.tx_queue_message_size = (ULONG) TX_QUEUE_MESSAGE_MAX_SIZE; for (service = ((UINT) 0); service < TEST_SERVICE_COUNT; service++) { return_value = test_dispatch(service, &test_protected_module, (ALIGN_TYPE) &test_queue, ((ALIGN_TYPE) 0)); snprintf(description, sizeof(description), "%s passes a null buffer to the queue service", test_service_names[service]); test_expect(description, test_service_calls, ((ULONG) 1)); snprintf(description, sizeof(description), "...and the %s service refuses it with TX_PTR_ERROR", test_service_names[service]); test_expect(description, (ULONG) return_value, (ULONG) TX_PTR_ERROR); } /**********************************************************************/ /* A buffer whose extent runs off the end of the address space. */ /**********************************************************************/ /* The manager's checks test for that before comparing anything, so a module cannot reach past the top of memory by wrapping the sum. */ test_queue.tx_queue_message_size = (ULONG) TX_QUEUE_MESSAGE_MAX_SIZE; for (service = ((UINT) 0); service < TEST_SERVICE_COUNT; service++) { snprintf(description, sizeof(description), "%s refuses a buffer whose extent wraps the address space", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_protected_module, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) (~((ALIGN_TYPE) 0) - ((ALIGN_TYPE) 3))), (ULONG) TX_FALSE); } /**********************************************************************/ /* A module without memory protection is unaffected. */ /**********************************************************************/ /* Every check the fix touches sits inside the dispatchers' memory-protection block. A module that was loaded without protection reaches the kernel service with no range check of any kind, before the fix and after it, and these cases are here so that a later change to the extent cannot quietly start refusing its requests. */ for (size_index = ((UINT) 0); size_index < ((UINT) TEST_MESSAGE_SIZE_COUNT); size_index++) { message_size = test_message_sizes[size_index]; test_queue.tx_queue_message_size = message_size; for (service = ((UINT) 0); service < TEST_SERVICE_COUNT; service++) { buffer_ptr = (TEST_DATA_START + TEST_REGION_BYTES) - message_size; snprintf(description, sizeof(description), "an unprotected module's %lu-word %s is accepted with room for the word count in bytes", (unsigned long) message_size, test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_unprotected_module, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) buffer_ptr), (ULONG) TX_TRUE); snprintf(description, sizeof(description), "an unprotected module's %s is accepted with a buffer outside every region it owns", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_unprotected_module, (ALIGN_TYPE) &test_queue, (ALIGN_TYPE) test_outside_buffer), (ULONG) TX_TRUE); snprintf(description, sizeof(description), "an unprotected module's %s is accepted with a queue inside its own data", test_service_names[service]); test_expect(description, (ULONG) test_accepted(service, &test_unprotected_module, (ALIGN_TYPE) TEST_DATA_START, (ALIGN_TYPE) test_outside_buffer), (ULONG) TX_TRUE); /* Nothing screens an unprotected module's arguments before the service, so the service is where a null queue is refused. It is the same for a null buffer, and for a protected module the object check is what refuses the queue first -- which is why these two cases are here and not above. */ return_value = test_dispatch(service, &test_unprotected_module, ((ALIGN_TYPE) 0), (ALIGN_TYPE) test_outside_buffer); snprintf(description, sizeof(description), "an unprotected module's %s reaches the service with a null queue", test_service_names[service]); test_expect(description, test_service_calls, ((ULONG) 1)); snprintf(description, sizeof(description), "...and the %s service refuses it with TX_QUEUE_ERROR", test_service_names[service]); test_expect(description, (ULONG) return_value, (ULONG) TX_QUEUE_ERROR); } } /**********************************************************************/ /* What the whole run has to have held. */ /**********************************************************************/ /* Nothing on these paths disables interrupts, and the shim counts them, so the lock has to have been left exactly as it was found. */ test_expect("the interrupt lock is balanced", test_interrupt_disable_depth, ((ULONG) 0)); test_expect("the interrupt lock never underflowed", (ULONG) test_interrupt_restore_underflows, ((ULONG) 0)); test_expect("every case ran", (ULONG) (test_checks > ((ULONG) 500)), (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); }