From nobody Thu Apr 30 01:47:41 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1776991462; cv=none; d=zohomail.com; s=zohoarc; b=PeCMff7vZhKpJpGKOxVl0hPltRM+A2+y5zSNZMyrMHxq7U0TTusZmuMuMBOuPOnaKu2hgvjHSvT8bvcTHtSa+A1NeiIvlsLRMYHdAuN9jaDX7frJkS3GR8tc69rEHtmJ63D6fidfBybXGaLGP+/X8rlKnsNjNe4C3flsDoDv3v0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1776991462; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=uA+3oshFCqGtJFNZ2+y4Ee11ofsxVMEKZKGagbGyBPI=; b=C67F68uLIXWREtoX83Ab7HRi76wDpPX3bBN3a2bqKtUIQiZ/OXvx83ZGqBwlJkU8CdZmU7FJ0YCE4QBP3hi/bDfT37qL1NJeJzlRtVuAYe8eEWfJ4xQSrLVwLVHxwScq76AvQh63lijM57L2uZ6VUTL6wuCwbRiB+6budUxqgBw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 177699146227742.00218274354427; Thu, 23 Apr 2026 17:44:22 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wG4ct-0004TK-El; Thu, 23 Apr 2026 20:42:35 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wG1jl-0002Zb-O6 for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:29 -0400 Received: from mail-wm1-x330.google.com ([2a00:1450:4864:20::330]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wG1jj-00017U-6d for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:29 -0400 Received: by mail-wm1-x330.google.com with SMTP id 5b1f17b1804b1-4891c569cb1so7275175e9.2 for ; Thu, 23 Apr 2026 14:37:26 -0700 (PDT) Received: from [10.94.10.196] ([223.123.19.204]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48919f54572sm138755225e9.26.2026.04.23.14.37.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Apr 2026 14:37:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776980246; x=1777585046; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=uA+3oshFCqGtJFNZ2+y4Ee11ofsxVMEKZKGagbGyBPI=; b=h7QPoEsFz+c550CVT9mFvjMSK8viWS6jD1E+VaX17WgYxwJJTYPx5H/f9xOWKSrEi3 0Fv5tEekXtRaCww8G8vOVSSjYyjzMc2u7Nep2DC+SIE5QX2crpZYJwjYtzicz4rJng+N ZywE+1Q6C3oPAjl4h2imZGQ9YJlMHG2C8skR33vKSvnULC5EMnUp56neqDxCMUeybm0d jiydv3g3HBhR9b5fLqTKEG+yDvXZKKf0X/MR/+e6escq4NtjPECiaqXpEjxCSMC6C1Ps 614iz5KsoKXx6SoNjfkSncz+4hF/RFrfulS6P9ybpJoyQQXCkAZlw6XE7+lOZduCrd5x rOXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776980246; x=1777585046; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=uA+3oshFCqGtJFNZ2+y4Ee11ofsxVMEKZKGagbGyBPI=; b=eIDby3b9Na6o+8djwW4sLDEtK/N9CKSnURdL9lpfiViydOe0vGeJ7WiEGceeLVQmit dOxjHXE+ybvfJPRlGUiaAWyjcWfq6HGpOY3hO85Bh7TQDTtWLHb5hfYRX3sKzI0Pc4t8 5PQQK3n9vW0iiFIy906SYoXL6zypQeZE4pNOJ1uldc+zDsgGjSUb6L63rLo0HCu1wdpB hTuehaqjmSNvXn5jjOsjLnMCiFXXFaeL3iepDM2vrPKTIdte2KYSiC0Y9CncktZrzQrM gz/nsC68AzE1MM4/2SE1CEvb7Fll9qcAgm22uIG4JC3KZ0VMXHQvakn1FiRLi7+1q3hW gRJQ== X-Gm-Message-State: AOJu0YwGhrEsnA/uktpQ4R1iJ2mzlU3C+j1IkTezkFK73f1LIiPFN/N1 gVGxw9zlIrg/DfuvB2jdszi1b0S2moHvkzx/m/hk+X6oxPukEiuJG3ZCDKZBZGMD X-Gm-Gg: AeBDievndWLorc8/6XhkmY5xxa8lVO8VER13NXi4rej92CLB546xc6xkbgTkWKn3gXw VQnqeY8wYZmVOmPuKs6NkyX4RpXY3QDZXR7n0Tp0jfAzNrNJpZdMN/8Ruq8JNrcGc00R2Ope5K0 gDCL6qCqIxy8S7QGgvmWpx0gVwsE0KloCxCt/bpkERkLxtjsaQenx2sMO7cI8W1JtD+7dDgYwHD eyT3fEOIsmmYiPi7yIoSmtivXe+KPzOI9BmA6MN2DjsZBLS1OIYnI1N6dk5GsEuKaeS9IiMWUVy /+ZPFA6YMaUIv/IWWx6pisyptCW3qBWADLAXuJuqDDs4AmAh09Mm/We67X3xRH5e5loN2ynKcRU po6bwOzzWYjBPz2l3JEeG79jMdpkRXOy8GunOqzUCIQj6GhPZXWNNa2bdcBP0ezTBW0g4c1RBTk saYagmwfhiS/yvAoBsx8KfGa4hzg2oXg/V1GLLlfGmUQ== X-Received: by 2002:a05:600c:1f82:b0:48a:5546:619e with SMTP id 5b1f17b1804b1-48a55466251mr92148725e9.4.1776980245251; Thu, 23 Apr 2026 14:37:25 -0700 (PDT) From: Ali Raza Date: Fri, 24 Apr 2026 02:36:38 +0500 Subject: [PATCH v2 1/3] linux-user: Filter /proc/*/task/ to hide QEMU-internal threads MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260424-master-v2-1-8b50b5c063ed@gmail.com> References: <20260424-master-v2-0-8b50b5c063ed@gmail.com> In-Reply-To: <20260424-master-v2-0-8b50b5c063ed@gmail.com> To: qemu-devel@nongnu.org Cc: Ali Raza , Laurent Vivier , Pierrick Bouvier , =?utf-8?q?Alex_Benn=C3=A9e?= , deller@gmx.de X-Mailer: b4 0.15.2 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::330; envelope-from=elirazamumtaz@gmail.com; helo=mail-wm1-x330.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Thu, 23 Apr 2026 20:42:33 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1776991464209154100 When a guest process reads /proc//task/ via getdents/getdents64, the host kernel returns directory entries for all host threads in the process, including QEMU-internal threads (RCU, TCG workers) that have no guest CPUState. This causes problems for guest libraries like libcap's PSX that enumerate threads via /proc/self/task/ and send signals to each one -- signals sent to QEMU-internal threads are never handled, leading to deadlocks. Add filtering to do_getdents() and do_getdents64() that detects when the fd refers to a /proc//task/ directory and skips entries whose TID does not belong to a guest thread. Guest thread TIDs are identified by iterating the CPU list via CPU_FOREACH(), matching the existing pattern used for /proc/self/stat num_threads. This fixes the psx_test hang reported in containers using QEMU user-mode emulation for cross-architecture builds. Resolves: https://github.com/AndrewGMorgan/libcap_mirror/issues/6 Signed-off-by: Ali Raza (@locus-x64) --- Changes in v2: - Use pid_t instead of long for the parsed tid in do_getdents() and do_getdents64() (suggested by Helge Deller). - Replace the ad-hoc readlink-at-getdents check with fd_trans-based tagging at open time, eliminating a TOCTOU race and a broad-prefix match that also matched /proc//task//fd. - Retry sys_getdents when an entire host batch is filtered out, so the guest does not see a false EOF. - Parse the dirent TID with qemu_strtoi() instead of strtol() so out-of-range values cannot wrap silently into pid_t. --- linux-user/fd-trans.c | 6 + linux-user/fd-trans.h | 18 +++ linux-user/syscall.c | 298 +++++++++++++++++++++++++++++++++++++---------= ---- 3 files changed, 244 insertions(+), 78 deletions(-) diff --git a/linux-user/fd-trans.c b/linux-user/fd-trans.c index 64dd0745d2..6bad892b78 100644 --- a/linux-user/fd-trans.c +++ b/linux-user/fd-trans.c @@ -1881,3 +1881,9 @@ static abi_long host_to_target_data_inotify(void *buf= , size_t len) TargetFdTrans target_inotify_trans =3D { .host_to_target_data =3D host_to_target_data_inotify, }; + +/* + * No callbacks: this trans entry is purely a marker to identify fds + * that refer to our own /proc//task directory. See getdents(). + */ +TargetFdTrans target_proc_pid_task_trans; diff --git a/linux-user/fd-trans.h b/linux-user/fd-trans.h index 3bbc15fa1f..76b8c35374 100644 --- a/linux-user/fd-trans.h +++ b/linux-user/fd-trans.h @@ -142,4 +142,22 @@ extern TargetFdTrans target_signalfd_trans; extern TargetFdTrans target_eventfd_trans; extern TargetFdTrans target_timerfd_trans; extern TargetFdTrans target_inotify_trans; + +/* + * Marker (no callbacks) used to tag fds that refer to /proc//task + * for our own process. Used by getdents to filter out QEMU-internal + * host threads (RCU, TCG workers) that have no guest CPUState. + */ +extern TargetFdTrans target_proc_pid_task_trans; + +static inline bool fd_trans_is_proc_pid_task(int fd) +{ + if (fd < 0) { + return false; + } + + QEMU_LOCK_GUARD(&target_fd_trans_lock); + return fd < target_fd_max + && target_fd_trans[fd] =3D=3D &target_proc_pid_task_trans; +} #endif diff --git a/linux-user/syscall.c b/linux-user/syscall.c index f4b74ad350..44f2cd851f 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -8870,6 +8870,7 @@ static int do_openat2(CPUArchState *cpu_env, abi_long= dirfd, } =20 fd_trans_unregister(ret); + maybe_tag_proc_pid_task(ret); unlock_user(pathname, guest_pathname, 0); return ret; } @@ -9108,6 +9109,58 @@ static int host_to_target_cpu_mask(const unsigned lo= ng *host_mask, return 0; } =20 +/* + * If the freshly-opened host fd refers to our own /proc//task + * directory, register a marker on it via fd_trans so getdents() can + * later filter out QEMU-internal host threads (RCU, TCG workers) that + * have no guest CPUState. Tagging at open time (rather than at + * getdents time) avoids TOCTOU on the fd's resolved path and lets us + * piggy-back on the existing fd_trans infrastructure for dup/close. + */ +static void maybe_tag_proc_pid_task(int fd) +{ + char link_path[64]; + char link_target[PATH_MAX]; + char expected[64]; + ssize_t len; + int expected_len; + + if (fd < 0) { + return; + } + + snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd); + len =3D readlink(link_path, link_target, sizeof(link_target) - 1); + if (len < 0) { + return; + } + link_target[len] =3D '\0'; + + expected_len =3D snprintf(expected, sizeof(expected), + "/proc/%d/task", getpid()); + if (expected_len > 0 && (ssize_t)expected_len =3D=3D len + && memcmp(link_target, expected, expected_len) =3D=3D 0) { + fd_trans_register(fd, &target_proc_pid_task_trans); + } +} + +/* + * Check if a given host TID belongs to a guest thread by looking it up + * in the CPU list. Must be called under RCU read lock. + */ +static bool is_guest_tid(pid_t tid) +{ + CPUState *cpu; + + CPU_FOREACH(cpu) { + TaskState *ts =3D get_task_state(cpu); + if (ts->ts_tid =3D=3D tid) { + return true; + } + } + return false; +} + #ifdef TARGET_NR_getdents static int do_getdents(abi_long dirfd, abi_long arg2, abi_long count) { @@ -9116,78 +9169,123 @@ static int do_getdents(abi_long dirfd, abi_long ar= g2, abi_long count) int hlen, hoff, toff; int hreclen, treclen; off_t prev_diroff =3D 0; + bool filter_task =3D fd_trans_is_proc_pid_task(dirfd); + bool stop =3D false; =20 hdirp =3D g_try_malloc(count); if (!hdirp) { return -TARGET_ENOMEM; } =20 -#ifdef EMULATE_GETDENTS_WITH_GETDENTS - hlen =3D sys_getdents(dirfd, hdirp, count); -#else - hlen =3D sys_getdents64(dirfd, hdirp, count); -#endif - - hlen =3D get_errno(hlen); - if (is_error(hlen)) { - return hlen; - } - tdirp =3D lock_user(VERIFY_WRITE, arg2, count, 0); if (!tdirp) { return -TARGET_EFAULT; } =20 - for (hoff =3D toff =3D 0; hoff < hlen; hoff +=3D hreclen, toff +=3D tr= eclen) { + toff =3D 0; + /* + * Loop until we either produce a visible target entry, hit a + * real EOF (host returns 0), or hit an error. Without this + * retry, a host batch consisting entirely of filtered entries + * would be reported to the guest as EOF. + */ + while (!stop) { #ifdef EMULATE_GETDENTS_WITH_GETDENTS - struct linux_dirent *hde =3D hdirp + hoff; + hlen =3D sys_getdents(dirfd, hdirp, count); #else - struct linux_dirent64 *hde =3D hdirp + hoff; + hlen =3D sys_getdents64(dirfd, hdirp, count); #endif - struct target_dirent *tde =3D tdirp + toff; - int namelen; - uint8_t type; + hlen =3D get_errno(hlen); + if (is_error(hlen)) { + unlock_user(tdirp, arg2, 0); + return hlen; + } + if (hlen =3D=3D 0) { + break; /* real EOF */ + } =20 - namelen =3D strlen(hde->d_name); - hreclen =3D hde->d_reclen; - treclen =3D offsetof(struct target_dirent, d_name) + namelen + 2; - treclen =3D QEMU_ALIGN_UP(treclen, __alignof(struct target_dirent)= ); + for (hoff =3D 0; hoff < hlen; hoff +=3D hreclen, toff +=3D treclen= ) { +#ifdef EMULATE_GETDENTS_WITH_GETDENTS + struct linux_dirent *hde =3D hdirp + hoff; +#else + struct linux_dirent64 *hde =3D hdirp + hoff; +#endif + struct target_dirent *tde =3D tdirp + toff; + int namelen; + uint8_t type; + + namelen =3D strlen(hde->d_name); + hreclen =3D hde->d_reclen; =20 - if (toff + treclen > count) { /* - * If the host struct is smaller than the target struct, or - * requires less alignment and thus packs into less space, - * then the host can return more entries than we can pass - * on to the guest. + * Filter /proc//task/ listings to hide QEMU-internal + * host threads (RCU, TCG) that have no guest CPUState. */ - if (toff =3D=3D 0) { - toff =3D -TARGET_EINVAL; /* result buffer is too small */ + if (filter_task) { + int tid_int; + if (qemu_strtoi(hde->d_name, NULL, 10, &tid_int) =3D=3D 0 + && tid_int > 0) { + pid_t tid =3D (pid_t)tid_int; + bool drop; + + WITH_RCU_READ_LOCK_GUARD() { + drop =3D !is_guest_tid(tid); + } + if (drop) { + treclen =3D 0; + continue; + } + } + } + treclen =3D offsetof(struct target_dirent, d_name) + namelen += 2; + treclen =3D QEMU_ALIGN_UP(treclen, __alignof(struct target_dir= ent)); + + if (toff + treclen > count) { + /* + * If the host struct is smaller than the target struct, or + * requires less alignment and thus packs into less space, + * then the host can return more entries than we can pass + * on to the guest. + */ + if (toff =3D=3D 0) { + toff =3D -TARGET_EINVAL; /* result buffer is too small= */ + stop =3D true; + break; + } + /* + * Return what we have, resetting the file pointer to the + * location of the first record not returned. + */ + lseek(dirfd, prev_diroff, SEEK_SET); + stop =3D true; break; } + + prev_diroff =3D hde->d_off; + tde->d_ino =3D tswapal(hde->d_ino); + tde->d_off =3D tswapal(hde->d_off); + tde->d_reclen =3D tswap16(treclen); + memcpy(tde->d_name, hde->d_name, namelen + 1); + /* - * Return what we have, resetting the file pointer to the - * location of the first record not returned. + * The getdents type is in what was formerly a padding byte at= the + * end of the structure. */ - lseek(dirfd, prev_diroff, SEEK_SET); - break; +#ifdef EMULATE_GETDENTS_WITH_GETDENTS + type =3D *((uint8_t *)hde + hreclen - 1); +#else + type =3D hde->d_type; +#endif + *((uint8_t *)tde + treclen - 1) =3D type; } =20 - prev_diroff =3D hde->d_off; - tde->d_ino =3D tswapal(hde->d_ino); - tde->d_off =3D tswapal(hde->d_off); - tde->d_reclen =3D tswap16(treclen); - memcpy(tde->d_name, hde->d_name, namelen + 1); - /* - * The getdents type is in what was formerly a padding byte at the - * end of the structure. + * If we produced any visible target entry, return; otherwise + * loop again to fetch more host entries (avoid false EOF). */ -#ifdef EMULATE_GETDENTS_WITH_GETDENTS - type =3D *((uint8_t *)hde + hreclen - 1); -#else - type =3D hde->d_type; -#endif - *((uint8_t *)tde + treclen - 1) =3D type; + if (toff !=3D 0) { + break; + } } =20 unlock_user(tdirp, arg2, toff); @@ -9203,57 +9301,99 @@ static int do_getdents64(abi_long dirfd, abi_long a= rg2, abi_long count) int hlen, hoff, toff; int hreclen, treclen; off_t prev_diroff =3D 0; + bool filter_task =3D fd_trans_is_proc_pid_task(dirfd); + bool stop =3D false; =20 hdirp =3D g_try_malloc(count); if (!hdirp) { return -TARGET_ENOMEM; } =20 - hlen =3D get_errno(sys_getdents64(dirfd, hdirp, count)); - if (is_error(hlen)) { - return hlen; - } - tdirp =3D lock_user(VERIFY_WRITE, arg2, count, 0); if (!tdirp) { return -TARGET_EFAULT; } =20 - for (hoff =3D toff =3D 0; hoff < hlen; hoff +=3D hreclen, toff +=3D tr= eclen) { - struct linux_dirent64 *hde =3D hdirp + hoff; - struct target_dirent64 *tde =3D tdirp + toff; - int namelen; + toff =3D 0; + /* + * Loop until we either produce a visible target entry, hit a + * real EOF (host returns 0), or hit an error. Without this + * retry, a host batch consisting entirely of filtered entries + * would be reported to the guest as EOF. + */ + while (!stop) { + hlen =3D get_errno(sys_getdents64(dirfd, hdirp, count)); + if (is_error(hlen)) { + unlock_user(tdirp, arg2, 0); + return hlen; + } + if (hlen =3D=3D 0) { + break; /* real EOF */ + } + + for (hoff =3D 0; hoff < hlen; hoff +=3D hreclen, toff +=3D treclen= ) { + struct linux_dirent64 *hde =3D hdirp + hoff; + struct target_dirent64 *tde =3D tdirp + toff; + int namelen; =20 - namelen =3D strlen(hde->d_name) + 1; - hreclen =3D hde->d_reclen; - treclen =3D offsetof(struct target_dirent64, d_name) + namelen; - treclen =3D QEMU_ALIGN_UP(treclen, __alignof(struct target_dirent6= 4)); + namelen =3D strlen(hde->d_name) + 1; + hreclen =3D hde->d_reclen; =20 - if (toff + treclen > count) { /* - * If the host struct is smaller than the target struct, or - * requires less alignment and thus packs into less space, - * then the host can return more entries than we can pass - * on to the guest. + * Filter /proc//task/ listings to hide QEMU-internal + * host threads (RCU, TCG) that have no guest CPUState. */ - if (toff =3D=3D 0) { - toff =3D -TARGET_EINVAL; /* result buffer is too small */ + if (filter_task) { + int tid_int; + if (qemu_strtoi(hde->d_name, NULL, 10, &tid_int) =3D=3D 0 + && tid_int > 0) { + pid_t tid =3D (pid_t)tid_int; + bool drop; + + WITH_RCU_READ_LOCK_GUARD() { + drop =3D !is_guest_tid(tid); + } + if (drop) { + treclen =3D 0; + continue; + } + } + } + treclen =3D offsetof(struct target_dirent64, d_name) + namelen; + treclen =3D QEMU_ALIGN_UP(treclen, __alignof(struct target_dir= ent64)); + + if (toff + treclen > count) { + /* + * If the host struct is smaller than the target struct, or + * requires less alignment and thus packs into less space, + * then the host can return more entries than we can pass + * on to the guest. + */ + if (toff =3D=3D 0) { + toff =3D -TARGET_EINVAL; /* result buffer is too small= */ + stop =3D true; + break; + } + /* + * Return what we have, resetting the file pointer to the + * location of the first record not returned. + */ + lseek(dirfd, prev_diroff, SEEK_SET); + stop =3D true; break; } - /* - * Return what we have, resetting the file pointer to the - * location of the first record not returned. - */ - lseek(dirfd, prev_diroff, SEEK_SET); - break; + + prev_diroff =3D hde->d_off; + tde->d_ino =3D tswap64(hde->d_ino); + tde->d_off =3D tswap64(hde->d_off); + tde->d_reclen =3D tswap16(treclen); + tde->d_type =3D hde->d_type; + memcpy(tde->d_name, hde->d_name, namelen); } =20 - prev_diroff =3D hde->d_off; - tde->d_ino =3D tswap64(hde->d_ino); - tde->d_off =3D tswap64(hde->d_off); - tde->d_reclen =3D tswap16(treclen); - tde->d_type =3D hde->d_type; - memcpy(tde->d_name, hde->d_name, namelen); + if (toff !=3D 0) { + break; + } } =20 unlock_user(tdirp, arg2, toff); @@ -9732,6 +9872,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, in= t num, abi_long arg1, target_to_host_bitmask(arg2, fcntl_flags= _tbl), arg3, true)); fd_trans_unregister(ret); + maybe_tag_proc_pid_task(ret); unlock_user(p, arg1, 0); return ret; #endif @@ -9742,6 +9883,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, in= t num, abi_long arg1, target_to_host_bitmask(arg3, fcntl_flags= _tbl), arg4, true)); fd_trans_unregister(ret); + maybe_tag_proc_pid_task(ret); unlock_user(p, arg2, 0); return ret; case TARGET_NR_openat2: --=20 2.43.0 From nobody Thu Apr 30 01:47:41 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1776991454; cv=none; d=zohomail.com; s=zohoarc; b=ZkDWgE58xRvZiT3r3BWNCqIfL34L2WyNqownsIqFMywWXmmQ4Uoasl8Bb71pKwYuETYAfNbfLrtw2t4tI7xjKGVxlYH9lgsSnweBqYSg0g54r9SSfNdLwE+cXd285y7F0PXeKxtq7SKvqu3su5Vu4HplP3j/9NIdnpd3P7iNDJE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1776991454; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=U7LovPah/QE6KW73zZhcUInjXBKP1mVggfWfskBESs4=; b=JkcnoRkQYu6bJgW68NoHmGLOVM1tgEbFT6VJ913Kxxxn1iccjJxTJ7UXwy3WQ8D9VAYcGhSnknhvehR5QLCERA5KZeDahLKAnA1mugNS6kFh4b7U395QjcnA2aLxYYWo5hNInWvtUShRbmgAm3GaioYiIL3wH72FE1L7vgfIoP4= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1776991454534585.769045619609; Thu, 23 Apr 2026 17:44:14 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wG4cw-0004U7-49; Thu, 23 Apr 2026 20:42:38 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wG1jo-0002Zz-JS for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:32 -0400 Received: from mail-wr1-x42e.google.com ([2a00:1450:4864:20::42e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wG1jm-000188-V0 for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:32 -0400 Received: by mail-wr1-x42e.google.com with SMTP id ffacd0b85a97d-43d7b879691so949512f8f.1 for ; Thu, 23 Apr 2026 14:37:30 -0700 (PDT) Received: from [10.94.10.196] ([223.123.19.204]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48919f54572sm138755225e9.26.2026.04.23.14.37.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Apr 2026 14:37:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776980249; x=1777585049; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=U7LovPah/QE6KW73zZhcUInjXBKP1mVggfWfskBESs4=; b=gEQWWbK8+P3TiWRgMBVl+hKy+CG8W5NmbwXB6Ph6cebqPIREarfkrEcsulsO4CJGlu Fud+OJteYc2OaWua2KZHPcHv/jhI9TnQ1kjk0b7gGDtYNBouIrQUPzbByoqumAvU1VDd tAQ2vb8LxHog83rcBBmWWLhn90i+CJG6qargJLRFOqTlWkbZt8h0bf4SNayh/eYJzbK3 vVJY5U57j6PakLHZSgRKUoSfzvD15Us1p44Rr8LVoqg6pzFZ5Se8ty/vwEDmFdjCLPbh VXA1yZNAPG6767WGuLtwbsWjrnGkL/AiQxX8z9ldgS+KKrJth1Bh/7/yM1qKTllPRcj6 GSSA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776980249; x=1777585049; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=U7LovPah/QE6KW73zZhcUInjXBKP1mVggfWfskBESs4=; b=OmOdaXBh/oQES9dpVJ5Pk6rSNO9TNUbsByLkIwacfiuPBadD3dT4TfP4Y0cYWT9pk+ RK6JU4RyXbwNEhtjQDrjlkWF5vQGUgDC79ZWFD+7nmMD/wtd8gkqlgZdB/LqimAq8DTo oKffIpbGKPJ//z4xcq1E+gI/EdSxDtwLEzWy/3j4f6uWf2tm6XvWjVix7LYIbuDxw9EM PXp7q98XMUIm3L/TZd8imP3lHNmQ8LIMETP61PYV/NjRP7g8I3UC3/DlXHN+49nqRCuN HmaB6WuaEdpw5AcZwB7WxENrNnZz2X8IXFUHMdrobdi5FqkOcmMEKC0Mrk+XwSvNrLTK Rbsw== X-Gm-Message-State: AOJu0YycAmJwsY/mcsEeK40kYNQsEGm/4BKl3OR5M00b+NBmjCu6e4ST qSxGrVIBJk4WHkjTgr0o71eDfs6sqbc+fQp0FQuaspxAm9hjSQSArnysOqw1CsDw X-Gm-Gg: AeBDieum6XVnaYaDVVtYOKTuGJeAC3/Pxs9fYxydmC3f5/cVaIE5wEG4jDSbWM9WFut ka6KJVvg9j9JMnYIC/p8fZwgT4msInmVc1CIJoo4vD2ILSuo9qIQel2D/aHhkNY8nug6rpvMKTD GLEiJNp2ERJHoUVb+D2JzBqu1TLjQuNauck+RF2PYI2epog3wnAJOKzeoOYgp1fbhU+AExDIpDl zS/a5+apFevpNNSBSoMKn8KahZOoGikJs1wWAPtu9EuRhFQ4j0f6czhUISm1df6iBsbxFkeiTz9 QMwY7iVudAix//gcaRENplcBu+CttrkxnFTLgU5pYDlgsWANnarvZNvUTaI9s9WjcnoBjStkMJQ NVl8moVi0lhJDEtnduuNonSfJ4Nf1Ra/PpnwIBbZHM/wo4Lhy4XkZ6SI4uWKqBSvsV4LWKqHKq7 pFWAg/y8ftHPtcyppwY/55Iz4APVwJUD+T/dp8/Q14Nw== X-Received: by 2002:a05:600c:1389:b0:487:1826:e138 with SMTP id 5b1f17b1804b1-488fb739780mr210684635e9.1.1776980249086; Thu, 23 Apr 2026 14:37:29 -0700 (PDT) From: Ali Raza Date: Fri, 24 Apr 2026 02:36:39 +0500 Subject: [PATCH v2 2/3] linux-user: Validate tkill/tgkill targets are guest threads MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260424-master-v2-2-8b50b5c063ed@gmail.com> References: <20260424-master-v2-0-8b50b5c063ed@gmail.com> In-Reply-To: <20260424-master-v2-0-8b50b5c063ed@gmail.com> To: qemu-devel@nongnu.org Cc: Ali Raza , Laurent Vivier , Pierrick Bouvier , =?utf-8?q?Alex_Benn=C3=A9e?= , deller@gmx.de X-Mailer: b4 0.15.2 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::42e; envelope-from=elirazamumtaz@gmail.com; helo=mail-wr1-x42e.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Thu, 23 Apr 2026 20:42:33 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1776991456103154100 The tkill and tgkill syscall handlers pass the guest-supplied TID directly to the host kernel without checking whether it belongs to a guest thread. This allows a guest to send signals to QEMU-internal host threads (RCU, TCG workers) that have no CPUState and no guest signal handlers, which can cause hangs or disrupt QEMU operation. Add validation that checks the target TID against the guest CPU list before forwarding the signal to the host. For tgkill, also verify that the tgid matches the current process. Return -ESRCH for TIDs that do not correspond to any guest thread, matching the behavior a real kernel would return for a nonexistent thread. This complements the /proc/*/task/ filtering in the previous commit to provide defense-in-depth: even if a guest discovers or guesses a QEMU-internal thread TID, it cannot send signals to it. Signed-off-by: Ali Raza (@locus-x64) Reviewed-by: Pierrick Bouvier --- Changes in v2: - Range-check tid/tgid before narrowing from abi_long to pid_t. - do_tkill: only reject signals whose target lives in our own host process and is not a guest thread; cross-process tkill is passed through to the kernel unchanged. - do_tgkill: drop the previous tgid=3D=3Dgetpid() blanket rejection and apply the same in-our-process-only filter, so legitimate cross-process tgkill keeps working. - New tid_is_qemu_internal() helper based on the CPU list and /proc/self/task/ presence. --- linux-user/syscall.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++= ++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 44f2cd851f..c7dea1086c 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -9161,6 +9161,27 @@ static bool is_guest_tid(pid_t tid) return false; } =20 +/* + * Return true iff @tid identifies a thread inside our own host process + * that is not one of the guest threads -- i.e. a QEMU-internal helper + * thread (RCU, TCG worker, ...). Cross-process tids and unknown tids + * are not classified here and the caller should pass the syscall + * through to the kernel unchanged. + */ +static bool tid_is_qemu_internal(pid_t tid) +{ + char path[64]; + + WITH_RCU_READ_LOCK_GUARD() { + if (is_guest_tid(tid)) { + return false; + } + } + + snprintf(path, sizeof(path), "/proc/self/task/%d", (int)tid); + return access(path, F_OK) =3D=3D 0; +} + #ifdef TARGET_NR_getdents static int do_getdents(abi_long dirfd, abi_long arg2, abi_long count) { @@ -13511,11 +13532,41 @@ static abi_long do_syscall1(CPUArchState *cpu_env= , int num, abi_long arg1, #endif =20 case TARGET_NR_tkill: - return get_errno(safe_tkill((int)arg1, target_to_host_signal(arg2)= )); + { + pid_t tid =3D (pid_t)arg1; + if ((abi_long)tid !=3D arg1) { + return -TARGET_ESRCH; + } + /* + * Reject signals that target one of our own QEMU-internal + * helper threads (RCU, TCG worker, ...) which share our + * host PID but have no guest CPUState. Cross-process tids + * are passed through unchanged. + */ + if (tid_is_qemu_internal(tid)) { + return -TARGET_ESRCH; + } + return get_errno(safe_tkill(tid, target_to_host_signal(arg2))); + } =20 case TARGET_NR_tgkill: - return get_errno(safe_tgkill((int)arg1, (int)arg2, + { + pid_t tgid =3D (pid_t)arg1; + pid_t tid =3D (pid_t)arg2; + if ((abi_long)tgid !=3D arg1 || (abi_long)tid !=3D arg2) { + return -TARGET_ESRCH; + } + /* + * Only screen targets in our own process; for cross-process + * tgkill we have no way to know which TIDs are guest threads + * in another QEMU instance, so the call is passed through. + */ + if (tgid =3D=3D getpid() && tid_is_qemu_internal(tid)) { + return -TARGET_ESRCH; + } + return get_errno(safe_tgkill(tgid, tid, target_to_host_signal(arg3))); + } =20 #ifdef TARGET_NR_set_robust_list case TARGET_NR_set_robust_list: --=20 2.43.0 From nobody Thu Apr 30 01:47:41 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1776991456; cv=none; d=zohomail.com; s=zohoarc; b=boJCzuytZTA7LYUvrYK8GrNqHrfXL2IpX5Su2mGQ3Tqx1wNsUb0NCEVie5M44WE7rtRkeRyRvnS7K90kvtE+4uk4+u2G1DIrUVhtjwBn/aNwtaNjhHLjYtcPmr6sIcF/ihDAyxTrS+hPQjTic/Tirrn/NxTaUMpY0LW+a5lcxng= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1776991456; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=r/MkEQmRXLYmx51oofgr61oVs//gZmrORdrLItZ2SQM=; b=R2tr77emgw2giNlXA9s3A+XD1yYNIh5tK4/oEfQkSLRAce9WvJbBQTfjEQmFPAxCIp7h6GxV74PTPtwbSgaqqd8kTJ/F+NwqGe/I5F6/FBu7wlvvKj+zjt8Pv7M4u9fmvHTWwh14QrTSbu7sG7o6FJXCpsYe/zIVrjEqMR8Sey0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1776991456765153.25541471084762; Thu, 23 Apr 2026 17:44:16 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wG4ct-0004Te-Sg; Thu, 23 Apr 2026 20:42:35 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wG1jr-0002a8-JO for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:35 -0400 Received: from mail-wr1-x430.google.com ([2a00:1450:4864:20::430]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wG1jp-0001FU-Qw for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:35 -0400 Received: by mail-wr1-x430.google.com with SMTP id ffacd0b85a97d-43d7b879691so949517f8f.1 for ; Thu, 23 Apr 2026 14:37:33 -0700 (PDT) Received: from [10.94.10.196] ([223.123.19.204]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48919f54572sm138755225e9.26.2026.04.23.14.37.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Apr 2026 14:37:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776980252; x=1777585052; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=r/MkEQmRXLYmx51oofgr61oVs//gZmrORdrLItZ2SQM=; b=UF1RupDWimT764MGZXSzRFg0eWEz1sWBLNiR0OKTF/C+ZCqeDX/w9avZBuvUG4eSf1 ldeKboIFwHWQW40vjzfGw2vZzROr4UZLNRkarSoc5yCbq0PQG2ME4J1r8dtcNoszl7bl wqhhq8cZsOhIDDyn8QwfHsAHKhN1dVDIUmZCXT5D77LwtXi2in+fEv3Z2H/aXraLCH8h efEUjaZMKqbnKau/0aeFl1Ujhks6lGoXhv0GcRAvRWci3JByYGRf+SWwYYJ4NhW68DfF gF53VwLiFb57VzOsWqvFfjXai0iynHM1ekaAA/2v/gZtV6UXKuiZtKqk9WIj/xSEN16P F+wg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776980252; x=1777585052; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=r/MkEQmRXLYmx51oofgr61oVs//gZmrORdrLItZ2SQM=; b=Or13HyNLShIOhaRI+zO+Kq0olySX0ZNOhrSLHTxtwTsRiljvZ1Ip4yr2ZOD+KOcZMO 6UryiPNgOtYwat1RYHwOHPZkiEa5zqDZkRF9/34C+e+HMF7GcbFrOV3ZCgFJ34Lr/nw0 PVmqt4BAmv0++auU3nmAnxm7I3q2lCdafnn2znimPKVPG1+UzKdV9lTS+EZUH3O25hXf 6DtxQVlPOypE4Im/uGZWzVoC0JvF90mr9eGcWPRC64HG/Cd+t9/q11o032E1bW030oAU 0LIpyDCRtQKxXGW2vQ+O/zF72bMQFVtn2uCXZaElUpdTdXWRqB94B3jCQTrprSJwVf7i 7rkg== X-Gm-Message-State: AOJu0YzU7EzkDe1JWy0NcTkYfSrrSbbKYJ8fldWVok7U4nhmcpNs/pg9 4Sy8Yjp57udsfHeM/FV+Z596weWYXEr+13Bb88ih22QYBUFoWWIK5dJs X-Gm-Gg: AeBDietisU43J6DwvZCMAogCXeM3IAR3ADJZHd7SRdsnmEpiyg8/KROnjDIGFaozaAV lfnY06YvQ77rsct5qbimfi856NzfX/dMYHJtveL71dCNJtMHBOj7nsFrNCCKksFHtF0Yr0o4jDb 1odT6o7ZGtk+Ll/6462WQ6cB4oCGM0tXNIe0wvUicuwkbQ1gRgz91Yz5Z6N3rn6hG8oRXzq3W4W alnfacW7oKfTcflQtuyUbo/QULcfG8ojdwQZMg9LnkkSswsk4JSb46SkG6Mtr4y700p7Nn233E9 +xOSVZd/v3uixNCzrw68NBhQG75zv8yEdIwG+Eh4F2+AZymCN3Ut9cpEh1FoyFqG0fLT0rQQwc7 N+zJdt8MHFfGU89fGg8GNtUgCkLJGDYUnDBA3+ZOULL6hxqYWw6jw2ymrmG2b13lGnA3cBmfg8s annifEBie3C0nEFU78sJwfAfHZmMjB+12F45ib5kRA5Q== X-Received: by 2002:a05:600c:c177:b0:488:9e7e:e1e3 with SMTP id 5b1f17b1804b1-488fb7390e6mr198494035e9.2.1776980252229; Thu, 23 Apr 2026 14:37:32 -0700 (PDT) From: Ali Raza Date: Fri, 24 Apr 2026 02:36:40 +0500 Subject: [PATCH v2 3/3] tests/tcg: Add test for /proc/self/task/ filtering and tkill validation MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260424-master-v2-3-8b50b5c063ed@gmail.com> References: <20260424-master-v2-0-8b50b5c063ed@gmail.com> In-Reply-To: <20260424-master-v2-0-8b50b5c063ed@gmail.com> To: qemu-devel@nongnu.org Cc: Ali Raza , Laurent Vivier , Pierrick Bouvier , =?utf-8?q?Alex_Benn=C3=A9e?= , deller@gmx.de X-Mailer: b4 0.15.2 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::430; envelope-from=elirazamumtaz@gmail.com; helo=mail-wr1-x430.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Thu, 23 Apr 2026 20:42:33 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1776991458892158500 Add a multiarch linux-user test that verifies: 1. /proc/self/task/ only lists guest threads -- spawns NUM_THREADS pthreads, reads the task directory, and asserts every entry maps to a known guest TID and the total count matches. 2. tkill(guest_tid, 0) succeeds for all guest threads. 3. tkill to a non-guest TID returns ESRCH. On native Linux this test trivially passes since there are no QEMU-internal threads. Under QEMU user-mode, it exercises the getdents64 filtering and tkill validation added in the previous two commits. Signed-off-by: Ali Raza (@locus-x64) Reviewed-by: Pierrick Bouvier --- Changes in v2: - Hard-assert ESRCH on tkill to a non-guest tid (was a printf). - Add tgkill coverage: success on guest tids; ESRCH on a wrong tgid. --- tests/tcg/multiarch/Makefile.target | 2 + tests/tcg/multiarch/linux/linux-proc-task.c | 193 ++++++++++++++++++++++++= ++++ 2 files changed, 195 insertions(+) diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Make= file.target index 508149d57b..8d6e2af617 100644 --- a/tests/tcg/multiarch/Makefile.target +++ b/tests/tcg/multiarch/Makefile.target @@ -39,6 +39,8 @@ testthread: LDFLAGS+=3D-lpthread =20 threadcount: LDFLAGS+=3D-lpthread =20 +linux-proc-task: LDFLAGS+=3D-lpthread + signals: LDFLAGS+=3D-lrt -lpthread =20 munmap-pthread: CFLAGS+=3D-pthread diff --git a/tests/tcg/multiarch/linux/linux-proc-task.c b/tests/tcg/multia= rch/linux/linux-proc-task.c new file mode 100644 index 0000000000..0c41a4443f --- /dev/null +++ b/tests/tcg/multiarch/linux/linux-proc-task.c @@ -0,0 +1,193 @@ +/* + * Test that /proc/self/task/ only lists guest threads. + * + * Under QEMU user-mode emulation, the host process may contain + * internal threads (RCU, TCG workers) that are not guest threads. + * These must be hidden from directory listings of /proc//task/ + * and signals to non-guest TIDs must return ESRCH. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NUM_THREADS 3 + +static pid_t guest_tids[NUM_THREADS + 1]; /* +1 for main thread */ +static int num_guest_tids; +static pthread_barrier_t barrier; + +static pid_t gettid_sys(void) +{ + return syscall(SYS_gettid); +} + +static void *thread_func(void *arg) +{ + int idx =3D (int)(intptr_t)arg; + + guest_tids[idx] =3D gettid_sys(); + pthread_barrier_wait(&barrier); + + /* Wait for main thread to finish testing. */ + pthread_barrier_wait(&barrier); + return NULL; +} + +/* + * Read /proc/self/task/ and return the count of numeric entries + * (thread TIDs). For each entry, verify it matches a known guest TID. + */ +static int count_and_verify_task_entries(void) +{ + DIR *dir; + struct dirent *de; + int count =3D 0; + + dir =3D opendir("/proc/self/task"); + assert(dir !=3D NULL); + + while ((de =3D readdir(dir)) !=3D NULL) { + char *endp; + long tid; + int i, found; + + if (de->d_name[0] =3D=3D '.') { + continue; /* skip "." and ".." */ + } + + tid =3D strtol(de->d_name, &endp, 10); + if (*endp !=3D '\0' || tid <=3D 0) { + continue; /* non-numeric entry */ + } + + /* Every TID in the listing must be a known guest thread. */ + found =3D 0; + for (i =3D 0; i < num_guest_tids; i++) { + if (guest_tids[i] =3D=3D (pid_t)tid) { + found =3D 1; + break; + } + } + if (!found) { + fprintf(stderr, "FAIL: /proc/self/task/ contains unknown TID %= ld\n", + tid); + fprintf(stderr, " Known guest TIDs:"); + for (i =3D 0; i < num_guest_tids; i++) { + fprintf(stderr, " %d", guest_tids[i]); + } + fprintf(stderr, "\n"); + } + assert(found); + count++; + } + + closedir(dir); + return count; +} + +/* + * Verify that tkill(tid, 0) succeeds for all guest TIDs and that + * tkill/tgkill to a TID that should not exist returns ESRCH. + */ +static void test_tkill_validation(void) +{ + int i, ret; + pid_t self =3D getpid(); + + /* Signal 0 to each guest TID should succeed via tkill and tgkill. */ + for (i =3D 0; i < num_guest_tids; i++) { + ret =3D syscall(SYS_tkill, guest_tids[i], 0); + if (ret !=3D 0) { + fprintf(stderr, "FAIL: tkill(%d, 0) returned %d (errno=3D%d)\n= ", + guest_tids[i], ret, errno); + } + assert(ret =3D=3D 0); + + ret =3D syscall(SYS_tgkill, self, guest_tids[i], 0); + if (ret !=3D 0) { + fprintf(stderr, + "FAIL: tgkill(%d, %d, 0) returned %d (errno=3D%d)\n", + self, guest_tids[i], ret, errno); + } + assert(ret =3D=3D 0); + } + + /* + * Try a TID that is very unlikely to be a guest thread. Use + * pid_max (typically 4194304) minus 1 as a probe. On a real + * kernel this returns ESRCH for a nonexistent thread; under QEMU + * with validation it must also return ESRCH. In the (extremely + * unlikely) case that the TID does exist on the host, the kernel + * may return 0; tolerate that to avoid spurious failures. + */ + ret =3D syscall(SYS_tkill, 4194303, 0); + assert(ret =3D=3D 0 || (ret =3D=3D -1 && errno =3D=3D ESRCH)); + + ret =3D syscall(SYS_tgkill, self, 4194303, 0); + assert(ret =3D=3D 0 || (ret =3D=3D -1 && errno =3D=3D ESRCH)); + + /* tgkill with a wrong tgid for a known-good tid must return ESRCH. */ + ret =3D syscall(SYS_tgkill, 4194303, guest_tids[0], 0); + assert(ret =3D=3D -1 && errno =3D=3D ESRCH); +} + +int main(void) +{ + pthread_t threads[NUM_THREADS]; + int i, task_count; + + pthread_barrier_init(&barrier, NULL, NUM_THREADS + 1); + + /* Record main thread TID. */ + guest_tids[0] =3D gettid_sys(); + num_guest_tids =3D 1; + + /* Spawn worker threads. */ + for (i =3D 0; i < NUM_THREADS; i++) { + int ret =3D pthread_create(&threads[i], NULL, thread_func, + (void *)(intptr_t)(i + 1)); + assert(ret =3D=3D 0); + } + + /* Wait for all threads to record their TIDs. */ + pthread_barrier_wait(&barrier); + num_guest_tids =3D NUM_THREADS + 1; + + printf("Guest TIDs:"); + for (i =3D 0; i < num_guest_tids; i++) { + printf(" %d", guest_tids[i]); + } + printf("\n"); + + /* Test 1: /proc/self/task/ entry count matches guest thread count. */ + task_count =3D count_and_verify_task_entries(); + printf("/proc/self/task/ entries: %d, expected: %d\n", + task_count, num_guest_tids); + assert(task_count =3D=3D num_guest_tids); + + /* Test 2: tkill validation. */ + test_tkill_validation(); + + /* Release worker threads. */ + pthread_barrier_wait(&barrier); + + for (i =3D 0; i < NUM_THREADS; i++) { + pthread_join(threads[i], NULL); + } + + pthread_barrier_destroy(&barrier); + + printf("PASS: /proc/self/task/ filtering and tkill validation\n"); + return EXIT_SUCCESS; +} --=20 2.43.0