1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
|
/***************************************************************************
* 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_<SERVICE>_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 <stdio.h>
#include <string.h>
#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);
}
|