Implement proc_listpids in the kernel emulation

proc_info callnum 1 (proc_listpids) previously fell through to the unsupported-callnum path and returned ENOTSUP, so process enumeration (e.g. the Riot client's Vanguard kext probe) logged 'sys_proc_info(): Unsupported callnum: 1'.

Walk /proc like _sysctl_proc and _proc_pidinfo_listthreads already do and mirror XNU's filter semantics for PROC_ALL_PIDS, PROC_PGRP_ONLY, PROC_PPID_ONLY, PROC_TTY_ONLY, PROC_UID_ONLY and PROC_RUID_ONLY, with XNU's size-query, ENOMEM and truncation contracts.
This commit is contained in:
lucannez64 committed 2026-10-15 16:50:25 +02:00
1 parent 7bb4554e8f
commit ac4a832ebc
1 file changed
+162 -2
+162 -2
View File
@@ -3221,10 +3221,18 @@ index 0000000..479294a
+ return -ENOSYS;
+}
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c
index c2f70fb..af904ed 100644
index c2f70fb..73c2a5a 100644
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/proc_info.c
@@ -83,6 +83,38 @@ long sys_proc_info(uint32_t callnum, int32_t pid, uint32_t flavor,
@@ -35,6 +35,7 @@
#define LINUX_PR_SET_NAME 15
static long _proc_pidinfo(int32_t pid, uint32_t flavor, uint64_t arg, void* buffer, int32_t bufsize);
+static long _proc_listpids(uint32_t type, uint32_t typeinfo, void* buffer, int32_t bufsize);
extern __SIZE_TYPE__ strlen(const char *s);
extern void *memset(void *s, int c, __SIZE_TYPE__ n);
@@ -83,7 +84,42 @@ long sys_proc_info(uint32_t callnum, int32_t pid, uint32_t flavor,
case 2: // proc_pidinfo
return _proc_pidinfo(pid, flavor, arg, buffer, bufsize);
case 3: // proc_pidfdinfo
@@ -3261,8 +3269,160 @@ index c2f70fb..af904ed 100644
+ return -ENOTSUP;
+ }
case 1: // proc_listpids
+ // `pid` carries the process type and `flavor` carries the type
+ // info (e.g. the pgid, ppid or uid to match).
+ return _proc_listpids(pid, flavor, buffer, bufsize);
default:
__simple_printf("sys_proc_info(): Unsupported callnum: %d\n",
callnum);
@@ -827,3 +863,147 @@ bail:
close_internal(fd);
return count * sizeof(uint64_t);
}
+
+// Returns whether `pid` should be included in a proc_listpids() query of the
+// given `type`, matching the semantics of XNU's bsd/kern/proc_info.c.
+static bool _proc_listpids_match(uint32_t type, uint32_t typeinfo, int32_t pid,
+ int32_t uid)
+{
+ char path[64], stat[1024];
+ char* statptr;
+ const char* elem;
+ int32_t ppid, pgrp, tty;
+
+ switch (type)
+ {
+ case PROC_ALL_PIDS:
+ return true;
+
+ case PROC_UID_ONLY:
+ case PROC_RUID_ONLY:
+ // Darling's container runs every guest process as our own
+ // user, which is also what _sysctl_proc() reports.
+ return typeinfo == (uint32_t) uid;
+
+ case PROC_PGRP_ONLY:
+ case PROC_PPID_ONLY:
+ case PROC_TTY_ONLY:
+ break;
+
+ default:
+ // PROC_KDBG_ONLY and anything else: no process carries the
+ // corresponding state under Darling.
+ return false;
+ }
+
+ __simple_sprintf(path, "/proc/%d/stat", pid);
+ if (!read_string(path, stat, sizeof(stat)))
+ return false;
+
+ statptr = stat;
+ skip_stat_elems(&statptr, 1); // pid
+
+ if (!(elem = next_stat_elem(&statptr)))
+ return false;
+ // comm
+
+ if (!(elem = next_stat_elem(&statptr)))
+ return false;
+ // state
+
+ if (!(elem = next_stat_elem(&statptr)))
+ return false;
+ ppid = __simple_atoi(elem, NULL);
+
+ if (!(elem = next_stat_elem(&statptr)))
+ return false;
+ pgrp = __simple_atoi(elem, NULL);
+
+ if (type == PROC_PPID_ONLY)
+ return ppid == (int32_t) typeinfo;
+
+ if (type == PROC_PGRP_ONLY)
+ return pgrp == (int32_t) typeinfo;
+
+ // PROC_TTY_ONLY: the (Linux-encoded) controlling terminal device.
+ if (!(elem = next_stat_elem(&statptr)))
+ return false;
+ // session
+
+ if (!(elem = next_stat_elem(&statptr)))
+ return false;
+ tty = __simple_atoi(elem, NULL);
+
+ return tty != 0 && (uint32_t) tty == typeinfo;
+}
+
+// Emulates XNU's proc_listpids(), i.e. proc_info(PROC_INFO_CALL_LISTPIDS).
+// Guest pids are Linux pids, so walking /proc gives the same set that the
+// other proc_info flavors and sysctl(KERN_PROC_*) already operate on.
+static long _proc_listpids(uint32_t type, uint32_t typeinfo, void* buffer, int32_t bufsize)
+{
+ char dents[256];
+ long basep = 0;
+ int fd, ret;
+ int32_t maxpids, count = 0;
+ static int uid = -1;
+
+ if (type != PROC_ALL_PIDS && type != PROC_PGRP_ONLY && type != PROC_TTY_ONLY &&
+ type != PROC_UID_ONLY && type != PROC_RUID_ONLY &&
+ type != PROC_PPID_ONLY && type != PROC_KDBG_ONLY)
+ return -EINVAL;
+
+ if (buffer && bufsize < (int32_t) sizeof(int))
+ return -ENOMEM;
+
+ // A null buffer asks for the size a caller should allocate.
+ maxpids = buffer ? (bufsize / (int32_t) sizeof(int)) : 0;
+
+ if (uid == -1)
+ uid = sys_getuid();
+
+ fd = sys_open_nocancel("/proc", BSD_O_RDONLY | BSD_O_DIRECTORY, 0);
+ if (fd < 0)
+ return fd;
+
+ while ((ret = sys_getdirentries(fd, (char*) dents, sizeof(dents), &basep)) > 0)
+ {
+ int pos = 0;
+
+ while (pos < ret)
+ {
+ struct bsd_dirent* dent = (struct bsd_dirent*) &dents[pos];
+
+ pos += dent->d_reclen;
+
+ if (dent->d_type != DT_DIR || !isdigit(dent->d_name[0]))
+ continue;
+
+ int pid = __simple_atoi(dent->d_name, NULL);
+
+ if (!_proc_listpids_match(type, typeinfo, pid, uid))
+ continue;
+
+ if (buffer)
+ {
+ // Truncate rather than overflow, like XNU does.
+ if (count >= maxpids)
+ goto bail;
+
+ ((int*) buffer)[count] = pid;
+ }
+
+ ++count;
+ }
+ }
+
+bail:
+ close_internal(fd);
+
+ // XNU reports a little extra headroom so that a size query followed by a
+ // fill call cannot come up short when processes appear in between.
+ if (!buffer)
+ return (long)(count + 20) * sizeof(int);
+
+ return (long) count * sizeof(int);
+}
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/ptrace.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/ptrace.c
index 0a8f1e4..55aadbe 100644
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/ptrace.c