Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 112 additions & 22 deletions linux-user/syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -14218,35 +14218,125 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1,
#endif /* TARGET_NR_getdents */
#if defined(TARGET_NR_getdents64) && defined(__NR_getdents64)
case TARGET_NR_getdents64:
{
struct linux_dirent64 *dirp;
abi_long count = arg3;
if (!(dirp = lock_user(VERIFY_WRITE, arg2, count, 0)))
return -TARGET_EFAULT;
ret = get_errno(sys_getdents64(arg1, dirp, count));
if (!is_error(ret)) {
struct linux_dirent64 *de;
int len = ret;
int reclen;
{
char proc_path[PATH_MAX];
char real_path[PATH_MAX];
int fd = arg1;
bool is_task_dir = false;

snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", fd);

ssize_t path_len = readlink(proc_path, real_path, sizeof(real_path) - 1);

if (path_len > 0) {
real_path[path_len] = '\0';
int pid;
char extra[2];

if (sscanf(real_path, "/proc/%d/task%1s", &pid, extra) == 1) {
#ifdef CONFIG_LATX_DEBUG
fprintf(stderr, "latx: Detected task directory: %s\n", real_path);
#endif
is_task_dir = true;
}
}

struct linux_dirent64 *dirp;
abi_long count = arg3;

if (!(dirp = lock_user(VERIFY_WRITE, arg2, count, 0)))
return -TARGET_EFAULT;
ret = get_errno(sys_getdents64(arg1, dirp, count));
if (!is_error(ret)) {
struct linux_dirent64 *de;
int len = ret;
/*
* Filter ghost threads only for /proc/<pid>/task
*/
if (is_task_dir) {

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.
int valid_count = 0;
CPUState *cpu_iter;

CPU_FOREACH(cpu_iter) {
TaskState *ts = (TaskState *)cpu_iter->opaque;
if (ts && valid_count < 512) {
valid_tids[valid_count++] = ts->ts_tid;
}
}
Comment on lines +14261 to +14267
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.

de = dirp;

while (len > 0) {
reclen = de->d_reclen;
if (reclen > len)
int reclen = tswap16(de->d_reclen);
if (reclen <= 0 || reclen > len)
break;
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
de = (struct linux_dirent64 *)((char *)de + reclen);
bool should_hide = true;

if (!strcmp(de->d_name, ".") ||
!strcmp(de->d_name, "..")) {
should_hide = false;
} else {
char *endptr;
long curr_tid = strtol(de->d_name, &endptr, 10);

if (*endptr == '\0') {
for (int i = 0; i < valid_count; i++) {
if (curr_tid == valid_tids[i]) {
should_hide = false;
break;
}
}
} else {
should_hide = false;
}
}

if (should_hide) {
#ifdef CONFIG_LATX_DEBUG
fprintf(stderr, "latx: Hiding ghost thread " "TID: %s\n", de->d_name);
#endif
int remaining_len = len - reclen;

if (remaining_len > 0) {
memmove(de, (char *)de + reclen, remaining_len);
}
ret -= reclen;
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.
de = (struct linux_dirent64 *) ((char *)de + reclen);
len -= reclen;
}
}
unlock_user(dirp, arg2, ret);

/*
* Common endian conversion
*/
de = dirp;
len = ret;

while (len > 0) {
int reclen = de->d_reclen;
if (reclen > len || reclen <= 0)
break;
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;
de->d_ino = tswap32(de->d_ino);
#endif
de = (struct linux_dirent64 *) ((char *)de + reclen);
len -= reclen;
}
}
return ret;

unlock_user(dirp, arg2, ret);
}
return ret;
#endif /* TARGET_NR_getdents64 */
#if defined(TARGET_NR__newselect)
case TARGET_NR__newselect:
Expand Down