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
|
/***************************************************************************
* 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 allocation size arithmetic */
/** */
/**************************************************************************/
/**************************************************************************/
/* A module asks the Module Manager for object memory and chooses the size. The
manager adds its own private header to that size, and the header it writes is
larger than the smallest allocation the byte pool will hand out, so the
addition has to be checked before the pool is asked for anything.
This test drives _txm_module_manager_object_allocate on the host with the
Cortex-A7 module port's control blocks. The byte pool behind it is modelled
rather than run: the model applies exactly the two bounds _txe_byte_allocate
applies, refusing a zero size and a size larger than the pool, and it hands
out a block of precisely the requested length with a guard band immediately
after it. An allocation that the manager then overruns is therefore caught as
a real out-of-bounds write rather than inferred from arithmetic, which is what
makes the refusals below testable: revert the check and the manager writes its
sixteen-byte header into an eight-byte block and stamps on the guard. */
#include <stdio.h>
#define TX_SOURCE_CODE
#include "tx_api.h"
#include "tx_byte_pool.h"
#include "tx_thread.h"
#include "txm_module.h"
#include "txm_module_manager_util.h"
/* Define the modelled pool.
The arena is physically larger than the pool the manager is told about, so the
guard band that follows every block has somewhere to live without the pool's
own accounting having to know about it. */
#define TEST_ARENA_BYTES 8192U
#define TEST_POOL_BYTES 4096UL
#define TEST_GUARD_BYTES 32U
#define TEST_GUARD_PATTERN 0xE7U
#define TEST_MAX_BLOCKS 32U
#define TEST_HEADER_BYTES ((ULONG) sizeof(TXM_MODULE_ALLOCATED_OBJECT))
#define TEST_ULONG_MAX ((ULONG) ~((ULONG) 0))
/* The object size that makes the unchecked addition wrap to a chosen value. */
#define TEST_SIZE_WRAPPING_TO(want) ((ULONG) (((ULONG) (want)) - TEST_HEADER_BYTES))
/* Define one block the modelled pool has handed out. */
typedef struct TEST_BLOCK_STRUCT
{
UCHAR *test_block_start;
ULONG test_block_bytes;
UINT test_block_live;
} TEST_BLOCK;
/* Define the state the modelled pool and the stubbed services answer from. */
static union
{
ALIGN_TYPE test_arena_alignment;
UCHAR test_arena_bytes[TEST_ARENA_BYTES];
} test_arena;
static ULONG test_arena_offset;
static TEST_BLOCK test_blocks[TEST_MAX_BLOCKS];
static UINT test_block_count;
static ULONG test_allocate_calls;
static ULONG test_allocate_size;
static ULONG test_allocate_wait;
static ULONG test_release_calls;
static ULONG test_mutex_get_calls;
static ULONG test_mutex_put_calls;
/* Counted across the whole run rather than per case, because what they answer --
was the pool ever asked for less than the header the manager then writes, and
was it ever asked on behalf of something other than itself -- is about the run
as a whole. */
static UINT test_undersized_requests;
static UINT test_misdirected_requests;
static UINT test_failures;
/* Define the manager state the code under test reaches for. */
TX_BYTE_POOL _txm_module_manager_object_pool;
UINT _txm_module_manager_object_pool_created;
TX_MUTEX _txm_module_manager_mutex;
TX_THREAD *_tx_thread_current_ptr;
/* 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;
/* 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 arithmetic the allocation does before any object exists. */
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;
/* This test models no module memory of its own, so no address is ever inside the
calling module's data. */
UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr,
ULONG obj_size, UINT write_request)
{
(VOID) module_instance;
(VOID) obj_ptr;
(VOID) obj_size;
(VOID) write_request;
return(TX_FALSE);
}
/* No module data is modelled here, so every range lies outside it. */
UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr,
ULONG obj_size)
{
(VOID) module_instance;
(VOID) obj_ptr;
(VOID) obj_size;
return(TX_TRUE);
}
/* Not reached from the allocation path under test. */
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;
}
/* Record the outcome of one expectation. */
static VOID test_expect(const char *description, ULONG actual, ULONG expected)
{
if (actual != expected)
{
printf("FAIL: %s (expected %lu, got %lu)\n", description, (unsigned long) expected, (unsigned long) actual);
test_failures++;
}
}
/* Round a request up the way _tx_byte_allocate does. */
static ULONG test_round_up(ULONG memory_size)
{
return((((memory_size + ((ULONG) sizeof(ALIGN_TYPE))) - ((ULONG) 1)) / ((ULONG) sizeof(ALIGN_TYPE))) * ((ULONG) sizeof(ALIGN_TYPE)));
}
/* Lay a guard band down after a block. */
static VOID test_guard_write(UCHAR *guard_ptr)
{
UINT index;
for (index = 0U; index < TEST_GUARD_BYTES; index++)
{
guard_ptr[index] = (UCHAR) TEST_GUARD_PATTERN;
}
}
/* Report how many guard bytes anything has written through, across every block
the modelled pool has ever handed out, live or released. */
static ULONG test_guard_damage(VOID)
{
UINT block;
UINT index;
UCHAR *guard_ptr;
ULONG damaged;
damaged = ((ULONG) 0);
for (block = 0U; block < test_block_count; block++)
{
guard_ptr = test_blocks[block].test_block_start + test_blocks[block].test_block_bytes;
for (index = 0U; index < TEST_GUARD_BYTES; index++)
{
if (guard_ptr[index] != (UCHAR) TEST_GUARD_PATTERN)
{
damaged++;
}
}
}
return(damaged);
}
/* Stand in for the byte pool the manager allocates objects from.
The two size bounds and the alignment round-up are the real ones. What is not
real is the placement: every block ends where a guard band begins, so nothing
the manager writes past the end of its allocation can land in memory that a
later assertion would have accepted. */
UINT _txe_byte_allocate(TX_BYTE_POOL *pool_ptr, VOID **memory_ptr, ULONG memory_size, ULONG wait_option)
{
ULONG rounded_size;
UCHAR *block_start;
test_allocate_calls++;
test_allocate_size = memory_size;
test_allocate_wait = wait_option;
/* The manager must never ask for less than the header it is about to write.
This is the invariant the checked addition exists to hold, so the model
records a breach here as well as letting the guard band catch the write. */
if (memory_size < TEST_HEADER_BYTES)
{
test_undersized_requests++;
}
/* _txe_byte_allocate answers a null pool, a pool that is not one, and a null
destination with TX_POOL_ERROR and TX_PTR_ERROR. The manager always supplies
its own pool and a real destination, so rather than model arms this test
could never reach, note a breach and let the assertion at the end of the run
report it. */
if ((pool_ptr != &_txm_module_manager_object_pool) ||
(pool_ptr -> tx_byte_pool_id != TX_BYTE_POOL_ID) ||
(memory_ptr == TX_NULL))
{
test_misdirected_requests++;
return(TX_POOL_ERROR);
}
if (memory_size == ((ULONG) 0))
{
return(TX_SIZE_ERROR);
}
if (memory_size > pool_ptr -> tx_byte_pool_size)
{
return(TX_SIZE_ERROR);
}
rounded_size = test_round_up(memory_size);
if ((rounded_size > pool_ptr -> tx_byte_pool_available) ||
(test_block_count == TEST_MAX_BLOCKS) ||
((test_arena_offset + rounded_size + ((ULONG) TEST_GUARD_BYTES)) > ((ULONG) TEST_ARENA_BYTES)))
{
return(TX_NO_MEMORY);
}
block_start = &test_arena.test_arena_bytes[test_arena_offset];
test_blocks[test_block_count].test_block_start = block_start;
test_blocks[test_block_count].test_block_bytes = rounded_size;
test_blocks[test_block_count].test_block_live = TX_TRUE;
test_block_count++;
test_guard_write(block_start + rounded_size);
test_arena_offset = test_arena_offset + rounded_size + ((ULONG) TEST_GUARD_BYTES);
pool_ptr -> tx_byte_pool_available = pool_ptr -> tx_byte_pool_available - rounded_size;
pool_ptr -> tx_byte_pool_fragments++;
pool_ptr -> tx_byte_pool_search = block_start;
*memory_ptr = (VOID *) block_start;
return(TX_SUCCESS);
}
/* Stand in for the release side, so a normal allocation can be given back. */
UINT _txe_byte_release(VOID *memory_ptr)
{
UINT block;
test_release_calls++;
for (block = 0U; block < test_block_count; block++)
{
if ((test_blocks[block].test_block_start == (UCHAR *) memory_ptr) &&
(test_blocks[block].test_block_live == TX_TRUE))
{
test_blocks[block].test_block_live = TX_FALSE;
_txm_module_manager_object_pool.tx_byte_pool_available =
_txm_module_manager_object_pool.tx_byte_pool_available + test_blocks[block].test_block_bytes;
return(TX_SUCCESS);
}
}
/* A release of something this pool never handed out, or handed out twice. */
return(TX_PTR_ERROR);
}
/* Stand in for the protection mutex, to show when it is and is not taken. */
UINT _txe_mutex_get(TX_MUTEX *mutex_ptr, ULONG wait_option)
{
(VOID) mutex_ptr;
(VOID) wait_option;
test_mutex_get_calls++;
return(TX_SUCCESS);
}
UINT _txe_mutex_put(TX_MUTEX *mutex_ptr)
{
(VOID) mutex_ptr;
test_mutex_put_calls++;
return(TX_SUCCESS);
}
/* Bring the modelled pool up, empty. */
static VOID test_pool_reset(VOID)
{
UINT index;
for (index = 0U; index < TEST_ARENA_BYTES; index++)
{
test_arena.test_arena_bytes[index] = (UCHAR) 0;
}
test_arena_offset = ((ULONG) 0);
test_block_count = 0U;
test_allocate_calls = ((ULONG) 0);
test_allocate_size = ((ULONG) 0);
test_allocate_wait = ((ULONG) 0);
test_release_calls = ((ULONG) 0);
test_mutex_get_calls = ((ULONG) 0);
test_mutex_put_calls = ((ULONG) 0);
_txm_module_manager_object_pool.tx_byte_pool_id = TX_BYTE_POOL_ID;
_txm_module_manager_object_pool.tx_byte_pool_start = test_arena.test_arena_bytes;
_txm_module_manager_object_pool.tx_byte_pool_size = TEST_POOL_BYTES;
_txm_module_manager_object_pool.tx_byte_pool_available = TEST_POOL_BYTES;
_txm_module_manager_object_pool.tx_byte_pool_fragments = 0U;
_txm_module_manager_object_pool.tx_byte_pool_search = test_arena.test_arena_bytes;
_txm_module_manager_object_pool_created = TX_TRUE;
}
/* Bring the pool and the module that allocates from it back to a known state.
Both are reset together deliberately. The module's allocation list points into
the pool, so a case that left objects behind would have the next case linking
new objects onto addresses this function has just zeroed, and every later
assertion would then be reading the wreckage of an earlier one rather than
what it was written to check. */
static VOID test_reset(TXM_MODULE_INSTANCE *module_instance)
{
test_pool_reset();
module_instance -> txm_module_instance_object_list_count = ((ULONG) 0);
module_instance -> txm_module_instance_object_list_head = TX_NULL;
}
/* Describe everything a refused request must have left alone. */
typedef struct TEST_SNAPSHOT_STRUCT
{
ULONG test_snapshot_available;
UINT test_snapshot_fragments;
UCHAR *test_snapshot_search;
ULONG test_snapshot_list_count;
TXM_MODULE_ALLOCATED_OBJECT *test_snapshot_list_head;
ULONG test_snapshot_allocate_calls;
ULONG test_snapshot_mutex_get_calls;
} TEST_SNAPSHOT;
static VOID test_snapshot_take(TEST_SNAPSHOT *snapshot, TXM_MODULE_INSTANCE *module_instance)
{
snapshot -> test_snapshot_available = _txm_module_manager_object_pool.tx_byte_pool_available;
snapshot -> test_snapshot_fragments = _txm_module_manager_object_pool.tx_byte_pool_fragments;
snapshot -> test_snapshot_search = _txm_module_manager_object_pool.tx_byte_pool_search;
snapshot -> test_snapshot_list_count = module_instance -> txm_module_instance_object_list_count;
snapshot -> test_snapshot_list_head = module_instance -> txm_module_instance_object_list_head;
snapshot -> test_snapshot_allocate_calls = test_allocate_calls;
snapshot -> test_snapshot_mutex_get_calls = test_mutex_get_calls;
}
/* Assert that a refused request changed nothing an accepted one would have. */
static VOID test_refusal_check(const char *description, TEST_SNAPSHOT *snapshot,
TXM_MODULE_INSTANCE *module_instance, VOID *object, UINT status)
{
test_expect(description, (ULONG) status, (ULONG) TXM_MODULE_MATH_OVERFLOW);
test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE);
test_expect("...without asking the pool for anything",
test_allocate_calls, snapshot -> test_snapshot_allocate_calls);
test_expect("...without taking the protection mutex",
test_mutex_get_calls, snapshot -> test_snapshot_mutex_get_calls);
test_expect("...leaving the bytes available unchanged",
_txm_module_manager_object_pool.tx_byte_pool_available, snapshot -> test_snapshot_available);
test_expect("...leaving the fragment count unchanged",
(ULONG) _txm_module_manager_object_pool.tx_byte_pool_fragments, (ULONG) snapshot -> test_snapshot_fragments);
test_expect("...leaving the search pointer unchanged",
(ULONG) (_txm_module_manager_object_pool.tx_byte_pool_search == snapshot -> test_snapshot_search), (ULONG) TX_TRUE);
test_expect("...leaving the module's object count unchanged",
module_instance -> txm_module_instance_object_list_count, snapshot -> test_snapshot_list_count);
test_expect("...leaving the module's object list head unchanged",
(ULONG) (module_instance -> txm_module_instance_object_list_head == snapshot -> test_snapshot_list_head), (ULONG) TX_TRUE);
test_expect("...and touching no guard band", test_guard_damage(), ((ULONG) 0));
}
/* Model the arithmetic this test guards against, so that each refused size can
show what the previous implementation would have asked the pool for. */
static ULONG test_unchecked_allocation_size(ULONG object_size)
{
return((ULONG) (object_size + sizeof(TXM_MODULE_ALLOCATED_OBJECT)));
}
/* Confirm that one accepted allocation is shaped the way the rest of the manager
requires: the pool was asked for the object plus the header, the module was
handed the address just past the header, and the header records the size that
_txm_module_manager_object_size_check will later be asked to match. */
static VOID test_accepted_check(const char *description, TXM_MODULE_INSTANCE *module_instance,
VOID *object, UINT status, ULONG object_size)
{
TXM_MODULE_ALLOCATED_OBJECT *header;
test_expect(description, (ULONG) status, (ULONG) TX_SUCCESS);
test_expect("...returning a non-null object pointer", (ULONG) (object != TX_NULL), (ULONG) TX_TRUE);
if (object == TX_NULL)
{
return;
}
test_expect("...having asked the pool for the object and the header",
test_allocate_size, object_size + TEST_HEADER_BYTES);
test_expect("...without waiting", test_allocate_wait, (ULONG) TX_NO_WAIT);
header = ((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1;
test_expect("...placing the object immediately after the header",
(ULONG) (((UCHAR *) header) == test_blocks[test_block_count - 1U].test_block_start), (ULONG) TX_TRUE);
/* The size recorded here is what _txm_module_manager_object_size_check later
compares an object-creation request against, so a size the header did not
record faithfully would fail every creation dispatcher. */
test_expect("...recording the requested size in the header",
header -> txm_module_object_size, object_size);
test_expect("...recording the owning module in the header",
(ULONG) (header -> txm_module_allocated_object_module_instance == module_instance), (ULONG) TX_TRUE);
test_expect("...and touching no guard band", test_guard_damage(), ((ULONG) 0));
}
/* Walk the module's allocation list and report whether it is a consistent ring
of the expected length, owned throughout by the expected module. */
static UINT test_list_consistent(TXM_MODULE_INSTANCE *module_instance, ULONG expected_count)
{
TXM_MODULE_ALLOCATED_OBJECT *object;
ULONG walked;
if (module_instance -> txm_module_instance_object_list_count != expected_count)
{
return(TX_FALSE);
}
if (expected_count == ((ULONG) 0))
{
return((UINT) (module_instance -> txm_module_instance_object_list_head == TX_NULL));
}
object = module_instance -> txm_module_instance_object_list_head;
if (object == TX_NULL)
{
return(TX_FALSE);
}
for (walked = ((ULONG) 0); walked < expected_count; walked++)
{
if (object -> txm_module_allocated_object_module_instance != module_instance)
{
return(TX_FALSE);
}
if (object -> txm_module_allocated_object_next -> txm_module_allocated_object_previous != object)
{
return(TX_FALSE);
}
object = object -> txm_module_allocated_object_next;
}
/* The ring must close on the head after exactly the expected number of steps. */
return((UINT) (object == module_instance -> txm_module_instance_object_list_head));
}
/* The control block sizes a module can ask the manager to allocate for it. */
typedef struct TEST_OBJECT_SIZE_STRUCT
{
const char *test_object_name;
ULONG test_object_size;
} TEST_OBJECT_SIZE;
int main(void)
{
TXM_MODULE_INSTANCE module;
TXM_MODULE_INSTANCE *module_instance;
TX_THREAD module_thread;
TEST_SNAPSHOT snapshot;
VOID *object;
VOID *objects[8];
UINT status;
UINT index;
ULONG object_size;
ULONG wrapped_size;
TEST_OBJECT_SIZE object_sizes[] =
{
{"a thread", (ULONG) sizeof(TX_THREAD)},
{"a queue", (ULONG) sizeof(TX_QUEUE)},
{"a semaphore", (ULONG) sizeof(TX_SEMAPHORE)},
{"a mutex", (ULONG) sizeof(TX_MUTEX)},
{"an event flags group",(ULONG) sizeof(TX_EVENT_FLAGS_GROUP)},
{"a block pool", (ULONG) sizeof(TX_BLOCK_POOL)},
{"a byte pool", (ULONG) sizeof(TX_BYTE_POOL)},
{"a timer", (ULONG) sizeof(TX_TIMER)}
};
#define TEST_OBJECT_SIZE_ENTRIES (sizeof(object_sizes) / sizeof(object_sizes[0]))
/* Unbuffered, so that the expectations reported below survive a crash. An
undersized allocation is a memory-safety failure, and a build in which one
gets through has to say which expectation noticed before it dies. */
setvbuf(stdout, TX_NULL, _IONBF, 0U);
printf("Module Manager object allocation size arithmetic test.................. ");
module_instance = &module;
module.txm_module_instance_id = TXM_MODULE_ID;
module.txm_module_instance_object_list_count = ((ULONG) 0);
module.txm_module_instance_object_list_head = TX_NULL;
module_thread.tx_thread_module_instance_ptr = (VOID *) module_instance;
_tx_thread_current_ptr = &module_thread;
test_reset(module_instance);
/**********************************************************************/
/* The header the manager writes is larger than the smallest block */
/* the pool will hand out, which is what makes a wrapped size unsafe. */
/**********************************************************************/
test_expect("the object header spans more than one pool alignment unit",
(ULONG) (TEST_HEADER_BYTES > (ULONG) sizeof(ALIGN_TYPE)), (ULONG) TX_TRUE);
/**********************************************************************/
/* Sizes for which the header-inclusive addition wraps. */
/**********************************************************************/
/* The smallest size that overflows at all, and so the tight side of the new
comparison. It wraps to exactly zero, a request the pool would have refused
on its own, so this boundary fails safe even unfixed; it is here for the
boundary rather than for the consequence. */
test_reset(module_instance);
object_size = (TEST_ULONG_MAX - TEST_HEADER_BYTES) + ((ULONG) 1);
test_expect("the smallest overflowing size is one past the largest that fits",
object_size, TEST_SIZE_WRAPPING_TO(0UL));
test_expect("...and wraps to zero", test_unchecked_allocation_size(object_size), ((ULONG) 0));
test_snapshot_take(&snapshot, module_instance);
object = (VOID *) &module;
status = _txm_module_manager_object_allocate(&object, object_size, module_instance);
test_refusal_check("the smallest overflowing size is refused",
&snapshot, module_instance, object, status);
/* Wrapping to eight. This is the exploitable one: the pool accepts an eight
byte request and the manager then writes sixteen bytes into it. */
test_reset(module_instance);
object_size = TEST_SIZE_WRAPPING_TO(8UL);
wrapped_size = test_unchecked_allocation_size(object_size);
test_expect("the size that wraps to eight is the one expected", wrapped_size, ((ULONG) 8));
test_expect("...a request the pool would have accepted",
(ULONG) ((wrapped_size != ((ULONG) 0)) && (wrapped_size <= TEST_POOL_BYTES)), (ULONG) TX_TRUE);
test_expect("...for fewer bytes than the header alone occupies",
(ULONG) (wrapped_size < TEST_HEADER_BYTES), (ULONG) TX_TRUE);
test_snapshot_take(&snapshot, module_instance);
object = (VOID *) &module;
status = _txm_module_manager_object_allocate(&object, object_size, module_instance);
test_refusal_check("a size whose header-inclusive total wraps to a block smaller than the header is refused",
&snapshot, module_instance, object, status);
/* Wrapping to one, the smallest request the pool would have served at all. */
test_reset(module_instance);
object_size = TEST_SIZE_WRAPPING_TO(1UL);
test_expect("the size that wraps to one is the one expected",
test_unchecked_allocation_size(object_size), ((ULONG) 1));
test_snapshot_take(&snapshot, module_instance);
object = (VOID *) &module;
status = _txm_module_manager_object_allocate(&object, object_size, module_instance);
test_refusal_check("a size whose header-inclusive total wraps to a single byte is refused",
&snapshot, module_instance, object, status);
/* The largest size expressible at all, which wraps to one byte less than the
header. */
test_reset(module_instance);
test_snapshot_take(&snapshot, module_instance);
object = (VOID *) &module;
status = _txm_module_manager_object_allocate(&object, TEST_ULONG_MAX, module_instance);
test_refusal_check("the largest expressible size is refused", &snapshot, module_instance, object, status);
/**********************************************************************/
/* The largest size that does not overflow. */
/**********************************************************************/
/* This one must pass the check and reach the pool carrying its true total,
which the pool then refuses on its own size bound. It covers the accepting
side of the new comparison at the tightest point there is. */
test_reset(module_instance);
object_size = TEST_ULONG_MAX - TEST_HEADER_BYTES;
test_snapshot_take(&snapshot, module_instance);
object = (VOID *) &module;
status = _txm_module_manager_object_allocate(&object, object_size, module_instance);
test_expect("the largest non-overflowing size is refused by the pool, not by the check",
(ULONG) status, (ULONG) TX_SIZE_ERROR);
test_expect("...having reached the pool", test_allocate_calls, snapshot.test_snapshot_allocate_calls + ((ULONG) 1));
test_expect("...asking for the full untruncated total", test_allocate_size, TEST_ULONG_MAX);
test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE);
test_expect("...leaving the module's object count unchanged",
module_instance -> txm_module_instance_object_list_count, snapshot.test_snapshot_list_count);
test_expect("...leaving the bytes available unchanged",
_txm_module_manager_object_pool.tx_byte_pool_available, snapshot.test_snapshot_available);
test_expect("...and releasing the protection mutex it took",
test_mutex_get_calls, test_mutex_put_calls);
/**********************************************************************/
/* Ordinary allocations, one for every control block a module can own. */
/**********************************************************************/
test_reset(module_instance);
for (index = 0U; index < (UINT) TEST_OBJECT_SIZE_ENTRIES; index++)
{
object = TX_NULL;
status = _txm_module_manager_object_allocate(&object, object_sizes[index].test_object_size, module_instance);
test_accepted_check(object_sizes[index].test_object_name, module_instance, object, status,
object_sizes[index].test_object_size);
objects[index] = object;
}
test_expect("the module's allocation list holds every object in a consistent ring",
(ULONG) test_list_consistent(module_instance, (ULONG) TEST_OBJECT_SIZE_ENTRIES), (ULONG) TX_TRUE);
/* Every one of them can be given back, and the list empties out. */
for (index = 0U; index < (UINT) TEST_OBJECT_SIZE_ENTRIES; index++)
{
status = _txm_module_manager_object_deallocate(objects[index]);
test_expect("an allocated object can be released", (ULONG) status, (ULONG) TX_SUCCESS);
}
test_expect("the allocation list is empty again",
(ULONG) test_list_consistent(module_instance, ((ULONG) 0)), (ULONG) TX_TRUE);
test_expect("every byte charged to the pool came back",
_txm_module_manager_object_pool.tx_byte_pool_available, TEST_POOL_BYTES);
test_expect("no guard band was touched by any of it", test_guard_damage(), ((ULONG) 0));
test_expect("the protection mutex was balanced throughout", test_mutex_get_calls, test_mutex_put_calls);
/**********************************************************************/
/* A zero-length object, the other end of the accepted range. */
/**********************************************************************/
test_reset(module_instance);
object = TX_NULL;
status = _txm_module_manager_object_allocate(&object, ((ULONG) 0), module_instance);
test_accepted_check("a zero-length object", module_instance, object, status, ((ULONG) 0));
test_expect("...for which the pool was asked for exactly the header",
test_allocate_size, TEST_HEADER_BYTES);
(VOID) _txm_module_manager_object_deallocate(object);
/**********************************************************************/
/* Sizes the pool cannot serve, which must still not corrupt anything. */
/**********************************************************************/
test_reset(module_instance);
object = TX_NULL;
status = _txm_module_manager_object_allocate(&object, TEST_POOL_BYTES, module_instance);
test_expect("a size the pool is too small for is refused by the pool",
(ULONG) status, (ULONG) TX_SIZE_ERROR);
test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE);
test_expect("...leaving the allocation list empty",
(ULONG) test_list_consistent(module_instance, ((ULONG) 0)), (ULONG) TX_TRUE);
object = TX_NULL;
status = _txm_module_manager_object_allocate(&object, ((ULONG) 4000), module_instance);
test_expect("a size the pool can serve once succeeds", (ULONG) status, (ULONG) TX_SUCCESS);
object = TX_NULL;
status = _txm_module_manager_object_allocate(&object, ((ULONG) 4000), module_instance);
test_expect("the same size again finds no memory", (ULONG) status, (ULONG) TX_NO_MEMORY);
test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE);
test_expect("...leaving the one live object in the list",
(ULONG) test_list_consistent(module_instance, ((ULONG) 1)), (ULONG) TX_TRUE);
test_expect("...and touching no guard band", test_guard_damage(), ((ULONG) 0));
/**********************************************************************/
/* Invalid and disabled configurations. */
/**********************************************************************/
test_reset(module_instance);
/* A null destination is rejected before the size is looked at, so it keeps
its own error even when the size would also have been refused. */
status = _txm_module_manager_object_allocate(TX_NULL, TEST_ULONG_MAX, module_instance);
test_expect("a null destination pointer is rejected on its own terms",
(ULONG) status, (ULONG) TXM_MODULE_INVALID_MEMORY);
test_expect("...without asking the pool for anything", test_allocate_calls, ((ULONG) 0));
test_expect("...without taking the protection mutex", test_mutex_get_calls, ((ULONG) 0));
/* With no object pool, an overflowing size is still refused as one: the
check runs before the manager looks at its own configuration. */
_txm_module_manager_object_pool_created = TX_FALSE;
test_snapshot_take(&snapshot, module_instance);
object = (VOID *) &module;
status = _txm_module_manager_object_allocate(&object, TEST_ULONG_MAX, module_instance);
test_refusal_check("an overflowing size is refused with no object pool created",
&snapshot, module_instance, object, status);
object = TX_NULL;
status = _txm_module_manager_object_allocate(&object, (ULONG) sizeof(TX_MUTEX), module_instance);
test_expect("an ordinary size reports the pool is not available",
(ULONG) status, (ULONG) TX_NOT_AVAILABLE);
test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE);
test_expect("...and releasing the protection mutex it took",
test_mutex_get_calls, test_mutex_put_calls);
_txm_module_manager_object_pool_created = TX_TRUE;
/**********************************************************************/
/* The invariant the whole check exists to hold, over the whole run. */
/**********************************************************************/
test_expect("the pool was never asked for less than the header it must hold",
(ULONG) test_undersized_requests, ((ULONG) 0));
test_expect("every request named the manager's own pool and a real destination",
(ULONG) test_misdirected_requests, ((ULONG) 0));
test_expect("and nothing wrote through a guard band", test_guard_damage(), ((ULONG) 0));
if (test_failures == 0U)
{
printf("SUCCESS!\n");
return(0);
}
printf("ERROR: %u expectation(s) failed\n", test_failures);
return(1);
}
|