diff --git a/test/posix/cmake/regression/CMakeLists.txt b/test/posix/cmake/regression/CMakeLists.txt index c8320b05..8f433bcb 100644 --- a/test/posix/cmake/regression/CMakeLists.txt +++ b/test/posix/cmake/regression/CMakeLists.txt @@ -19,6 +19,7 @@ set(posix_test_cases ${SOURCE_DIR}/posix_mq_basic_test.c ${SOURCE_DIR}/posix_mq_send_abort_test.c ${SOURCE_DIR}/posix_pthread_basic_test.c + ${SOURCE_DIR}/posix_pthread_self_test.c ) set(WEAK_DEFAULTS_SOURCE ${CMAKE_CURRENT_LIST_DIR}/../../../shared/regression/testcontrol_weak_defaults.c) diff --git a/test/posix/regression/posix_pthread_self_test.c b/test/posix/regression/posix_pthread_self_test.c new file mode 100644 index 00000000..7cbb6450 --- /dev/null +++ b/test/posix/regression/posix_pthread_self_test.c @@ -0,0 +1,177 @@ +/***************************************************************************/ +/* 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 covers pthread_self() being called from a thread that is not a + pthread. + + Nothing stops an application mixing tx_thread_create() with the POSIX layer, + and a ThreadX thread created that way has no POSIX control block. + posix_thread2tcb() returns NULL for it, and pthread_self() used to read the + pthread ID through that null pointer, then read the signal fields of a + POSIX_TCB out of a plain TX_THREAD that never had any. + + The test calls pthread_self() from both kinds of thread: a real pthread, + which must get the ID pthread_create() handed back, and a plain ThreadX + thread, which must get zero rather than a fault or a value read out of + whatever follows its control block. */ + +#include +#include "tx_api.h" +#include "pthread.h" + +#define TEST_STACK_BYTES 4096 + +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t worker; +static pthread_attr_t worker_attr; + +/* A plain ThreadX thread, deliberately not created through pthread_create(). */ +static TX_THREAD plain_thread; + +static volatile INT worker_done; +static volatile INT plain_done; +static pthread_t worker_reported_id; +static pthread_t plain_reported_id; + +extern void test_control_return(UINT status); + +static VOID *worker_entry(VOID *input); +static VOID plain_entry(ULONG input); +static VOID checker_entry(ULONG input); + +static TX_THREAD checker_thread; + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_pthread_self_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; +CHAR *pointer; + + pointer = (CHAR *) first_unused_memory; + + storage_ptr = posix_initialize(posix_region); + + /* A genuine pthread. */ + pthread_attr_init(&worker_attr); + param.sched_priority = 15; + pthread_attr_setschedparam(&worker_attr, ¶m); + pthread_attr_setstackaddr(&worker_attr, storage_ptr); + + if (pthread_create(&worker, &worker_attr, worker_entry, NULL) != OK) + { + + printf("Running POSIX Pthread Self Test.................................. ERROR #1\n"); + test_control_return(1); + } + + /* A ThreadX thread that the POSIX layer knows nothing about. */ + if (tx_thread_create(&plain_thread, "plain threadx thread", plain_entry, 0, + pointer, TEST_STACK_BYTES, + 20, 20, TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) + { + + printf("Running POSIX Pthread Self Test.................................. ERROR #2\n"); + test_control_return(1); + } + pointer = pointer + TEST_STACK_BYTES; + + /* Runs last and inspects what the other two reported. */ + if (tx_thread_create(&checker_thread, "checker", checker_entry, 0, + pointer, TEST_STACK_BYTES, + 25, 25, TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) + { + + printf("Running POSIX Pthread Self Test.................................. ERROR #3\n"); + test_control_return(1); + } +} + + +static VOID *worker_entry(VOID *input) +{ + + (void) input; + + worker_reported_id = pthread_self(); + worker_done = 1; + + return (NULL); +} + + +/* The case that used to fault: a ThreadX thread asking for its pthread ID. */ +static VOID plain_entry(ULONG input) +{ + + (void) input; + + plain_reported_id = pthread_self(); + plain_done = 1; +} + + +static VOID checker_entry(ULONG input) +{ + + (void) input; + + printf("Running POSIX Pthread Self Test.................................. "); + + /* Both threads run above this one, so they have finished by now. */ + if ((worker_done == 0) || (plain_done == 0)) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + /* A pthread must report the ID pthread_create() produced. */ + if (worker_reported_id != worker) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + /* A pthread ID is the address of its control block, so a real one is never + zero. */ + if (worker_reported_id == (pthread_t) 0) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + + /* The plain ThreadX thread has no pthread ID, and must be told so rather + than handed something read out of memory that follows its TX_THREAD. */ + if (plain_reported_id != (pthread_t) 0) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); +} diff --git a/utility/rtos_compatibility_layers/posix/px_pth_init.c b/utility/rtos_compatibility_layers/posix/px_pth_init.c index a2f97be7..ff0a01a9 100644 --- a/utility/rtos_compatibility_layers/posix/px_pth_init.c +++ b/utility/rtos_compatibility_layers/posix/px_pth_init.c @@ -550,6 +550,18 @@ POSIX_TCB *p_tcb; /* Get the TCB for this pthread */ p_tcb = posix_thread2tcb(thread_ptr); + + /* A running ThreadX thread that was not created through pthread_create() + has no TCB in the pool, and posix_thread2tcb() returns NULL for it. It + has no pthread ID either, so report zero rather than reading through the + null pointer. Zero can never collide with a real ID, because + px_pth_create.c uses the address of the TCB as the ID. */ + if (!p_tcb) + { + + return((pthread_t) 0); + } + thread_ID = p_tcb->pthreadID; /* All done. */ diff --git a/utility/rtos_compatibility_layers/posix/px_pth_self.c b/utility/rtos_compatibility_layers/posix/px_pth_self.c index fd876459..2272fa31 100644 --- a/utility/rtos_compatibility_layers/posix/px_pth_self.c +++ b/utility/rtos_compatibility_layers/posix/px_pth_self.c @@ -74,12 +74,21 @@ pthread_t thread_ID; thread_ID = posix_thread2tid(thread_ptr); - /* Determine if this thread is actually the signal thread helper. */ - if (((POSIX_TCB *) thread_ptr) -> signals.signal_handler) + /* Only a pthread has the signal fields read below: they belong to the + POSIX_TCB that surrounds the thread, and reading them through anything + else runs off the end of a plain TX_THREAD. A zero ID means the caller + is not a pthread, either because it is a ThreadX thread created directly + or because there is no current thread at all, so leave it alone. */ + if (thread_ID != (pthread_t) 0) { - /* Yes, override the thread_ID with the non-signal thread ID. */ - thread_ID = (pthread_t) ((POSIX_TCB *) thread_ptr) -> signals.base_thread_ptr; + /* Determine if this thread is actually the signal thread helper. */ + if (((POSIX_TCB *) thread_ptr) -> signals.signal_handler) + { + + /* Yes, override the thread_ID with the non-signal thread ID. */ + thread_ID = (pthread_t) ((POSIX_TCB *) thread_ptr) -> signals.base_thread_ptr; + } }