Skip to content

LATX, fix: fix getdents64 interference with /proc/[pid]/task#228

Open
rmjskhy wants to merge 2 commits intolat-opensource:masterfrom
rmjskhy:temp1
Open

LATX, fix: fix getdents64 interference with /proc/[pid]/task#228
rmjskhy wants to merge 2 commits intolat-opensource:masterfrom
rmjskhy:temp1

Conversation

@rmjskhy
Copy link
Contributor

@rmjskhy rmjskhy commented Jan 15, 2026

The psx_test from libcap fails in the latx environment. This is because psx_test scans /proc/[pid]/task to synchronize all threads. Extra "ghost" threads introduced by the emulator cause psx_test to enter an infinite loop while waiting for synchronization signals that never arrive.

@LaurenIsACoder
Copy link
Contributor

@codex review

@chatgpt-codex-connector
Copy link

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

We were unable to download your code in a timely manner.
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an issue where the psx_test from libcap fails in the LATX environment due to "ghost" threads introduced by the emulator. The fix filters out emulator-internal threads from /proc/[pid]/task directory listings so that applications scanning this directory only see legitimate guest threads.

Changes:

  • Added filtering logic to getdents64 syscall to hide emulator threads from /proc/[pid]/task listings
  • Added support for SCREL and LLACQ atomic instructions in the shared_private_interpret function
  • Added support for AMCAS (atomic compare-and-swap) instructions

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
linux-user/syscall.c Implements getdents64 filtering to detect /proc/[pid]/task directory access and hide ghost threads by comparing TIDs against valid guest threads
accel/tcg/translate-all.c Adds interpreter support for LoongArch atomic instructions (SCREL, LLACQ, AMCAS) in the shared-private memory interpretation handler

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

struct linux_dirent64 *de;
int len = ret;

int valid_tids[512];
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hard-coded array size of 512 may be insufficient for systems with many threads and could lead to silent truncation of the valid TID list. If the number of threads exceeds 512, some valid threads will not be included in valid_tids, causing them to be incorrectly hidden from /proc/[pid]/task listings. Consider using a dynamic allocation approach or defining this as a constant with appropriate documentation of its limits.

Copilot uses AI. Check for mistakes.

de = dirp;
while (len > 0) {
int reclen = de->d_reclen;
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation for d_reclen. The code reads de->d_reclen without checking if it's valid (e.g., if reclen > len). Looking at the pattern in lines 14171-14173, all other getdents64 loops include a check "if (reclen > len) break;" to prevent buffer overruns. Without this check, a malformed d_reclen value could cause the loop to read beyond the buffer boundaries, leading to potential crashes or security vulnerabilities.

Suggested change
int reclen = de->d_reclen;
int reclen = de->d_reclen;
if (reclen > len) {
break;
}

Copilot uses AI. Check for mistakes.
len -= reclen;
continue;
}

Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task_dir filtering path does not perform the necessary byte-swapping operations on dirent64 fields. Looking at lines 14174-14180, the else branch performs tswap16 on d_reclen, tswap64s on d_ino and d_off, and conditional tswap32 for 32-bit targets. These byte-swap operations are necessary for correct cross-architecture emulation. The task_dir path should apply the same byte-swapping to the remaining entries after filtering.

Suggested change
/* Byte-swap dirent fields for target, mirroring non-task_dir path */
de->d_reclen = tswap16(reclen);
tswap64s((uint64_t *)&de->d_ino);
tswap64s((uint64_t *)&de->d_off);
#if TARGET_ABI_BITS == 32
de->d_off = (int32_t)de->d_off; /* int32; Fix for ext4 filesystem */
de->d_ino = tswap32(de->d_ino); /* uint32; Fix for ext4 filesystem */
#endif

Copilot uses AI. Check for mistakes.
ssize_t len = readlink(proc_path, real_path, sizeof(real_path) - 1);
if (len != -1) {
real_path[len] = '\0';
if (strstr(real_path, "/proc/") && strstr(real_path, "/task")) {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detection logic using strstr could produce false positives. The current check matches any path containing both "/proc/" and "/task" anywhere in the string, which could incorrectly match paths like "/home/proc/my-task" or "/proc/cpuinfo" if someone created unusual file paths. A more robust approach would be to use a regex pattern or verify the path structure matches "/proc/[number]/task" specifically.

Copilot uses AI. Check for mistakes.
Comment on lines 5208 to 5210
case 0xe15e1: /* SCREL.W */
case 0xe15e3: /* SCREL.D */
if (no_right(siaddr, 4, PAGE_READ | PAGE_WRITE, &siaddr)) {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code checks no_right with size 4 for both SCREL.W and SCREL.D instructions. However, SCREL.D operates on 8-byte data, so the size parameter should be 8 for the SCREL.D case (0xe15e3). This could lead to incorrect permission checking when the operation spans page boundaries or when checking memory access rights.

Copilot uses AI. Check for mistakes.
Comment on lines 5233 to 5235
if (no_right(siaddr, 4, PAGE_READ | PAGE_WRITE, &siaddr)) {
info->si_addr = (void *)siaddr;
return 1;
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code checks no_right with size 4 for both LLACQ.W and LLACQ.D instructions. However, LLACQ.D operates on 8-byte data, so the size parameter should be 8 for the LLACQ.D case (0xe15e2). This could lead to incorrect permission checking when the operation spans page boundaries or when checking memory access rights.

Suggested change
if (no_right(siaddr, 4, PAGE_READ | PAGE_WRITE, &siaddr)) {
info->si_addr = (void *)siaddr;
return 1;
{
int access_size = ((inst >> 10) == 0xe15e2) ? 8 : 4;
if (no_right(siaddr, access_size, PAGE_READ | PAGE_WRITE, &siaddr)) {
info->si_addr = (void *)siaddr;
return 1;
}

Copilot uses AI. Check for mistakes.
info->si_addr = (void *)siaddr;
return 1;
}
assert(rj >= 11 && rj <= 20);
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assert statement validates that rj is between 11 and 20, but this validation should occur before using rj to extract the register value on line 5177 (rj = (inst >> 5) & 0x1f). If an invalid instruction reaches this point with rj outside this range, the code has already used rj to calculate real_guest_addr on line 5180. The assert should be moved earlier or replaced with proper error handling to prevent potential security issues from malformed instructions.

Suggested change
assert(rj >= 11 && rj <= 20);
if (rj < 11 || rj > 20) {
/*
* Malformed instruction: rj is outside the expected range.
* Do not access UC_GR(uc)[rj]; just exit this fast path.
*/
goto end;
}

Copilot uses AI. Check for mistakes.
Comment on lines +14124 to +14130

CPU_FOREACH(cpu_iter) {
TaskState *ts = (TaskState *)cpu_iter->opaque;
if (ts && valid_count < 512) {
valid_tids[valid_count++] = ts->ts_tid;
}
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CPU_FOREACH macro iterates over a shared CPU list that requires lock protection. Based on the codebase conventions (see linux-user/elfload.c:4010 and linux-user/main.c:215), CPU_FOREACH should be protected with cpu_list_lock() and cpu_list_unlock(). Without this protection, the list traversal may race with CPU additions or removals, potentially leading to crashes or incorrect behavior.

Suggested change
CPU_FOREACH(cpu_iter) {
TaskState *ts = (TaskState *)cpu_iter->opaque;
if (ts && valid_count < 512) {
valid_tids[valid_count++] = ts->ts_tid;
}
}
cpu_list_lock();
CPU_FOREACH(cpu_iter) {
TaskState *ts = (TaskState *)cpu_iter->opaque;
if (ts && valid_count < 512) {
valid_tids[valid_count++] = ts->ts_tid;
}
}
cpu_list_unlock();

Copilot uses AI. Check for mistakes.
The psx_test from libcap fails in the latx environment.
This is because psx_test scans /proc/[pid]/task to synchronize all threads.
Extra "ghost" threads introduced by the emulator cause psx_test to enter
an infinite loop while waiting for synchronization signals that never arrive.

Signed-off-by: liuchaoyi <[email protected]>
	The test program is as follows:

typedef long (*calc_func)(int val, long count);

// 48 89 f8    : mov rax, rdi
// 48 83 c0 01 : add rax, 1
// 48 ff ca    : dec rdx
// 75 f8       : jne -8
// c3          : ret
unsigned char func_code[] __attribute__((aligned(4096))) = {
    0x48, 0x89, 0xf8,       // 0-2
    0x48, 0x83, 0xc0, 0x01, // 3-6
    0x48, 0xff, 0xca,       // 7-9
    0x75, 0xf7,             // 10-11
    0xc3                    // 12
};

volatile int a_ready = 0;

void* thread_a(void* arg) {
    printf("thread_a id (LWP) = %ld\n", syscall(SYS_gettid));
    calc_func f = (calc_func)func_code;
    printf("[Thread A] circel start (100 0000 0000)...\n");
    a_ready = 1;

    unsigned long long res = f(0, 10000000000L);

    printf("[Thread A] circel end, result = %llu\n", res);
    return NULL;
}

void* thread_b(void* arg) {
    printf("thread_b id (LWP) = %ld\n", syscall(SYS_gettid));
    while(!a_ready);

    for(volatile int i=0; i<5000000; i++);

    printf("[Thread B] add 1 -> add 10...\n");
    func_code[6] = 0x0a;
    printf("[Thread B] completed\n");
    return NULL;
}

int main() {
    size_t page_size = sysconf(_SC_PAGESIZE);
    uintptr_t addr = (uintptr_t)func_code & ~(page_size - 1);
    mprotect((void*)addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);

    pthread_t ta, tb;
    pthread_create(&ta, NULL, thread_a, NULL);
    pthread_create(&tb, NULL, thread_b, NULL);
    pthread_join(ta, NULL);
    pthread_join(tb, NULL);
    return 0;
}

Signed-off-by: liuchaoyi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants