mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
Stopped pthread_self() faulting when the caller is not a pthread (#627)
Nothing prevents an application mixing tx_thread_create() with the POSIX layer,
and a thread created that way has no POSIX control block. posix_thread2tcb()
returns NULL for it, which posix_thread2tid() then read through:
p_tcb = posix_thread2tcb(thread_ptr);
thread_ID = p_tcb->pthreadID;
pthread_self() went on to compound it, reading the signal fields of a POSIX_TCB
out of a thread that is only a TX_THREAD:
if (((POSIX_TCB *) thread_ptr) -> signals.signal_handler)
The first is a null dereference and the second runs off the end of the control
block into whatever the linker put there. Under qemu-system-riscv32 the first one
lands first: mcause=0x5, a load access fault, with mtval=0xb4 for the offset of
pthreadID.
Have posix_thread2tid() report zero for a thread with no control block, which is
what px_pth_join.c already does for the same call, and have pthread_self() skip
the signal check unless the ID says the caller really is a pthread. Zero cannot
collide with a real ID because px_pth_create.c uses the address of the control
block as the ID.
This also covers the case where there is no current thread at all, from an ISR or
before the scheduler starts: tx_thread_identify() returns NULL, and the same
zero comes back instead of a fault.
Add posix_pthread_self_test, which asks both kinds of thread for their ID: a
pthread, which has to report what pthread_create() returned, and a plain ThreadX
thread, which has to report zero. Reverting either half of the fix turns the test
into the load access fault above.
Verified with riscv64-unknown-elf and qemu-system-riscv32: 4 tests across the
default build, 4 of 4 passing.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 <stdio.h>
|
||||
#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);
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user