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
|
/***************************************************************************
* 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
**************************************************************************/
/* This test writes a later trace event over a byte allocate's own entry.
The byte allocate records a trace event on the way in, saves the entry's time
stamp, and comes back to fill in the address it returned once it has one --
but only if the time stamp says nothing has overwritten the entry meanwhile.
Ordinary use never takes the other arm. The service holds the protection
across the whole of it except for the search, and nothing in the kernel
writes a trace event inside that gap, so the entry the allocate saved is
always still its own by the time it looks again.
The gap is reachable from the test side, because the Linux SMP port already
calls out to the harness inside it: TX_BYTE_ALLOCATE_EXTENSION runs between
the restore and the disable that bracket the search, and exists for exactly
this kind of coverage work. The extension gained a second clause, guarded by
its own flag, that calls the routine below.
The restore that opens the gap is a real release on SMP. TX_RESTORE is
_tx_thread_smp_unprotect, which gives the protection structure up once the
nesting count reaches zero and the preempt disable flag is clear, and
_tx_byte_allocate raises neither before it gets there. So the hook below runs
outside the protection and may take it again for an insert of its own.
Writing an event is not on its own enough. With the byte pool event class
enabled the allocate's own insert advances the buffer, so a later event lands
on the next entry and leaves the saved one alone. With the class filtered the
insert writes nothing and the buffer stays parked on the saved entry, so the
next event written by anything lands exactly on it. Filtering the class is
what makes the entry overwritable; the port hook is what puts an event in the
window. Neither closes the arm without the other.
The kernel is not modified and no symbol is replaced: the allocate runs its
own code throughout, and the only thing the test supplies is an event at a
moment the service already offers to the harness.
The arm is reachable in one configuration only, and not for want of trying in
the other. Under TX_MISRA_ENABLE the Linux SMP port takes its trace time
stamp from _tx_misra_time_stamp_get(), whose body is `return(0);`, so every
entry in the buffer carries a time stamp of zero and the allocate's
comparison reads 0 == 0 whatever is written over the entry. No event, no
interrupt and no other core can make it false there. The overwrite itself is
still driven in that configuration -- the hook runs and the entry is written
-- but only the configuration with a real time source can assert the
consequence. */
#include <stdio.h>
#include "tx_api.h"
#define TX_SOURCE_CODE
#include "tx_thread.h"
#include "tx_trace.h"
#define TEST_PRIORITY 16
/* The pool the allocate under test comes from, and the size taken from it. */
#define POOL_SIZE 2048
#define ALLOCATION_SIZE 100
/* The trace buffer. Its size does not matter to this test -- the entry that
gets overwritten is the one the buffer is parked on, not one reached by
wrapping -- but the object registry has to hold the objects the test
creates. */
#define TRACE_BUFFER_SIZE 8192
#define TRACE_REGISTRY_ENTRIES 32
static unsigned long thread_0_counter = 0;
static TX_THREAD thread_0;
static TX_BYTE_POOL pool_0;
static UCHAR trace_buffer[TRACE_BUFFER_SIZE];
/* How many times the port hook called the routine below. */
static volatile ULONG overwrite_count = 0;
/* Define thread prototypes. */
static void thread_0_entry(ULONG thread_input);
/* Prototype for test control return. */
void test_control_return(UINT status);
/* Called from TX_BYTE_ALLOCATE_EXTENSION, inside the byte allocate's own window
between the release and the next protection, with the trace buffer parked on
the entry the allocate saved. The user event written here therefore lands on
that entry and gives it a new time stamp.
The harness declares this weakly and does nothing with it; this is the one
test that gives it a body. */
void overwrite_byte_allocate_trace_entry(void)
{
overwrite_count++;
#ifdef TX_ENABLE_EVENT_TRACE
tx_trace_user_event_insert(TX_TRACE_USER_EVENT_START, 0, 0, 0, 0);
#endif
}
static void thread_0_entry(ULONG thread_input)
{
UINT status;
CHAR *memory_ptr;
#ifdef TX_ENABLE_EVENT_TRACE
TX_TRACE_BUFFER_ENTRY *saved_entry;
ULONG saved_time_stamp;
#endif
/* Increment thread 0 counter. */
thread_0_counter++;
/* Inform user of success getting to this test. */
printf("Running Byte Pool Trace Overwrite Test............................ ");
#ifdef TX_ENABLE_EVENT_TRACE
status = tx_trace_enable(trace_buffer, sizeof(trace_buffer), TRACE_REGISTRY_ENTRIES);
if (status != TX_SUCCESS)
{
printf("ERROR #2\n");
test_control_return(1);
}
/* Filter the byte pool events, so that the allocate's own insert writes
nothing and leaves the buffer parked where it is. */
status = tx_trace_event_filter(TX_TRACE_BYTE_POOL_EVENTS);
if (status != TX_SUCCESS)
{
printf("ERROR #3\n");
test_control_return(1);
}
/* The entry the allocate is about to save, and what it holds now. The
allocate reads the same entry back after the search and compares. */
saved_entry = _tx_trace_buffer_current_ptr;
saved_time_stamp = saved_entry -> tx_trace_buffer_entry_time_stamp;
/* Arm the port hook for the next allocate. */
threadx_byte_allocate_trace_test = ((TEST_FLAG) 1);
#endif
status = tx_byte_allocate(&pool_0, (VOID **) &memory_ptr, ALLOCATION_SIZE, TX_NO_WAIT);
if (status != TX_SUCCESS)
{
printf("ERROR #4\n");
test_control_return(1);
}
#ifdef TX_ENABLE_EVENT_TRACE
/* The hook ran, inside the allocate. */
if (overwrite_count != ((ULONG) 1))
{
printf("ERROR #5\n");
test_control_return(1);
}
/* The entry the allocate saved was written over, whichever configuration
this is: the identifier is the user event's rather than the allocate's.
The identifier carries the writing core in its top byte, so only the
event half of it is compared. */
if ((saved_entry -> tx_trace_buffer_entry_event_id & ((ULONG) 0x00FFFFFF)) != ((ULONG) TX_TRACE_USER_EVENT_START))
{
printf("ERROR #6\n");
test_control_return(1);
}
#ifndef TX_MISRA_ENABLE
/* With a real time source the entry now reads differently from what the
allocate recorded, which is what its own check compares against. */
if (saved_entry -> tx_trace_buffer_entry_time_stamp == saved_time_stamp)
{
printf("ERROR #7\n");
test_control_return(1);
}
/* So the allocate left the entry alone rather than writing its address into
it, which is the arm under test. */
if (saved_entry -> tx_trace_buffer_entry_information_field_2 == TX_POINTER_TO_ULONG_CONVERT(memory_ptr))
{
printf("ERROR #8\n");
test_control_return(1);
}
#endif
/* Put the event class back and leave tracing off, so nothing after this
test inherits either. */
status = tx_trace_event_unfilter(TX_TRACE_BYTE_POOL_EVENTS);
if (status != TX_SUCCESS)
{
printf("ERROR #9\n");
test_control_return(1);
}
status = tx_trace_disable();
if (status != TX_SUCCESS)
{
printf("ERROR #10\n");
test_control_return(1);
}
#endif
status = tx_byte_release(memory_ptr);
if (status != TX_SUCCESS)
{
printf("ERROR #11\n");
test_control_return(1);
}
/* Success! */
printf("SUCCESS!\n");
test_control_return(0);
}
/* Define what the initial system looks like. */
#ifdef CTEST
void test_application_define(void *first_unused_memory)
#else
void threadx_byte_pool_trace_overwrite_application_define(void *first_unused_memory)
#endif
{
UINT status;
CHAR *pointer;
/* Put first available memory address into a character pointer. */
pointer = (CHAR *) first_unused_memory;
status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
pointer, TEST_STACK_SIZE_PRINTF,
TEST_PRIORITY, TEST_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);
pointer = pointer + TEST_STACK_SIZE_PRINTF;
/* Check for status. */
if (status != TX_SUCCESS)
{
printf("Running Byte Pool Trace Overwrite Test............................ ERROR #1\n");
test_control_return(1);
}
status = tx_byte_pool_create(&pool_0, "pool 0", pointer, POOL_SIZE);
pointer = pointer + POOL_SIZE;
/* Check for status. */
if (status != TX_SUCCESS)
{
printf("Running Byte Pool Trace Overwrite Test............................ ERROR #1\n");
test_control_return(1);
}
}
|