PRE_MEM_WRITE( "getdents(dirp)", ARG2, ARG3 );
}
+/* Check if the given file descriptor points to a /proc/PID/fd or /proc/PID/fdinfo directory.
+ Returns True if it's a directory we should filter Valgrind FDs from. */
+static Bool is_proc_fd_directory(Int fd)
+{
+ const HChar* path;
+ if (!VG_(resolve_filename)(fd, &path))
+ return False;
+
+ HChar ppfd[32]; /* large enough for /proc/<pid>/fd and /proc/<pid>/fdinfo */
+ HChar ppfdinfo[32];
+ VG_(sprintf)(ppfd, "/proc/%d/fd", VG_(getpid)());
+ VG_(sprintf)(ppfdinfo, "/proc/%d/fdinfo", VG_(getpid)());
+
+ if (VG_(strcmp)(path, "/proc/self/fd") == 0
+ || VG_(strcmp)(path, "/proc/self/fdinfo") == 0
+ || VG_(strcmp)(path, ppfd) == 0
+ || VG_(strcmp)(path, ppfdinfo) == 0)
+ return True;
+
+ return False;
+}
+
+
+/* Helper function to decide if an FD entry should be kept */
+static Bool should_keep_fd_entry(const HChar *name)
+{
+ if (VG_(strcmp)(name, ".") == 0 || VG_(strcmp)(name, "..") == 0)
+ return True;
+
+ Bool is_numeric = True;
+ for (Int i = 0; name[i] != '\0'; i++) {
+ if (name[i] < '0' || name[i] > '9') {
+ is_numeric = False;
+ if (VG_(clo_verbosity) > 1)
+ VG_(dmsg)("Warning: Non-numeric entry '%s' found in /proc/*/fd directory\n", name);
+ break;
+ }
+ }
+
+ if (is_numeric && name[0] != '\0') {
+ Int fd_num = VG_(strtoll10)(name, NULL);
+ /* Hide FDs beyond soft limit or Valgrind's output FDs */
+ if (fd_num >= VG_(fd_soft_limit) ||
+ fd_num == VG_(log_output_sink).fd ||
+ fd_num == VG_(xml_output_sink).fd)
+ return False;
+ }
+
+ return True;
+}
+
+/* Filter and compact dirent entries */
+static SizeT filter_dirent_entries(struct vki_dirent *dirp, SizeT orig_size)
+{
+ struct vki_dirent *dp = dirp;
+ struct vki_dirent *write_pos = dirp;
+ SizeT bytes_processed = 0;
+ SizeT new_size = 0;
+
+ while (bytes_processed < orig_size) {
+ if (should_keep_fd_entry(dp->d_name)) {
+ if (write_pos != dp)
+ VG_(memmove)(write_pos, dp, dp->d_reclen);
+ new_size += dp->d_reclen;
+ write_pos = (struct vki_dirent *)((HChar *)write_pos + dp->d_reclen);
+ }
+
+ bytes_processed += dp->d_reclen;
+ dp = (struct vki_dirent *)((HChar *)dp + dp->d_reclen);
+ }
+
+ return new_size;
+}
+
+/* Filter and compact dirent64 entries */
+static SizeT filter_dirent64_entries(struct vki_dirent64 *dirp, SizeT orig_size)
+{
+ struct vki_dirent64 *dp = dirp;
+ struct vki_dirent64 *write_pos = dirp;
+ SizeT bytes_processed = 0;
+ SizeT new_size = 0;
+
+ while (bytes_processed < orig_size) {
+ if (should_keep_fd_entry(dp->d_name)) {
+ if (write_pos != dp)
+ VG_(memmove)(write_pos, dp, dp->d_reclen);
+ new_size += dp->d_reclen;
+ write_pos = (struct vki_dirent64 *)((HChar *)write_pos + dp->d_reclen);
+ }
+
+ bytes_processed += dp->d_reclen;
+ dp = (struct vki_dirent64 *)((HChar *)dp + dp->d_reclen);
+ }
+
+ return new_size;
+}
+
+/* Filter out Valgrind's internal file descriptors from getdents results with refill capability.
+ When entries are filtered out, attempts to read more entries to avoid empty results.
+ Returns filtered size on success, or -1 if retry syscall failed. */
+static SizeT filter_valgrind_fds_from_getdents_with_refill(Int fd, struct vki_dirent *dirp, SizeT orig_size, SizeT buf_size, /*MOD*/SyscallStatus* status)
+{
+ SizeT new_size = filter_dirent_entries(dirp, orig_size);
+
+ /* If we filtered out everything, try to read more entries.
+ Since new_size == 0, the buffer is completely unused. */
+ while (new_size == 0) {
+ SysRes retry = VG_(do_syscall3)(__NR_getdents, fd, (UWord)dirp, buf_size);
+ if (sr_isError(retry)) {
+ /* Set the syscall status and return error indicator */
+ SET_STATUS_from_SysRes(retry);
+ return -1;
+ }
+ if (sr_Res(retry) == 0) {
+ /* Real EOF - stop trying */
+ break;
+ }
+
+ /* Filter the new batch */
+ SizeT retry_size = sr_Res(retry);
+ new_size = filter_dirent_entries(dirp, retry_size);
+ }
+
+ return new_size;
+}
+
+/* Filter out Valgrind's internal file descriptors from getdents64 results with refill capability.
+ Same logic as getdents version but for 64-bit dirent structures.
+ Returns filtered size on success, or -1 if retry syscall failed. */
+static SizeT filter_valgrind_fds_from_getdents64_with_refill(Int fd, struct vki_dirent64 *dirp, SizeT orig_size, SizeT buf_size, /*MOD*/SyscallStatus* status)
+{
+ SizeT new_size = filter_dirent64_entries(dirp, orig_size);
+
+ /* If we filtered out everything, try to read more entries.
+ Since new_size == 0, the buffer is completely unused. */
+ while (new_size == 0) {
+ SysRes retry = VG_(do_syscall3)(__NR_getdents64, fd, (UWord)dirp, buf_size);
+ if (sr_isError(retry)) {
+ /* Set the syscall status and return error indicator */
+ SET_STATUS_from_SysRes(retry);
+ return -1;
+ }
+ if (sr_Res(retry) == 0) {
+ /* Real EOF - stop trying */
+ break;
+ }
+
+ /* Filter the new batch */
+ SizeT retry_size = sr_Res(retry);
+ new_size = filter_dirent64_entries(dirp, retry_size);
+ }
+
+ return new_size;
+}
+
POST(sys_getdents)
{
vg_assert(SUCCESS);
- if (RES > 0)
- POST_MEM_WRITE( ARG2, RES );
+ if (RES > 0) {
+ SizeT result_size = RES;
+
+ /* Only filter Valgrind FDs when listing /proc/PID/fd or /proc/PID/fdinfo directories */
+ if (is_proc_fd_directory(ARG1)) {
+ SizeT filtered_size = filter_valgrind_fds_from_getdents_with_refill(ARG1, (struct vki_dirent *)ARG2, RES, ARG3, status);
+ if ((SizeT)filtered_size == (SizeT)-1) {
+ /* Error already set by filter function */
+ return;
+ }
+ result_size = filtered_size;
+ if (result_size != RES)
+ SET_STATUS_Success(result_size);
+ }
+
+ POST_MEM_WRITE( ARG2, result_size );
+ }
}
PRE(sys_getdents64)
POST(sys_getdents64)
{
vg_assert(SUCCESS);
- if (RES > 0)
- POST_MEM_WRITE( ARG2, RES );
+ if (RES > 0) {
+ SizeT result_size = RES;
+
+ /* Only filter Valgrind FDs when listing /proc/PID/fd or /proc/PID/fdinfo directories */
+ if (is_proc_fd_directory(ARG1)) {
+ SizeT filtered_size = filter_valgrind_fds_from_getdents64_with_refill(ARG1, (struct vki_dirent64 *)ARG2, RES, ARG3, status);
+ if ((SizeT)filtered_size == (SizeT)-1) {
+ /* Error already set by filter function */
+ return;
+ }
+ result_size = filtered_size;
+ if (result_size != RES)
+ SET_STATUS_Success(result_size);
+ }
+
+ POST_MEM_WRITE( ARG2, result_size );
+ }
}
PRE(sys_getgroups)
--- /dev/null
+/* Regression test for bug #331311: Valgrind file descriptors visible in /proc/self/fd and /proc/self/fdinfo */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+
+struct linux_dirent {
+ unsigned long d_ino;
+ off_t d_off;
+ unsigned short d_reclen;
+ char d_name[];
+};
+
+/* Small buffer size to test retry logic when all entries get filtered */
+#define SMALL_BUF_SIZE 64
+
+/* Helper function to scan a /proc directory and print numeric FD entries with a prefix.
+ Returns 0 on success, -1 if directory unavailable */
+static int scan_proc_fd_directory(const char *dir_path, const char *prefix)
+{
+ DIR *d = opendir(dir_path);
+ if (d == NULL) {
+ printf("%s not available\n", dir_path);
+ return -1;
+ }
+
+ struct dirent *entry;
+ while ((entry = readdir(d)) != NULL) {
+ if (entry->d_name[0] == '.')
+ continue;
+
+ char *endptr;
+ long fd = strtol(entry->d_name, &endptr, 10);
+ if (*endptr == '\0' && fd >= 0) {
+ if (prefix) {
+ printf("%s:%ld\n", prefix, fd);
+ } else {
+ printf("%ld\n", fd);
+ }
+ }
+ }
+ closedir(d);
+ return 0;
+}
+
+/*
+ * Test retry logic with raw getdents syscall and small buffer.
+ *
+ * This test validates the filtering refill mechanism in syswrap-generic.c.
+ * When using a tiny buffer (64 bytes), getdents may return only Valgrind FDs
+ * in a single call. The filtering code should detect this (new_size == 0) and
+ * automatically retry the syscall to get more entries until it finds client FDs
+ * or reaches EOF. This prevents the client from seeing empty results when
+ * Valgrind FDs get filtered out.
+ */
+static void test_retry_logic_with_small_buffer(void)
+{
+ int fd;
+ char buf[SMALL_BUF_SIZE];
+ long nread;
+ struct linux_dirent *d;
+
+ printf("retry_test_start\n");
+
+ fd = open("/proc/self/fd", O_RDONLY | O_DIRECTORY);
+ if (fd == -1) {
+ printf("retry_test_unavailable\n");
+ return;
+ }
+
+ /*
+ * Use raw getdents syscall with tiny 64-byte buffer. This forces multiple
+ * syscalls since each directory entry is typically 20+ bytes. Some calls
+ * may return only Valgrind FDs, which will trigger the retry mechanism.
+ */
+ for (;;) {
+ nread = syscall(SYS_getdents, fd, buf, SMALL_BUF_SIZE);
+
+ if (nread == -1) {
+ printf("retry_test_error\n");
+ close(fd);
+ return;
+ }
+
+ if (nread == 0) {
+ break; /* EOF - no more entries */
+ }
+
+ /* Print client FD entries found in this buffer (excluding . and ..) */
+ for (size_t bpos = 0; bpos < nread;) {
+ d = (struct linux_dirent *)(buf + bpos);
+ if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
+ char *endptr;
+ long fd_num = strtol(d->d_name, &endptr, 10);
+ if (*endptr == '\0' && fd_num >= 0) {
+ printf("retry:%ld\n", fd_num);
+ }
+ }
+ bpos += d->d_reclen;
+ }
+ }
+
+ close(fd);
+ printf("retry_test_end\n");
+}
+
+int main(void)
+{
+ /* Test 0: Retry logic with small buffer (tests the filtering refill mechanism) */
+ test_retry_logic_with_small_buffer();
+
+ /* Test 1: /proc/self/fd should have Valgrind FDs filtered out */
+ if (scan_proc_fd_directory("/proc/self/fd", NULL) == -1) {
+ return 0; /* Skip remaining tests if /proc/self/fd unavailable */
+ }
+
+ /* Test 2: /proc/self/fdinfo should have Valgrind FDs filtered out */
+ scan_proc_fd_directory("/proc/self/fdinfo", "fdinfo");
+
+ /* Test 3: Regular directory should show all numbered files */
+ DIR *d;
+ struct dirent *entry;
+ if (mkdir("testdir", 0755) == 0) {
+ FILE *f1 = fopen("testdir/1000", "w");
+ FILE *f2 = fopen("testdir/1001", "w");
+ if (f1) { fprintf(f1, "test"); fclose(f1); }
+ if (f2) { fprintf(f2, "test"); fclose(f2); }
+
+ d = opendir("testdir");
+ if (d != NULL) {
+ while ((entry = readdir(d)) != NULL) {
+ if (entry->d_name[0] != '.') {
+ char *endptr;
+ long num = strtol(entry->d_name, &endptr, 10);
+ if (*endptr == '\0' && num >= 1000) {
+ printf("regular:%ld\n", num);
+ }
+ }
+ }
+ closedir(d);
+ }
+
+ unlink("testdir/1000");
+ unlink("testdir/1001");
+ rmdir("testdir");
+ }
+
+
+ return 0;
+}