From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail.openvz.org (unknown [69.168.225.77]) by lore.virtuozzo.com (Postfix) with ESMTPS id DCDD6802A5 for ; Mon, 28 Sep 2026 12:32:50 +0000 (UTC) Received: from mail.openvz.org (localhost [127.0.0.1]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 68SCVSdV008930; Mon, 28 Sep 2026 15:31:29 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 68SCVSdV008930 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="DrQpWVwq" Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 68SCVRvg008924 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Mon, 28 Sep 2026 15:31:27 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 68SCVRvg008924 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-48879e03053so1669874f8f.0 for ; Mon, 28 Sep 2026 05:31:27 -0700 (PDT) X-Gm-Message-State: AFuF++nm5nqwQEyHaAgx4Vq6BHQCjm8vZDjRcqyh51U3zA3p9EMF9HIL hChuVMlMRgl29625zoz/WfTwvUh6/qqxOW93XEQLc0V6ct05v/8mCSJVP9u+dlCngZwIPmZ3/zB Tpbz334tDVM7o4FZ9oJS1UhzfaB6vd9NtsH8N4qDnYFUZSm0tpuxPjg== X-Gm-Gg: AYBFou0bf8X4MPi5Bfe+Nsw8kozo656e/KY7rfBlxMvH/4u6zwZ7Z/tJ/Lf4i1wZT3T a0JUgHnLD3wL618oe0+I7ojOzdbUXbLn6Wmw0YPz7iIVmP8BYNuF80MIubZHOBMYPhVmY4LdBRR uJ05x94cM81QCf1mDgncZ7ZC7YWuIImT24YcUMRHwggAvgTR1GWK9n1kDthjlszn37TK3AAKMzd IgfTSElvB8ezTXuN1UtP5eYwa5bE1fLRRlGAFQte/m56Pw6TjKpitjEiNdNqqIzMDSEeZo+EfNH TApOr0viW5r6HGKrxIf+z6a8cfMvRGr0B8+JIe4GVK41uGdaoMIC9A6rqeU0eSCDBUsEOKy/ayq tohvuaVlQ6wa6jxgn X-Received: by 2002:a05:600c:4705:b0:49e:65f2:db64 with SMTP id 5b1f17b1804b1-49fe66d03b6mr226218225e9.5.1790598686881; Mon, 28 Sep 2026 05:31:26 -0700 (PDT) X-Received: by 2002:a05:600c:4705:b0:49e:65f2:db64 with SMTP id 5b1f17b1804b1-49fe66d03b6mr226217145e9.5.1790598686029; Mon, 28 Sep 2026 05:31:26 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1790598686; cv=none; d=google.com; s=arc-20260327; b=JUqqpGJYrn7xcQkd68mOka1Q/OIKP2ZiihalfxeEY7kAz+8xrr5Yk4fQzt5i5dA0im U3geei/zHu/Xzq5fXn6HiYoUIbWuKtvjKuoY9Gkl2JMCIHMLdgkW8lnkwskD/idlnhFW TDd99rJxT/cdMg35LQpC1Jqwjlk8AsvOy0MA/1Ak8ThbXIqVYB5s35vVHX0ZC5U9k9fX pBX3VfYS/tpFZiyQax/m6o+VYUJIFf0GLo1ulGFevEZP6/dwRDlX3FNxfpu5oAJnKu+4 tHcKOjwvFcQLqtKiXX4TtafiDKi7HVMK98TWpFGgr00B8EVtvE+zU7tk1ZSFjWPWoL/I 67VQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:message-id:content-transfer-encoding:mime-version:subject :date:from:dkim-signature; bh=wfvZ/3yFimtjJw2t4WMxU/KRx1qbVpadQIVfbcXj7Dk=; fh=ldq4YnX7FUrr4roGLWGn9L1e93EaAOSb0AHw6AkGrgs=; b=LrV6Ivwmu7SOO21FkYya7Z7Wg+3esp6B5ytW+JzoIUNGhLQggwhiiFJ0CGDeGYKGl0 pNoEOlCDEFaZTmym+ercASfOWW28TZN5ntoFHm61FugggPL3NE7RAZkeG/NVCcFts6bg Ycjbg5KaxKto3EVJ1sieO9VbYKmEAIk7roVd5JtNZdLeFZhjSedn2539zXZHc795ZsWM i0z3pE07R47Vfyl4vdRHFy6oWy6fTYh7AMN8ql7a1IYTSpe1Z3bD9+W8OY5GOHX9Ehqt MBGoCaurM8raJYOfnuLUmEpRBhjHHIL7QeGyx3cJOr06NnKZXbUVTHIaB1Ez8rLqO0zF Gy8w==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=DrQpWVwq; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: from relay.virtuozzo.com (relay.virtuozzo.com. [130.117.225.111]) by mx.google.com with ESMTPS id ffacd0b85a97d-4887a361971si21320580f8f.79.2026.09.28.05.31.25 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 28 Sep 2026 05:31:25 -0700 (PDT) Received-SPF: pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) client-ip=130.117.225.111; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=DrQpWVwq; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=Message-Id:Content-Type:MIME-Version:Subject:Date :From; bh=wfvZ/3yFimtjJw2t4WMxU/KRx1qbVpadQIVfbcXj7Dk=; b=DrQpWVwqvcl1uQ86VyP eOIVYFluf9vwDcGuo0hE1fIlUTApdgmjW6/MDZCzlP8l9A5rwf/Yc90C6ICbTZAl1OeHXzBWRH0Rg 3bjQemDZ7MxsWxdk1BvQNavAT+I8eLD8KD9Gh+okecOWCLSvN2NhvnZJv2l2UkMA/iHk6G7FwBGFO 5tNM/HOzIepFP/f2HwEL7S4zK7K9jCiWjBPZvIWOu7OgYm3D7ZABCCsOgw3ZaWdgd9tSlueCcLo55 xHWUSPbiQ48Sj94NhrXlxUYZRUliU8MK31ZSIqOOBQEB3yHk86EN9vSW1sQAijDCx2EbyFV/Cju4x +5zRSVn+8cnRoew==; Received: from ch-demo-asa.virtuozzo.com ([130.117.225.8] helo=f0.sw.ru) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xBASu-001lkA-1L; Mon, 28 Sep 2026 14:31:16 +0200 From: Konstantin Khorenko Date: Mon, 28 Sep 2026 14:31:18 +0200 MIME-Version: 1.0 Message-Id: <20260928-b4-prep-kh-vstor-147641-fuse_notify_inval_files-v3-1-43a1c1d0ebee@virtuozzo.com> X-B4-Tracking: v=1; b=H4sIABVeumoC/yWNUQ6CMBAFr0L2203a0qB4FWNIga2skkK60KiEu 1v1c17yZjYQikwC52KDSImFp5ChPBTQDS7cCLnPDEaZStXmhK3FOdKMjwGTLFNEbY+V1ehXoSZ MC/tXwyG5sfE8kqDRZedU76yuHWRrPnt+/ooXSG+t4PpfZW3v1C3fGOz7B7n4i3SZAAAA X-Change-ID: 20260928-b4-prep-kh-vstor-147641-fuse_notify_inval_files-213ca0da419a To: Liu Kui , Alexey Kuznetsov , Pavel Tikhomirov X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=8563; i=khorenko@virtuozzo.com; h=from:subject:message-id; bh=k+7te/JiXVlDkzYjWxTOgIvOEczDtfIrHJ6ewT0/gVc=; b=owEBbQKS/ZANAwAKAVGWCkf5YVwqAcsmYgBqul4cPI4ycVV2kvNWNer23NydNTYJXD3/LB1Lc YuZv7mS5iGJAjMEAAEKAB0WIQRWD23IPcXT0GO9CCxRlgpH+WFcKgUCarpeHAAKCRBRlgpH+WFc KkWSEAC+fk0mWolJbKhDK2akSRtnTz1+D6LOrGyyFY+QflVsXq6veWBz1NzkxDXrCxXS8zWtif3 muDXQQxq5fNDGH/rlVv7cE9DOix3E/qMhqwW46c70CsY4OvTbqw9UKlKcJlmIWb+9XQQTVhla2R cTH7Fio7vck62Fd/MhwDOjxzpjlgq7fJWWYI2mt+W+GtkETT7AEyOGCi9eO91fDvCsOfhUWhLUF rSTzXL8RKKCNT8drqXO+3Q9X3GUz45YIKSEf3NFBGimWQ1Uyptr9zENIB+hRK9N4bCCukELftBO ZTJ92ehxhs23B6LbWZJLB26EyqYdsnegJbmyhjY/Ki69nvgCb9ddv0jB7/or1GC/Qy1zAojXWoD PyQP1GKoeri4DiCBzBf+3xGODp2VIDqNbRZHxyEexrpHpw92XJZLdjYdqqw7B2O8q1lZq+jWRIa hQDj+tMRqnTEMgoHqovFrg+fDCdnuOeqm3ADr7JUiazNIVqGeFd2kkHlvjSO0WGQWrxf3JyJHU8 rf56e2oIMu+QRh7IgKQtMJVdE+RKsa2VVKj5pke6UbqO8t3RdKzqvmeAxoCoitzhjlZ9xaBdbvB WUew4InufP74gMhq0357C3GHIpK1sASgviW3tZGDw8bVTEgDEIqnupPl/ZpsqH7pBfiIt7TuWJN UVKxVPK4mytEmqw== X-Developer-Key: i=khorenko@virtuozzo.com; a=openpgp; fpr=560F6DC83DC5D3D063BD082C51960A47F9615C2A X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH vz10 v3] fuse: reject FUSE_NOTIFY_INVAL_FILES for non-regular inodes X-BeenThere: devel@openvz.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: OpenVZ development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: devel-bounces@openvz.org Errors-To: devel-bounces@openvz.org FUSE_NOTIFY_INVAL_FILES invalidates the read/write io state of an inode, which only regular files have. The notification carries a bare nodeid, and the actual work is deferred to a workqueue: fuse_dev_do_write() fuse_notify() fuse_notify_inval_files() fuse_invalidate_files() inode = fuse_ilookup(fc, nodeid) queue_delayed_work(fuse_inval_files_wq, &fc->inval_files_work) ... fuse_inval_files_work() worker context wake_up(&fi->page_waitq) regular file state What the bytes behind fi->page_waitq are depends on the inode type, see fuse_init_inode(): S_ISREG fuse_init_file_inode() init_waitqueue_head(&fi->page_waitq) S_ISDIR fuse_init_dir() the same union holds fi->rdc, the readdir cache (rdc.lock, rdc.version, rdc.mtime, ...) other - nothing, fuse_alloc_inode() leaves the union as allocated So for a nodeid which is not a regular file - the mount root, say - the worker calls wake_up() on storage that is not a waitqueue and walks whatever lies there as a waitqueue list: an oops in the worker, or a lockup on a bogus lock, depending on what the overlapping fields happen to hold. The notification is only accepted on a close_wait mount, so the sender is vstorage, and vstorage never names anything but a regular file. Nothing in the protocol or in the kernel makes that a rule though: every nodeid the server has handed out is a candidate, FUSE_ROOT_ID included - the mount root is always a directory and always in the inode cache. https://virtuozzo.atlassian.net/browse/VSTOR-147641 Fixes: 19dd5fa442ea ("fuse: skip waiting for fuse writeback") Feature: vStorage Signed-off-by: Konstantin Khorenko --- FUSE_NOTIFY_INVAL_FILES carries a bare nodeid, and the kernel serves it for whatever inode that nodeid resolves to. Only a regular file has the io state the notification invalidates: for a directory the same bytes hold the readdir cache, for a symlink or a device node they hold whatever the slab handed out. The deferred worker wakes them as a wait queue all the same. The reproducer below keeps /dev/fuse to itself, answers the FUSE_INIT handshake by hand and sends one notification for FUSE_ROOT_ID - the mount root, always a directory and always resolvable without a preceding LOOKUP. It wants root in ve0: since ae86620879381 ("fs/fuse: strictly segregate vstorage features of generic fuse") the notification is only accepted on a close_wait mount, and close_wait takes ve0 root with CAP_SYS_ADMIN (is_close_wait_allowed()). Unpatched, the notification is accepted and the fuse_inval_files_work() worker dies on it a moment later; patched, the write(2) returns EOPNOTSUPP and nothing is queued. // SPDX-License-Identifier: GPL-2.0 /* * FUSE_NOTIFY_INVAL_FILES for a directory nodeid. Run as root in ve0. * Unfixed kernel: the fuse_inval_files_work() worker dies on the * notification. Fixed kernel: the write(2) fails with EOPNOTSUPP. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #ifndef FUSE_NOTIFY_INVAL_FILES #define FUSE_NOTIFY_INVAL_FILES 77 struct fuse_notify_inval_files_out { uint64_t ino; }; #endif static int answer_init(int fd) { char buf[FUSE_MIN_READ_BUFFER]; struct fuse_in_header *inh = (void *)buf; struct fuse_init_in *in; struct { struct fuse_out_header oh; struct fuse_init_out arg; } __attribute__((packed)) rep; ssize_t n; do { n = read(fd, buf, sizeof(buf)); if (n < (ssize_t)(sizeof(*inh) + sizeof(*in))) return -1; } while (inh->opcode != FUSE_INIT); in = (void *)(buf + sizeof(*inh)); memset(&rep, 0, sizeof(rep)); rep.oh.len = sizeof(rep); rep.oh.unique = inh->unique; rep.arg.major = FUSE_KERNEL_VERSION; rep.arg.minor = FUSE_KERNEL_MINOR_VERSION; rep.arg.max_readahead = in->max_readahead; /* fuse_invalidate_files() refuses to run without async_read */ rep.arg.flags = FUSE_ASYNC_READ; rep.arg.max_write = 1 << 20; return write(fd, &rep, sizeof(rep)) == (ssize_t)sizeof(rep) ? 0 : -1; } int main(void) { struct { struct fuse_out_header oh; struct fuse_notify_inval_files_out arg; } __attribute__((packed)) msg; char mnt[] = "/tmp/fuse_inval_XXXXXX"; char opts[128]; int fd; fd = open("/dev/fuse", O_RDWR); if (fd < 0) { perror("/dev/fuse"); return 1; } if (unshare(CLONE_NEWNS) || mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) || !mkdtemp(mnt)) { perror("mount namespace"); return 1; } snprintf(opts, sizeof(opts), "fd=%d,rootmode=40755,user_id=0,group_id=0,close_wait", fd); if (mount("repro", mnt, "fuse", 0, opts)) { /* close_wait takes CAP_SYS_ADMIN in ve0 */ perror("mount"); return 1; } if (answer_init(fd)) { fprintf(stderr, "FUSE_INIT handshake failed\n"); return 1; } memset(&msg, 0, sizeof(msg)); msg.oh.len = sizeof(msg); msg.oh.error = FUSE_NOTIFY_INVAL_FILES; msg.oh.unique = 0; msg.arg.ino = FUSE_ROOT_ID; if (write(fd, &msg, sizeof(msg)) < 0) perror("FUSE_NOTIFY_INVAL_FILES"); else printf("notification accepted\n"); return 0; } That close_wait gate is also why the selftest on the selftests branch, b8d769b55b3b ("selftests/ve: add fuse invalidate_files notification selftest"), stops reaching this code once it is rebased onto a branch that has ae86620879381: it mounts a plain fuse filesystem, so every notification it writes fails with EINVAL in fuse_notify_inval_files() before fuse_invalidate_files() is called. It needs the close_wait mount option to keep testing anything, and its root_inode_is_a_directory_and_is_accepted case has to expect EOPNOTSUPP once this patch is in. That series also carries an alternative fix for this crash, 2d489f8b4c05 ("fuse: don't wake page_waitq for non-regular inodes in the invalidate worker"), which keeps the notification successful and only skips the wake_up() in the worker. Only one of the two should land. Refusing the notification outright also keeps the worker from truncating the page cache of a directory - that is the readdir cache - for a notification that means nothing for a directory. Worth a word from vstorage before this is applied: on a close_wait mount fuse_notify() refuses every notification code but INVAL_FILES, so an INVAL_FILES for a directory is the only handle the daemon has on a directory's page cache today. If anything leans on that, the worker side guard is the fix to take instead. --- Changes in v3: - fix the Fixes: tag. eab013c7511c is the hash "fuse: skip waiting for fuse writeback" has on branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz; the rebase to 211.52.1.22 renumbered it to 19dd5fa442ea - drop the claim that an unprivileged fuse server can trigger this: the notification now takes a close_wait mount, so the sender is vstorage - drop the account of how the bug was found - add the reproducer and the note about the selftest above - Link to v2: http://lore.virtuozzo.com/20260819160529.490608-1-khorenko@virtuozzo.com - Link to v1: http://lore.virtuozzo.com/20260706110002.1024515-3-khorenko@virtuozzo.com The patch itself is unchanged since v2. --- fs/fuse/inode.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index eb2e9a4f87564..025984980c65f 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -667,6 +667,16 @@ int fuse_invalidate_files(struct fuse_dev *fud, u64 nodeid) if (!inode) return -ENOENT; + /* + * fuse_inval_files_work() wakes fi->page_waitq, which is initialised + * for regular files only - for other inode types it is not a + * waitqueue at all. + */ + if (!S_ISREG(inode->i_mode)) { + iput(inode); + return -EOPNOTSUPP; + } + fi = get_fuse_inode(inode); /* --- base-commit: ca54f152160587a74ff17cb52feef16f7b9d4e2b change-id: 20260928-b4-prep-kh-vstor-147641-fuse_notify_inval_files-213ca0da419a Best regards, -- Konstantin Khorenko _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel