LATX, fix: fix getdents64 interference with /proc/[pid]/task#228
LATX, fix: fix getdents64 interference with /proc/[pid]/task#228rmjskhy wants to merge 2 commits intolat-opensource:masterfrom
Conversation
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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.
|
|
||
| de = dirp; | ||
| while (len > 0) { | ||
| int reclen = de->d_reclen; |
There was a problem hiding this comment.
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.
| int reclen = de->d_reclen; | |
| int reclen = de->d_reclen; | |
| if (reclen > len) { | |
| break; | |
| } |
| len -= reclen; | ||
| continue; | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| /* 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 |
| 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")) { |
There was a problem hiding this comment.
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.
accel/tcg/translate-all.c
Outdated
| case 0xe15e1: /* SCREL.W */ | ||
| case 0xe15e3: /* SCREL.D */ | ||
| if (no_right(siaddr, 4, PAGE_READ | PAGE_WRITE, &siaddr)) { |
There was a problem hiding this comment.
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.
accel/tcg/translate-all.c
Outdated
| if (no_right(siaddr, 4, PAGE_READ | PAGE_WRITE, &siaddr)) { | ||
| info->si_addr = (void *)siaddr; | ||
| return 1; |
There was a problem hiding this comment.
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.
| 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; | |
| } |
accel/tcg/translate-all.c
Outdated
| info->si_addr = (void *)siaddr; | ||
| return 1; | ||
| } | ||
| assert(rj >= 11 && rj <= 20); |
There was a problem hiding this comment.
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.
| 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; | |
| } |
|
|
||
| CPU_FOREACH(cpu_iter) { | ||
| TaskState *ts = (TaskState *)cpu_iter->opaque; | ||
| if (ts && valid_count < 512) { | ||
| valid_tids[valid_count++] = ts->ts_tid; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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(); |
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]>
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.