From d4c00e39037854fafd72a9aeae54ac5bcd6fc97c Mon Sep 17 00:00:00 2001 From: Roxana Nicolescu Date: Thu, 12 Mar 2026 14:46:21 +0100 Subject: [PATCH 01/98] github actions: Add kernelCI for rlc-10 Signed-off-by: Roxana Nicolescu --- .container_build_image | 1 + .github/workflows/kernel-build-and-test-multiarch.yml | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 .container_build_image create mode 100644 .github/workflows/kernel-build-and-test-multiarch.yml diff --git a/.container_build_image b/.container_build_image new file mode 100644 index 0000000000000..787e80d3642d7 --- /dev/null +++ b/.container_build_image @@ -0,0 +1 @@ +rocky-10-kernel-builder diff --git a/.github/workflows/kernel-build-and-test-multiarch.yml b/.github/workflows/kernel-build-and-test-multiarch.yml new file mode 100644 index 0000000000000..b79cb515ed69f --- /dev/null +++ b/.github/workflows/kernel-build-and-test-multiarch.yml @@ -0,0 +1,11 @@ +name: Automated kernel build and test x86_64 and aarch64 + +on: + push: + branches: + - '*_rlc-10/**' + +jobs: + kernelCI: + uses: ctrliq/kernel-src-tree/.github/workflows/kernel-build-and-test-multiarch.yml@main + secrets: inherit From bda23a0a74a6655324acb3f393e259b1770a7dde Mon Sep 17 00:00:00 2001 From: Roxana Nicolescu Date: Thu, 26 Mar 2026 13:05:48 +0100 Subject: [PATCH 02/98] github actions: Use trigger for kernelCI Signed-off-by: Roxana Nicolescu --- ...tiarch.yml => kernel-build-and-test-multiarch-trigger.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{kernel-build-and-test-multiarch.yml => kernel-build-and-test-multiarch-trigger.yml} (61%) diff --git a/.github/workflows/kernel-build-and-test-multiarch.yml b/.github/workflows/kernel-build-and-test-multiarch-trigger.yml similarity index 61% rename from .github/workflows/kernel-build-and-test-multiarch.yml rename to .github/workflows/kernel-build-and-test-multiarch-trigger.yml index b79cb515ed69f..ae7c4550640f4 100644 --- a/.github/workflows/kernel-build-and-test-multiarch.yml +++ b/.github/workflows/kernel-build-and-test-multiarch-trigger.yml @@ -1,4 +1,4 @@ -name: Automated kernel build and test x86_64 and aarch64 +name: Trigger Automated kernel build and test (multi-arch) on: push: @@ -7,5 +7,5 @@ on: jobs: kernelCI: - uses: ctrliq/kernel-src-tree/.github/workflows/kernel-build-and-test-multiarch.yml@main + uses: ctrliq/kernel-src-tree/.github/workflows/kernel-build-and-test-multiarch-trigger.yml@main secrets: inherit From c5572186f5c23fdfc64e11da89f55fac41570c75 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Mon, 11 May 2026 18:01:20 -0400 Subject: [PATCH 03/98] github actions: Pin Checkout action to v6.0.2 --- .github/workflows/build-check_aarch64-rt.yml | 2 +- .github/workflows/build-check_aarch64.yml | 2 +- .github/workflows/build-check_x86_64-rt.yml | 2 +- .github/workflows/build-check_x86_64.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-check_aarch64-rt.yml b/.github/workflows/build-check_aarch64-rt.yml index 9f667f3216e31..22a7b9abf486f 100644 --- a/.github/workflows/build-check_aarch64-rt.yml +++ b/.github/workflows/build-check_aarch64-rt.yml @@ -26,7 +26,7 @@ jobs: dnf groupinstall 'Development Tools' -y dnf install openssl -y - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: "${{ github.event.pull_request.head.sha }}" fetch-depth: 0 diff --git a/.github/workflows/build-check_aarch64.yml b/.github/workflows/build-check_aarch64.yml index 62234d160d291..cfae1a3a30aa9 100644 --- a/.github/workflows/build-check_aarch64.yml +++ b/.github/workflows/build-check_aarch64.yml @@ -26,7 +26,7 @@ jobs: dnf groupinstall 'Development Tools' -y dnf install openssl -y - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: "${{ github.event.pull_request.head.sha }}" fetch-depth: 0 diff --git a/.github/workflows/build-check_x86_64-rt.yml b/.github/workflows/build-check_x86_64-rt.yml index 8dadc0f02e404..eac9f2f5abda3 100644 --- a/.github/workflows/build-check_x86_64-rt.yml +++ b/.github/workflows/build-check_x86_64-rt.yml @@ -26,7 +26,7 @@ jobs: dnf groupinstall 'Development Tools' -y dnf install openssl -y - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: "${{ github.event.pull_request.head.sha }}" fetch-depth: 0 diff --git a/.github/workflows/build-check_x86_64.yml b/.github/workflows/build-check_x86_64.yml index f5f8da22fcf43..cb42fcd96f50e 100644 --- a/.github/workflows/build-check_x86_64.yml +++ b/.github/workflows/build-check_x86_64.yml @@ -26,7 +26,7 @@ jobs: dnf groupinstall 'Development Tools' -y dnf install openssl -y - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: "${{ github.event.pull_request.head.sha }}" fetch-depth: 0 From e1c6a88d4069ac34215d36c55101bc8879ce6f0c Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Mon, 11 May 2026 18:02:54 -0400 Subject: [PATCH 04/98] github actions: set make to `nproc` rather than hardcoded --- .github/workflows/build-check_aarch64-rt.yml | 2 +- .github/workflows/build-check_aarch64.yml | 2 +- .github/workflows/build-check_x86_64-rt.yml | 2 +- .github/workflows/build-check_x86_64.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-check_aarch64-rt.yml b/.github/workflows/build-check_aarch64-rt.yml index 22a7b9abf486f..97913e53a54a3 100644 --- a/.github/workflows/build-check_aarch64-rt.yml +++ b/.github/workflows/build-check_aarch64-rt.yml @@ -35,4 +35,4 @@ jobs: git config --global --add safe.directory /__w/kernel-src-tree/kernel-src-tree cp configs/kernel-aarch64-rt-rhel.config .config make olddefconfig - make -j8 + make -j$(nproc) diff --git a/.github/workflows/build-check_aarch64.yml b/.github/workflows/build-check_aarch64.yml index cfae1a3a30aa9..dd892cf789a1f 100644 --- a/.github/workflows/build-check_aarch64.yml +++ b/.github/workflows/build-check_aarch64.yml @@ -35,4 +35,4 @@ jobs: git config --global --add safe.directory /__w/kernel-src-tree/kernel-src-tree cp configs/kernel-aarch64-rhel.config .config make olddefconfig - make -j8 + make -j$(nproc) diff --git a/.github/workflows/build-check_x86_64-rt.yml b/.github/workflows/build-check_x86_64-rt.yml index eac9f2f5abda3..38559e1b1fb88 100644 --- a/.github/workflows/build-check_x86_64-rt.yml +++ b/.github/workflows/build-check_x86_64-rt.yml @@ -35,4 +35,4 @@ jobs: git config --global --add safe.directory /__w/kernel-src-tree/kernel-src-tree cp configs/kernel-x86_64-rt-rhel.config .config make olddefconfig - make -j8 + make -j$(nproc) diff --git a/.github/workflows/build-check_x86_64.yml b/.github/workflows/build-check_x86_64.yml index cb42fcd96f50e..2c5de23997536 100644 --- a/.github/workflows/build-check_x86_64.yml +++ b/.github/workflows/build-check_x86_64.yml @@ -35,4 +35,4 @@ jobs: git config --global --add safe.directory /__w/kernel-src-tree/kernel-src-tree cp configs/kernel-x86_64-rhel.config .config make olddefconfig - make -j8 + make -j$(nproc) From 21e2568be8b573d062c70504c81cd5bdeea1e4a7 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Mon, 9 Jun 2025 15:49:43 -0400 Subject: [PATCH 05/98] tools: hv: Enable debug logs for hv_kvp_daemon jira LE-3207 feature tools_hv commit-author Shradha Gupta commit a9c0b33ef2306327dd2db02c6274107065ff9307 Allow the KVP daemon to log the KVP updates triggered in the VM with a new debug flag(-d). When the daemon is started with this flag, it logs updates and debug information in syslog with loglevel LOG_DEBUG. This information comes in handy for debugging issues where the key-value pairs for certain pools show mismatch/incorrect values. The distro-vendors can further consume these changes and modify the respective service files to redirect the logs to specific files as needed. Signed-off-by: Shradha Gupta Reviewed-by: Naman Jain Reviewed-by: Dexuan Cui Link: https://lore.kernel.org/r/1744715978-8185-1-git-send-email-shradhagupta@linux.microsoft.com Signed-off-by: Wei Liu Message-ID: <1744715978-8185-1-git-send-email-shradhagupta@linux.microsoft.com> (cherry picked from commit a9c0b33ef2306327dd2db02c6274107065ff9307) Signed-off-by: Jonathan Maple --- tools/hv/hv_kvp_daemon.c | 64 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index ae57bf69ad4af..902513e36a36d 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -83,6 +83,7 @@ enum { }; static int in_hand_shake; +static int debug; static char *os_name = ""; static char *os_major = ""; @@ -183,6 +184,20 @@ static void kvp_update_file(int pool) kvp_release_lock(pool); } +static void kvp_dump_initial_pools(int pool) +{ + int i; + + syslog(LOG_DEBUG, "===Start dumping the contents of pool %d ===\n", + pool); + + for (i = 0; i < kvp_file_info[pool].num_records; i++) + syslog(LOG_DEBUG, "pool: %d, %d/%d key=%s val=%s\n", + pool, i + 1, kvp_file_info[pool].num_records, + kvp_file_info[pool].records[i].key, + kvp_file_info[pool].records[i].value); +} + static void kvp_update_mem_state(int pool) { FILE *filep; @@ -270,6 +285,8 @@ static int kvp_file_init(void) return 1; kvp_file_info[i].num_records = 0; kvp_update_mem_state(i); + if (debug) + kvp_dump_initial_pools(i); } return 0; @@ -297,6 +314,9 @@ static int kvp_key_delete(int pool, const __u8 *key, int key_size) * Found a match; just move the remaining * entries up. */ + if (debug) + syslog(LOG_DEBUG, "%s: deleting the KVP: pool=%d key=%s val=%s", + __func__, pool, record[i].key, record[i].value); if (i == (num_records - 1)) { kvp_file_info[pool].num_records--; kvp_update_file(pool); @@ -315,20 +335,36 @@ static int kvp_key_delete(int pool, const __u8 *key, int key_size) kvp_update_file(pool); return 0; } + + if (debug) + syslog(LOG_DEBUG, "%s: could not delete KVP: pool=%d key=%s. Record not found", + __func__, pool, key); + return 1; } static int kvp_key_add_or_modify(int pool, const __u8 *key, int key_size, const __u8 *value, int value_size) { - int i; - int num_records; struct kvp_record *record; + int num_records; int num_blocks; + int i; + + if (debug) + syslog(LOG_DEBUG, "%s: got a KVP: pool=%d key=%s val=%s", + __func__, pool, key, value); if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) || - (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) + (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) { + syslog(LOG_ERR, "%s: Too long key or value: key=%s, val=%s", + __func__, key, value); + + if (debug) + syslog(LOG_DEBUG, "%s: Too long key or value: pool=%d, key=%s, val=%s", + __func__, pool, key, value); return 1; + } /* * First update the in-memory state. @@ -348,6 +384,9 @@ static int kvp_key_add_or_modify(int pool, const __u8 *key, int key_size, */ memcpy(record[i].value, value, value_size); kvp_update_file(pool); + if (debug) + syslog(LOG_DEBUG, "%s: updated: pool=%d key=%s val=%s", + __func__, pool, key, value); return 0; } @@ -359,8 +398,10 @@ static int kvp_key_add_or_modify(int pool, const __u8 *key, int key_size, record = realloc(record, sizeof(struct kvp_record) * ENTRIES_PER_BLOCK * (num_blocks + 1)); - if (record == NULL) + if (!record) { + syslog(LOG_ERR, "%s: Memory alloc failure", __func__); return 1; + } kvp_file_info[pool].num_blocks++; } @@ -368,6 +409,11 @@ static int kvp_key_add_or_modify(int pool, const __u8 *key, int key_size, memcpy(record[i].key, key, key_size); kvp_file_info[pool].records = record; kvp_file_info[pool].num_records++; + + if (debug) + syslog(LOG_DEBUG, "%s: added: pool=%d key=%s val=%s", + __func__, pool, key, value); + kvp_update_file(pool); return 0; } @@ -1661,6 +1707,7 @@ void print_usage(char *argv[]) fprintf(stderr, "Usage: %s [options]\n" "Options are:\n" " -n, --no-daemon stay in foreground, don't daemonize\n" + " -d, --debug Enable debug logs(syslog debug by default)\n" " -h, --help print this help\n", argv[0]); } @@ -1682,10 +1729,11 @@ int main(int argc, char *argv[]) static struct option long_options[] = { {"help", no_argument, 0, 'h' }, {"no-daemon", no_argument, 0, 'n' }, + {"debug", no_argument, 0, 'd' }, {0, 0, 0, 0 } }; - while ((opt = getopt_long(argc, argv, "hn", long_options, + while ((opt = getopt_long(argc, argv, "hnd", long_options, &long_index)) != -1) { switch (opt) { case 'n': @@ -1694,6 +1742,9 @@ int main(int argc, char *argv[]) case 'h': print_usage(argv); exit(0); + case 'd': + debug = 1; + break; default: print_usage(argv); exit(EXIT_FAILURE); @@ -1716,6 +1767,9 @@ int main(int argc, char *argv[]) */ kvp_get_domain_name(full_domain_name, sizeof(full_domain_name)); + if (debug) + syslog(LOG_INFO, "Logging debug info in syslog(debug)"); + if (kvp_file_init()) { syslog(LOG_ERR, "Failed to initialize the pools"); exit(EXIT_FAILURE); From dcadadac5db8b51fdb6d37d7149c96d9862d34c1 Mon Sep 17 00:00:00 2001 From: Roxana Nicolescu Date: Tue, 17 Feb 2026 16:12:43 +0100 Subject: [PATCH 06/98] dcache: export shrink_dentry_list() and add new helper d_dispose_if_unused() jira SECO-468 commit-author Luis Henriques commit 395b95530343e7f4bdd2870190d985a222997fb6 Add and export a new helper d_dispose_if_unused() which is simply a wrapper around to_shrink_list(), to add an entry to a dispose list if it's not used anymore. Also export shrink_dentry_list() to kill all dentries in a dispose list. Suggested-by: Miklos Szeredi Signed-off-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 395b95530343e7f4bdd2870190d985a222997fb6) Signed-off-by: Roxana Nicolescu --- fs/dcache.c | 18 ++++++++++++------ include/linux/dcache.h | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index cd7c019f92f9b..17541c28ae9d0 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1032,6 +1032,15 @@ struct dentry *d_find_alias_rcu(struct inode *inode) return de; } +void d_dispose_if_unused(struct dentry *dentry, struct list_head *dispose) +{ + spin_lock(&dentry->d_lock); + if (!dentry->d_lockref.count) + to_shrink_list(dentry, dispose); + spin_unlock(&dentry->d_lock); +} +EXPORT_SYMBOL(d_dispose_if_unused); + /* * Try to kill dentries associated with this inode. * WARNING: you must own a reference to inode. @@ -1042,12 +1051,8 @@ void d_prune_aliases(struct inode *inode) struct dentry *dentry; spin_lock(&inode->i_lock); - hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) { - spin_lock(&dentry->d_lock); - if (!dentry->d_lockref.count) - to_shrink_list(dentry, &dispose); - spin_unlock(&dentry->d_lock); - } + hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) + d_dispose_if_unused(dentry, &dispose); spin_unlock(&inode->i_lock); shrink_dentry_list(&dispose); } @@ -1087,6 +1092,7 @@ void shrink_dentry_list(struct list_head *list) shrink_kill(dentry); } } +EXPORT_SYMBOL(shrink_dentry_list); static enum lru_status dentry_lru_isolate(struct list_head *item, struct list_lru_one *lru, void *arg) diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 69782344e7670..d0d64d13cdbc3 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -256,6 +256,8 @@ extern void d_tmpfile(struct file *, struct inode *); extern struct dentry *d_find_alias(struct inode *); extern void d_prune_aliases(struct inode *); +extern void d_dispose_if_unused(struct dentry *, struct list_head *); +extern void shrink_dentry_list(struct list_head *); extern struct dentry *d_find_alias_rcu(struct inode *); From f85700616ba5e66a361f9f40429051297893508f Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 31 Mar 2026 15:25:55 -0400 Subject: [PATCH 07/98] fuse: don't truncate cached, mutated symlink jira SECO-478 RFBugFix: FUSE commit-author Miklos Szeredi commit b4c173dfbb6c78568578ff18f9e8822d7bd0e31b Fuse allows the value of a symlink to change and this property is exploited by some filesystems (e.g. CVMFS). It has been observed, that sometimes after changing the symlink contents, the value is truncated to the old size. This is caused by fuse_getattr() racing with fuse_reverse_inval_inode(). fuse_reverse_inval_inode() updates the fuse_inode's attr_version, which results in fuse_change_attributes() exiting before updating the cached attributes This is okay, as the cached attributes remain invalid and the next call to fuse_change_attributes() will likely update the inode with the correct values. The reason this causes problems is that cached symlinks will be returned through page_get_link(), which truncates the symlink to inode->i_size. This is correct for filesystems that don't mutate symlinks, but in this case it causes bad behavior. The solution is to just remove this truncation. This can cause a regression in a filesystem that relies on supplying a symlink larger than the file size, but this is unlikely. If that happens we'd need to make this behavior conditional. Reported-by: Laura Promberger Tested-by: Sam Lewis Signed-off-by: Miklos Szeredi Link: https://lore.kernel.org/r/20250220100258.793363-1-mszeredi@redhat.com Reviewed-by: Bernd Schubert Signed-off-by: Christian Brauner (cherry picked from commit b4c173dfbb6c78568578ff18f9e8822d7bd0e31b) Signed-off-by: Jonathan Maple --- fs/fuse/dir.c | 2 +- fs/namei.c | 24 +++++++++++++++++++----- include/linux/fs.h | 2 ++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 54104dd48af7c..70fb57714f79d 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1632,7 +1632,7 @@ static const char *fuse_get_link(struct dentry *dentry, struct inode *inode, goto out_err; if (fc->cache_symlinks) - return page_get_link(dentry, inode, callback); + return page_get_link_raw(dentry, inode, callback); err = -ECHILD; if (!dentry) diff --git a/fs/namei.c b/fs/namei.c index 858592582449b..3e961dd22da8b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -5334,10 +5334,9 @@ const char *vfs_get_link(struct dentry *dentry, struct delayed_call *done) EXPORT_SYMBOL(vfs_get_link); /* get the link contents into pagecache */ -const char *page_get_link(struct dentry *dentry, struct inode *inode, - struct delayed_call *callback) +static char *__page_get_link(struct dentry *dentry, struct inode *inode, + struct delayed_call *callback) { - char *kaddr; struct page *page; struct address_space *mapping = inode->i_mapping; @@ -5356,8 +5355,23 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode, } set_delayed_call(callback, page_put_link, page); BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM); - kaddr = page_address(page); - nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1); + return page_address(page); +} + +const char *page_get_link_raw(struct dentry *dentry, struct inode *inode, + struct delayed_call *callback) +{ + return __page_get_link(dentry, inode, callback); +} +EXPORT_SYMBOL_GPL(page_get_link_raw); + +const char *page_get_link(struct dentry *dentry, struct inode *inode, + struct delayed_call *callback) +{ + char *kaddr = __page_get_link(dentry, inode, callback); + + if (!IS_ERR(kaddr)) + nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1); return kaddr; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 96a3b653d1fdf..5a74045e8a94b 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3404,6 +3404,8 @@ extern const struct file_operations generic_ro_fops; extern int readlink_copy(char __user *, int, const char *, int); extern int page_readlink(struct dentry *, char __user *, int); +extern const char *page_get_link_raw(struct dentry *, struct inode *, + struct delayed_call *); extern const char *page_get_link(struct dentry *, struct inode *, struct delayed_call *); extern void page_put_link(void *); From 3458d0a6a89ee280c87fd1ae06761200a76afc5c Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 31 Mar 2026 15:34:16 -0400 Subject: [PATCH 08/98] fuse: add more control over cache invalidation behaviour jira SECO-478 RFBugFix: FUSE commit-author Luis Henriques commit 2396356a945bb022aff02656f59c2a45d457043f upstream-diff | conflict in fs/fuse/dir.c due to missing this piece: d701902c13df - fuse: return correct dentry for ->mkdir Which is a part of a larger changeset here that we're not going to take: https://lore.kernel.org/all/20250227013949.536172-1-neilb@suse.de/ | Additionally this bumps the Kernel FUSE API minor version from 41 to 44. The interface into via fuse3 currently in Rocky 10.1 is limited to API 38 anyways at 3.16.2. | There is a build conflict due to a major rewrite of the d_revalidate calls which now includes the parent directory being passed. 5be1fa8abd7b Pass parent directory inode and expected name to ->d_revalidate() In this case we can use the dentry->i_sb because we only need the superblock for get_fuse_conn_super(). Currently userspace is able to notify the kernel to invalidate the cache for an inode. This means that, if all the inodes in a filesystem need to be invalidated, then userspace needs to iterate through all of them and do this kernel notification separately. This patch adds the concept of 'epoch': each fuse connection will have the current epoch initialized and every new dentry will have it's d_time set to the current epoch value. A new operation will then allow userspace to increment the epoch value. Every time a dentry is d_revalidate()'ed, it's epoch is compared with the current connection epoch and invalidated if it's value is different. Signed-off-by: Luis Henriques Tested-by: Laura Promberger Signed-off-by: Miklos Szeredi (cherry picked from commit 2396356a945bb022aff02656f59c2a45d457043f) Signed-off-by: Jonathan Maple build fix: fuse: add more control over cache invalidation behaviour --- fs/fuse/dev.c | 16 ++++++++++++++++ fs/fuse/dir.c | 22 +++++++++++++++++++--- fs/fuse/fuse_i.h | 3 +++ fs/fuse/inode.c | 1 + fs/fuse/readdir.c | 3 +++ include/uapi/linux/fuse.h | 6 +++++- 6 files changed, 47 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index d370e8a29c636..f9e72d293f3c4 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1897,6 +1897,19 @@ static int fuse_notify_resend(struct fuse_conn *fc) return 0; } +/* + * Increments the fuse connection epoch. This will result of dentries from + * previous epochs to be invalidated. + * + * XXX optimization: add call to shrink_dcache_sb()? + */ +static int fuse_notify_inc_epoch(struct fuse_conn *fc) +{ + atomic_inc(&fc->epoch); + + return 0; +} + static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, unsigned int size, struct fuse_copy_state *cs) { @@ -1925,6 +1938,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, case FUSE_NOTIFY_RESEND: return fuse_notify_resend(fc); + case FUSE_NOTIFY_INC_EPOCH: + return fuse_notify_inc_epoch(fc); + default: fuse_copy_finish(cs); return -EINVAL; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 70fb57714f79d..f9e4ce8adfc06 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -197,9 +197,14 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) struct inode *inode; struct dentry *parent; struct fuse_mount *fm; + struct fuse_conn *fc; struct fuse_inode *fi; int ret; + fc = get_fuse_conn_super(entry->d_sb); + if (entry->d_time < atomic_read(&fc->epoch)) + goto invalid; + inode = d_inode_rcu(entry); if (inode && fuse_is_bad(inode)) goto invalid; @@ -415,16 +420,20 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, unsigned int flags) { - int err; struct fuse_entry_out outarg; + struct fuse_conn *fc; struct inode *inode; struct dentry *newent; + int err, epoch; bool outarg_valid = true; bool locked; if (fuse_is_bad(dir)) return ERR_PTR(-EIO); + fc = get_fuse_conn_super(dir->i_sb); + epoch = atomic_read(&fc->epoch); + locked = fuse_lock_inode(dir); err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name, &outarg, &inode); @@ -446,6 +455,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, goto out_err; entry = newent ? newent : entry; + entry->d_time = epoch; if (outarg_valid) fuse_change_entry_timeout(entry, &outarg); else @@ -618,7 +628,6 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, struct dentry *entry, struct file *file, unsigned int flags, umode_t mode, u32 opcode) { - int err; struct inode *inode; struct fuse_mount *fm = get_fuse_mount(dir); FUSE_ARGS(args); @@ -628,11 +637,13 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, struct fuse_entry_out outentry; struct fuse_inode *fi; struct fuse_file *ff; + int epoch, err; bool trunc = flags & O_TRUNC; /* Userspace expects S_IFREG in create mode */ BUG_ON((mode & S_IFMT) != S_IFREG); + epoch = atomic_read(&fm->fc->epoch); forget = fuse_alloc_forget(); err = -ENOMEM; if (!forget) @@ -701,6 +712,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, } kfree(forget); d_instantiate(entry, inode); + entry->d_time = epoch; fuse_change_entry_timeout(entry, &outentry); fuse_dir_changed(dir); err = generic_file_open(inode, file); @@ -787,12 +799,14 @@ static int create_new_entry(struct mnt_idmap *idmap, struct fuse_mount *fm, struct fuse_entry_out outarg; struct inode *inode; struct dentry *d; - int err; struct fuse_forget_link *forget; + int epoch, err; if (fuse_is_bad(dir)) return -EIO; + epoch = atomic_read(&fm->fc->epoch); + forget = fuse_alloc_forget(); if (!forget) return -ENOMEM; @@ -835,9 +849,11 @@ static int create_new_entry(struct mnt_idmap *idmap, struct fuse_mount *fm, return PTR_ERR(d); if (d) { + d->d_time = epoch; fuse_change_entry_timeout(d, &outarg); dput(d); } else { + entry->d_time = epoch; fuse_change_entry_timeout(entry, &outarg); } fuse_dir_changed(dir); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index e6cc3d552b138..6873c5f401a27 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -604,6 +604,9 @@ struct fuse_conn { /** Number of fuse_dev's */ atomic_t dev_count; + /** Current epoch for up-to-date dentries */ + atomic_t epoch; + struct rcu_head rcu; /** The user id for this mount */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index fd3321e29a3e5..8802111e0b478 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -926,6 +926,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, init_rwsem(&fc->killsb); refcount_set(&fc->count, 1); atomic_set(&fc->dev_count, 1); + atomic_set(&fc->epoch, 1); init_waitqueue_head(&fc->blocked_waitq); fuse_iqueue_init(&fc->iq, fiq_ops, fiq_priv); INIT_LIST_HEAD(&fc->bg_queue); diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index 0377b6dc24c80..1987cdbeeb075 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -161,6 +161,7 @@ static int fuse_direntplus_link(struct file *file, struct fuse_conn *fc; struct inode *inode; DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); + int epoch; if (!o->nodeid) { /* @@ -190,6 +191,7 @@ static int fuse_direntplus_link(struct file *file, return -EIO; fc = get_fuse_conn(dir); + epoch = atomic_read(&fc->epoch); name.hash = full_name_hash(parent, name.name, name.len); dentry = d_lookup(parent, &name); @@ -256,6 +258,7 @@ static int fuse_direntplus_link(struct file *file, } if (fc->readdirplus_auto) set_bit(FUSE_I_INIT_RDPLUS, &get_fuse_inode(inode)->state); + dentry->d_time = epoch; fuse_change_entry_timeout(dentry, o); dput(dentry); diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index f1e99458e29e4..a022b3707ad6d 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -220,6 +220,9 @@ * * 7.41 * - add FUSE_ALLOW_IDMAP + * + * 7.44 + * - add FUSE_NOTIFY_INC_EPOCH */ #ifndef _LINUX_FUSE_H @@ -255,7 +258,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 41 +#define FUSE_KERNEL_MINOR_VERSION 44 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -655,6 +658,7 @@ enum fuse_notify_code { FUSE_NOTIFY_RETRIEVE = 5, FUSE_NOTIFY_DELETE = 6, FUSE_NOTIFY_RESEND = 7, + FUSE_NOTIFY_INC_EPOCH = 8, FUSE_NOTIFY_CODE_MAX, }; From 01ff89021e3b87931ba07629454840228940d68f Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 31 Mar 2026 16:31:50 -0400 Subject: [PATCH 09/98] fuse: fix possibly missing fuse_copy_finish() call in fuse_notify() jira SECO-478 BUGFIX: FUSE commit-author Miklos Szeredi commit 0b563aad1c0a05dc7d123f68a9f82f79de206dad In case of FUSE_NOTIFY_RESEND and FUSE_NOTIFY_INC_EPOCH fuse_copy_finish() isn't called. Fix by always calling fuse_copy_finish() after fuse_notify(). It's a no-op if called a second time. Fixes: 760eac73f9f6 ("fuse: Introduce a new notification type for resend pending requests") Fixes: 2396356a945b ("fuse: add more control over cache invalidation behaviour") Cc: # v6.9 Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 0b563aad1c0a05dc7d123f68a9f82f79de206dad) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index f9e72d293f3c4..c09f6ea0b4e97 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2015,7 +2015,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, */ if (!oh.unique) { err = fuse_notify(fc, oh.error, nbytes - sizeof(oh), cs); - goto out; + goto copy_finish; } err = -EINVAL; From 2c5df5aa20737e5b5935282c2b72da40fa1bce41 Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Mon, 4 May 2026 10:12:39 +0000 Subject: [PATCH 10/98] fs: fuse: add dev id to /dev/fuse fdinfo jira SECO-511 commit-author Chen Linxuan commit f09222980d775199de2f5d739cf453f7bf39aa4a upstream-diff | There were conflicts seen while applying this patch due to the following missing commit :- 786412a73e7e ("fuse: enable fuse-over-io-uring") This commit add fuse connection device id to fdinfo of opened /dev/fuse files. Related discussions can be found at links below. Link: https://lore.kernel.org/all/CAJfpegvEYUgEbpATpQx8NqVR33Mv-VK96C+gbTag1CEUeBqvnA@mail.gmail.com/ Signed-off-by: Chen Linxuan Signed-off-by: Miklos Szeredi (cherry picked from commit f09222980d775199de2f5d739cf453f7bf39aa4a) Signed-off-by: Shreeya Patel --- fs/fuse/dev.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index c09f6ea0b4e97..79957d39368ef 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -21,6 +21,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include "fuse_trace.h" @@ -2479,6 +2480,17 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd, } } +#ifdef CONFIG_PROC_FS +static void fuse_dev_show_fdinfo(struct seq_file *seq, struct file *file) +{ + struct fuse_dev *fud = fuse_get_dev(file); + if (!fud) + return; + + seq_printf(seq, "fuse_connection:\t%u\n", fud->fc->dev); +} +#endif + const struct file_operations fuse_dev_operations = { .owner = THIS_MODULE, .open = fuse_dev_open, @@ -2491,6 +2503,9 @@ const struct file_operations fuse_dev_operations = { .fasync = fuse_dev_fasync, .unlocked_ioctl = fuse_dev_ioctl, .compat_ioctl = compat_ptr_ioctl, +#ifdef CONFIG_PROC_FS + .show_fdinfo = fuse_dev_show_fdinfo, +#endif }; EXPORT_SYMBOL_GPL(fuse_dev_operations); From 65b4e381a530b5dbb8d811aecd955fe272956c10 Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Mon, 4 May 2026 10:14:42 +0000 Subject: [PATCH 11/98] fuse: respect FOPEN_KEEP_CACHE on opendir jira SECO-518 commit-author Amir Goldstein commit 03f275adb8fbd7b4ebe96a1ad5044d8e602692dc The re-factoring of fuse_dir_open() missed the need to invalidate directory inode page cache with open flag FOPEN_KEEP_CACHE. Fixes: 7de64d521bf92 ("fuse: break up fuse_open_common()") Reported-by: Prince Kumar Closes: https://lore.kernel.org/linux-fsdevel/CAEW=TRr7CYb4LtsvQPLj-zx5Y+EYBmGfM24SuzwyDoGVNoKm7w@mail.gmail.com/ Signed-off-by: Amir Goldstein Link: https://lore.kernel.org/r/20250101130037.96680-1-amir73il@gmail.com Reviewed-by: Bernd Schubert Signed-off-by: Christian Brauner (cherry picked from commit 03f275adb8fbd7b4ebe96a1ad5044d8e602692dc) Signed-off-by: Shreeya Patel --- fs/fuse/dir.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f9e4ce8adfc06..f2fadaf084843 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1696,6 +1696,8 @@ static int fuse_dir_open(struct inode *inode, struct file *file) */ if (ff->open_flags & (FOPEN_STREAM | FOPEN_NONSEEKABLE)) nonseekable_open(inode, file); + if (!(ff->open_flags & FOPEN_KEEP_CACHE)) + invalidate_inode_pages2(inode->i_mapping); } return err; From ce325706e29062ffa8f017f60fd851fa798918db Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Mon, 17 Aug 2026 19:38:18 +0000 Subject: [PATCH 12/98] gve: fix zero-length skb frag with header-split jira KERNEL-1217 commit-author Jordan Rhee commit 6bf14575c65569dcded90ef78afb8a6d57323f04 upstream-diff RLC 10 predates the page-pool conversion and has no gve_free_buffer(); the freed buffer goes through gve_enqueue_buf_state(rx, &rx->dqo.recycled_buf_states, buf_state), the same form this tree's rx error path uses, which is what upstream's helper reduces to on non-page-pool queues. When header split is enabled and a header-only packet is received such as a pure TCP ACK, GVE will indicate an RX SKB with a zero-length fragment. If this SKB is then hairpinned and sent back out, the GVE TX path will emit a zero-length descriptor. Hardware considers this an illegal descriptor and stops the queue, causing a TX timeout and interface reset. Fix it by not adding the zero-length skb frag. Cc: stable@vger.kernel.org Fixes: 5e37d8254e7f ("gve: Add header split data path") Suggested-by: Praveen Kaligineedi Co-developed-by: Ziwei Xiao Signed-off-by: Ziwei Xiao Signed-off-by: Jordan Rhee Signed-off-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260807224315.234152-2-hramamurthy@google.com Signed-off-by: Jakub Kicinski (cherry picked from commit 6bf14575c65569dcded90ef78afb8a6d57323f04) Signed-off-by: Shreeya Patel --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 7e52abe89e767..4b997c8bef460 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -869,6 +869,12 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, rx->rx_hsplit_unsplit_pkt += unsplit; rx->rx_hsplit_bytes += hdr_len; u64_stats_update_end(&rx->statss); + + if (!buf_len) { + gve_enqueue_buf_state(rx, &rx->dqo.recycled_buf_states, + buf_state); + return 0; + } } /* Sync the portion of dma buffer for CPU to read. */ From a1c7cebd09ba17297ee7dc51af6eb65baebf4878 Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Tue, 11 Aug 2026 18:19:46 +0000 Subject: [PATCH 13/98] gve: bound DQO-QPL TX buffer count to the s16 free-list range jira KERNEL-1217 gve_tx_qpl_buf_init() sizes the TX buffer free list as GVE_TX_BUFS_PER_PAGE_DQO * num_entries, but the free-list links (tx_qpl_buf_next) are s16. GVE_TX_BUFS_PER_PAGE_DQO is PAGE_SIZE >> 11, so on 64K-page builds (the aarch64-64k config ships CONFIG_GVE=m) a 4096-entry TX ring yields 2048 pages and 65536 buffers: entry 32767 is seeded with a link of 32768, which reads back as -32768, and once 32768 buffers have been allocated gve_alloc_tx_qpl_buf() dereferences tx_qpl_buf_next[-32768] and returns ids that index outside the QPL. Clamp the count to S16_MAX, leaving an oversized QPL's tail unused rather than mislinked. This is not unique to this backport: upstream has the same unclamped math at its tip. Kept as a separate commit so it can be dropped in favor of the upstream fix once one lands; Fixes: 07993df56091 ("gve: Update QPL page registration logic") Signed-off-by: Shreeya Patel --- drivers/net/ethernet/google/gve/gve_tx_dqo.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 0992c6d11eb06..d6d81c29c915e 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -272,8 +272,9 @@ static void gve_tx_free_ring_dqo(struct gve_priv *priv, struct gve_tx_ring *tx, static int gve_tx_qpl_buf_init(struct gve_tx_ring *tx) { - int num_tx_qpl_bufs = GVE_TX_BUFS_PER_PAGE_DQO * - tx->dqo.qpl->num_entries; + int num_tx_qpl_bufs = min_t(int, + GVE_TX_BUFS_PER_PAGE_DQO * tx->dqo.qpl->num_entries, + S16_MAX); int i; tx->dqo.tx_qpl_buf_next = kvcalloc(num_tx_qpl_bufs, From 4c6ec58cb89c5df59f20046341e55ed856ca64af Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 31 Aug 2026 09:36:54 -0400 Subject: [PATCH 14/98] xfrm: nat_keepalive: avoid double free on send error cve CVE-2026-72137 commit-author Qianyu Luo commit 226f4a490d1a938fc838d8f8c46a4eca864c0d78 nat_keepalive_send() frees the keepalive skb whenever the IPv4 or IPv6 send helper reports an error. That cleanup is only correct before the skb is handed to the output path. Once ip_build_and_send_pkt() or ip6_xmit() takes ownership, the networking stack may already have consumed the skb before returning an error, so freeing it again is unsafe. Handle the pre-handoff failure cases inside nat_keepalive_send_ipv4() and nat_keepalive_send_ipv6(), where the caller still owns the skb, and keep nat_keepalive_send() responsible only for family dispatch and the unsupported-family cleanup path. Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Xin Liu Signed-off-by: Qianyu Luo Signed-off-by: Ren Wei Reviewed-by: Eyal Birger Signed-off-by: Steffen Klassert (cherry picked from commit 226f4a490d1a938fc838d8f8c46a4eca864c0d78) Signed-off-by: Brett Mastbergen --- net/xfrm/xfrm_nat_keepalive.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c index ebf95d48e86c1..6b96a952c88f2 100644 --- a/net/xfrm/xfrm_nat_keepalive.c +++ b/net/xfrm/xfrm_nat_keepalive.c @@ -55,8 +55,10 @@ static int nat_keepalive_send_ipv4(struct sk_buff *skb, ka->encap_sport, sock_net_uid(net, NULL)); rt = ip_route_output_key(net, &fl4); - if (IS_ERR(rt)) + if (IS_ERR(rt)) { + kfree_skb(skb); return PTR_ERR(rt); + } skb_dst_set(skb, &rt->dst); @@ -101,6 +103,7 @@ static int nat_keepalive_send_ipv6(struct sk_buff *skb, dst = ipv6_stub->ipv6_dst_lookup_flow(net, sk, &fl6, NULL); if (IS_ERR(dst)) { local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock); + kfree_skb(skb); return PTR_ERR(dst); } @@ -118,7 +121,6 @@ static void nat_keepalive_send(struct nat_keepalive *ka) sizeof(struct ipv6hdr)) + sizeof(struct udphdr); const u8 nat_ka_payload = 0xFF; - int err = -EAFNOSUPPORT; struct sk_buff *skb; struct udphdr *uh; @@ -140,16 +142,17 @@ static void nat_keepalive_send(struct nat_keepalive *ka) switch (ka->family) { case AF_INET: - err = nat_keepalive_send_ipv4(skb, ka); + nat_keepalive_send_ipv4(skb, ka); break; #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: - err = nat_keepalive_send_ipv6(skb, ka, uh); + nat_keepalive_send_ipv6(skb, ka, uh); break; #endif - } - if (err) + default: kfree_skb(skb); + break; + } } struct nat_keepalive_work_ctx { From 2a21b5638171f68a9bf85890e475ecb5d616fe2d Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 31 Aug 2026 10:54:51 -0400 Subject: [PATCH 15/98] af_unix: Set error only when needed in unix_dgram_sendmsg(). cve-pre CVE-2026-53361 commit-author Kuniyuki Iwashima commit 001a25088c35ab69bd4b2f208e47eb8acbce6353 upstream-diff | Context difference due to backported commit by redhat that technically comes after this change. 384900542dc85f3aac7918fea8e7ef62141e3ea6 [af_unix: Don't call wait_for_unix_gc() on every sendmsg().] We will introduce skb drop reason for AF_UNIX, then we need to set an errno and a drop reason for each path. Let's set an error only when it's needed in unix_dgram_sendmsg(). Then, we need not (re)set 0 to err. Signed-off-by: Kuniyuki Iwashima Signed-off-by: Paolo Abeni (cherry picked from commit 001a25088c35ab69bd4b2f208e47eb8acbce6353) Signed-off-by: Brett Mastbergen --- net/unix/af_unix.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index ecf09941a0f3c..f69bb9f6e6416 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -1969,9 +1969,10 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, if (err < 0) return err; - err = -EOPNOTSUPP; - if (msg->msg_flags&MSG_OOB) + if (msg->msg_flags & MSG_OOB) { + err = -EOPNOTSUPP; goto out; + } if (msg->msg_namelen) { err = unix_validate_addr(sunaddr, msg->msg_namelen); @@ -1986,10 +1987,11 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, goto out; } else { sunaddr = NULL; - err = -ENOTCONN; other = unix_peer_get(sk); - if (!other) + if (!other) { + err = -ENOTCONN; goto out; + } } if ((test_bit(SOCK_PASSCRED, &sock->flags) || @@ -2000,9 +2002,10 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, goto out; } - err = -EMSGSIZE; - if (len > READ_ONCE(sk->sk_sndbuf) - 32) + if (len > READ_ONCE(sk->sk_sndbuf) - 32) { + err = -EMSGSIZE; goto out; + } if (len > SKB_MAX_ALLOC) { data_len = min_t(size_t, @@ -2034,9 +2037,10 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, restart: if (!other) { - err = -ECONNRESET; - if (sunaddr == NULL) + if (!sunaddr) { + err = -ECONNRESET; goto out_free; + } other = unix_find_other(sock_net(sk), sunaddr, msg->msg_namelen, sk->sk_type); @@ -2056,9 +2060,11 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, sk_locked = 0; unix_state_lock(other); restart_locked: - err = -EPERM; - if (!unix_may_send(sk, other)) + + if (!unix_may_send(sk, other)) { + err = -EPERM; goto out_unlock; + } if (unlikely(sock_flag(other, SOCK_DEAD))) { /* @@ -2071,7 +2077,6 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, if (!sk_locked) unix_state_lock(sk); - err = 0; if (sk->sk_type == SOCK_SEQPACKET) { /* We are here only when racing with unix_release_sock() * is clearing @other. Never change state to TCP_CLOSE @@ -2099,9 +2104,10 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, goto restart; } - err = -EPIPE; - if (other->sk_shutdown & RCV_SHUTDOWN) + if (other->sk_shutdown & RCV_SHUTDOWN) { + err = -EPIPE; goto out_unlock; + } if (sk->sk_type != SOCK_SEQPACKET) { err = security_unix_may_send(sk->sk_socket, other->sk_socket); From 9d2d9a695749c6c4265a02b242527756d21551cb Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Tue, 8 Sep 2026 13:10:32 -0400 Subject: [PATCH 16/98] sctp: fix auth_hmacs array size in struct sctp_cookie cve CVE-2026-68376 commit-author Xin Long commit e0b5252a59383b77d1b8dbeda00b7184dd95f4d3 The auth_hmacs array in struct sctp_cookie is supposed to store a complete SCTP_AUTH_HMAC_ALGO parameter, which consists of a struct sctp_paramhdr followed by N HMAC identifiers. However, the array size was calculated using an extra 2 bytes instead of sizeof(struct sctp_paramhdr), which is 4 bytes. When four HMAC identifiers are configured, the HMAC-ALGO parameter stored in the endpoint is larger than the auth_hmacs buffer in the cookie. As a result, sctp_association_init() copies beyond the end of auth_hmacs when initializing the association, corrupting the adjacent auth_chunks field. This can lead to an invalid HMAC identifier being accepted and later cause an out-of-bounds read in sctp_auth_get_hmac(). Fix the array size calculation by including the full SCTP parameter header size. Fixes: 1f485649f529 ("[SCTP]: Implement SCTP-AUTH internals") Reported-by: Yuan Tan Reported-by: Xin Liu Reported-by: Zihan Xi Reported-by: Ren Wei Signed-off-by: Xin Long Link: https://patch.msgid.link/634a0de0d5de29532915e6d47c92a0cbc206e03f.1783707155.git.lucien.xin@gmail.com Signed-off-by: Paolo Abeni (cherry picked from commit e0b5252a59383b77d1b8dbeda00b7184dd95f4d3) Signed-off-by: Brett Mastbergen --- include/net/sctp/structs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index 521a86ae64855..9e3d782342f6a 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h @@ -319,7 +319,8 @@ struct sctp_cookie { __u8 auth_random[sizeof(struct sctp_paramhdr) + SCTP_AUTH_RANDOM_LENGTH]; - __u8 auth_hmacs[SCTP_AUTH_NUM_HMACS * sizeof(__u16) + 2]; + __u8 auth_hmacs[sizeof(struct sctp_paramhdr) + + SCTP_AUTH_NUM_HMACS * sizeof(__u16)]; __u8 auth_chunks[sizeof(struct sctp_paramhdr) + SCTP_AUTH_MAX_CHUNKS]; /* This is a shim for my peer's INIT packet, followed by From 8b3c97b66ae5637717e09b626b51f893cb59cfb2 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Tue, 8 Sep 2026 13:10:37 -0400 Subject: [PATCH 17/98] openvswitch: defer tunnel netdev_put to RCU release cve CVE-2026-31678 commit-author Yang Yang commit 6931d21f87bc6d657f145798fad0bf077b82486c ovs_netdev_tunnel_destroy() may run after NETDEV_UNREGISTER already detached the device. Dropping the netdev reference in destroy can race with concurrent readers that still observe vport->dev. Do not release vport->dev in ovs_netdev_tunnel_destroy(). Instead, let vport_netdev_free() drop the reference from the RCU callback, matching the non-tunnel destroy path and avoiding additional synchronization under RTNL. Fixes: a9020fde67a6 ("openvswitch: Move tunnel destroy function to oppenvswitch module.") Reported-by: Yifan Wu Reported-by: Juefei Pu Tested-by: Ao Zhou Co-developed-by: Yuan Tan Signed-off-by: Yuan Tan Suggested-by: Xin Liu Signed-off-by: Yang Yang Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20260319074241.3405262-1-n05ec@lzu.edu.cn Signed-off-by: Jakub Kicinski (cherry picked from commit 6931d21f87bc6d657f145798fad0bf077b82486c) Signed-off-by: Brett Mastbergen --- net/openvswitch/vport-netdev.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c index c688dee96503f..12055af832dc0 100644 --- a/net/openvswitch/vport-netdev.c +++ b/net/openvswitch/vport-netdev.c @@ -196,8 +196,6 @@ void ovs_netdev_tunnel_destroy(struct vport *vport) */ if (vport->dev->reg_state == NETREG_REGISTERED) rtnl_delete_link(vport->dev, 0, NULL); - netdev_put(vport->dev, &vport->dev_tracker); - vport->dev = NULL; rtnl_unlock(); call_rcu(&vport->rcu, vport_netdev_free); From 1d82cc5cea6af9990075c1e30b57226b8a82c0b1 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Tue, 8 Sep 2026 15:50:36 -0400 Subject: [PATCH 18/98] openvswitch: vport: fix self-deadlock on release of tunnel ports cve CVE-2026-46165 commit-author Ilya Maximets commit aa69918bd418e700309fdd08509dba324fb24296 vports are used concurrently and protected by RCU, so netdev_put() must happen after the RCU grace period. So, either in an RCU call or after the synchronize_net(). The rtnl_delete_link() must happen under RTNL and so can't be executed in RCU context. Calling synchronize_net() while holding RTNL is not a good idea for performance and system stability under load in general, so calling netdev_put() in RCU call is the right solution here. However, when the device is deleted, rtnl_unlock() will call netdev_run_todo() and block until all the references are gone. In the current code this means that we never reach the call_rcu() and the vport is never freed and the reference is never released, causing a self-deadlock on device removal. Fix that by moving the rcu_call() before the rtnl_unlock(), so the scheduled RCU callback will be executed when synchronize_net() is called from the rtnl_unlock()->netdev_run_todo() while the RTNL itself is already released. Fixes: 6931d21f87bc ("openvswitch: defer tunnel netdev_put to RCU release") Cc: stable@vger.kernel.org Acked-by: Eelco Chaudron Signed-off-by: Ilya Maximets Acked-by: Aaron Conole Link: https://patch.msgid.link/20260430233848.440994-2-i.maximets@ovn.org Signed-off-by: Paolo Abeni (cherry picked from commit aa69918bd418e700309fdd08509dba324fb24296) Signed-off-by: Brett Mastbergen --- net/openvswitch/vport-netdev.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c index 12055af832dc0..a1df551e915bc 100644 --- a/net/openvswitch/vport-netdev.c +++ b/net/openvswitch/vport-netdev.c @@ -196,9 +196,13 @@ void ovs_netdev_tunnel_destroy(struct vport *vport) */ if (vport->dev->reg_state == NETREG_REGISTERED) rtnl_delete_link(vport->dev, 0, NULL); - rtnl_unlock(); + /* We can't put the device reference yet, since it can still be in + * use, but rtnl_unlock()->netdev_run_todo() will block until all + * the references are released, so the RCU call must be before it. + */ call_rcu(&vport->rcu, vport_netdev_free); + rtnl_unlock(); } EXPORT_SYMBOL_GPL(ovs_netdev_tunnel_destroy); From 0800bed3020d12227688493c79698b8d9c190166 Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Tue, 8 Sep 2026 13:16:25 +0000 Subject: [PATCH 19/98] ip6_tunnel: clear skb2->cb[] in ip6ip6_err() cve CVE-2026-74597 commit-author Zhiling Zou commit f803c086399da277b5d0ff36a107d0f162751800 ip6ip6_err() clones an outer IPv6 ICMP error skb, pulls it to the quoted inner IPv6 packet, and then passes the clone to icmpv6_send(). The clone still carries the outer packet's inet6_skb_parm in skb->cb. If the outer packet had a Home Address Option, IP6CB(skb2)->dsthao remains non-zero after skb_pull(). icmpv6_send() later calls mip6_addr_swap(), which uses that stale dsthao offset against the quoted inner packet. A malformed inner destination-options header can then make the HAO lookup and address swap run past the end of the quoted packet and corrupt skb_shared_info. Clear skb2->cb[] before pulling the quoted inner IPv6 packet so the reply path does not reuse metadata left by the outer IPv6 stack. Fixes: e490d1d85cf5 ("[IPV6] IP6TUNNEL: Split out generic routine in ip6ip6_err().") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/fe1a5e765fbca88d69391887f0ed26a19e3e4d39.1785736562.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski (cherry picked from commit f803c086399da277b5d0ff36a107d0f162751800) Signed-off-by: Shreeya Patel --- net/ipv6/ip6_tunnel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index e807c5e15b052..c6d6f28d5fa32 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -678,6 +678,9 @@ ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, if (!skb2) return 0; + /* Remove debris left by outer IPv6 stack. */ + memset(IP6CB(skb2), 0, sizeof(*IP6CB(skb2))); + skb_dst_drop(skb2); skb_pull(skb2, offset); skb_reset_network_header(skb2); From fc8bf3e520149970355b8c87c48c3915e9d40f2f Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 14:50:16 -0500 Subject: [PATCH 20/98] fuse: rename to fuse_dev_end_requests and make non-static jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 92270d076115ae81b83f0605703602d2d5a866e2 This function is needed by fuse_uring.c to clean ring queues, so make it non static. Especially in non-static mode the function name 'end_requests' should be prefixed with fuse_ Signed-off-by: Bernd Schubert Reviewed-by: Josef Bacik Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 92270d076115ae81b83f0605703602d2d5a866e2) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 13 +++++++------ fs/fuse/fuse_dev_i.h | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 fs/fuse/fuse_dev_i.h diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 79957d39368ef..e3db380043a6e 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -7,6 +7,7 @@ */ #include "fuse_i.h" +#include "fuse_dev_i.h" #include #include @@ -35,8 +36,6 @@ MODULE_ALIAS("devname:fuse"); static struct kmem_cache *fuse_req_cachep; -static void end_requests(struct list_head *head); - static struct fuse_dev *fuse_get_dev(struct file *file) { /* @@ -1872,7 +1871,9 @@ static void fuse_resend(struct fuse_conn *fc) spin_lock(&fiq->lock); if (!fiq->connected) { spin_unlock(&fiq->lock); - end_requests(&to_queue); + list_for_each_entry(req, &to_queue, list) + clear_bit(FR_PENDING, &req->flags); + fuse_dev_end_requests(&to_queue); return; } /* @@ -2219,7 +2220,7 @@ static __poll_t fuse_dev_poll(struct file *file, poll_table *wait) } /* Abort all requests on the given list (pending or processing) */ -static void end_requests(struct list_head *head) +void fuse_dev_end_requests(struct list_head *head) { while (!list_empty(head)) { struct fuse_req *req; @@ -2322,7 +2323,7 @@ void fuse_abort_conn(struct fuse_conn *fc) wake_up_all(&fc->blocked_waitq); spin_unlock(&fc->lock); - end_requests(&to_end); + fuse_dev_end_requests(&to_end); } else { spin_unlock(&fc->lock); } @@ -2352,7 +2353,7 @@ int fuse_dev_release(struct inode *inode, struct file *file) list_splice_init(&fpq->processing[i], &to_end); spin_unlock(&fpq->lock); - end_requests(&to_end); + fuse_dev_end_requests(&to_end); /* Are we the last open device? */ if (atomic_dec_and_test(&fc->dev_count)) { diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h new file mode 100644 index 0000000000000..4fcff2223fa60 --- /dev/null +++ b/fs/fuse/fuse_dev_i.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * FUSE: Filesystem in Userspace + * Copyright (C) 2001-2008 Miklos Szeredi + */ +#ifndef _FS_FUSE_DEV_I_H +#define _FS_FUSE_DEV_I_H + +#include + +void fuse_dev_end_requests(struct list_head *head); + +#endif + From cf58aad8bd0dd2c83871ceffa5782e9fed53d182 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 14:50:50 -0500 Subject: [PATCH 21/98] fuse: Move fuse_get_dev to header file jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 867d93dcdede5ea453543085d1ed0bf43cde60de Another preparation patch, as this function will be needed by fuse/dev.c and fuse/dev_uring.c. Signed-off-by: Bernd Schubert Reviewed-by: Josef Bacik Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 867d93dcdede5ea453543085d1ed0bf43cde60de) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 9 --------- fs/fuse/fuse_dev_i.h | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index e3db380043a6e..ab02f3106c535 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -36,15 +36,6 @@ MODULE_ALIAS("devname:fuse"); static struct kmem_cache *fuse_req_cachep; -static struct fuse_dev *fuse_get_dev(struct file *file) -{ - /* - * Lockless access is OK, because file->private data is set - * once during mount and is valid until the file is released. - */ - return READ_ONCE(file->private_data); -} - static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req) { INIT_LIST_HEAD(&req->list); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 4fcff2223fa60..e7ea1b21c1820 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -8,6 +8,15 @@ #include +static inline struct fuse_dev *fuse_get_dev(struct file *file) +{ + /* + * Lockless access is OK, because file->private data is set + * once during mount and is valid until the file is released. + */ + return READ_ONCE(file->private_data); +} + void fuse_dev_end_requests(struct list_head *head); #endif From 4c77c0b81fa4b9c2e0f522206308d64735cd5d23 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 14:51:44 -0500 Subject: [PATCH 22/98] fuse: Move request bits jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 88be7aa98d91f5761f8764273ebd961915e4632d These are needed by fuse-over-io-uring. Signed-off-by: Bernd Schubert Reviewed-by: Josef Bacik Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 88be7aa98d91f5761f8764273ebd961915e4632d) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 4 ---- fs/fuse/fuse_dev_i.h | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index ab02f3106c535..7e54e23ed73b2 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -30,10 +30,6 @@ MODULE_ALIAS_MISCDEV(FUSE_MINOR); MODULE_ALIAS("devname:fuse"); -/* Ordinary requests have even IDs, while interrupts IDs are odd */ -#define FUSE_INT_REQ_BIT (1ULL << 0) -#define FUSE_REQ_ID_STEP (1ULL << 1) - static struct kmem_cache *fuse_req_cachep; static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req) diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index e7ea1b21c1820..08a7e88e00277 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -8,6 +8,10 @@ #include +/* Ordinary requests have even IDs, while interrupts IDs are odd */ +#define FUSE_INT_REQ_BIT (1ULL << 0) +#define FUSE_REQ_ID_STEP (1ULL << 1) + static inline struct fuse_dev *fuse_get_dev(struct file *file) { /* From 61204e895912698627744d2e651f48611b5cf53b Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 15:13:32 -0500 Subject: [PATCH 23/98] fuse: make args->in_args[0] to be always the header jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 7ccd86ba3a485a8bc33478776eb7053d9adb7816 upstream-diff | Context conflict on missing fs/fuse/fuse_i.h commit 41748675c0bf -virtiofs: use pages instead of pointer for kernel direct IO This change sets up FUSE operations to always have headers in args.in_args[0], even for opcodes without an actual header. This step prepares for a clean separation of payload from headers, initially it is used by fuse-over-io-uring. For opcodes without a header, we use a zero-sized struct as a placeholder. This approach: - Keeps things consistent across all FUSE operations - Will help with payload alignment later - Avoids future issues when header sizes change Op codes that already have an op code specific header do not need modification. Op codes that have neither payload nor op code headers are not modified either (FUSE_READLINK and FUSE_DESTROY). FUSE_BATCH_FORGET already has the header in the right place, but is not using fuse_copy_args - as -over-uring is currently not handling forgets it does not matter for now, but header separation will later need special attention for that op code. Correct the struct fuse_args->in_args array max size. Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 7ccd86ba3a485a8bc33478776eb7053d9adb7816) Signed-off-by: Jonathan Maple --- fs/fuse/dax.c | 11 ++++++----- fs/fuse/dev.c | 9 +++++---- fs/fuse/dir.c | 32 ++++++++++++++++++-------------- fs/fuse/fuse_i.h | 15 ++++++++++++++- fs/fuse/xattr.c | 7 ++++--- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c index 12ef91d170bb3..44bd30d448e4e 100644 --- a/fs/fuse/dax.c +++ b/fs/fuse/dax.c @@ -240,11 +240,12 @@ static int fuse_send_removemapping(struct inode *inode, args.opcode = FUSE_REMOVEMAPPING; args.nodeid = fi->nodeid; - args.in_numargs = 2; - args.in_args[0].size = sizeof(*inargp); - args.in_args[0].value = inargp; - args.in_args[1].size = inargp->count * sizeof(*remove_one); - args.in_args[1].value = remove_one; + args.in_numargs = 3; + fuse_set_zero_arg0(&args); + args.in_args[1].size = sizeof(*inargp); + args.in_args[1].value = inargp; + args.in_args[2].size = inargp->count * sizeof(*remove_one); + args.in_args[2].value = remove_one; return fuse_simple_request(fm, &args); } diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 7e54e23ed73b2..690ec7c74e64b 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1738,7 +1738,7 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, args = &ap->args; args->nodeid = outarg->nodeid; args->opcode = FUSE_NOTIFY_REPLY; - args->in_numargs = 2; + args->in_numargs = 3; args->in_pages = true; args->end = fuse_retrieve_end; @@ -1765,9 +1765,10 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, } ra->inarg.offset = outarg->offset; ra->inarg.size = total_len; - args->in_args[0].size = sizeof(ra->inarg); - args->in_args[0].value = &ra->inarg; - args->in_args[1].size = total_len; + fuse_set_zero_arg0(args); + args->in_args[1].size = sizeof(ra->inarg); + args->in_args[1].value = &ra->inarg; + args->in_args[2].size = total_len; err = fuse_simple_notify_reply(fm, args, outarg->notify_unique); if (err) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f2fadaf084843..be3a6720fcba1 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -175,9 +175,10 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args, memset(outarg, 0, sizeof(struct fuse_entry_out)); args->opcode = FUSE_LOOKUP; args->nodeid = nodeid; - args->in_numargs = 1; - args->in_args[0].size = name->len + 1; - args->in_args[0].value = name->name; + args->in_numargs = 2; + fuse_set_zero_arg0(args); + args->in_args[1].size = name->len + 1; + args->in_args[1].value = name->name; args->out_numargs = 1; args->out_args[0].size = sizeof(struct fuse_entry_out); args->out_args[0].value = outarg; @@ -943,11 +944,12 @@ static int fuse_symlink(struct mnt_idmap *idmap, struct inode *dir, FUSE_ARGS(args); args.opcode = FUSE_SYMLINK; - args.in_numargs = 2; - args.in_args[0].size = entry->d_name.len + 1; - args.in_args[0].value = entry->d_name.name; - args.in_args[1].size = len; - args.in_args[1].value = link; + args.in_numargs = 3; + fuse_set_zero_arg0(&args); + args.in_args[1].size = entry->d_name.len + 1; + args.in_args[1].value = entry->d_name.name; + args.in_args[2].size = len; + args.in_args[2].value = link; return create_new_entry(idmap, fm, &args, dir, entry, S_IFLNK); } @@ -1007,9 +1009,10 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry) args.opcode = FUSE_UNLINK; args.nodeid = get_node_id(dir); - args.in_numargs = 1; - args.in_args[0].size = entry->d_name.len + 1; - args.in_args[0].value = entry->d_name.name; + args.in_numargs = 2; + fuse_set_zero_arg0(&args); + args.in_args[1].size = entry->d_name.len + 1; + args.in_args[1].value = entry->d_name.name; err = fuse_simple_request(fm, &args); if (!err) { fuse_dir_changed(dir); @@ -1030,9 +1033,10 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) args.opcode = FUSE_RMDIR; args.nodeid = get_node_id(dir); - args.in_numargs = 1; - args.in_args[0].size = entry->d_name.len + 1; - args.in_args[0].value = entry->d_name.name; + args.in_numargs = 2; + fuse_set_zero_arg0(&args); + args.in_args[1].size = entry->d_name.len + 1; + args.in_args[1].value = entry->d_name.name; err = fuse_simple_request(fm, &args); if (!err) { fuse_dir_changed(dir); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6873c5f401a27..696479fa17c9a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -309,7 +309,7 @@ struct fuse_args { bool may_block:1; bool is_ext:1; bool is_pinned:1; - struct fuse_in_arg in_args[3]; + struct fuse_in_arg in_args[4]; struct fuse_arg out_args[2]; void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); }; @@ -941,6 +941,19 @@ struct fuse_mount { struct rcu_head rcu; }; +/* + * Empty header for FUSE opcodes without specific header needs. + * Used as a placeholder in args->in_args[0] for consistency + * across all FUSE operations, simplifying request handling. + */ +struct fuse_zero_header {}; + +static inline void fuse_set_zero_arg0(struct fuse_args *args) +{ + args->in_args[0].size = sizeof(struct fuse_zero_header); + args->in_args[0].value = NULL; +} + static inline struct fuse_mount *get_fuse_mount_super(struct super_block *sb) { return sb->s_fs_info; diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index 9f568d345c512..93dfb06b6cea0 100644 --- a/fs/fuse/xattr.c +++ b/fs/fuse/xattr.c @@ -164,9 +164,10 @@ int fuse_removexattr(struct inode *inode, const char *name) args.opcode = FUSE_REMOVEXATTR; args.nodeid = get_node_id(inode); - args.in_numargs = 1; - args.in_args[0].size = strlen(name) + 1; - args.in_args[0].value = name; + args.in_numargs = 2; + fuse_set_zero_arg0(&args); + args.in_args[1].size = strlen(name) + 1; + args.in_args[1].value = name; err = fuse_simple_request(fm, &args); if (err == -ENOSYS) { fm->fc->no_removexattr = 1; From 86b1edba6fab15ca4b2d036c089e2f9661e447f9 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 15:27:53 -0500 Subject: [PATCH 24/98] fuse: {io-uring} Handle SQEs - register commands jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 24fe962c86f55347385933a1b06ca71b60854690 upstream-diff | Context diff due to missing commit on fs/fuse/Makefile 2b3933b1e0a0 - fuse: enable dynamic configuration of fuse max pages limit (FUSE_MAX_MAX_PAGES) Context difference due to future feature enabled earlier in git log order as well. This adds basic support for ring SQEs (with opcode=IORING_OP_URING_CMD). For now only FUSE_IO_URING_CMD_REGISTER is handled to register queue entries. Signed-off-by: Bernd Schubert Reviewed-by: Pavel Begunkov # io_uring Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 24fe962c86f55347385933a1b06ca71b60854690) Signed-off-by: Jonathan Maple --- fs/fuse/Kconfig | 12 ++ fs/fuse/Makefile | 1 + fs/fuse/dev_uring.c | 326 ++++++++++++++++++++++++++++++++++++++ fs/fuse/dev_uring_i.h | 113 +++++++++++++ fs/fuse/fuse_i.h | 5 + fs/fuse/inode.c | 10 ++ include/uapi/linux/fuse.h | 75 +++++++++ 7 files changed, 542 insertions(+) create mode 100644 fs/fuse/dev_uring.c create mode 100644 fs/fuse/dev_uring_i.h diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig index 8674dbfbe59db..ca215a3cba3e3 100644 --- a/fs/fuse/Kconfig +++ b/fs/fuse/Kconfig @@ -63,3 +63,15 @@ config FUSE_PASSTHROUGH to be performed directly on a backing file. If you want to allow passthrough operations, answer Y. + +config FUSE_IO_URING + bool "FUSE communication over io-uring" + default y + depends on FUSE_FS + depends on IO_URING + help + This allows sending FUSE requests over the io-uring interface and + also adds request core affinity. + + If you want to allow fuse server/client communication through io-uring, + answer Y diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index ce0ff7a9007b9..fcf16b1c391a9 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -14,5 +14,6 @@ fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o fuse-y += iomode.o fuse-$(CONFIG_FUSE_DAX) += dax.o fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o +fuse-$(CONFIG_FUSE_IO_URING) += dev_uring.o virtiofs-y := virtio_fs.o diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c new file mode 100644 index 0000000000000..42092a234570e --- /dev/null +++ b/fs/fuse/dev_uring.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE: Filesystem in Userspace + * Copyright (c) 2023-2024 DataDirect Networks. + */ + +#include "fuse_i.h" +#include "dev_uring_i.h" +#include "fuse_dev_i.h" + +#include +#include + +static bool __read_mostly enable_uring; +module_param(enable_uring, bool, 0644); +MODULE_PARM_DESC(enable_uring, + "Enable userspace communication through io-uring"); + +#define FUSE_URING_IOV_SEGS 2 /* header and payload */ + + +bool fuse_uring_enabled(void) +{ + return enable_uring; +} + +void fuse_uring_destruct(struct fuse_conn *fc) +{ + struct fuse_ring *ring = fc->ring; + int qid; + + if (!ring) + return; + + for (qid = 0; qid < ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = ring->queues[qid]; + + if (!queue) + continue; + + WARN_ON(!list_empty(&queue->ent_avail_queue)); + WARN_ON(!list_empty(&queue->ent_commit_queue)); + + kfree(queue); + ring->queues[qid] = NULL; + } + + kfree(ring->queues); + kfree(ring); + fc->ring = NULL; +} + +/* + * Basic ring setup for this connection based on the provided configuration + */ +static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) +{ + struct fuse_ring *ring; + size_t nr_queues = num_possible_cpus(); + struct fuse_ring *res = NULL; + size_t max_payload_size; + + ring = kzalloc(sizeof(*fc->ring), GFP_KERNEL_ACCOUNT); + if (!ring) + return NULL; + + ring->queues = kcalloc(nr_queues, sizeof(struct fuse_ring_queue *), + GFP_KERNEL_ACCOUNT); + if (!ring->queues) + goto out_err; + + max_payload_size = max(FUSE_MIN_READ_BUFFER, fc->max_write); + max_payload_size = max(max_payload_size, fc->max_pages * PAGE_SIZE); + + spin_lock(&fc->lock); + if (fc->ring) { + /* race, another thread created the ring in the meantime */ + spin_unlock(&fc->lock); + res = fc->ring; + goto out_err; + } + + fc->ring = ring; + ring->nr_queues = nr_queues; + ring->fc = fc; + ring->max_payload_sz = max_payload_size; + + spin_unlock(&fc->lock); + return ring; + +out_err: + kfree(ring->queues); + kfree(ring); + return res; +} + +static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, + int qid) +{ + struct fuse_conn *fc = ring->fc; + struct fuse_ring_queue *queue; + + queue = kzalloc(sizeof(*queue), GFP_KERNEL_ACCOUNT); + if (!queue) + return NULL; + queue->qid = qid; + queue->ring = ring; + spin_lock_init(&queue->lock); + + INIT_LIST_HEAD(&queue->ent_avail_queue); + INIT_LIST_HEAD(&queue->ent_commit_queue); + + spin_lock(&fc->lock); + if (ring->queues[qid]) { + spin_unlock(&fc->lock); + kfree(queue); + return ring->queues[qid]; + } + + /* + * write_once and lock as the caller mostly doesn't take the lock at all + */ + WRITE_ONCE(ring->queues[qid], queue); + spin_unlock(&fc->lock); + + return queue; +} + +/* + * Make a ring entry available for fuse_req assignment + */ +static void fuse_uring_ent_avail(struct fuse_ring_ent *ent, + struct fuse_ring_queue *queue) +{ + WARN_ON_ONCE(!ent->cmd); + list_move(&ent->list, &queue->ent_avail_queue); + ent->state = FRRS_AVAILABLE; +} + +/* + * fuse_uring_req_fetch command handling + */ +static void fuse_uring_do_register(struct fuse_ring_ent *ent, + struct io_uring_cmd *cmd, + unsigned int issue_flags) +{ + struct fuse_ring_queue *queue = ent->queue; + + spin_lock(&queue->lock); + ent->cmd = cmd; + fuse_uring_ent_avail(ent, queue); + spin_unlock(&queue->lock); +} + +/* + * sqe->addr is a ptr to an iovec array, iov[0] has the headers, iov[1] + * the payload + */ +static int fuse_uring_get_iovec_from_sqe(const struct io_uring_sqe *sqe, + struct iovec iov[FUSE_URING_IOV_SEGS]) +{ + struct iovec __user *uiov = u64_to_user_ptr(READ_ONCE(sqe->addr)); + struct iov_iter iter; + ssize_t ret; + + if (sqe->len != FUSE_URING_IOV_SEGS) + return -EINVAL; + + /* + * Direction for buffer access will actually be READ and WRITE, + * using write for the import should include READ access as well. + */ + ret = import_iovec(WRITE, uiov, FUSE_URING_IOV_SEGS, + FUSE_URING_IOV_SEGS, &iov, &iter); + if (ret < 0) + return ret; + + return 0; +} + +static struct fuse_ring_ent * +fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, + struct fuse_ring_queue *queue) +{ + struct fuse_ring *ring = queue->ring; + struct fuse_ring_ent *ent; + size_t payload_size; + struct iovec iov[FUSE_URING_IOV_SEGS]; + int err; + + err = fuse_uring_get_iovec_from_sqe(cmd->sqe, iov); + if (err) { + pr_info_ratelimited("Failed to get iovec from sqe, err=%d\n", + err); + return ERR_PTR(err); + } + + err = -EINVAL; + if (iov[0].iov_len < sizeof(struct fuse_uring_req_header)) { + pr_info_ratelimited("Invalid header len %zu\n", iov[0].iov_len); + return ERR_PTR(err); + } + + payload_size = iov[1].iov_len; + if (payload_size < ring->max_payload_sz) { + pr_info_ratelimited("Invalid req payload len %zu\n", + payload_size); + return ERR_PTR(err); + } + + err = -ENOMEM; + ent = kzalloc(sizeof(*ent), GFP_KERNEL_ACCOUNT); + if (!ent) + return ERR_PTR(err); + + INIT_LIST_HEAD(&ent->list); + + ent->queue = queue; + ent->headers = iov[0].iov_base; + ent->payload = iov[1].iov_base; + + return ent; +} + +/* + * Register header and payload buffer with the kernel and puts the + * entry as "ready to get fuse requests" on the queue + */ +static int fuse_uring_register(struct io_uring_cmd *cmd, + unsigned int issue_flags, struct fuse_conn *fc) +{ + const struct fuse_uring_cmd_req *cmd_req = io_uring_sqe_cmd(cmd->sqe); + struct fuse_ring *ring = fc->ring; + struct fuse_ring_queue *queue; + struct fuse_ring_ent *ent; + int err; + unsigned int qid = READ_ONCE(cmd_req->qid); + + err = -ENOMEM; + if (!ring) { + ring = fuse_uring_create(fc); + if (!ring) + return err; + } + + if (qid >= ring->nr_queues) { + pr_info_ratelimited("fuse: Invalid ring qid %u\n", qid); + return -EINVAL; + } + + queue = ring->queues[qid]; + if (!queue) { + queue = fuse_uring_create_queue(ring, qid); + if (!queue) + return err; + } + + /* + * The created queue above does not need to be destructed in + * case of entry errors below, will be done at ring destruction time. + */ + + ent = fuse_uring_create_ring_ent(cmd, queue); + if (IS_ERR(ent)) + return PTR_ERR(ent); + + fuse_uring_do_register(ent, cmd, issue_flags); + + return 0; +} + +/* + * Entry function from io_uring to handle the given passthrough command + * (op code IORING_OP_URING_CMD) + */ +int __maybe_unused fuse_uring_cmd(struct io_uring_cmd *cmd, + unsigned int issue_flags) +{ + struct fuse_dev *fud; + struct fuse_conn *fc; + u32 cmd_op = cmd->cmd_op; + int err; + + if (!enable_uring) { + pr_info_ratelimited("fuse-io-uring is disabled\n"); + return -EOPNOTSUPP; + } + + /* This extra SQE size holds struct fuse_uring_cmd_req */ + if (!(issue_flags & IO_URING_F_SQE128)) + return -EINVAL; + + fud = fuse_get_dev(cmd->file); + if (!fud) { + pr_info_ratelimited("No fuse device found\n"); + return -ENOTCONN; + } + fc = fud->fc; + + if (fc->aborted) + return -ECONNABORTED; + if (!fc->connected) + return -ENOTCONN; + + /* + * fuse_uring_register() needs the ring to be initialized, + * we need to know the max payload size + */ + if (!fc->initialized) + return -EAGAIN; + + switch (cmd_op) { + case FUSE_IO_URING_CMD_REGISTER: + err = fuse_uring_register(cmd, issue_flags, fc); + if (err) { + pr_info_once("FUSE_IO_URING_CMD_REGISTER failed err=%d\n", + err); + return err; + } + break; + default: + return -EINVAL; + } + + return -EIOCBQUEUED; +} diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h new file mode 100644 index 0000000000000..ae1536355b368 --- /dev/null +++ b/fs/fuse/dev_uring_i.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * FUSE: Filesystem in Userspace + * Copyright (c) 2023-2024 DataDirect Networks. + */ + +#ifndef _FS_FUSE_DEV_URING_I_H +#define _FS_FUSE_DEV_URING_I_H + +#include "fuse_i.h" + +#ifdef CONFIG_FUSE_IO_URING + +enum fuse_ring_req_state { + FRRS_INVALID = 0, + + /* The ring entry received from userspace and it is being processed */ + FRRS_COMMIT, + + /* The ring entry is waiting for new fuse requests */ + FRRS_AVAILABLE, + + /* The ring entry is in or on the way to user space */ + FRRS_USERSPACE, +}; + +/** A fuse ring entry, part of the ring queue */ +struct fuse_ring_ent { + /* userspace buffer */ + struct fuse_uring_req_header __user *headers; + void __user *payload; + + /* the ring queue that owns the request */ + struct fuse_ring_queue *queue; + + /* fields below are protected by queue->lock */ + + struct io_uring_cmd *cmd; + + struct list_head list; + + enum fuse_ring_req_state state; + + struct fuse_req *fuse_req; +}; + +struct fuse_ring_queue { + /* + * back pointer to the main fuse uring structure that holds this + * queue + */ + struct fuse_ring *ring; + + /* queue id, corresponds to the cpu core */ + unsigned int qid; + + /* + * queue lock, taken when any value in the queue changes _and_ also + * a ring entry state changes. + */ + spinlock_t lock; + + /* available ring entries (struct fuse_ring_ent) */ + struct list_head ent_avail_queue; + + /* + * entries in the process of being committed or in the process + * to be sent to userspace + */ + struct list_head ent_commit_queue; +}; + +/** + * Describes if uring is for communication and holds alls the data needed + * for uring communication + */ +struct fuse_ring { + /* back pointer */ + struct fuse_conn *fc; + + /* number of ring queues */ + size_t nr_queues; + + /* maximum payload/arg size */ + size_t max_payload_sz; + + struct fuse_ring_queue **queues; +}; + +bool fuse_uring_enabled(void); +void fuse_uring_destruct(struct fuse_conn *fc); +int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); + +#else /* CONFIG_FUSE_IO_URING */ + +struct fuse_ring; + +static inline void fuse_uring_create(struct fuse_conn *fc) +{ +} + +static inline void fuse_uring_destruct(struct fuse_conn *fc) +{ +} + +static inline bool fuse_uring_enabled(void) +{ + return false; +} + +#endif /* CONFIG_FUSE_IO_URING */ + +#endif /* _FS_FUSE_DEV_URING_I_H */ diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 696479fa17c9a..a50f6356e9b4c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -917,6 +917,11 @@ struct fuse_conn { /** IDR for backing files ids */ struct idr backing_files_map; #endif + +#ifdef CONFIG_FUSE_IO_URING + /** uring connection information*/ + struct fuse_ring *ring; +#endif }; /* diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8802111e0b478..7836e4b75907b 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -7,6 +7,7 @@ */ #include "fuse_i.h" +#include "dev_uring_i.h" #include #include @@ -960,6 +961,8 @@ static void delayed_release(struct rcu_head *p) { struct fuse_conn *fc = container_of(p, struct fuse_conn, rcu); + fuse_uring_destruct(fc); + put_user_ns(fc->user_ns); fc->release(fc); } @@ -1414,6 +1417,13 @@ void fuse_send_init(struct fuse_mount *fm) if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) flags |= FUSE_PASSTHROUGH; + /* + * This is just an information flag for fuse server. No need to check + * the reply - server is either sending IORING_OP_URING_CMD or not. + */ + if (fuse_uring_enabled()) + flags |= FUSE_OVER_IO_URING; + ia->in.flags = flags; ia->in.flags2 = flags >> 32; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index a022b3707ad6d..914aa300bcc1a 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -221,6 +221,16 @@ * 7.41 * - add FUSE_ALLOW_IDMAP * + * 7.42 + * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data + * structures: + * - struct fuse_uring_ent_in_out + * - struct fuse_uring_req_header + * - struct fuse_uring_cmd_req + * - FUSE_URING_IN_OUT_HEADER_SZ + * - FUSE_URING_OP_IN_OUT_SZ + * - enum fuse_uring_cmd + * * 7.44 * - add FUSE_NOTIFY_INC_EPOCH */ @@ -428,6 +438,7 @@ struct fuse_file_lock { * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit * of the request ID indicates resend requests * FUSE_ALLOW_IDMAP: allow creation of idmapped mounts + * FUSE_OVER_IO_URING: Indicate that client supports io-uring */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -474,6 +485,7 @@ struct fuse_file_lock { /* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ #define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP #define FUSE_ALLOW_IDMAP (1ULL << 40) +#define FUSE_OVER_IO_URING (1ULL << 41) /** * CUSE INIT request/reply flags @@ -1210,4 +1222,67 @@ struct fuse_supp_groups { uint32_t groups[]; }; +/** + * Size of the ring buffer header + */ +#define FUSE_URING_IN_OUT_HEADER_SZ 128 +#define FUSE_URING_OP_IN_OUT_SZ 128 + +/* Used as part of the fuse_uring_req_header */ +struct fuse_uring_ent_in_out { + uint64_t flags; + + /* + * commit ID to be used in a reply to a ring request (see also + * struct fuse_uring_cmd_req) + */ + uint64_t commit_id; + + /* size of user payload buffer */ + uint32_t payload_sz; + uint32_t padding; + + uint64_t reserved; +}; + +/** + * Header for all fuse-io-uring requests + */ +struct fuse_uring_req_header { + /* struct fuse_in_header / struct fuse_out_header */ + char in_out[FUSE_URING_IN_OUT_HEADER_SZ]; + + /* per op code header */ + char op_in[FUSE_URING_OP_IN_OUT_SZ]; + + struct fuse_uring_ent_in_out ring_ent_in_out; +}; + +/** + * sqe commands to the kernel + */ +enum fuse_uring_cmd { + FUSE_IO_URING_CMD_INVALID = 0, + + /* register the request buffer and fetch a fuse request */ + FUSE_IO_URING_CMD_REGISTER = 1, + + /* commit fuse request result and fetch next request */ + FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2, +}; + +/** + * In the 80B command area of the SQE. + */ +struct fuse_uring_cmd_req { + uint64_t flags; + + /* entry identifier for commits */ + uint64_t commit_id; + + /* queue the command is for (queue index) */ + uint16_t qid; + uint8_t padding[6]; +}; + #endif /* _LINUX_FUSE_H */ From f3b48a22b13b38752d157dab041a7f2c35658f63 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 15:29:49 -0500 Subject: [PATCH 25/98] fuse: Make fuse_copy non static jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit d0f9c62aaf7a98412b46a91fe7daad76b316b3b7 Move 'struct fuse_copy_state' and fuse_copy_* functions to fuse_dev_i.h to make it available for fuse-io-uring. 'copy_out_args()' is renamed to 'fuse_copy_out_args'. Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit d0f9c62aaf7a98412b46a91fe7daad76b316b3b7) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 30 ++++++++---------------------- fs/fuse/fuse_dev_i.h | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 690ec7c74e64b..2e1c39ab97da3 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -681,22 +681,8 @@ static int unlock_request(struct fuse_req *req) return err; } -struct fuse_copy_state { - int write; - struct fuse_req *req; - struct iov_iter *iter; - struct pipe_buffer *pipebufs; - struct pipe_buffer *currbuf; - struct pipe_inode_info *pipe; - unsigned long nr_segs; - struct page *pg; - unsigned len; - unsigned offset; - unsigned move_pages:1; -}; - -static void fuse_copy_init(struct fuse_copy_state *cs, int write, - struct iov_iter *iter) +void fuse_copy_init(struct fuse_copy_state *cs, int write, + struct iov_iter *iter) { memset(cs, 0, sizeof(*cs)); cs->write = write; @@ -1047,9 +1033,9 @@ static int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size) } /* Copy request arguments to/from userspace buffer */ -static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, - unsigned argpages, struct fuse_arg *args, - int zeroing) +int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, + unsigned argpages, struct fuse_arg *args, + int zeroing) { int err = 0; unsigned i; @@ -1950,8 +1936,8 @@ static struct fuse_req *request_find(struct fuse_pqueue *fpq, u64 unique) return NULL; } -static int copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args, - unsigned nbytes) +int fuse_copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args, + unsigned nbytes) { unsigned reqsize = sizeof(struct fuse_out_header); @@ -2053,7 +2039,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, if (oh.error) err = nbytes != sizeof(oh) ? -EINVAL : 0; else - err = copy_out_args(cs, req->args, nbytes); + err = fuse_copy_out_args(cs, req->args, nbytes); fuse_copy_finish(cs); spin_lock(&fpq->lock); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 08a7e88e00277..21eb1bdb492d0 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -12,6 +12,23 @@ #define FUSE_INT_REQ_BIT (1ULL << 0) #define FUSE_REQ_ID_STEP (1ULL << 1) +struct fuse_arg; +struct fuse_args; + +struct fuse_copy_state { + int write; + struct fuse_req *req; + struct iov_iter *iter; + struct pipe_buffer *pipebufs; + struct pipe_buffer *currbuf; + struct pipe_inode_info *pipe; + unsigned long nr_segs; + struct page *pg; + unsigned int len; + unsigned int offset; + unsigned int move_pages:1; +}; + static inline struct fuse_dev *fuse_get_dev(struct file *file) { /* @@ -23,5 +40,13 @@ static inline struct fuse_dev *fuse_get_dev(struct file *file) void fuse_dev_end_requests(struct list_head *head); +void fuse_copy_init(struct fuse_copy_state *cs, int write, + struct iov_iter *iter); +int fuse_copy_args(struct fuse_copy_state *cs, unsigned int numargs, + unsigned int argpages, struct fuse_arg *args, + int zeroing); +int fuse_copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args, + unsigned int nbytes); + #endif From 23e618da66b164489d818be7867f7131a57f2a48 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 15:30:21 -0500 Subject: [PATCH 26/98] fuse: Add fuse-io-uring handling into fuse_copy jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit f773a7c2c3d934d00542c6471170066d150d152f Add special fuse-io-uring into the fuse argument copy handler. Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit f773a7c2c3d934d00542c6471170066d150d152f) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 12 +++++++++++- fs/fuse/fuse_dev_i.h | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 2e1c39ab97da3..b3e75a30c7cd6 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -789,6 +789,9 @@ static int fuse_copy_do(struct fuse_copy_state *cs, void **val, unsigned *size) *size -= ncpy; cs->len -= ncpy; cs->offset += ncpy; + if (cs->is_uring) + cs->ring.copied_sz += ncpy; + return ncpy; } @@ -1939,7 +1942,14 @@ static struct fuse_req *request_find(struct fuse_pqueue *fpq, u64 unique) int fuse_copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args, unsigned nbytes) { - unsigned reqsize = sizeof(struct fuse_out_header); + + unsigned int reqsize = 0; + + /* + * Uring has all headers separated from args - args is payload only + */ + if (!cs->is_uring) + reqsize = sizeof(struct fuse_out_header); reqsize += fuse_len_args(args->out_numargs, args->out_args); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 21eb1bdb492d0..4a8a4feb2df53 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -27,6 +27,10 @@ struct fuse_copy_state { unsigned int len; unsigned int offset; unsigned int move_pages:1; + unsigned int is_uring:1; + struct { + unsigned int copied_sz; /* copied size into the user buffer */ + } ring; }; static inline struct fuse_dev *fuse_get_dev(struct file *file) From 8e1a76a411662e00f26eaa4740b08043d3208ac5 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 15:31:07 -0500 Subject: [PATCH 27/98] fuse: {io-uring} Make hash-list req unique finding functions non-static jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 3821336530616776414aa7a879640052b89def28 fuse-over-io-uring uses existing functions to find requests based on their unique id - make these functions non-static. Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 3821336530616776414aa7a879640052b89def28) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 6 +++--- fs/fuse/fuse_dev_i.h | 5 +++++ fs/fuse/fuse_i.h | 5 +++++ fs/fuse/inode.c | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index b3e75a30c7cd6..cfba340f38430 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -222,7 +222,7 @@ u64 fuse_get_unique(struct fuse_iqueue *fiq) } EXPORT_SYMBOL_GPL(fuse_get_unique); -static unsigned int fuse_req_hash(u64 unique) +unsigned int fuse_req_hash(u64 unique) { return hash_long(unique & ~FUSE_INT_REQ_BIT, FUSE_PQ_HASH_BITS); } @@ -1927,7 +1927,7 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, } /* Look up request on processing list by unique ID */ -static struct fuse_req *request_find(struct fuse_pqueue *fpq, u64 unique) +struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique) { unsigned int hash = fuse_req_hash(unique); struct fuse_req *req; @@ -2011,7 +2011,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, spin_lock(&fpq->lock); req = NULL; if (fpq->connected) - req = request_find(fpq, oh.unique & ~FUSE_INT_REQ_BIT); + req = fuse_request_find(fpq, oh.unique & ~FUSE_INT_REQ_BIT); err = -ENOENT; if (!req) { diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 4a8a4feb2df53..b64ab84cbc0d5 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -14,6 +14,8 @@ struct fuse_arg; struct fuse_args; +struct fuse_pqueue; +struct fuse_req; struct fuse_copy_state { int write; @@ -42,6 +44,9 @@ static inline struct fuse_dev *fuse_get_dev(struct file *file) return READ_ONCE(file->private_data); } +unsigned int fuse_req_hash(u64 unique); +struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique); + void fuse_dev_end_requests(struct list_head *head); void fuse_copy_init(struct fuse_copy_state *cs, int write, diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index a50f6356e9b4c..d8005e56dffa4 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1224,6 +1224,11 @@ void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o); */ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc); +/** + * Initialize the fuse processing queue + */ +void fuse_pqueue_init(struct fuse_pqueue *fpq); + /** * Initialize fuse_conn */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 7836e4b75907b..ebf13adae51e9 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -906,7 +906,7 @@ static void fuse_iqueue_init(struct fuse_iqueue *fiq, fiq->priv = priv; } -static void fuse_pqueue_init(struct fuse_pqueue *fpq) +void fuse_pqueue_init(struct fuse_pqueue *fpq) { unsigned int i; From 13f4aabd542210a8152436387307098ba934cd98 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 15:31:33 -0500 Subject: [PATCH 28/98] fuse: Add io-uring sqe commit and fetch support jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit c090c8abae4b6b77a1bee116aa6c385456ebef96 This adds support for fuse request completion through ring SQEs (FUSE_URING_CMD_COMMIT_AND_FETCH handling). After committing the ring entry it becomes available for new fuse requests. Handling of requests through the ring (SQE/CQE handling) is complete now. Fuse request data are copied through the mmaped ring buffer, there is no support for any zero copy yet. Signed-off-by: Bernd Schubert Reviewed-by: Pavel Begunkov # io_uring Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit c090c8abae4b6b77a1bee116aa6c385456ebef96) Signed-off-by: Jonathan Maple --- fs/fuse/dev_uring.c | 441 ++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev_uring_i.h | 12 ++ fs/fuse/fuse_i.h | 4 + 3 files changed, 457 insertions(+) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 42092a234570e..1030c1720990e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -24,6 +24,17 @@ bool fuse_uring_enabled(void) return enable_uring; } +static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, + int error) +{ + ent->fuse_req = NULL; + if (error) + req->out.h.error = error; + + clear_bit(FR_SENT, &req->flags); + fuse_request_end(req); +} + void fuse_uring_destruct(struct fuse_conn *fc) { struct fuse_ring *ring = fc->ring; @@ -39,8 +50,11 @@ void fuse_uring_destruct(struct fuse_conn *fc) continue; WARN_ON(!list_empty(&queue->ent_avail_queue)); + WARN_ON(!list_empty(&queue->ent_w_req_queue)); WARN_ON(!list_empty(&queue->ent_commit_queue)); + WARN_ON(!list_empty(&queue->ent_in_userspace)); + kfree(queue->fpq.processing); kfree(queue); ring->queues[qid] = NULL; } @@ -99,20 +113,34 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, { struct fuse_conn *fc = ring->fc; struct fuse_ring_queue *queue; + struct list_head *pq; queue = kzalloc(sizeof(*queue), GFP_KERNEL_ACCOUNT); if (!queue) return NULL; + pq = kcalloc(FUSE_PQ_HASH_SIZE, sizeof(struct list_head), GFP_KERNEL); + if (!pq) { + kfree(queue); + return NULL; + } + queue->qid = qid; queue->ring = ring; spin_lock_init(&queue->lock); INIT_LIST_HEAD(&queue->ent_avail_queue); INIT_LIST_HEAD(&queue->ent_commit_queue); + INIT_LIST_HEAD(&queue->ent_w_req_queue); + INIT_LIST_HEAD(&queue->ent_in_userspace); + INIT_LIST_HEAD(&queue->fuse_req_queue); + + queue->fpq.processing = pq; + fuse_pqueue_init(&queue->fpq); spin_lock(&fc->lock); if (ring->queues[qid]) { spin_unlock(&fc->lock); + kfree(queue->fpq.processing); kfree(queue); return ring->queues[qid]; } @@ -126,6 +154,214 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, return queue; } +/* + * Checks for errors and stores it into the request + */ +static int fuse_uring_out_header_has_err(struct fuse_out_header *oh, + struct fuse_req *req, + struct fuse_conn *fc) +{ + int err; + + err = -EINVAL; + if (oh->unique == 0) { + /* Not supported through io-uring yet */ + pr_warn_once("notify through fuse-io-uring not supported\n"); + goto err; + } + + if (oh->error <= -ERESTARTSYS || oh->error > 0) + goto err; + + if (oh->error) { + err = oh->error; + goto err; + } + + err = -ENOENT; + if ((oh->unique & ~FUSE_INT_REQ_BIT) != req->in.h.unique) { + pr_warn_ratelimited("unique mismatch, expected: %llu got %llu\n", + req->in.h.unique, + oh->unique & ~FUSE_INT_REQ_BIT); + goto err; + } + + /* + * Is it an interrupt reply ID? + * XXX: Not supported through fuse-io-uring yet, it should not even + * find the request - should not happen. + */ + WARN_ON_ONCE(oh->unique & FUSE_INT_REQ_BIT); + + err = 0; +err: + return err; +} + +static int fuse_uring_copy_from_ring(struct fuse_ring *ring, + struct fuse_req *req, + struct fuse_ring_ent *ent) +{ + struct fuse_copy_state cs; + struct fuse_args *args = req->args; + struct iov_iter iter; + int err; + struct fuse_uring_ent_in_out ring_in_out; + + err = copy_from_user(&ring_in_out, &ent->headers->ring_ent_in_out, + sizeof(ring_in_out)); + if (err) + return -EFAULT; + + err = import_ubuf(ITER_SOURCE, ent->payload, ring->max_payload_sz, + &iter); + if (err) + return err; + + fuse_copy_init(&cs, 0, &iter); + cs.is_uring = 1; + cs.req = req; + + return fuse_copy_out_args(&cs, args, ring_in_out.payload_sz); +} + + /* + * Copy data from the req to the ring buffer + */ +static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, + struct fuse_ring_ent *ent) +{ + struct fuse_copy_state cs; + struct fuse_args *args = req->args; + struct fuse_in_arg *in_args = args->in_args; + int num_args = args->in_numargs; + int err; + struct iov_iter iter; + struct fuse_uring_ent_in_out ent_in_out = { + .flags = 0, + .commit_id = req->in.h.unique, + }; + + err = import_ubuf(ITER_DEST, ent->payload, ring->max_payload_sz, &iter); + if (err) { + pr_info_ratelimited("fuse: Import of user buffer failed\n"); + return err; + } + + fuse_copy_init(&cs, 1, &iter); + cs.is_uring = 1; + cs.req = req; + + if (num_args > 0) { + /* + * Expectation is that the first argument is the per op header. + * Some op code have that as zero size. + */ + if (args->in_args[0].size > 0) { + err = copy_to_user(&ent->headers->op_in, in_args->value, + in_args->size); + if (err) { + pr_info_ratelimited( + "Copying the header failed.\n"); + return -EFAULT; + } + } + in_args++; + num_args--; + } + + /* copy the payload */ + err = fuse_copy_args(&cs, num_args, args->in_pages, + (struct fuse_arg *)in_args, 0); + if (err) { + pr_info_ratelimited("%s fuse_copy_args failed\n", __func__); + return err; + } + + ent_in_out.payload_sz = cs.ring.copied_sz; + err = copy_to_user(&ent->headers->ring_ent_in_out, &ent_in_out, + sizeof(ent_in_out)); + return err ? -EFAULT : 0; +} + +static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, + struct fuse_req *req) +{ + struct fuse_ring_queue *queue = ent->queue; + struct fuse_ring *ring = queue->ring; + int err; + + err = -EIO; + if (WARN_ON(ent->state != FRRS_FUSE_REQ)) { + pr_err("qid=%d ring-req=%p invalid state %d on send\n", + queue->qid, ent, ent->state); + return err; + } + + err = -EINVAL; + if (WARN_ON(req->in.h.unique == 0)) + return err; + + /* copy the request */ + err = fuse_uring_args_to_ring(ring, req, ent); + if (unlikely(err)) { + pr_info_ratelimited("Copy to ring failed: %d\n", err); + return err; + } + + /* copy fuse_in_header */ + err = copy_to_user(&ent->headers->in_out, &req->in.h, + sizeof(req->in.h)); + if (err) { + err = -EFAULT; + return err; + } + + return 0; +} + +static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, + struct fuse_req *req) +{ + int err; + + err = fuse_uring_copy_to_ring(ent, req); + if (!err) + set_bit(FR_SENT, &req->flags); + else + fuse_uring_req_end(ent, req, err); + + return err; +} + +/* + * Write data to the ring buffer and send the request to userspace, + * userspace will read it + * This is comparable with classical read(/dev/fuse) + */ +static int fuse_uring_send_next_to_ring(struct fuse_ring_ent *ent, + struct fuse_req *req, + unsigned int issue_flags) +{ + struct fuse_ring_queue *queue = ent->queue; + int err; + struct io_uring_cmd *cmd; + + err = fuse_uring_prepare_send(ent, req); + if (err) + return err; + + spin_lock(&queue->lock); + cmd = ent->cmd; + ent->cmd = NULL; + ent->state = FRRS_USERSPACE; + list_move(&ent->list, &queue->ent_in_userspace); + spin_unlock(&queue->lock); + + io_uring_cmd_done(cmd, 0, 0, issue_flags); + return 0; +} + /* * Make a ring entry available for fuse_req assignment */ @@ -137,6 +373,203 @@ static void fuse_uring_ent_avail(struct fuse_ring_ent *ent, ent->state = FRRS_AVAILABLE; } +/* Used to find the request on SQE commit */ +static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent, + struct fuse_req *req) +{ + struct fuse_ring_queue *queue = ent->queue; + struct fuse_pqueue *fpq = &queue->fpq; + unsigned int hash; + + req->ring_entry = ent; + hash = fuse_req_hash(req->in.h.unique); + list_move_tail(&req->list, &fpq->processing[hash]); +} + +/* + * Assign a fuse queue entry to the given entry + */ +static void fuse_uring_add_req_to_ring_ent(struct fuse_ring_ent *ent, + struct fuse_req *req) +{ + struct fuse_ring_queue *queue = ent->queue; + struct fuse_conn *fc = req->fm->fc; + struct fuse_iqueue *fiq = &fc->iq; + + lockdep_assert_held(&queue->lock); + + if (WARN_ON_ONCE(ent->state != FRRS_AVAILABLE && + ent->state != FRRS_COMMIT)) { + pr_warn("%s qid=%d state=%d\n", __func__, ent->queue->qid, + ent->state); + } + + spin_lock(&fiq->lock); + clear_bit(FR_PENDING, &req->flags); + spin_unlock(&fiq->lock); + ent->fuse_req = req; + ent->state = FRRS_FUSE_REQ; + list_move(&ent->list, &queue->ent_w_req_queue); + fuse_uring_add_to_pq(ent, req); +} + +/* Fetch the next fuse request if available */ +static struct fuse_req *fuse_uring_ent_assign_req(struct fuse_ring_ent *ent) + __must_hold(&queue->lock) +{ + struct fuse_req *req; + struct fuse_ring_queue *queue = ent->queue; + struct list_head *req_queue = &queue->fuse_req_queue; + + lockdep_assert_held(&queue->lock); + + /* get and assign the next entry while it is still holding the lock */ + req = list_first_entry_or_null(req_queue, struct fuse_req, list); + if (req) + fuse_uring_add_req_to_ring_ent(ent, req); + + return req; +} + +/* + * Read data from the ring buffer, which user space has written to + * This is comparible with handling of classical write(/dev/fuse). + * Also make the ring request available again for new fuse requests. + */ +static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, + unsigned int issue_flags) +{ + struct fuse_ring *ring = ent->queue->ring; + struct fuse_conn *fc = ring->fc; + ssize_t err = 0; + + err = copy_from_user(&req->out.h, &ent->headers->in_out, + sizeof(req->out.h)); + if (err) { + req->out.h.error = -EFAULT; + goto out; + } + + err = fuse_uring_out_header_has_err(&req->out.h, req, fc); + if (err) { + /* req->out.h.error already set */ + goto out; + } + + err = fuse_uring_copy_from_ring(ring, req, ent); +out: + fuse_uring_req_end(ent, req, err); +} + +/* + * Get the next fuse req and send it + */ +static void fuse_uring_next_fuse_req(struct fuse_ring_ent *ent, + struct fuse_ring_queue *queue, + unsigned int issue_flags) +{ + int err; + struct fuse_req *req; + +retry: + spin_lock(&queue->lock); + fuse_uring_ent_avail(ent, queue); + req = fuse_uring_ent_assign_req(ent); + spin_unlock(&queue->lock); + + if (req) { + err = fuse_uring_send_next_to_ring(ent, req, issue_flags); + if (err) + goto retry; + } +} + +static int fuse_ring_ent_set_commit(struct fuse_ring_ent *ent) +{ + struct fuse_ring_queue *queue = ent->queue; + + lockdep_assert_held(&queue->lock); + + if (WARN_ON_ONCE(ent->state != FRRS_USERSPACE)) + return -EIO; + + ent->state = FRRS_COMMIT; + list_move(&ent->list, &queue->ent_commit_queue); + + return 0; +} + +/* FUSE_URING_CMD_COMMIT_AND_FETCH handler */ +static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, + struct fuse_conn *fc) +{ + const struct fuse_uring_cmd_req *cmd_req = io_uring_sqe_cmd(cmd->sqe); + struct fuse_ring_ent *ent; + int err; + struct fuse_ring *ring = fc->ring; + struct fuse_ring_queue *queue; + uint64_t commit_id = READ_ONCE(cmd_req->commit_id); + unsigned int qid = READ_ONCE(cmd_req->qid); + struct fuse_pqueue *fpq; + struct fuse_req *req; + + err = -ENOTCONN; + if (!ring) + return err; + + if (qid >= ring->nr_queues) + return -EINVAL; + + queue = ring->queues[qid]; + if (!queue) + return err; + fpq = &queue->fpq; + + spin_lock(&queue->lock); + /* Find a request based on the unique ID of the fuse request + * This should get revised, as it needs a hash calculation and list + * search. And full struct fuse_pqueue is needed (memory overhead). + * As well as the link from req to ring_ent. + */ + req = fuse_request_find(fpq, commit_id); + err = -ENOENT; + if (!req) { + pr_info("qid=%d commit_id %llu not found\n", queue->qid, + commit_id); + spin_unlock(&queue->lock); + return err; + } + list_del_init(&req->list); + ent = req->ring_entry; + req->ring_entry = NULL; + + err = fuse_ring_ent_set_commit(ent); + if (err != 0) { + pr_info_ratelimited("qid=%d commit_id %llu state %d", + queue->qid, commit_id, ent->state); + spin_unlock(&queue->lock); + req->out.h.error = err; + clear_bit(FR_SENT, &req->flags); + fuse_request_end(req); + return err; + } + + ent->cmd = cmd; + spin_unlock(&queue->lock); + + /* without the queue lock, as other locks are taken */ + fuse_uring_commit(ent, req, issue_flags); + + /* + * Fetching the next request is absolutely required as queued + * fuse requests would otherwise not get processed - committing + * and fetching is done in one step vs legacy fuse, which has separated + * read (fetch request) and write (commit result). + */ + fuse_uring_next_fuse_req(ent, queue, issue_flags); + return 0; +} + /* * fuse_uring_req_fetch command handling */ @@ -318,6 +751,14 @@ int __maybe_unused fuse_uring_cmd(struct io_uring_cmd *cmd, return err; } break; + case FUSE_IO_URING_CMD_COMMIT_AND_FETCH: + err = fuse_uring_commit_fetch(cmd, issue_flags, fc); + if (err) { + pr_info_once("FUSE_IO_URING_COMMIT_AND_FETCH failed err=%d\n", + err); + return err; + } + break; default: return -EINVAL; } diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index ae1536355b368..44bf237f0d5ab 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -20,6 +20,9 @@ enum fuse_ring_req_state { /* The ring entry is waiting for new fuse requests */ FRRS_AVAILABLE, + /* The ring entry got assigned a fuse req */ + FRRS_FUSE_REQ, + /* The ring entry is in or on the way to user space */ FRRS_USERSPACE, }; @@ -67,7 +70,16 @@ struct fuse_ring_queue { * entries in the process of being committed or in the process * to be sent to userspace */ + struct list_head ent_w_req_queue; struct list_head ent_commit_queue; + + /* entries in userspace */ + struct list_head ent_in_userspace; + + /* fuse requests waiting for an entry slot */ + struct list_head fuse_req_queue; + + struct fuse_pqueue fpq; }; /** diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index d8005e56dffa4..e6af5a64b4542 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -435,6 +435,10 @@ struct fuse_req { /** fuse_mount this request belongs to */ struct fuse_mount *fm; + +#ifdef CONFIG_FUSE_IO_URING + void *ring_entry; +#endif }; struct fuse_iqueue; From bdf66d1122757d4e75c8f50eb7fe226cb09a1872 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:05:48 -0500 Subject: [PATCH 29/98] fuse: {io-uring} Handle teardown of ring entries jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 4a9bfb9b6850fec0685447aed280533cf980de70 On teardown struct file_operations::uring_cmd requests need to be completed by calling io_uring_cmd_done(). Not completing all ring entries would result in busy io-uring tasks giving warning messages in intervals and unreleased struct file. Additionally the fuse connection and with that the ring can only get released when all io-uring commands are completed. Completion is done with ring entries that are a) in waiting state for new fuse requests - io_uring_cmd_done is needed b) already in userspace - io_uring_cmd_done through teardown is not needed, the request can just get released. If fuse server is still active and commits such a ring entry, fuse_uring_cmd() already checks if the connection is active and then complete the io-uring itself with -ENOTCONN. I.e. special handling is not needed. This scheme is basically represented by the ring entry state FRRS_WAIT and FRRS_USERSPACE. Entries in state: - FRRS_INIT: No action needed, do not contribute to ring->queue_refs yet - All other states: Are currently processed by other tasks, async teardown is needed and it has to wait for the two states above. It could be also solved without an async teardown task, but would require additional if conditions in hot code paths. Also in my personal opinion the code looks cleaner with async teardown. Signed-off-by: Bernd Schubert Reviewed-by: Pavel Begunkov # io_uring Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 4a9bfb9b6850fec0685447aed280533cf980de70) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 9 ++ fs/fuse/dev_uring.c | 207 ++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev_uring_i.h | 51 +++++++++++ 3 files changed, 267 insertions(+) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index cfba340f38430..301385526327f 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -6,6 +6,7 @@ See the file COPYING. */ +#include "dev_uring_i.h" #include "fuse_i.h" #include "fuse_dev_i.h" @@ -2308,6 +2309,12 @@ void fuse_abort_conn(struct fuse_conn *fc) spin_unlock(&fc->lock); fuse_dev_end_requests(&to_end); + + /* + * fc->lock must not be taken to avoid conflicts with io-uring + * locks + */ + fuse_uring_abort(fc); } else { spin_unlock(&fc->lock); } @@ -2319,6 +2326,8 @@ void fuse_wait_aborted(struct fuse_conn *fc) /* matches implicit memory barrier in fuse_drop_waiting() */ smp_mb(); wait_event(fc->blocked_waitq, atomic_read(&fc->num_waiting) == 0); + + fuse_uring_wait_stopped_queues(fc); } int fuse_dev_release(struct inode *inode, struct file *file) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 1030c1720990e..1161b9aa5e11c 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -35,6 +35,37 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, fuse_request_end(req); } +/* Abort all list queued request on the given ring queue */ +static void fuse_uring_abort_end_queue_requests(struct fuse_ring_queue *queue) +{ + struct fuse_req *req; + LIST_HEAD(req_list); + + spin_lock(&queue->lock); + list_for_each_entry(req, &queue->fuse_req_queue, list) + clear_bit(FR_PENDING, &req->flags); + list_splice_init(&queue->fuse_req_queue, &req_list); + spin_unlock(&queue->lock); + + /* must not hold queue lock to avoid order issues with fi->lock */ + fuse_dev_end_requests(&req_list); +} + +void fuse_uring_abort_end_requests(struct fuse_ring *ring) +{ + int qid; + struct fuse_ring_queue *queue; + + for (qid = 0; qid < ring->nr_queues; qid++) { + queue = READ_ONCE(ring->queues[qid]); + if (!queue) + continue; + + queue->stopped = true; + fuse_uring_abort_end_queue_requests(queue); + } +} + void fuse_uring_destruct(struct fuse_conn *fc) { struct fuse_ring *ring = fc->ring; @@ -94,10 +125,13 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) goto out_err; } + init_waitqueue_head(&ring->stop_waitq); + fc->ring = ring; ring->nr_queues = nr_queues; ring->fc = fc; ring->max_payload_sz = max_payload_size; + atomic_set(&ring->queue_refs, 0); spin_unlock(&fc->lock); return ring; @@ -154,6 +188,175 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, return queue; } +static void fuse_uring_stop_fuse_req_end(struct fuse_req *req) +{ + clear_bit(FR_SENT, &req->flags); + req->out.h.error = -ECONNABORTED; + fuse_request_end(req); +} + +/* + * Release a request/entry on connection tear down + */ +static void fuse_uring_entry_teardown(struct fuse_ring_ent *ent) +{ + struct fuse_req *req; + struct io_uring_cmd *cmd; + + struct fuse_ring_queue *queue = ent->queue; + + spin_lock(&queue->lock); + cmd = ent->cmd; + ent->cmd = NULL; + req = ent->fuse_req; + ent->fuse_req = NULL; + if (req) { + /* remove entry from queue->fpq->processing */ + list_del_init(&req->list); + } + spin_unlock(&queue->lock); + + if (cmd) + io_uring_cmd_done(cmd, -ENOTCONN, 0, IO_URING_F_UNLOCKED); + + if (req) + fuse_uring_stop_fuse_req_end(req); + + list_del_init(&ent->list); + kfree(ent); +} + +static void fuse_uring_stop_list_entries(struct list_head *head, + struct fuse_ring_queue *queue, + enum fuse_ring_req_state exp_state) +{ + struct fuse_ring *ring = queue->ring; + struct fuse_ring_ent *ent, *next; + ssize_t queue_refs = SSIZE_MAX; + LIST_HEAD(to_teardown); + + spin_lock(&queue->lock); + list_for_each_entry_safe(ent, next, head, list) { + if (ent->state != exp_state) { + pr_warn("entry teardown qid=%d state=%d expected=%d", + queue->qid, ent->state, exp_state); + continue; + } + + list_move(&ent->list, &to_teardown); + } + spin_unlock(&queue->lock); + + /* no queue lock to avoid lock order issues */ + list_for_each_entry_safe(ent, next, &to_teardown, list) { + fuse_uring_entry_teardown(ent); + queue_refs = atomic_dec_return(&ring->queue_refs); + WARN_ON_ONCE(queue_refs < 0); + } +} + +static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) +{ + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, + FRRS_USERSPACE); + fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, + FRRS_AVAILABLE); +} + +/* + * Log state debug info + */ +static void fuse_uring_log_ent_state(struct fuse_ring *ring) +{ + int qid; + struct fuse_ring_ent *ent; + + for (qid = 0; qid < ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = ring->queues[qid]; + + if (!queue) + continue; + + spin_lock(&queue->lock); + /* + * Log entries from the intermediate queue, the other queues + * should be empty + */ + list_for_each_entry(ent, &queue->ent_w_req_queue, list) { + pr_info(" ent-req-queue ring=%p qid=%d ent=%p state=%d\n", + ring, qid, ent, ent->state); + } + list_for_each_entry(ent, &queue->ent_commit_queue, list) { + pr_info(" ent-commit-queue ring=%p qid=%d ent=%p state=%d\n", + ring, qid, ent, ent->state); + } + spin_unlock(&queue->lock); + } + ring->stop_debug_log = 1; +} + +static void fuse_uring_async_stop_queues(struct work_struct *work) +{ + int qid; + struct fuse_ring *ring = + container_of(work, struct fuse_ring, async_teardown_work.work); + + /* XXX code dup */ + for (qid = 0; qid < ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]); + + if (!queue) + continue; + + fuse_uring_teardown_entries(queue); + } + + /* + * Some ring entries might be in the middle of IO operations, + * i.e. in process to get handled by file_operations::uring_cmd + * or on the way to userspace - we could handle that with conditions in + * run time code, but easier/cleaner to have an async tear down handler + * If there are still queue references left + */ + if (atomic_read(&ring->queue_refs) > 0) { + if (time_after(jiffies, + ring->teardown_time + FUSE_URING_TEARDOWN_TIMEOUT)) + fuse_uring_log_ent_state(ring); + + schedule_delayed_work(&ring->async_teardown_work, + FUSE_URING_TEARDOWN_INTERVAL); + } else { + wake_up_all(&ring->stop_waitq); + } +} + +/* + * Stop the ring queues + */ +void fuse_uring_stop_queues(struct fuse_ring *ring) +{ + int qid; + + for (qid = 0; qid < ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]); + + if (!queue) + continue; + + fuse_uring_teardown_entries(queue); + } + + if (atomic_read(&ring->queue_refs) > 0) { + ring->teardown_time = jiffies; + INIT_DELAYED_WORK(&ring->async_teardown_work, + fuse_uring_async_stop_queues); + schedule_delayed_work(&ring->async_teardown_work, + FUSE_URING_TEARDOWN_INTERVAL); + } else { + wake_up_all(&ring->stop_waitq); + } +} + /* * Checks for errors and stores it into the request */ @@ -525,6 +728,9 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, return err; fpq = &queue->fpq; + if (!READ_ONCE(fc->connected) || READ_ONCE(queue->stopped)) + return err; + spin_lock(&queue->lock); /* Find a request based on the unique ID of the fuse request * This should get revised, as it needs a hash calculation and list @@ -652,6 +858,7 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, ent->headers = iov[0].iov_base; ent->payload = iov[1].iov_base; + atomic_inc(&ring->queue_refs); return ent; } diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 44bf237f0d5ab..a4316e118cbd8 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -11,6 +11,9 @@ #ifdef CONFIG_FUSE_IO_URING +#define FUSE_URING_TEARDOWN_TIMEOUT (5 * HZ) +#define FUSE_URING_TEARDOWN_INTERVAL (HZ/20) + enum fuse_ring_req_state { FRRS_INVALID = 0, @@ -80,6 +83,8 @@ struct fuse_ring_queue { struct list_head fuse_req_queue; struct fuse_pqueue fpq; + + bool stopped; }; /** @@ -97,12 +102,51 @@ struct fuse_ring { size_t max_payload_sz; struct fuse_ring_queue **queues; + + /* + * Log ring entry states on stop when entries cannot be released + */ + unsigned int stop_debug_log : 1; + + wait_queue_head_t stop_waitq; + + /* async tear down */ + struct delayed_work async_teardown_work; + + /* log */ + unsigned long teardown_time; + + atomic_t queue_refs; }; bool fuse_uring_enabled(void); void fuse_uring_destruct(struct fuse_conn *fc); +void fuse_uring_stop_queues(struct fuse_ring *ring); +void fuse_uring_abort_end_requests(struct fuse_ring *ring); int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); +static inline void fuse_uring_abort(struct fuse_conn *fc) +{ + struct fuse_ring *ring = fc->ring; + + if (ring == NULL) + return; + + if (atomic_read(&ring->queue_refs) > 0) { + fuse_uring_abort_end_requests(ring); + fuse_uring_stop_queues(ring); + } +} + +static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) +{ + struct fuse_ring *ring = fc->ring; + + if (ring) + wait_event(ring->stop_waitq, + atomic_read(&ring->queue_refs) == 0); +} + #else /* CONFIG_FUSE_IO_URING */ struct fuse_ring; @@ -120,6 +164,13 @@ static inline bool fuse_uring_enabled(void) return false; } +static inline void fuse_uring_abort(struct fuse_conn *fc) +{ +} + +static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) +{ +} #endif /* CONFIG_FUSE_IO_URING */ #endif /* _FS_FUSE_DEV_URING_I_H */ From 75269c0ec243460349139eeff27abdc3bc1ed4c8 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:06:12 -0500 Subject: [PATCH 30/98] fuse: {io-uring} Make fuse_dev_queue_{interrupt,forget} non-static jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit ba74ba571189668697a8d8da906ad6d44762ebc6 These functions are also needed by fuse-over-io-uring. Signed-off-by: Bernd Schubert Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit ba74ba571189668697a8d8da906ad6d44762ebc6) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 5 +++-- fs/fuse/fuse_dev_i.h | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 301385526327f..d12f715ae18a2 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -239,7 +239,8 @@ __releases(fiq->lock) spin_unlock(&fiq->lock); } -static void fuse_dev_queue_forget(struct fuse_iqueue *fiq, struct fuse_forget_link *forget) +void fuse_dev_queue_forget(struct fuse_iqueue *fiq, + struct fuse_forget_link *forget) { spin_lock(&fiq->lock); if (fiq->connected) { @@ -252,7 +253,7 @@ static void fuse_dev_queue_forget(struct fuse_iqueue *fiq, struct fuse_forget_li } } -static void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) +void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) { spin_lock(&fiq->lock); /* Repeat FR_SENT test after obtaining the lock to prevent race with fuse_resend() */ diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index b64ab84cbc0d5..3b2bfe1248d35 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -16,6 +16,8 @@ struct fuse_arg; struct fuse_args; struct fuse_pqueue; struct fuse_req; +struct fuse_iqueue; +struct fuse_forget_link; struct fuse_copy_state { int write; @@ -56,6 +58,9 @@ int fuse_copy_args(struct fuse_copy_state *cs, unsigned int numargs, int zeroing); int fuse_copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args, unsigned int nbytes); +void fuse_dev_queue_forget(struct fuse_iqueue *fiq, + struct fuse_forget_link *forget); +void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req); #endif From f90d980e974debbe201c5f65d3a78113831a4b46 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:06:49 -0500 Subject: [PATCH 31/98] fuse: Allow to queue fg requests through io-uring jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit c2c9af9a0b13261c36909036057a116f2edb5e1a This prepares queueing and sending foreground requests through io-uring. Signed-off-by: Bernd Schubert Reviewed-by: Pavel Begunkov # io_uring Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit c2c9af9a0b13261c36909036057a116f2edb5e1a) Signed-off-by: Jonathan Maple --- fs/fuse/dev_uring.c | 180 ++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev_uring_i.h | 8 ++ 2 files changed, 188 insertions(+) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 1161b9aa5e11c..728000434589b 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -24,6 +24,29 @@ bool fuse_uring_enabled(void) return enable_uring; } +struct fuse_uring_pdu { + struct fuse_ring_ent *ent; +}; + +static const struct fuse_iqueue_ops fuse_io_uring_ops; + +static void uring_cmd_set_ring_ent(struct io_uring_cmd *cmd, + struct fuse_ring_ent *ring_ent) +{ + struct fuse_uring_pdu *pdu = + io_uring_cmd_to_pdu(cmd, struct fuse_uring_pdu); + + pdu->ent = ring_ent; +} + +static struct fuse_ring_ent *uring_cmd_to_ring_ent(struct io_uring_cmd *cmd) +{ + struct fuse_uring_pdu *pdu = + io_uring_cmd_to_pdu(cmd, struct fuse_uring_pdu); + + return pdu->ent; +} + static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, int error) { @@ -776,6 +799,31 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, return 0; } +static bool is_ring_ready(struct fuse_ring *ring, int current_qid) +{ + int qid; + struct fuse_ring_queue *queue; + bool ready = true; + + for (qid = 0; qid < ring->nr_queues && ready; qid++) { + if (current_qid == qid) + continue; + + queue = ring->queues[qid]; + if (!queue) { + ready = false; + break; + } + + spin_lock(&queue->lock); + if (list_empty(&queue->ent_avail_queue)) + ready = false; + spin_unlock(&queue->lock); + } + + return ready; +} + /* * fuse_uring_req_fetch command handling */ @@ -784,11 +832,23 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent, unsigned int issue_flags) { struct fuse_ring_queue *queue = ent->queue; + struct fuse_ring *ring = queue->ring; + struct fuse_conn *fc = ring->fc; + struct fuse_iqueue *fiq = &fc->iq; spin_lock(&queue->lock); ent->cmd = cmd; fuse_uring_ent_avail(ent, queue); spin_unlock(&queue->lock); + + if (!ring->ready) { + bool ready = is_ring_ready(ring, queue->qid); + + if (ready) { + WRITE_ONCE(fiq->ops, &fuse_io_uring_ops); + WRITE_ONCE(ring->ready, true); + } + } } /* @@ -972,3 +1032,123 @@ int __maybe_unused fuse_uring_cmd(struct io_uring_cmd *cmd, return -EIOCBQUEUED; } + +static void fuse_uring_send(struct fuse_ring_ent *ent, struct io_uring_cmd *cmd, + ssize_t ret, unsigned int issue_flags) +{ + struct fuse_ring_queue *queue = ent->queue; + + spin_lock(&queue->lock); + ent->state = FRRS_USERSPACE; + list_move(&ent->list, &queue->ent_in_userspace); + ent->cmd = NULL; + spin_unlock(&queue->lock); + + io_uring_cmd_done(cmd, ret, 0, issue_flags); +} + +/* + * This prepares and sends the ring request in fuse-uring task context. + * User buffers are not mapped yet - the application does not have permission + * to write to it - this has to be executed in ring task context. + */ +static void fuse_uring_send_in_task(struct io_uring_cmd *cmd, + unsigned int issue_flags) +{ + struct fuse_ring_ent *ent = uring_cmd_to_ring_ent(cmd); + struct fuse_ring_queue *queue = ent->queue; + int err; + + if (!(issue_flags & IO_URING_F_TASK_DEAD)) { + err = fuse_uring_prepare_send(ent, ent->fuse_req); + if (err) { + fuse_uring_next_fuse_req(ent, queue, issue_flags); + return; + } + } else { + err = -ECANCELED; + } + + fuse_uring_send(ent, cmd, err, issue_flags); +} + +static struct fuse_ring_queue *fuse_uring_task_to_queue(struct fuse_ring *ring) +{ + unsigned int qid; + struct fuse_ring_queue *queue; + + qid = task_cpu(current); + + if (WARN_ONCE(qid >= ring->nr_queues, + "Core number (%u) exceeds nr queues (%zu)\n", qid, + ring->nr_queues)) + qid = 0; + + queue = ring->queues[qid]; + WARN_ONCE(!queue, "Missing queue for qid %d\n", qid); + + return queue; +} + +static void fuse_uring_dispatch_ent(struct fuse_ring_ent *ent) +{ + struct io_uring_cmd *cmd = ent->cmd; + + uring_cmd_set_ring_ent(cmd, ent); + io_uring_cmd_complete_in_task(cmd, fuse_uring_send_in_task); +} + +/* queue a fuse request and send it if a ring entry is available */ +void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req) +{ + struct fuse_conn *fc = req->fm->fc; + struct fuse_ring *ring = fc->ring; + struct fuse_ring_queue *queue; + struct fuse_ring_ent *ent = NULL; + int err; + + err = -EINVAL; + queue = fuse_uring_task_to_queue(ring); + if (!queue) + goto err; + + if (req->in.h.opcode != FUSE_NOTIFY_REPLY) + req->in.h.unique = fuse_get_unique(fiq); + + spin_lock(&queue->lock); + err = -ENOTCONN; + if (unlikely(queue->stopped)) + goto err_unlock; + + ent = list_first_entry_or_null(&queue->ent_avail_queue, + struct fuse_ring_ent, list); + if (ent) + fuse_uring_add_req_to_ring_ent(ent, req); + else + list_add_tail(&req->list, &queue->fuse_req_queue); + spin_unlock(&queue->lock); + + if (ent) + fuse_uring_dispatch_ent(ent); + + return; + +err_unlock: + spin_unlock(&queue->lock); +err: + req->out.h.error = err; + clear_bit(FR_PENDING, &req->flags); + fuse_request_end(req); +} + +static const struct fuse_iqueue_ops fuse_io_uring_ops = { + /* should be send over io-uring as enhancement */ + .send_forget = fuse_dev_queue_forget, + + /* + * could be send over io-uring, but interrupts should be rare, + * no need to make the code complex + */ + .send_interrupt = fuse_dev_queue_interrupt, + .send_req = fuse_uring_queue_fuse_req, +}; diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index a4316e118cbd8..0517a6eafc917 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -117,6 +117,8 @@ struct fuse_ring { unsigned long teardown_time; atomic_t queue_refs; + + bool ready; }; bool fuse_uring_enabled(void); @@ -124,6 +126,7 @@ void fuse_uring_destruct(struct fuse_conn *fc); void fuse_uring_stop_queues(struct fuse_ring *ring); void fuse_uring_abort_end_requests(struct fuse_ring *ring); int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); +void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req); static inline void fuse_uring_abort(struct fuse_conn *fc) { @@ -147,6 +150,11 @@ static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) atomic_read(&ring->queue_refs) == 0); } +static inline bool fuse_uring_ready(struct fuse_conn *fc) +{ + return fc->ring && fc->ring->ready; +} + #else /* CONFIG_FUSE_IO_URING */ struct fuse_ring; From f3b3049ea737db6c6d69c0e68106d7c50fff9bfa Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:06:56 -0500 Subject: [PATCH 32/98] fuse: Allow to queue bg requests through io-uring jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 857b0263f30eebe13ab4b6a65156a0d6c8fc2210 This prepares queueing and sending background requests through io-uring. Signed-off-by: Bernd Schubert Reviewed-by: Pavel Begunkov # io_uring Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 857b0263f30eebe13ab4b6a65156a0d6c8fc2210) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 26 +++++++++++- fs/fuse/dev_uring.c | 99 +++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev_uring_i.h | 12 ++++++ 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index d12f715ae18a2..4b8b20db6f912 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -571,7 +571,25 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, return ret; } -static bool fuse_request_queue_background(struct fuse_req *req) +#ifdef CONFIG_FUSE_IO_URING +static bool fuse_request_queue_background_uring(struct fuse_conn *fc, + struct fuse_req *req) +{ + struct fuse_iqueue *fiq = &fc->iq; + + req->in.h.unique = fuse_get_unique(fiq); + req->in.h.len = sizeof(struct fuse_in_header) + + fuse_len_args(req->args->in_numargs, + (struct fuse_arg *) req->args->in_args); + + return fuse_uring_queue_bq_req(req); +} +#endif + +/* + * @return true if queued + */ +static int fuse_request_queue_background(struct fuse_req *req) { struct fuse_mount *fm = req->fm; struct fuse_conn *fc = fm->fc; @@ -583,6 +601,12 @@ static bool fuse_request_queue_background(struct fuse_req *req) atomic_inc(&fc->num_waiting); } __set_bit(FR_ISREPLY, &req->flags); + +#ifdef CONFIG_FUSE_IO_URING + if (fuse_uring_ready(fc)) + return fuse_request_queue_background_uring(fc, req); +#endif + spin_lock(&fc->bg_lock); if (likely(fc->connected)) { fc->num_background++; diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 728000434589b..27bc103c17c89 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -47,10 +47,53 @@ static struct fuse_ring_ent *uring_cmd_to_ring_ent(struct io_uring_cmd *cmd) return pdu->ent; } +static void fuse_uring_flush_bg(struct fuse_ring_queue *queue) +{ + struct fuse_ring *ring = queue->ring; + struct fuse_conn *fc = ring->fc; + + lockdep_assert_held(&queue->lock); + lockdep_assert_held(&fc->bg_lock); + + /* + * Allow one bg request per queue, ignoring global fc limits. + * This prevents a single queue from consuming all resources and + * eliminates the need for remote queue wake-ups when global + * limits are met but this queue has no more waiting requests. + */ + while ((fc->active_background < fc->max_background || + !queue->active_background) && + (!list_empty(&queue->fuse_req_bg_queue))) { + struct fuse_req *req; + + req = list_first_entry(&queue->fuse_req_bg_queue, + struct fuse_req, list); + fc->active_background++; + queue->active_background++; + + list_move_tail(&req->list, &queue->fuse_req_queue); + } +} + static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, int error) { + struct fuse_ring_queue *queue = ent->queue; + struct fuse_ring *ring = queue->ring; + struct fuse_conn *fc = ring->fc; + + lockdep_assert_not_held(&queue->lock); + spin_lock(&queue->lock); ent->fuse_req = NULL; + if (test_bit(FR_BACKGROUND, &req->flags)) { + queue->active_background--; + spin_lock(&fc->bg_lock); + fuse_uring_flush_bg(queue); + spin_unlock(&fc->bg_lock); + } + + spin_unlock(&queue->lock); + if (error) req->out.h.error = error; @@ -78,6 +121,7 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring) { int qid; struct fuse_ring_queue *queue; + struct fuse_conn *fc = ring->fc; for (qid = 0; qid < ring->nr_queues; qid++) { queue = READ_ONCE(ring->queues[qid]); @@ -85,6 +129,13 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring) continue; queue->stopped = true; + + WARN_ON_ONCE(ring->fc->max_background != UINT_MAX); + spin_lock(&queue->lock); + spin_lock(&fc->bg_lock); + fuse_uring_flush_bg(queue); + spin_unlock(&fc->bg_lock); + spin_unlock(&queue->lock); fuse_uring_abort_end_queue_requests(queue); } } @@ -190,6 +241,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, INIT_LIST_HEAD(&queue->ent_w_req_queue); INIT_LIST_HEAD(&queue->ent_in_userspace); INIT_LIST_HEAD(&queue->fuse_req_queue); + INIT_LIST_HEAD(&queue->fuse_req_bg_queue); queue->fpq.processing = pq; fuse_pqueue_init(&queue->fpq); @@ -1141,6 +1193,53 @@ void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req) fuse_request_end(req); } +bool fuse_uring_queue_bq_req(struct fuse_req *req) +{ + struct fuse_conn *fc = req->fm->fc; + struct fuse_ring *ring = fc->ring; + struct fuse_ring_queue *queue; + struct fuse_ring_ent *ent = NULL; + + queue = fuse_uring_task_to_queue(ring); + if (!queue) + return false; + + spin_lock(&queue->lock); + if (unlikely(queue->stopped)) { + spin_unlock(&queue->lock); + return false; + } + + list_add_tail(&req->list, &queue->fuse_req_bg_queue); + + ent = list_first_entry_or_null(&queue->ent_avail_queue, + struct fuse_ring_ent, list); + spin_lock(&fc->bg_lock); + fc->num_background++; + if (fc->num_background == fc->max_background) + fc->blocked = 1; + fuse_uring_flush_bg(queue); + spin_unlock(&fc->bg_lock); + + /* + * Due to bg_queue flush limits there might be other bg requests + * in the queue that need to be handled first. Or no further req + * might be available. + */ + req = list_first_entry_or_null(&queue->fuse_req_queue, struct fuse_req, + list); + if (ent && req) { + fuse_uring_add_req_to_ring_ent(ent, req); + spin_unlock(&queue->lock); + + fuse_uring_dispatch_ent(ent); + } else { + spin_unlock(&queue->lock); + } + + return true; +} + static const struct fuse_iqueue_ops fuse_io_uring_ops = { /* should be send over io-uring as enhancement */ .send_forget = fuse_dev_queue_forget, diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 0517a6eafc917..0182be61778b2 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -82,8 +82,13 @@ struct fuse_ring_queue { /* fuse requests waiting for an entry slot */ struct list_head fuse_req_queue; + /* background fuse requests */ + struct list_head fuse_req_bg_queue; + struct fuse_pqueue fpq; + unsigned int active_background; + bool stopped; }; @@ -127,6 +132,7 @@ void fuse_uring_stop_queues(struct fuse_ring *ring); void fuse_uring_abort_end_requests(struct fuse_ring *ring); int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req); +bool fuse_uring_queue_bq_req(struct fuse_req *req); static inline void fuse_uring_abort(struct fuse_conn *fc) { @@ -179,6 +185,12 @@ static inline void fuse_uring_abort(struct fuse_conn *fc) static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) { } + +static inline bool fuse_uring_ready(struct fuse_conn *fc) +{ + return false; +} + #endif /* CONFIG_FUSE_IO_URING */ #endif /* _FS_FUSE_DEV_URING_I_H */ From 2e08f8acea7b010b0d91b28defe991e77ed951e4 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:07:00 -0500 Subject: [PATCH 33/98] fuse: {io-uring} Prevent mount point hang on fuse-server termination jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit b6236c8407cba5d7a108facb1bcfab24994d3814 When the fuse-server terminates while the fuse-client or kernel still has queued URING_CMDs, these commands retain references to the struct file used by the fuse connection. This prevents fuse_dev_release() from being invoked, resulting in a hung mount point. This patch addresses the issue by making queued URING_CMDs cancelable, allowing fuse_dev_release() to proceed as expected and preventing the mount point from hanging. Signed-off-by: Bernd Schubert Reviewed-by: Pavel Begunkov # io_uring Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit b6236c8407cba5d7a108facb1bcfab24994d3814) Signed-off-by: Jonathan Maple --- fs/fuse/dev_uring.c | 69 +++++++++++++++++++++++++++++++++++++++++-- fs/fuse/dev_uring_i.h | 9 ++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 27bc103c17c89..fa0451176385e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -150,6 +150,7 @@ void fuse_uring_destruct(struct fuse_conn *fc) for (qid = 0; qid < ring->nr_queues; qid++) { struct fuse_ring_queue *queue = ring->queues[qid]; + struct fuse_ring_ent *ent, *next; if (!queue) continue; @@ -159,6 +160,12 @@ void fuse_uring_destruct(struct fuse_conn *fc) WARN_ON(!list_empty(&queue->ent_commit_queue)); WARN_ON(!list_empty(&queue->ent_in_userspace)); + list_for_each_entry_safe(ent, next, &queue->ent_released, + list) { + list_del_init(&ent->list); + kfree(ent); + } + kfree(queue->fpq.processing); kfree(queue); ring->queues[qid] = NULL; @@ -242,6 +249,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, INIT_LIST_HEAD(&queue->ent_in_userspace); INIT_LIST_HEAD(&queue->fuse_req_queue); INIT_LIST_HEAD(&queue->fuse_req_bg_queue); + INIT_LIST_HEAD(&queue->ent_released); queue->fpq.processing = pq; fuse_pqueue_init(&queue->fpq); @@ -289,6 +297,15 @@ static void fuse_uring_entry_teardown(struct fuse_ring_ent *ent) /* remove entry from queue->fpq->processing */ list_del_init(&req->list); } + + /* + * The entry must not be freed immediately, due to access of direct + * pointer access of entries through IO_URING_F_CANCEL - there is a risk + * of race between daemon termination (which triggers IO_URING_F_CANCEL + * and accesses entries without checking the list state first + */ + list_move(&ent->list, &queue->ent_released); + ent->state = FRRS_RELEASED; spin_unlock(&queue->lock); if (cmd) @@ -296,9 +313,6 @@ static void fuse_uring_entry_teardown(struct fuse_ring_ent *ent) if (req) fuse_uring_stop_fuse_req_end(req); - - list_del_init(&ent->list); - kfree(ent); } static void fuse_uring_stop_list_entries(struct list_head *head, @@ -318,6 +332,7 @@ static void fuse_uring_stop_list_entries(struct list_head *head, continue; } + ent->state = FRRS_TEARDOWN; list_move(&ent->list, &to_teardown); } spin_unlock(&queue->lock); @@ -432,6 +447,46 @@ void fuse_uring_stop_queues(struct fuse_ring *ring) } } +/* + * Handle IO_URING_F_CANCEL, typically should come on daemon termination. + * + * Releasing the last entry should trigger fuse_dev_release() if + * the daemon was terminated + */ +static void fuse_uring_cancel(struct io_uring_cmd *cmd, + unsigned int issue_flags) +{ + struct fuse_ring_ent *ent = uring_cmd_to_ring_ent(cmd); + struct fuse_ring_queue *queue; + bool need_cmd_done = false; + + /* + * direct access on ent - it must not be destructed as long as + * IO_URING_F_CANCEL might come up + */ + queue = ent->queue; + spin_lock(&queue->lock); + if (ent->state == FRRS_AVAILABLE) { + ent->state = FRRS_USERSPACE; + list_move(&ent->list, &queue->ent_in_userspace); + need_cmd_done = true; + ent->cmd = NULL; + } + spin_unlock(&queue->lock); + + if (need_cmd_done) { + /* no queue lock to avoid lock order issues */ + io_uring_cmd_done(cmd, -ENOTCONN, 0, issue_flags); + } +} + +static void fuse_uring_prepare_cancel(struct io_uring_cmd *cmd, int issue_flags, + struct fuse_ring_ent *ring_ent) +{ + uring_cmd_set_ring_ent(cmd, ring_ent); + io_uring_cmd_mark_cancelable(cmd, issue_flags); +} + /* * Checks for errors and stores it into the request */ @@ -839,6 +894,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, spin_unlock(&queue->lock); /* without the queue lock, as other locks are taken */ + fuse_uring_prepare_cancel(cmd, issue_flags, ent); fuse_uring_commit(ent, req, issue_flags); /* @@ -888,6 +944,8 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent, struct fuse_conn *fc = ring->fc; struct fuse_iqueue *fiq = &fc->iq; + fuse_uring_prepare_cancel(cmd, issue_flags, ent); + spin_lock(&queue->lock); ent->cmd = cmd; fuse_uring_ent_avail(ent, queue); @@ -1038,6 +1096,11 @@ int __maybe_unused fuse_uring_cmd(struct io_uring_cmd *cmd, return -EOPNOTSUPP; } + if ((unlikely(issue_flags & IO_URING_F_CANCEL))) { + fuse_uring_cancel(cmd, issue_flags); + return 0; + } + /* This extra SQE size holds struct fuse_uring_cmd_req */ if (!(issue_flags & IO_URING_F_SQE128)) return -EINVAL; diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 0182be61778b2..2102b3d0c1aed 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -28,6 +28,12 @@ enum fuse_ring_req_state { /* The ring entry is in or on the way to user space */ FRRS_USERSPACE, + + /* The ring entry is in teardown */ + FRRS_TEARDOWN, + + /* The ring entry is released, but not freed yet */ + FRRS_RELEASED, }; /** A fuse ring entry, part of the ring queue */ @@ -79,6 +85,9 @@ struct fuse_ring_queue { /* entries in userspace */ struct list_head ent_in_userspace; + /* entries that are released */ + struct list_head ent_released; + /* fuse requests waiting for an entry slot */ struct list_head fuse_req_queue; From 3e6422c3af4ed5070c84ab03e79e34d1669a4fe1 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:09:28 -0500 Subject: [PATCH 34/98] fuse: block request allocation until io-uring init is complete jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 3393ff964e0fa5def66570c54a4612bf9df06b76 upstream-diff | Context conflict on missing fs/fuse/fuse_i.h commit 41748675c0bf -virtiofs: use pages instead of pointer for kernel direct IO Avoid races and block request allocation until io-uring queues are ready. This is a especially important for background requests, as bg request completion might cause lock order inversion of the typical queue->lock and then fc->bg_lock fuse_request_end spin_lock(&fc->bg_lock); flush_bg_queue fuse_send_one fuse_uring_queue_fuse_req spin_lock(&queue->lock); Signed-off-by: Bernd Schubert Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 3393ff964e0fa5def66570c54a4612bf9df06b76) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 3 ++- fs/fuse/dev_uring.c | 3 +++ fs/fuse/fuse_i.h | 3 +++ fs/fuse/inode.c | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 4b8b20db6f912..f58302c242112 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -78,7 +78,8 @@ void fuse_set_initialized(struct fuse_conn *fc) static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) { - return !fc->initialized || (for_background && fc->blocked); + return !fc->initialized || (for_background && fc->blocked) || + (fc->io_uring && !fuse_uring_ready(fc)); } static void fuse_drop_waiting(struct fuse_conn *fc) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index fa0451176385e..ea197ccd4c51f 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -957,6 +957,7 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent, if (ready) { WRITE_ONCE(fiq->ops, &fuse_io_uring_ops); WRITE_ONCE(ring->ready, true); + wake_up_all(&fc->blocked_waitq); } } } @@ -1130,6 +1131,8 @@ int __maybe_unused fuse_uring_cmd(struct io_uring_cmd *cmd, if (err) { pr_info_once("FUSE_IO_URING_CMD_REGISTER failed err=%d\n", err); + fc->io_uring = 0; + wake_up_all(&fc->blocked_waitq); return err; } break; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index e6af5a64b4542..b8f2200f884fe 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -864,6 +864,9 @@ struct fuse_conn { /** Passthrough support for read/write IO */ unsigned int passthrough:1; + /* Use io_uring for communication */ + unsigned int io_uring; + /** Maximum stack depth for passthrough backing files */ int max_stack_depth; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index ebf13adae51e9..9a8b3bc214fd1 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1358,6 +1358,8 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, else ok = false; } + if (flags & FUSE_OVER_IO_URING && fuse_uring_enabled()) + fc->io_uring = 1; } else { ra_pages = fc->max_read / PAGE_SIZE; fc->no_lock = 1; From 943184255e041d8ae6fc9e40450070f52ae0f2f6 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:10:54 -0500 Subject: [PATCH 35/98] fuse: enable fuse-over-io-uring jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 786412a73e7ee5b00ef3437bbf2f3a250759b2ae All required parts are handled now, fuse-io-uring can be enabled. Signed-off-by: Bernd Schubert Reviewed-by: Pavel Begunkov # io_uring Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 786412a73e7ee5b00ef3437bbf2f3a250759b2ae) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 3 +++ fs/fuse/dev_uring.c | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index f58302c242112..623ea9c8273ac 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2523,6 +2523,9 @@ const struct file_operations fuse_dev_operations = { .fasync = fuse_dev_fasync, .unlocked_ioctl = fuse_dev_ioctl, .compat_ioctl = compat_ptr_ioctl, +#ifdef CONFIG_FUSE_IO_URING + .uring_cmd = fuse_uring_cmd, +#endif #ifdef CONFIG_PROC_FS .show_fdinfo = fuse_dev_show_fdinfo, #endif diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index ea197ccd4c51f..3bdc75518e5b1 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1084,8 +1084,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, * Entry function from io_uring to handle the given passthrough command * (op code IORING_OP_URING_CMD) */ -int __maybe_unused fuse_uring_cmd(struct io_uring_cmd *cmd, - unsigned int issue_flags) +int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) { struct fuse_dev *fud; struct fuse_conn *fc; From 6bac0a7308dc048ed523306f50ec5ea19996220b Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:10:58 -0500 Subject: [PATCH 36/98] fuse: prevent disabling io-uring on active connections jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 2d4fde59fd502a65c1698b61ad4d0f10a9ab665a The enable_uring module parameter allows administrators to enable/disable io-uring support for FUSE at runtime. However, disabling io-uring while connections already have it enabled can lead to an inconsistent state. Fix this by keeping io-uring enabled on connections that were already using it, even if the module parameter is later disabled. This ensures active FUSE mounts continue to function correctly. Signed-off-by: Bernd Schubert Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi (cherry picked from commit 2d4fde59fd502a65c1698b61ad4d0f10a9ab665a) Signed-off-by: Jonathan Maple --- fs/fuse/dev_uring.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 3bdc75518e5b1..ebd2931b4f2ac 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1091,11 +1091,6 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) u32 cmd_op = cmd->cmd_op; int err; - if (!enable_uring) { - pr_info_ratelimited("fuse-io-uring is disabled\n"); - return -EOPNOTSUPP; - } - if ((unlikely(issue_flags & IO_URING_F_CANCEL))) { fuse_uring_cancel(cmd, issue_flags); return 0; @@ -1112,6 +1107,12 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) } fc = fud->fc; + /* Once a connection has io-uring enabled on it, it can't be disabled */ + if (!enable_uring && !fc->io_uring) { + pr_info_ratelimited("fuse-io-uring is disabled\n"); + return -EOPNOTSUPP; + } + if (fc->aborted) return -ECONNABORTED; if (!fc->connected) From 717803b9c7b0f8a505840a6c5ea72e0f94dc9689 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:11:34 -0500 Subject: [PATCH 37/98] fuse: fix uring race condition for null dereference of fc jira SECO-478 RFE: FUSE_IO_URING commit-author Joanne Koong commit d9ecc77193cad25402ff5517fb26fb22b4db0e10 There is a race condition leading to a kernel crash from a null dereference when attemping to access fc->lock in fuse_uring_create_queue(). fc may be NULL in the case where another thread is creating the uring in fuse_uring_create() and has set fc->ring but has not yet set ring->fc when fuse_uring_create_queue() reads ring->fc. There is another race condition as well where in fuse_uring_register(), ring->nr_queues may still be 0 and not yet set to the new value when we compare qid against it. This fix sets fc->ring only after ring->fc and ring->nr_queues have been set, which guarantees now that ring->fc is a proper pointer when any queues are created and ring->nr_queues reflects the right number of queues if ring is not NULL. We must use smp_store_release() and smp_load_acquire() semantics to ensure the ordering will remain correct where fc->ring is assigned only after ring->fc and ring->nr_queues have been assigned. Signed-off-by: Joanne Koong Link: https://lore.kernel.org/r/20250318003028.3330599-1-joannelkoong@gmail.com Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands") Acked-by: Miklos Szeredi Reviewed-by: Bernd Schubert Signed-off-by: Christian Brauner (cherry picked from commit d9ecc77193cad25402ff5517fb26fb22b4db0e10) Signed-off-by: Jonathan Maple --- fs/fuse/dev_uring.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index ebd2931b4f2ac..82bf458fa9db5 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -208,11 +208,11 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) init_waitqueue_head(&ring->stop_waitq); - fc->ring = ring; ring->nr_queues = nr_queues; ring->fc = fc; ring->max_payload_sz = max_payload_size; atomic_set(&ring->queue_refs, 0); + smp_store_release(&fc->ring, ring); spin_unlock(&fc->lock); return ring; @@ -1041,7 +1041,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, unsigned int issue_flags, struct fuse_conn *fc) { const struct fuse_uring_cmd_req *cmd_req = io_uring_sqe_cmd(cmd->sqe); - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = smp_load_acquire(&fc->ring); struct fuse_ring_queue *queue; struct fuse_ring_ent *ent; int err; From 9cf7f0cda3d07136a0737df1733209e22b6ab8c1 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:11:16 -0500 Subject: [PATCH 38/98] fuse: fix possible deadlock if rings are never initialized jira SECO-478 RFE: FUSE_IO_URING commit-author Luis Henriques commit d55011469b41d9da6c06cb1c4a4da7a87fe155bc When mounting a user-space filesystem using io_uring, the initialization of the rings is done separately in the server side. If for some reason (e.g. a server bug) this step is not performed it will be impossible to unmount the filesystem if there are already requests waiting. This issue is easily reproduced with the libfuse passthrough_ll example, if the queue depth is set to '0' and a request is queued before trying to unmount the filesystem. When trying to force the unmount, fuse_abort_conn() will try to wake up all tasks waiting in fc->blocked_waitq, but because the rings were never initialized, fuse_uring_ready() will never return 'true'. Fixes: 3393ff964e0f ("fuse: block request allocation until io-uring init is complete") Signed-off-by: Luis Henriques Link: https://lore.kernel.org/r/20250306111218.13734-1-luis@igalia.com Acked-by: Miklos Szeredi Reviewed-by: Bernd Schubert Signed-off-by: Christian Brauner (cherry picked from commit d55011469b41d9da6c06cb1c4a4da7a87fe155bc) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 623ea9c8273ac..436c28318bf9b 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -79,7 +79,7 @@ void fuse_set_initialized(struct fuse_conn *fc) static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) { return !fc->initialized || (for_background && fc->blocked) || - (fc->io_uring && !fuse_uring_ready(fc)); + (fc->io_uring && fc->connected && !fuse_uring_ready(fc)); } static void fuse_drop_waiting(struct fuse_conn *fc) From 0a18f1e6a4414d82298ff6becb8cf8da491efc51 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:11:01 -0500 Subject: [PATCH 39/98] fuse: removed unused function fuse_uring_create() from header jira SECO-478 RFE: FUSE_IO_URING commit-author Luis Henriques commit 841c7b812c038661e4f659d1b9c9a366c6d24b71 Function fuse_uring_create() is used only from dev_uring.c and does not need to be exposed in the header file. Furthermore, it has the wrong signature. While there, also remove the 'struct fuse_ring' forward declaration. Signed-off-by: Luis Henriques Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi (cherry picked from commit 841c7b812c038661e4f659d1b9c9a366c6d24b71) Signed-off-by: Jonathan Maple --- fs/fuse/dev_uring_i.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 2102b3d0c1aed..0d67738850c5a 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -172,12 +172,6 @@ static inline bool fuse_uring_ready(struct fuse_conn *fc) #else /* CONFIG_FUSE_IO_URING */ -struct fuse_ring; - -static inline void fuse_uring_create(struct fuse_conn *fc) -{ -} - static inline void fuse_uring_destruct(struct fuse_conn *fc) { } From 7588551dbd275ca512e53c8991cd3b369e482c16 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:11:56 -0500 Subject: [PATCH 40/98] fuse: {io-uring} Fix a possible req cancellation race jira SECO-478 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 09098e62e4be8f0755e58d6078aaf27cbd9a3a8d task-A (application) might be in request_wait_answer and try to remove the request when it has FR_PENDING set. task-B (a fuse-server io-uring task) might handle this request with FUSE_IO_URING_CMD_COMMIT_AND_FETCH, when fetching the next request and accessed the req from the pending list in fuse_uring_ent_assign_req(). That code path was not protected by fiq->lock and so might race with task-A. For scaling reasons we better don't use fiq->lock, but add a handler to remove canceled requests from the queue. This also removes usage of fiq->lock from fuse_uring_add_req_to_ring_ent() altogether, as it was there just to protect against this race and incomplete. Also added is a comment why FR_PENDING is not cleared. Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: # v6.14 Reported-by: Joanne Koong Closes: https://lore.kernel.org/all/CAJnrk1ZgHNb78dz-yfNTpxmW7wtT88A=m-zF0ZoLXKLUHRjNTw@mail.gmail.com/ Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 09098e62e4be8f0755e58d6078aaf27cbd9a3a8d) Signed-off-by: Jonathan Maple --- fs/fuse/dev.c | 34 +++++++++++++++++++++++++--------- fs/fuse/dev_uring.c | 15 +++++++++++---- fs/fuse/dev_uring_i.h | 6 ++++++ fs/fuse/fuse_dev_i.h | 1 + fs/fuse/fuse_i.h | 3 +++ 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 436c28318bf9b..823ce2424c16a 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -410,6 +410,24 @@ static int queue_interrupt(struct fuse_req *req) return 0; } +bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock) +{ + spin_lock(lock); + if (test_bit(FR_PENDING, &req->flags)) { + /* + * FR_PENDING does not get cleared as the request will end + * up in destruction anyway. + */ + list_del(&req->list); + spin_unlock(lock); + __fuse_put_request(req); + req->out.h.error = -EINTR; + return true; + } + spin_unlock(lock); + return false; +} + static void request_wait_answer(struct fuse_req *req) { struct fuse_conn *fc = req->fm->fc; @@ -431,22 +449,20 @@ static void request_wait_answer(struct fuse_req *req) } if (!test_bit(FR_FORCE, &req->flags)) { + bool removed; + /* Only fatal signals may interrupt this */ err = wait_event_killable(req->waitq, test_bit(FR_FINISHED, &req->flags)); if (!err) return; - spin_lock(&fiq->lock); - /* Request is not yet in userspace, bail out */ - if (test_bit(FR_PENDING, &req->flags)) { - list_del(&req->list); - spin_unlock(&fiq->lock); - __fuse_put_request(req); - req->out.h.error = -EINTR; + if (test_bit(FR_URING, &req->flags)) + removed = fuse_uring_remove_pending_req(req); + else + removed = fuse_remove_pending_req(req, &fiq->lock); + if (removed) return; - } - spin_unlock(&fiq->lock); } /* diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 82bf458fa9db5..5f1566c69ddcc 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -726,8 +726,6 @@ static void fuse_uring_add_req_to_ring_ent(struct fuse_ring_ent *ent, struct fuse_req *req) { struct fuse_ring_queue *queue = ent->queue; - struct fuse_conn *fc = req->fm->fc; - struct fuse_iqueue *fiq = &fc->iq; lockdep_assert_held(&queue->lock); @@ -737,9 +735,7 @@ static void fuse_uring_add_req_to_ring_ent(struct fuse_ring_ent *ent, ent->state); } - spin_lock(&fiq->lock); clear_bit(FR_PENDING, &req->flags); - spin_unlock(&fiq->lock); ent->fuse_req = req; ent->state = FRRS_FUSE_REQ; list_move(&ent->list, &queue->ent_w_req_queue); @@ -1238,6 +1234,8 @@ void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req) if (unlikely(queue->stopped)) goto err_unlock; + set_bit(FR_URING, &req->flags); + req->ring_queue = queue; ent = list_first_entry_or_null(&queue->ent_avail_queue, struct fuse_ring_ent, list); if (ent) @@ -1276,6 +1274,8 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req) return false; } + set_bit(FR_URING, &req->flags); + req->ring_queue = queue; list_add_tail(&req->list, &queue->fuse_req_bg_queue); ent = list_first_entry_or_null(&queue->ent_avail_queue, @@ -1306,6 +1306,13 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req) return true; } +bool fuse_uring_remove_pending_req(struct fuse_req *req) +{ + struct fuse_ring_queue *queue = req->ring_queue; + + return fuse_remove_pending_req(req, &queue->lock); +} + static const struct fuse_iqueue_ops fuse_io_uring_ops = { /* should be send over io-uring as enhancement */ .send_forget = fuse_dev_queue_forget, diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 0d67738850c5a..d87d0a55cb5f8 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -142,6 +142,7 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring); int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req); bool fuse_uring_queue_bq_req(struct fuse_req *req); +bool fuse_uring_remove_pending_req(struct fuse_req *req); static inline void fuse_uring_abort(struct fuse_conn *fc) { @@ -194,6 +195,11 @@ static inline bool fuse_uring_ready(struct fuse_conn *fc) return false; } +static inline bool fuse_uring_remove_pending_req(struct fuse_req *req) +{ + return false; +} + #endif /* CONFIG_FUSE_IO_URING */ #endif /* _FS_FUSE_DEV_URING_I_H */ diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 3b2bfe1248d35..2481da3388c5f 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -61,6 +61,7 @@ int fuse_copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args, void fuse_dev_queue_forget(struct fuse_iqueue *fiq, struct fuse_forget_link *forget); void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req); +bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock); #endif diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index b8f2200f884fe..7cd634f497411 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -375,6 +375,7 @@ struct fuse_io_priv { * FR_FINISHED: request is finished * FR_PRIVATE: request is on private list * FR_ASYNC: request is asynchronous + * FR_URING: request is handled through fuse-io-uring */ enum fuse_req_flag { FR_ISREPLY, @@ -389,6 +390,7 @@ enum fuse_req_flag { FR_FINISHED, FR_PRIVATE, FR_ASYNC, + FR_URING, }; /** @@ -438,6 +440,7 @@ struct fuse_req { #ifdef CONFIG_FUSE_IO_URING void *ring_entry; + void *ring_queue; #endif }; From edbaf9d742ee5df6f6d81ae07feefd9a2f08508a Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Thu, 5 Mar 2026 16:11:39 -0500 Subject: [PATCH 41/98] fuse: remove unneeded atomic set in uring creation jira SECO-478 RFE: FUSE_IO_URING commit-author Joanne Koong commit 2d066800a4276340a97acc75c148892eb6f8781a When the ring is allocated, it is kzalloc-ed. ring->queue_refs will already be initialized to 0 by default. It does not need to be atomically set to 0. Signed-off-by: Joanne Koong Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi (cherry picked from commit 2d066800a4276340a97acc75c148892eb6f8781a) Signed-off-by: Jonathan Maple --- fs/fuse/dev_uring.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 5f1566c69ddcc..3dbf2e8c35670 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -211,7 +211,6 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) ring->nr_queues = nr_queues; ring->fc = fc; ring->max_payload_sz = max_payload_size; - atomic_set(&ring->queue_refs, 0); smp_store_release(&fc->ring, ring); spin_unlock(&fc->lock); From d91d97d24dd8c46164718ba25d7ad13dda15b4a5 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Tue, 21 Oct 2025 22:46:42 +0200 Subject: [PATCH 42/98] fuse: missing copy_finish in fuse-over-io-uring argument copies jira SECO-478 cve CVE-2025-68791 RFE: FUSE_IO_URING commit-author Cheng Ding commit 6e0d7f7f4a43ac8868e98c87ecf48805aa8c24dd upstream-diff | Context diffs from missing upstream type changes (bool write, is_uring = true) and surrounding declarations Fix a possible reference count leak of payload pages during fuse argument copies. [Joanne: simplified error cleanup] Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@vger.kernel.org # v6.14 Signed-off-by: Cheng Ding Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 6e0d7f7f4a43ac8868e98c87ecf48805aa8c24dd) Signed-off-by: Brett Mastbergen --- fs/fuse/dev.c | 2 +- fs/fuse/dev_uring.c | 5 ++++- fs/fuse/fuse_dev_i.h | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 823ce2424c16a..27bf3c3cdf6cd 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -733,7 +733,7 @@ void fuse_copy_init(struct fuse_copy_state *cs, int write, } /* Unmap and put previous page of userspace buffer */ -static void fuse_copy_finish(struct fuse_copy_state *cs) +void fuse_copy_finish(struct fuse_copy_state *cs) { if (cs->currbuf) { struct pipe_buffer *buf = cs->currbuf; diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 3dbf2e8c35670..7e2fd7e19dad1 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -554,7 +554,9 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring, cs.is_uring = 1; cs.req = req; - return fuse_copy_out_args(&cs, args, ring_in_out.payload_sz); + err = fuse_copy_out_args(&cs, args, ring_in_out.payload_sz); + fuse_copy_finish(&cs); + return err; } /* @@ -605,6 +607,7 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, /* copy the payload */ err = fuse_copy_args(&cs, num_args, args->in_pages, (struct fuse_arg *)in_args, 0); + fuse_copy_finish(&cs); if (err) { pr_info_ratelimited("%s fuse_copy_args failed\n", __func__); return err; diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 2481da3388c5f..f3ce625351858 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -53,6 +53,7 @@ void fuse_dev_end_requests(struct list_head *head); void fuse_copy_init(struct fuse_copy_state *cs, int write, struct iov_iter *iter); +void fuse_copy_finish(struct fuse_copy_state *cs); int fuse_copy_args(struct fuse_copy_state *cs, unsigned int numargs, unsigned int argpages, struct fuse_arg *args, int zeroing); From e211fe71625091a59c78efa86e99edef4e14c9ef Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Tue, 25 Nov 2025 10:13:47 -0800 Subject: [PATCH 43/98] fuse: fix io-uring list corruption for terminated non-committed requests jira SECO-478 cve CVE-2025-68805 RFE: FUSE_IO_URING commit-author Joanne Koong commit 95c39eef7c2b666026c69ab5b30471da94ea2874 When a request is terminated before it has been committed, the request is not removed from the queue's list. This leaves a dangling list entry that leads to list corruption and use-after-free issues. Remove the request from the queue's list for terminated non-committed requests. Signed-off-by: Joanne Koong Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@vger.kernel.org Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi (cherry picked from commit 95c39eef7c2b666026c69ab5b30471da94ea2874) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 7e2fd7e19dad1..de706aa363db3 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -85,6 +85,7 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, lockdep_assert_not_held(&queue->lock); spin_lock(&queue->lock); ent->fuse_req = NULL; + list_del_init(&req->list); if (test_bit(FR_BACKGROUND, &req->flags)) { queue->active_background--; spin_lock(&fc->bg_lock); From d0587204c54a001d6ed16424e2a2f8e5ffe940b5 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Fri, 5 Jun 2026 12:27:06 -0700 Subject: [PATCH 44/98] fuse-uring: fix EFAULT clobber in fuse_uring_commit jira SECO-478 cve CVE-2026-64264 RFE: FUSE_IO_URING commit-author Chris Mason commit 3a0a8bc51a13951c5141262bf770eeea3e0b6228 copy_from_user() returns the number of bytes not copied as an unsigned residual on failure (1..sizeof(struct fuse_out_header)). fuse_uring_commit stores that residual in ssize_t err, sets req->out.h.error to -EFAULT, then jumps to out: with err still holding the positive residual. err = copy_from_user(&req->out.h, &ent->headers->in_out, sizeof(req->out.h)); if (err) { req->out.h.error = -EFAULT; goto out; /* err is the positive residual */ } ... out: fuse_uring_req_end(ent, req, err); fuse_uring_req_end() then runs if (error) req->out.h.error = error; which overwrites the just-assigned -EFAULT with the positive residual. FUSE callers such as fuse_simple_request() test err < 0 to detect failure, so the positive value is interpreted as success and the caller proceeds with an uninitialised or partial req->out.args. Fix by assigning err = -EFAULT in the failure branch before jumping to out, so fuse_uring_req_end() receives a negative errno and sets req->out.h.error to -EFAULT. Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@vger.kernel.org Reviewed-by: Joanne Koong Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi (cherry picked from commit 3a0a8bc51a13951c5141262bf770eeea3e0b6228) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index de706aa363db3..7a1f3f33e157e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -773,14 +773,11 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, { struct fuse_ring *ring = ent->queue->ring; struct fuse_conn *fc = ring->fc; - ssize_t err = 0; + ssize_t err = -EFAULT; - err = copy_from_user(&req->out.h, &ent->headers->in_out, - sizeof(req->out.h)); - if (err) { - req->out.h.error = -EFAULT; + if (copy_from_user(&req->out.h, &ent->headers->in_out, + sizeof(req->out.h))) goto out; - } err = fuse_uring_out_header_has_err(&req->out.h, req, fc); if (err) { From 2f80b922a4d516d3ee6ffd6e60e81103384d665d Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Fri, 5 Jun 2026 12:27:07 -0700 Subject: [PATCH 45/98] fuse-uring: fix data races on ring->ready jira SECO-478 cve CVE-2026-64588 RFE: FUSE_IO_URING commit-author Chris Mason commit 46725a0056c884cf58a6897f222892807327d82d On weakly-ordered architectures, the store to fiq->ops can be reordered past the store to ring->ready, allowing a CPU that sees ring->ready == true via fuse_uring_ready() to dispatch requests through a stale fiq->ops pointer. Upgrade the store to smp_store_release() and the load in fuse_uring_ready() to smp_load_acquire() so that the preceding WRITE_ONCE(fiq->ops, ...) is visible to any CPU that observes ring->ready == true. Additionally, fuse_uring_do_register() publishes ring->ready with WRITE_ONCE() but the fast-path check reads it with a plain load. This is a marked-vs-unmarked access that KCSAN will flag. Wrap it in READ_ONCE() to mark it without adding unnecessary ordering. Also wrap the fc->ring load in fuse_uring_ready() in READ_ONCE() to prevent the compiler from reloading it between the NULL check and the dereference. Fixes: c2c9af9a0b13 ("fuse: Allow to queue fg requests through io-uring") Cc: stable@vger.kernel.org Reviewed-by: Joanne Koong Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi (cherry picked from commit 46725a0056c884cf58a6897f222892807327d82d) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 4 ++-- fs/fuse/dev_uring_i.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 7a1f3f33e157e..2da1a4bc9d41e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -947,12 +947,12 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent, fuse_uring_ent_avail(ent, queue); spin_unlock(&queue->lock); - if (!ring->ready) { + if (!READ_ONCE(ring->ready)) { bool ready = is_ring_ready(ring, queue->qid); if (ready) { WRITE_ONCE(fiq->ops, &fuse_io_uring_ops); - WRITE_ONCE(ring->ready, true); + smp_store_release(&ring->ready, true); wake_up_all(&fc->blocked_waitq); } } diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index d87d0a55cb5f8..3cfdae0bbf08d 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -168,7 +168,9 @@ static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) static inline bool fuse_uring_ready(struct fuse_conn *fc) { - return fc->ring && fc->ring->ready; + struct fuse_ring *ring = READ_ONCE(fc->ring); + + return ring && smp_load_acquire(&ring->ready); } #else /* CONFIG_FUSE_IO_URING */ From fa659e2d02572ec440c659c8c51ad9d418a05451 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 8 Jun 2026 12:21:47 -0700 Subject: [PATCH 46/98] fuse-uring: fix race between registration and connection abortion jira SECO-478 cve CVE-2026-68095 RFE: FUSE_IO_URING commit-author Joanne Koong commit 952b5d36f6a298f57c52a59e72076c69386a8aaf upstream-diff | fch->lock/fch->connected changed to fc->lock/fc->connected. Upstream splits fuse_conn into fuse_conn + fuse_chan via: b03404ea3a05 - fuse: change ring->fc to ring->chan bf9932623d20 - fuse: split off fch->lock from fc->lock 599ad4427bbc - fuse: move request blocking related members to fuse_chan These are not in this kernel, so fc->lock/fc->connected are used directly wherever upstream uses fch->lock/fch->connected, and ring->fc wherever upstream uses ring->chan->conn. This fixes this race: - thread a: io_uring_enter -> register sqe -> fuse_uring_create_ring_ent -> allocate ent but doesn't grab queue_ref yet - thread b: fuse_conn_destroy() -> fuse_chan_abort() -> fuse_uring_abort() is a no-op due to queue ref being 0 - thread a: grabs the queue_ref, queue_ref is now 1, rest of fuse_uring_do_register() logic executes - thread b: fuse_chan_abort() returns, fuse_chan_wait_aborted() now runs and calls "wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0);" The abort/unmount thread will hang indefinitely in unkillable state as nothing will decrement queue_refs or wake stop_waitq, and the ring, queue, and ent are leaked. Fix this by checking fch->connected under fch->lock after the created ent has grabbed a ref count on the queue. This ensures that in the scenario above, it is guaranteed that we either release the queue ref and wake up stop_waitq (in case fuse_chan_wait_aborted() is already waiting) in fuse_uring_do_register() when we detect !fch->connected, or if the connection is aborted after the check, it is guaranteed that the async teardown worker will be running in the background cleaning up ents and decrementing the ent's ref on the queue, which will unblock the eventual queue and ring teardown. Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands") Cc: stable@vger.kernel.org Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 952b5d36f6a298f57c52a59e72076c69386a8aaf) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 2da1a4bc9d41e..99a53dee6b139 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -931,15 +931,26 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid) /* * fuse_uring_req_fetch command handling */ -static void fuse_uring_do_register(struct fuse_ring_ent *ent, - struct io_uring_cmd *cmd, - unsigned int issue_flags) +static int fuse_uring_do_register(struct fuse_ring_ent *ent, + struct io_uring_cmd *cmd, + unsigned int issue_flags) { struct fuse_ring_queue *queue = ent->queue; struct fuse_ring *ring = queue->ring; struct fuse_conn *fc = ring->fc; struct fuse_iqueue *fiq = &fc->iq; + spin_lock(&fc->lock); + /* abort teardown path is running or has run */ + if (!fc->connected) { + spin_unlock(&fc->lock); + if (atomic_dec_and_test(&ring->queue_refs)) + wake_up_all(&ring->stop_waitq); + kfree(ent); + return -ECONNABORTED; + } + spin_unlock(&fc->lock); + fuse_uring_prepare_cancel(cmd, issue_flags, ent); spin_lock(&queue->lock); @@ -956,6 +967,7 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent, wake_up_all(&fc->blocked_waitq); } } + return 0; } /* @@ -1071,9 +1083,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, if (IS_ERR(ent)) return PTR_ERR(ent); - fuse_uring_do_register(ent, cmd, issue_flags); - - return 0; + return fuse_uring_do_register(ent, cmd, issue_flags); } /* From e478b47157ecc6ee0bd9b863ec472ab39476baad Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 13 Aug 2026 12:06:35 -0400 Subject: [PATCH 47/98] fuse-uring: check connection abort during ring creation jira SECO-478 RFE: FUSE_IO_URING commit-author Joanne Koong commit c146284c4355e96550e50e8f6e694223d107b621 upstream-diff | fch->lock/fch->connected changed to fc->lock/fc->connected in fuse_uring_do_register() already applied in 23e9c1c. Only the fuse_uring_create() hunk is new. Check fc->connected under fc->lock in fuse_uring_create() before attaching a new ring. Without this, a race between fuse_uring_create() and fuse_conn_destroy() can result in the ring, queue, and fpq.processing table being created after fuse_uring_abort() has already run, leading to unnecessary allocation and teardown. These are eventually cleaned up by fuse_uring_destruct() but will linger until the process exits, even with the connection aborted. Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit c146284c4355e96550e50e8f6e694223d107b621) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 99a53dee6b139..d804e3d5ece5d 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -200,6 +200,10 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) max_payload_size = max(max_payload_size, fc->max_pages * PAGE_SIZE); spin_lock(&fc->lock); + if (!fc->connected) { + spin_unlock(&fc->lock); + goto out_err; + } if (fc->ring) { /* race, another thread created the ring in the meantime */ spin_unlock(&fc->lock); From 360e9d210071f0e2fff95c8757399ce75ec3231c Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 13 Aug 2026 12:07:57 -0400 Subject: [PATCH 48/98] fuse-uring: fix moving cancelled entry to ent_in_userspace list jira SECO-478 cve CVE-2026-64263 RFE: FUSE_IO_URING commit-author Joanne Koong commit 198f45eeb9f78b2a2d6d8be95e4e43468eb2c6bc upstream-diff | io_uring_cmd_done() uses 4 args (res2=0) vs upstream 3 args. list_move used instead of list_move_tail. fuse_uring_cancel() moves entries that are available (these have no reqs attached) to the ent_in_userspace list. ent_list_request_expired() checks the first entry on ent_in_userspace and dereferences ent->fuse_req unconditionally, which will crash on a cancelled entry that was moved to this list. Fix this by freeing the entry and dropping queue_refs directly in fuse_uring_cancel(). This is safe because cancel is the cancel handler itself - after io_uring_cmd_done(), no more cancels will be dispatched for this command, and teardown serializes with cancel via queue->lock. Since cancel now decrements queue_refs, fuse_uring_abort() must no longer gate fuse_uring_abort_end_requests() on queue_refs > 0, as cancelled entries may have already dropped queue_refs while requests are still queued. Remove the gate so abort always flushes requests and stops queues. Reported-by: Heechan Kang Tested-by: Heechan Kang Reviewed-by: Bernd Schubert Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check") Cc: stable@vger.kernel.org Suggested-by: Jian Huang Li Suggested-by: Horst Birthelmer Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 198f45eeb9f78b2a2d6d8be95e4e43468eb2c6bc) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 6 ++++-- fs/fuse/dev_uring_i.h | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index d804e3d5ece5d..d9ea6aa58bc6d 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -471,8 +471,7 @@ static void fuse_uring_cancel(struct io_uring_cmd *cmd, queue = ent->queue; spin_lock(&queue->lock); if (ent->state == FRRS_AVAILABLE) { - ent->state = FRRS_USERSPACE; - list_move(&ent->list, &queue->ent_in_userspace); + list_del_init(&ent->list); need_cmd_done = true; ent->cmd = NULL; } @@ -481,6 +480,9 @@ static void fuse_uring_cancel(struct io_uring_cmd *cmd, if (need_cmd_done) { /* no queue lock to avoid lock order issues */ io_uring_cmd_done(cmd, -ENOTCONN, 0, issue_flags); + kfree(ent); + if (atomic_dec_and_test(&queue->ring->queue_refs)) + wake_up_all(&queue->ring->stop_waitq); } } diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 3cfdae0bbf08d..89d96a06ddd4f 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -151,10 +151,10 @@ static inline void fuse_uring_abort(struct fuse_conn *fc) if (ring == NULL) return; - if (atomic_read(&ring->queue_refs) > 0) { - fuse_uring_abort_end_requests(ring); + fuse_uring_abort_end_requests(ring); + + if (atomic_read(&ring->queue_refs) > 0) fuse_uring_stop_queues(ring); - } } static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) From 66f3c8bc78724d5330b971e783206a8b8b19db8c Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 8 Jun 2026 17:28:55 -0700 Subject: [PATCH 49/98] fuse-uring: end fuse_req on io-uring cancel task work jira SECO-478 cve CVE-2026-64262 RFE: FUSE_IO_URING commit-author Chris Mason commit bea4fe98204b6ce7eb8e29f7bf867dd7619b3ddd upstream-diff | fuse_uring_send_in_task has different signature (missing io_tw_req/io_tw_token_t API in this kernel) io_uring_cmd_done() call corrected to pass 4 args (res2=0 was missing in upstream adaptation). Upstream dropped the res2 param in: ef9f603 - io_uring/cmd: drop unused res2 param from io_uring_cmd_done() When io_uring delivers task work with tw.cancel set (PF_EXITING, PF_KTHREAD fallback, or percpu_ref_is_dying on the ring context), fuse_uring_send_in_task() takes the cancel branch, assigns -ECANCELED, and falls through to fuse_uring_send(). That path only flips the entry to FRRS_USERSPACE and completes the io_uring cmd; it never discharges the ring entry's owning reference to the fuse_req that fuse_uring_add_req_to_ring_ent() handed it at dispatch time. fuse_uring_send_in_task() tw.cancel == true err = -ECANCELED fuse_uring_send(ent, cmd, err, issue_flags) ent->state = FRRS_USERSPACE list_move(&ent->list, &queue->ent_in_userspace) ent->cmd = NULL io_uring_cmd_done(-ECANCELED) /* ent->fuse_req still set, req still hashed */ The fuse_req stays linked on fpq->processing[hash] and fuse_request_end() is never invoked. The originating syscall thread blocks in D-state in request_wait_answer() until fuse_abort_conn() runs, which can be the entire connection lifetime. For FR_BACKGROUND requests fc->num_background is never decremented either, so repeated cancels inflate the counter until max_background is hit and all later background ops stall. tw.cancel does not imply a connection abort (e.g. a single io_uring worker thread exits while the fuse connection stays up), so this cannot be left for fuse_abort_conn() to clean up. Ending the req but still routing the entry through fuse_uring_send() is not enough: that leaves a req-less entry on ent_in_userspace, and ent_list_request_expired() dereferences ent->fuse_req unconditionally on the head of that list, which would then NULL-deref. Fix the cancel branch to release the entry directly. Remove it from the queue, complete the io_uring cmd, end the fuse_req, free the entry, and drop its queue_refs (waking the teardown waiter if it was the last). Fixes: c2c9af9a0b13 ("fuse: Allow to queue fg requests through io-uring") Cc: stable@vger.kernel.org Reviewed-by: Joanne Koong Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Signed-off-by: Miklos Szeredi (cherry picked from commit bea4fe98204b6ce7eb8e29f7bf867dd7619b3ddd) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index d9ea6aa58bc6d..446c01601267e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1195,11 +1195,21 @@ static void fuse_uring_send_in_task(struct io_uring_cmd *cmd, fuse_uring_next_fuse_req(ent, queue, issue_flags); return; } + fuse_uring_send(ent, cmd, err, issue_flags); } else { err = -ECANCELED; - } - fuse_uring_send(ent, cmd, err, issue_flags); + spin_lock(&queue->lock); + list_del_init(&ent->list); + spin_unlock(&queue->lock); + + io_uring_cmd_done(cmd, err, 0, issue_flags); + + fuse_uring_req_end(ent, ent->fuse_req, err); + kfree(ent); + if (atomic_dec_and_test(&queue->ring->queue_refs)) + wake_up_all(&queue->ring->stop_waitq); + } } static struct fuse_ring_queue *fuse_uring_task_to_queue(struct fuse_ring *ring) From b944db99997aff5cbf2d5d3c354fffe77f2c6567 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 13 Aug 2026 14:21:37 -0400 Subject: [PATCH 50/98] fuse-uring: Avoid use-after-free in fuse_uring_async_stop_queues jira SECO-478 cve CVE-2026-64261 RFE: FUSE_IO_URING commit-author Bernd Schubert commit d351da75066955144515cb2f9aa959f24a04287a upstream-diff | ring->fc used instead of ring->chan->conn fuse_uring_async_stop_queues() might run when the last reference on ring->queue_refs was already dropped. In order to avoid an early destruction a reference on struct fuse_conn is now taken before starting fuse_uring_async_stop_queues() and that reference is only released when that delayed work queue terminates. Fixes: 4a9bfb9b6850 ("fuse: {io-uring} Handle teardown of ring entries") Cc: stable@kernel.org # 6.14 Reported-by: Berkant Koc Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit d351da75066955144515cb2f9aa959f24a04287a) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 446c01601267e..56d33ba5b843f 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -421,6 +421,7 @@ static void fuse_uring_async_stop_queues(struct work_struct *work) FUSE_URING_TEARDOWN_INTERVAL); } else { wake_up_all(&ring->stop_waitq); + fuse_conn_put(ring->fc); } } @@ -441,6 +442,7 @@ void fuse_uring_stop_queues(struct fuse_ring *ring) } if (atomic_read(&ring->queue_refs) > 0) { + fuse_conn_get(ring->fc); ring->teardown_time = jiffies; INIT_DELAYED_WORK(&ring->async_teardown_work, fuse_uring_async_stop_queues); From f9fee86070bc39b6ded714e9f7183cb9211f5e4b Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 13 Aug 2026 15:08:13 -0400 Subject: [PATCH 51/98] fuse-uring: Avoid queue->stopped races and set/read that value under lock jira SECO-478 cve CVE-2026-64260 RFE: FUSE_IO_URING commit-author Bernd Schubert commit b70a3aca16934c196f92abb17b01c1647b9bb63c upstream-diff | ring->fc used instead of ring->chan->conn There are several readers of queue->stopped that check the value under lock, but fuse_uring_commit_fetch() did not and actually the value was not set under the lock in fuse_uring_abort_end_requests() either. Especially in fuse_uring_commit_fetch it is important to check under a lock, because due to races 'struct fuse_req' might be freed with fuse_request_end, but another thread/cpu might already do teardown work. Cc: stable@kernel.org # 6.14 Fixes: 4a9bfb9b6850fec ("fuse: {io-uring} Handle teardown of ring entries") Reported-by: Berkant Koc Reported-by: xlabai Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit b70a3aca16934c196f92abb17b01c1647b9bb63c) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 56d33ba5b843f..7f9b77ee3961a 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -129,10 +129,9 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring) if (!queue) continue; - queue->stopped = true; - WARN_ON_ONCE(ring->fc->max_background != UINT_MAX); spin_lock(&queue->lock); + queue->stopped = true; spin_lock(&fc->bg_lock); fuse_uring_flush_bg(queue); spin_unlock(&fc->bg_lock); @@ -862,10 +861,15 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, return err; fpq = &queue->fpq; - if (!READ_ONCE(fc->connected) || READ_ONCE(queue->stopped)) + if (!READ_ONCE(fc->connected)) return err; spin_lock(&queue->lock); + if (unlikely(queue->stopped)) { + spin_unlock(&queue->lock); + return err; + } + /* Find a request based on the unique ID of the fuse request * This should get revised, as it needs a hash calculation and list * search. And full struct fuse_pqueue is needed (memory overhead). From 24ebc840b3c53fc7f41387ae66ba3b88a728d970 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 8 Jun 2026 23:03:45 +0200 Subject: [PATCH 52/98] fuse-uring: make a fuse_req on SQE commit only findable after memcpy jira SECO-478 cve CVE-2026-64259 RFE: FUSE_IO_URING commit-author Bernd Schubert commit 1efd3d474fc0ba74dfd984249bca78807d739812 upstream-diff | list_move used instead of list_move_tail and io_uring_cmd_done has extra arg (older API in this kernel) Bad userspace might try to trick us and send commit SQEs request unique / commit-id of requests that are not even send to fuse-server (io_uring_cmd_done() not called) yet. fuse_uring_commit_fetch() ends the fuse request when the ring entry has a wrong state, but that could have caused a use-after-free with the memcpy operations in fuse_uring_send_in_task(). In order to avoid such races the call of fuse_uring_add_to_pq() is moved after the copy operations and just before completing the io-uring request - malicious userspace cannot find the request anymore until all prepration work in fuse-client/kernel is completed. This also moves fuse_uring_add_to_pq() a bit up in the code to avoid a forward declaration. Also not with a preparation commit, to make it easier to back port to older kernels. Reported-by: xlabai Reported-by: Berkant Koc Fixes: c090c8abae4b6b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@kernel.org # 6.14 Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 1efd3d474fc0ba74dfd984249bca78807d739812) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 7f9b77ee3961a..ce1fea0416856 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -677,6 +677,19 @@ static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, return err; } +/* Used to find the request on SQE commit */ +static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent) +{ + struct fuse_ring_queue *queue = ent->queue; + struct fuse_pqueue *fpq = &queue->fpq; + unsigned int hash; + struct fuse_req *req = ent->fuse_req; + + req->ring_entry = ent; + hash = fuse_req_hash(req->in.h.unique); + list_move_tail(&req->list, &fpq->processing[hash]); +} + /* * Write data to the ring buffer and send the request to userspace, * userspace will read it @@ -699,6 +712,7 @@ static int fuse_uring_send_next_to_ring(struct fuse_ring_ent *ent, ent->cmd = NULL; ent->state = FRRS_USERSPACE; list_move(&ent->list, &queue->ent_in_userspace); + fuse_uring_add_to_pq(ent); spin_unlock(&queue->lock); io_uring_cmd_done(cmd, 0, 0, issue_flags); @@ -716,19 +730,6 @@ static void fuse_uring_ent_avail(struct fuse_ring_ent *ent, ent->state = FRRS_AVAILABLE; } -/* Used to find the request on SQE commit */ -static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent, - struct fuse_req *req) -{ - struct fuse_ring_queue *queue = ent->queue; - struct fuse_pqueue *fpq = &queue->fpq; - unsigned int hash; - - req->ring_entry = ent; - hash = fuse_req_hash(req->in.h.unique); - list_move_tail(&req->list, &fpq->processing[hash]); -} - /* * Assign a fuse queue entry to the given entry */ @@ -746,10 +747,13 @@ static void fuse_uring_add_req_to_ring_ent(struct fuse_ring_ent *ent, } clear_bit(FR_PENDING, &req->flags); + + /* Until fuse_uring_add_to_pq() the req is not attached to any list */ + list_del_init(&req->list); + ent->fuse_req = req; ent->state = FRRS_FUSE_REQ; list_move(&ent->list, &queue->ent_w_req_queue); - fuse_uring_add_to_pq(ent, req); } /* Fetch the next fuse request if available */ @@ -1178,6 +1182,7 @@ static void fuse_uring_send(struct fuse_ring_ent *ent, struct io_uring_cmd *cmd, ent->state = FRRS_USERSPACE; list_move(&ent->list, &queue->ent_in_userspace); ent->cmd = NULL; + fuse_uring_add_to_pq(ent); spin_unlock(&queue->lock); io_uring_cmd_done(cmd, ret, 0, issue_flags); From 7dff1a13e0048ed7b68f39fda3fa553eec825763 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 13 Aug 2026 12:13:10 -0400 Subject: [PATCH 53/98] fuse-uring: remove request-less entries from ent_w_req_queue to fix NULL deref jira SECO-478 cve CVE-2026-64258 RFE: FUSE_IO_URING commit-author Joanne Koong commit 1c57a69be962d459c5e705f5cb4355b841b3461c If a copy into the userspace ring buffer fails, a request will be terminated and fuse_uring_req_end() will set ent->fuse_req to NULL but it will leave the entry on ent_w_req_queue in FRRS_FUSE_REQ state. This can lead to a NULL deref if the request expiration logic scans ent_w_req_queue in the window before the entry is moved off it. Fix this by taking the entry off ent_w_req_queue and changing its state from FRRS_FUSE_REQ to FRRS_INVALID before terminating the request. Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check") Cc: stable@kernel.org Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 1c57a69be962d459c5e705f5cb4355b841b3461c) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index ce1fea0416856..80c38e44d1ee8 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -669,10 +669,20 @@ static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, int err; err = fuse_uring_copy_to_ring(ent, req); - if (!err) + if (!err) { set_bit(FR_SENT, &req->flags); - else + } else { + /* + * Copying the request failed. Remove the entry from the + * ent_w_req_queue list and terminate the request + */ + spin_lock(&ent->queue->lock); + list_del_init(&ent->list); + ent->state = FRRS_INVALID; + spin_unlock(&ent->queue->lock); + fuse_uring_req_end(ent, req, err); + } return err; } From af1c671f6700c063e3305af080d6f464821b64f6 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Wed, 8 Apr 2026 10:25:10 -0700 Subject: [PATCH 54/98] fuse: fix io-uring background queue dispatch on request completion jira SECO-478 RFE: FUSE_IO_URING commit-author Joanne Koong commit 31da059891bd3be9c6e59280b8e1777ead90db34 upstream-diff | fuse_copy_init uses int write vs bool write in this kernel When a background request completes via the io_uring path, the background queue gets flushed to dispatch pending background requests, but this is done before the connection-level background counters (fc->num_background, fc->active_background) are properly accounted, which may reduce effective queue depth to one. The connection-level counters are decremented in fuse_request_end(), but flush_bg_queue() flushes the /dev/fuse path queue (fc->bg_queue), not the io_uring per-queue bg one, which means pending uring background requests on the queue are never dispatched in this path. Fix this by accounting the connection-level background counters first before flushing the queue's background queue. Since fuse_request_bg_finish() clears FR_BACKGROUND, fuse_request_end() will skip the background cleanup branch entirely, which avoids any double-decrements; it will call the wake_up(&req->waitq) branch but this is effectively a no-op as background requests have no waiters on req->waitq. Reviewed-by: Bernd Schubert Fixes: 857b0263f30e ("fuse: Allow to queue bg requests through io-uring") Cc: stable@vger.kernel.org Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 31da059891bd3be9c6e59280b8e1777ead90db34) Signed-off-by: Brett Mastbergen --- fs/fuse/dev.c | 41 ++++++++++++++++++++++++----------------- fs/fuse/dev_uring.c | 1 + fs/fuse/fuse_dev_i.h | 1 + 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 27bf3c3cdf6cd..46b6cca5360cb 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -334,6 +334,29 @@ static void flush_bg_queue(struct fuse_conn *fc) } } +void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req) +{ + lockdep_assert_held(&fc->bg_lock); + + clear_bit(FR_BACKGROUND, &req->flags); + if (fc->num_background == fc->max_background) { + fc->blocked = 0; + wake_up(&fc->blocked_waitq); + } else if (!fc->blocked) { + /* + * Wake up next waiter, if any. It's okay to use + * waitqueue_active(), as we've already synced up + * fc->blocked with waiters with the wake_up() call + * above. + */ + if (waitqueue_active(&fc->blocked_waitq)) + wake_up(&fc->blocked_waitq); + } + + fc->num_background--; + fc->active_background--; +} + /* * This function is called when a request is finished. Either a reply * has arrived or it was aborted (and not yet sent) or some error @@ -366,23 +389,7 @@ void fuse_request_end(struct fuse_req *req) WARN_ON(test_bit(FR_SENT, &req->flags)); if (test_bit(FR_BACKGROUND, &req->flags)) { spin_lock(&fc->bg_lock); - clear_bit(FR_BACKGROUND, &req->flags); - if (fc->num_background == fc->max_background) { - fc->blocked = 0; - wake_up(&fc->blocked_waitq); - } else if (!fc->blocked) { - /* - * Wake up next waiter, if any. It's okay to use - * waitqueue_active(), as we've already synced up - * fc->blocked with waiters with the wake_up() call - * above. - */ - if (waitqueue_active(&fc->blocked_waitq)) - wake_up(&fc->blocked_waitq); - } - - fc->num_background--; - fc->active_background--; + fuse_request_bg_finish(fc, req); flush_bg_queue(fc); spin_unlock(&fc->bg_lock); } else { diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 80c38e44d1ee8..311d73b48f12c 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -89,6 +89,7 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, if (test_bit(FR_BACKGROUND, &req->flags)) { queue->active_background--; spin_lock(&fc->bg_lock); + fuse_request_bg_finish(fc, req); fuse_uring_flush_bg(queue); spin_unlock(&fc->bg_lock); } diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index f3ce625351858..734606ade0acb 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -50,6 +50,7 @@ unsigned int fuse_req_hash(u64 unique); struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique); void fuse_dev_end_requests(struct list_head *head); +void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req); void fuse_copy_init(struct fuse_copy_state *cs, int write, struct iov_iter *iter); From 5eec5f03311d561a0613f6ab09cdb0926e7826c0 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 13 Aug 2026 12:17:06 -0400 Subject: [PATCH 55/98] fuse-uring: clear ent->fuse_req in commit_fetch error path jira SECO-478 RFE: FUSE_IO_URING commit-author Zhenghang Xiao commit 7d87a5a284bb34edb3f4e7e312ef403b3385a7b7 fuse_uring_commit_fetch() error path called fuse_request_end(req) without clearing ent->fuse_req when fuse_ring_ent_set_commit() fails. The still-pending fuse_uring_send_in_task() task-work later dereferences the dangling pointer through fuse_uring_prepare_send(), causing a use-after-free. End the request with fuse_uring_req_end(), which handles all conditions already. Annotation/edition by Bernd: The UAF should be fixed by other means already and actually has to be avoided that way. Just checking for ent->fuse_req == NULL in fuse_uring_send_in_task() would be prone to race conditions, because if malicious userspace would commit requests that have passed the NULL check, but are in doing args copy, it would still trigger a use-after-free. Setting ent->fuse_req = NULL in fuse_uring_commit_fetch() still makes sense, though. Reported-by: Shuvam Pandey Reported-by: Berkant Koc Signed-off-by: Zhenghang Xiao Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 7d87a5a284bb34edb3f4e7e312ef403b3385a7b7) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 311d73b48f12c..d195279f4e6cb 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -907,9 +907,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, pr_info_ratelimited("qid=%d commit_id %llu state %d", queue->qid, commit_id, ent->state); spin_unlock(&queue->lock); - req->out.h.error = err; - clear_bit(FR_SENT, &req->flags); - fuse_request_end(req); + fuse_uring_req_end(ent, req, err); return err; } From 212eb5c7c74bcc46c7e33d0df19760ba6ae8f700 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 3 Sep 2026 13:22:41 -0400 Subject: [PATCH 56/98] fuse: fix missing barrier when checking io-uring readiness jira SECO-478 RFE: FUSE_IO_URING cve CVE-2026-80859 commit-author Joanne Koong commit edb310bc27f0ad83e7fd558a3caf1a94ca511654 upstream-diff | fuse_conn/fc used instead of fuse_chan/fch in this kernel fuse_block_alloc() reads fch->initialized and then fch->io_uring. fch->io_uring is set before fch->initialized, ordered by the smp_wmb() in fuse_chan_set_intialized(), but fuse_block_alloc() has no matching read barrier between the two loads. This may lead a CPU to observe fch->initialized=1 but fch->io_uring=0, and skip the check that blocks request allocation until the io-uring queues are ready. This can reintroduce the lock-order inversion deadlock that commit 3393ff964e0f prevents. Add an smp_rmb() barrier to pair with the smp_wmb() in fuse_chan_set_initialized() to prevent this. Fixes: 3393ff964e0f ("fuse: block request allocation until io-uring init is complete") Cc: stable@vger.kernel.org Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit edb310bc27f0ad83e7fd558a3caf1a94ca511654) Signed-off-by: Brett Mastbergen --- fs/fuse/dev.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 46b6cca5360cb..41830ee9459d3 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -78,7 +78,13 @@ void fuse_set_initialized(struct fuse_conn *fc) static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) { - return !fc->initialized || (for_background && fc->blocked) || + if (!fc->initialized) + return true; + + /* Pairs with smp_wmb() in fuse_set_initialized() */ + smp_rmb(); + + return (for_background && fc->blocked) || (fc->io_uring && fc->connected && !fuse_uring_ready(fc)); } @@ -117,8 +123,6 @@ static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, !fuse_block_alloc(fc, for_background))) goto out; } - /* Matches smp_wmb() in fuse_set_initialized() */ - smp_rmb(); err = -ENOTCONN; if (!fc->connected) From 4b6ef9b53e50866afbb9a77d96692dd88d52e1e2 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 3 Sep 2026 13:25:29 -0400 Subject: [PATCH 57/98] fuse: publish io-uring queues with release semantics jira SECO-478 RFE: FUSE_IO_URING cve CVE-2026-80858 commit-author Joanne Koong commit 42df916e5a5f8fb4b60c8cefb54318d1ec02c580 upstream-diff | fc->lock used instead of fch->lock in this kernel fuse_uring_create_queue() initializes a fuse_ring_queue and then publishes the pointer into ring->queues[qid] with WRITE_ONCE() under the fch->lock. There are several readers that may concurrently be fetching that pointer locklessly and then deferencing it. WRITE_ONCE() doesn't ensure ordering of the queue's field initialization before the ring->queues[qid] pointer assignment. The queue must be published with smp_store_release() so the field initialization is guaranteed to happen before. Readers in paths where the read may happen concurrently with the store need to use READ_ONCE() because any race involving a plain access is undefined. Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands") Cc: stable@vger.kernel.org Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit 42df916e5a5f8fb4b60c8cefb54318d1ec02c580) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index d195279f4e6cb..a31f6da81c2fe 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -150,7 +150,7 @@ void fuse_uring_destruct(struct fuse_conn *fc) return; for (qid = 0; qid < ring->nr_queues; qid++) { - struct fuse_ring_queue *queue = ring->queues[qid]; + struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]); struct fuse_ring_ent *ent, *next; if (!queue) @@ -169,7 +169,7 @@ void fuse_uring_destruct(struct fuse_conn *fc) kfree(queue->fpq.processing); kfree(queue); - ring->queues[qid] = NULL; + WRITE_ONCE(ring->queues[qid], NULL); } kfree(ring->queues); @@ -267,9 +267,11 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, } /* - * write_once and lock as the caller mostly doesn't take the lock at all + * fc->lock serializes concurrent creators for this qid. + * smp_store_release() are for the lockless readers who must see a + * fully initialized queue after &ring->queues[qid] is set */ - WRITE_ONCE(ring->queues[qid], queue); + smp_store_release(&ring->queues[qid], queue); spin_unlock(&fc->lock); return queue; @@ -366,7 +368,7 @@ static void fuse_uring_log_ent_state(struct fuse_ring *ring) struct fuse_ring_ent *ent; for (qid = 0; qid < ring->nr_queues; qid++) { - struct fuse_ring_queue *queue = ring->queues[qid]; + struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]); if (!queue) continue; @@ -871,7 +873,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, if (qid >= ring->nr_queues) return -EINVAL; - queue = ring->queues[qid]; + queue = READ_ONCE(ring->queues[qid]); if (!queue) return err; fpq = &queue->fpq; @@ -938,7 +940,7 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid) if (current_qid == qid) continue; - queue = ring->queues[qid]; + queue = READ_ONCE(ring->queues[qid]); if (!queue) { ready = false; break; @@ -1092,7 +1094,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, return -EINVAL; } - queue = ring->queues[qid]; + queue = READ_ONCE(ring->queues[qid]); if (!queue) { queue = fuse_uring_create_queue(ring, qid); if (!queue) @@ -1244,7 +1246,7 @@ static struct fuse_ring_queue *fuse_uring_task_to_queue(struct fuse_ring *ring) ring->nr_queues)) qid = 0; - queue = ring->queues[qid]; + queue = READ_ONCE(ring->queues[qid]); WARN_ONCE(!queue, "Missing queue for qid %d\n", qid); return queue; From 8f11d66c60b6b14d32aaf5ccd61a1cf358167c14 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 3 Sep 2026 13:32:27 -0400 Subject: [PATCH 58/98] fuse: copy request headers via a stack buffer for io-uring jira SECO-478 RFE: FUSE_IO_URING cve CVE-2026-80946` commit-author Xiang Mei commit fd10f40af314f07b6d6e028b1ca25c8b49903aab upstream-diff | copy_{to,from}_user used directly instead of copy_header_{to,from}_ring helpers in this kernel; fc used instead of fch The fuse-io-uring transport copies req->in.h out to the ring in fuse_uring_copy_to_ring() and req->out.h back in fuse_uring_commit(). Both headers live inside the fuse_request slab object, whose cache (fuse_req_cachep) is created without a usercopy whitelist, so copying them directly to/from userspace trips CONFIG_HARDENED_USERCOPY and panics: usercopy: Kernel memory exposure attempt detected from SLUB object 'fuse_request' (offset 56, size 40)! kernel BUG at mm/usercopy.c:102! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI RIP: 0010:usercopy_abort (mm/usercopy.c:90) Call Trace: __check_heap_object (mm/slub.c:8268) __check_object_size (mm/usercopy.c:197 mm/usercopy.c:258 mm/usercopy.c:223) copy_header_to_ring (fs/fuse/dev_uring.c:618) fuse_uring_prepare_send (fs/fuse/dev_uring.c:776 fs/fuse/dev_uring.c:785) fuse_uring_send_in_task (fs/fuse/dev_uring.c:1306) tctx_task_work_run (io_uring/tw.c:96) task_work_run (kernel/task_work.c:233) io_run_task_work (io_uring/tw.h:84) io_cqring_wait (io_uring/wait.c:278) __do_sys_io_uring_enter (io_uring/io_uring.c:2685) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Bounce both headers through an on-stack copy so the usercopy touches stack memory, not the slab object. Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@vger.kernel.org Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Reviewed-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi (cherry picked from commit fd10f40af314f07b6d6e028b1ca25c8b49903aab) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index a31f6da81c2fe..11c90026d1b5d 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -635,6 +635,7 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, { struct fuse_ring_queue *queue = ent->queue; struct fuse_ring *ring = queue->ring; + struct fuse_in_header in_header; int err; err = -EIO; @@ -656,8 +657,9 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, } /* copy fuse_in_header */ - err = copy_to_user(&ent->headers->in_out, &req->in.h, - sizeof(req->in.h)); + in_header = req->in.h; + err = copy_to_user(&ent->headers->in_out, &in_header, + sizeof(in_header)); if (err) { err = -EFAULT; return err; @@ -797,11 +799,13 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, { struct fuse_ring *ring = ent->queue->ring; struct fuse_conn *fc = ring->fc; + struct fuse_out_header out_header; ssize_t err = -EFAULT; - if (copy_from_user(&req->out.h, &ent->headers->in_out, - sizeof(req->out.h))) + if (copy_from_user(&out_header, &ent->headers->in_out, + sizeof(out_header))) goto out; + req->out.h = out_header; err = fuse_uring_out_header_has_err(&req->out.h, req, fc); if (err) { From 4129d33daf1dc287f348c7f0c791cea231827815 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Thu, 3 Sep 2026 13:47:12 -0400 Subject: [PATCH 59/98] fuse: Fix the condition to enable over-io-uring jira SECO-478 RFE: FUSE_IO_URING cve CVE-2026-90095 commit-author Bernd Schubert commit 1f59015e958174e89be58cc8db16d70a60d17255 upstream-diff | fc/fc->aborted used instead of fch/fch->abort_with_err; fuse_set_initialized() used instead of fuse_chan_set_initialized() in this kernel The existing condition in fuse_uring_cmd() is there only to avoid disabling io-uring for connections that already run with it, missing was a condition to refuse any IORING_OP_URING_CMD if the connection/channel didn't get enabled because of missing FUSE_INIT reply flag FUSE_OVER_IO_URING. Without the reply flag the barrier in fuse_uring_ready() doesn't work and IO could already be going on and cause deadlock states (at a minimum one between fch->bg_lock and queue->lock). The change itself is trivial, but brings behavior change, FUSE_OVER_IO_URING has to be set in the FUSE_INIT_REPLY by fuse servers to accept any IORING_OP_URING_CMD. Libfuse does that and the only non-libfuse implementation I found (fractal-fuse) also does it. Qemu patches for fuse-io-uring are not merged yet, as far as I know. Moved up is the smp_load_acquire(&fch->initialized) check, as a fuse-server implementation might try to setup io-uring before FUSE_INIT is processed and might have gotten -EOPNOTSUPP instead of -EAGAIN. Also fixed is a stale comment that explains the handling of the FUSE_OVER_IO_URING flag in early RFC versions. If there should be a report from any library or application we probably need to revert this commit. Fixes: 3393ff964e0f ("fuse: block request allocation until io-uring init is complete") Signed-off-by: Bernd Schubert Signed-off-by: Miklos Szeredi (cherry picked from commit 1f59015e958174e89be58cc8db16d70a60d17255) Signed-off-by: Brett Mastbergen --- fs/fuse/dev_uring.c | 29 ++++++++++++++++++----------- fs/fuse/inode.c | 4 ---- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 11c90026d1b5d..d1774eeab4b79 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1144,23 +1144,30 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) } fc = fud->fc; - /* Once a connection has io-uring enabled on it, it can't be disabled */ - if (!enable_uring && !fc->io_uring) { - pr_info_ratelimited("fuse-io-uring is disabled\n"); - return -EOPNOTSUPP; - } + /* + * The ring is sized from values negotiated by FUSE_INIT + * + * Pairs with smp_wmb() in fuse_set_initialized() + */ + if (!smp_load_acquire(&fc->initialized)) + return -EAGAIN; if (fc->aborted) return -ECONNABORTED; if (!fc->connected) return -ENOTCONN; - /* - * fuse_uring_register() needs the ring to be initialized, - * we need to know the max payload size - */ - if (!fc->initialized) - return -EAGAIN; + /* Once a connection has io-uring enabled on it, it can't be disabled */ + if (!enable_uring && !fc->io_uring) { + pr_info_ratelimited("fuse-io-uring is disabled by module parameter\n"); + return -EOPNOTSUPP; + } + + if (!fc->io_uring) { + pr_info_ratelimited( + "fuse-io-uring not enabled on this connection\n"); + return -EOPNOTSUPP; + } switch (cmd_op) { case FUSE_IO_URING_CMD_REGISTER: diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 9a8b3bc214fd1..8df3a89453854 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1419,10 +1419,6 @@ void fuse_send_init(struct fuse_mount *fm) if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) flags |= FUSE_PASSTHROUGH; - /* - * This is just an information flag for fuse server. No need to check - * the reply - server is either sending IORING_OP_URING_CMD or not. - */ if (fuse_uring_enabled()) flags |= FUSE_OVER_IO_URING; From 9b1d61fb4c6f97d32500fe7fd3aa1c5c76150afc Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Mon, 10 Aug 2026 17:47:41 -0400 Subject: [PATCH 60/98] configs: enable CONFIG_FUSE_IO_URING=y on x86_64 and aarch64 jira SECO-478 RFE: FUSE_IO_URING Enable FUSE io-uring support in x86_64 and aarch64 configs that have both prerequisites (CONFIG_FUSE_FS=m and CONFIG_IO_URING=y), across all variants (debug, rt, 64k). Skipped configs lacking prerequisites: - kernel-riscv64-*.config (no CONFIG_FUSE_FS or CONFIG_IO_URING) - kernel-s390x-zfcpdump-rhel.config (CONFIG_FUSE_FS is not set) The feature is gated at runtime by the enable_uring module parameter which defaults to disabled. Signed-off-by: Brett Mastbergen --- configs/kernel-aarch64-64k-debug-rhel.config | 1 + configs/kernel-aarch64-64k-rhel.config | 1 + configs/kernel-aarch64-debug-rhel.config | 1 + configs/kernel-aarch64-rhel.config | 1 + configs/kernel-aarch64-rt-64k-debug-rhel.config | 1 + configs/kernel-aarch64-rt-64k-rhel.config | 1 + configs/kernel-aarch64-rt-debug-rhel.config | 1 + configs/kernel-aarch64-rt-rhel.config | 1 + configs/kernel-x86_64-debug-rhel.config | 1 + configs/kernel-x86_64-rhel.config | 1 + configs/kernel-x86_64-rt-debug-rhel.config | 1 + configs/kernel-x86_64-rt-rhel.config | 1 + 12 files changed, 12 insertions(+) diff --git a/configs/kernel-aarch64-64k-debug-rhel.config b/configs/kernel-aarch64-64k-debug-rhel.config index 8c954151d3f05..f3faa3d7dd1b8 100644 --- a/configs/kernel-aarch64-64k-debug-rhel.config +++ b/configs/kernel-aarch64-64k-debug-rhel.config @@ -2300,6 +2300,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-aarch64-64k-rhel.config b/configs/kernel-aarch64-64k-rhel.config index d7184d179d8a2..5f470f09028db 100644 --- a/configs/kernel-aarch64-64k-rhel.config +++ b/configs/kernel-aarch64-64k-rhel.config @@ -2284,6 +2284,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-aarch64-debug-rhel.config b/configs/kernel-aarch64-debug-rhel.config index c728fb0f15ace..64f4b0a27a691 100644 --- a/configs/kernel-aarch64-debug-rhel.config +++ b/configs/kernel-aarch64-debug-rhel.config @@ -2297,6 +2297,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-aarch64-rhel.config b/configs/kernel-aarch64-rhel.config index 7a7f317b61f37..5e99da15a0831 100644 --- a/configs/kernel-aarch64-rhel.config +++ b/configs/kernel-aarch64-rhel.config @@ -2281,6 +2281,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-aarch64-rt-64k-debug-rhel.config b/configs/kernel-aarch64-rt-64k-debug-rhel.config index fe4a1928f05bd..b9dad62152aed 100644 --- a/configs/kernel-aarch64-rt-64k-debug-rhel.config +++ b/configs/kernel-aarch64-rt-64k-debug-rhel.config @@ -2339,6 +2339,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-aarch64-rt-64k-rhel.config b/configs/kernel-aarch64-rt-64k-rhel.config index c15f4ee5a01c6..358794debfae8 100644 --- a/configs/kernel-aarch64-rt-64k-rhel.config +++ b/configs/kernel-aarch64-rt-64k-rhel.config @@ -2323,6 +2323,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-aarch64-rt-debug-rhel.config b/configs/kernel-aarch64-rt-debug-rhel.config index 2bb35d529aafa..7a0d5c21e335b 100644 --- a/configs/kernel-aarch64-rt-debug-rhel.config +++ b/configs/kernel-aarch64-rt-debug-rhel.config @@ -2336,6 +2336,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-aarch64-rt-rhel.config b/configs/kernel-aarch64-rt-rhel.config index 39be57f9d2ea8..9689925d7f700 100644 --- a/configs/kernel-aarch64-rt-rhel.config +++ b/configs/kernel-aarch64-rt-rhel.config @@ -2320,6 +2320,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-x86_64-debug-rhel.config b/configs/kernel-x86_64-debug-rhel.config index 2985ba5f55986..9acaed39d2317 100644 --- a/configs/kernel-x86_64-debug-rhel.config +++ b/configs/kernel-x86_64-debug-rhel.config @@ -2167,6 +2167,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-x86_64-rhel.config b/configs/kernel-x86_64-rhel.config index b390068c4142a..9338ba7f8b268 100644 --- a/configs/kernel-x86_64-rhel.config +++ b/configs/kernel-x86_64-rhel.config @@ -2151,6 +2151,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-x86_64-rt-debug-rhel.config b/configs/kernel-x86_64-rt-debug-rhel.config index 8b1a48730fb57..b2333db6a99f7 100644 --- a/configs/kernel-x86_64-rt-debug-rhel.config +++ b/configs/kernel-x86_64-rt-debug-rhel.config @@ -2206,6 +2206,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set diff --git a/configs/kernel-x86_64-rt-rhel.config b/configs/kernel-x86_64-rt-rhel.config index 6823de63e269a..ef2e800124764 100644 --- a/configs/kernel-x86_64-rt-rhel.config +++ b/configs/kernel-x86_64-rt-rhel.config @@ -2190,6 +2190,7 @@ CONFIG_FUNCTION_TRACER=y # CONFIG_FUN_ETH is not set CONFIG_FUSE_DAX=y CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y CONFIG_FUSE_PASSTHROUGH=y # CONFIG_FUSION_CTL is not set # CONFIG_FUSION_FC is not set From e015641ecdf28f5c501d8b897b77ede6687b9ea2 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Wed, 12 Aug 2026 11:32:53 -0400 Subject: [PATCH 61/98] selftests: filesystems: Add functional test for the abort file in fusectl jira SECO-478 RFE: FUSE_IO_URING commit-author Chen Linxuan commit 1a7b13781b0d48312e0ead2585776b0afd1343cd This patch add a simple functional test for the 'abort' file in fusectlfs (/sys/fs/fuse/connections/ID/abort). A simple fuse daemon is added for testing. Signed-off-by: Chen Linxuan Acked-by: Shuah Khan Reviewed-by: Amir Goldstein Signed-off-by: Miklos Szeredi (cherry picked from commit 1a7b13781b0d48312e0ead2585776b0afd1343cd) Signed-off-by: Brett Mastbergen --- MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + .../selftests/filesystems/fuse/.gitignore | 3 + .../selftests/filesystems/fuse/Makefile | 21 +++ .../selftests/filesystems/fuse/fuse_mnt.c | 146 ++++++++++++++++++ .../selftests/filesystems/fuse/fusectl_test.c | 140 +++++++++++++++++ 6 files changed, 312 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fuse/.gitignore create mode 100644 tools/testing/selftests/filesystems/fuse/Makefile create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_mnt.c create mode 100644 tools/testing/selftests/filesystems/fuse/fusectl_test.c diff --git a/MAINTAINERS b/MAINTAINERS index 9523da3c767cb..f297f7cc2936b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9443,6 +9443,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git F: Documentation/filesystems/fuse.rst F: fs/fuse/ F: include/uapi/linux/fuse.h +F: tools/testing/selftests/filesystems/fuse/ FUTEX SUBSYSTEM M: Thomas Gleixner diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 2207e51e4d079..d48ab0b95dec9 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -32,6 +32,7 @@ TARGETS += filesystems TARGETS += filesystems/binderfs TARGETS += filesystems/epoll TARGETS += filesystems/fat +TARGETS += filesystems/fuse TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += firmware diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore new file mode 100644 index 0000000000000..3e72e742d08e8 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +fuse_mnt +fusectl_test diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile new file mode 100644 index 0000000000000..612aad69a93aa --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +TEST_GEN_PROGS := fusectl_test +TEST_GEN_FILES := fuse_mnt + +include ../../lib.mk + +VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null) +ifeq ($(VAR_CFLAGS),) +VAR_CFLAGS := -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse +endif + +VAR_LDLIBS := $(shell pkg-config fuse --libs 2>/dev/null) +ifeq ($(VAR_LDLIBS),) +VAR_LDLIBS := -lfuse -pthread +endif + +$(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS) +$(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_mnt.c b/tools/testing/selftests/filesystems/fuse/fuse_mnt.c new file mode 100644 index 0000000000000..d12b17f30fadc --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_mnt.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fusectl test file-system + * Creates a simple FUSE filesystem with a single read-write file (/test) + */ + +#define FUSE_USE_VERSION 26 + +#include +#include +#include +#include +#include +#include +#include + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +static char *content; +static size_t content_size = 0; +static const char test_path[] = "/test"; + +static int test_getattr(const char *path, struct stat *st) +{ + memset(st, 0, sizeof(*st)); + + if (!strcmp(path, "/")) { + st->st_mode = S_IFDIR | 0755; + st->st_nlink = 2; + return 0; + } + + if (!strcmp(path, test_path)) { + st->st_mode = S_IFREG | 0664; + st->st_nlink = 1; + st->st_size = content_size; + return 0; + } + + return -ENOENT; +} + +static int test_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi) +{ + if (strcmp(path, "/")) + return -ENOENT; + + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + filler(buf, test_path + 1, NULL, 0); + + return 0; +} + +static int test_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path, test_path)) + return -ENOENT; + + return 0; +} + +static int test_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + if (strcmp(path, test_path) != 0) + return -ENOENT; + + if (!content || content_size == 0) + return 0; + + if (offset >= content_size) + return 0; + + if (offset + size > content_size) + size = content_size - offset; + + memcpy(buf, content + offset, size); + + return size; +} + +static int test_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + size_t new_size; + + if (strcmp(path, test_path) != 0) + return -ENOENT; + + if(offset > content_size) + return -EINVAL; + + new_size = MAX(offset + size, content_size); + + if (new_size > content_size) + content = realloc(content, new_size); + + content_size = new_size; + + if (!content) + return -ENOMEM; + + memcpy(content + offset, buf, size); + + return size; +} + +static int test_truncate(const char *path, off_t size) +{ + if (strcmp(path, test_path) != 0) + return -ENOENT; + + if (size == 0) { + free(content); + content = NULL; + content_size = 0; + return 0; + } + + content = realloc(content, size); + + if (!content) + return -ENOMEM; + + if (size > content_size) + memset(content + content_size, 0, size - content_size); + + content_size = size; + return 0; +} + +static struct fuse_operations memfd_ops = { + .getattr = test_getattr, + .readdir = test_readdir, + .open = test_open, + .read = test_read, + .write = test_write, + .truncate = test_truncate, +}; + +int main(int argc, char *argv[]) +{ + return fuse_main(argc, argv, &memfd_ops, NULL); +} diff --git a/tools/testing/selftests/filesystems/fuse/fusectl_test.c b/tools/testing/selftests/filesystems/fuse/fusectl_test.c new file mode 100644 index 0000000000000..8d124d1cacb26 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fusectl_test.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2025 Chen Linxuan + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define FUSECTL_MOUNTPOINT "/sys/fs/fuse/connections" +#define FUSE_MOUNTPOINT "/tmp/fuse_mnt_XXXXXX" +#define FUSE_DEVICE "/dev/fuse" +#define FUSECTL_TEST_VALUE "1" + +static void write_file(struct __test_metadata *const _metadata, + const char *path, const char *val) +{ + int fd = open(path, O_WRONLY); + size_t len = strlen(val); + + ASSERT_GE(fd, 0); + ASSERT_EQ(write(fd, val, len), len); + ASSERT_EQ(close(fd), 0); +} + +FIXTURE(fusectl){ + char fuse_mountpoint[sizeof(FUSE_MOUNTPOINT)]; + int connection; +}; + +FIXTURE_SETUP(fusectl) +{ + const char *fuse_mnt_prog = "./fuse_mnt"; + int status, pid; + struct stat statbuf; + uid_t uid = getuid(); + gid_t gid = getgid(); + char buf[32]; + + /* Setup userns */ + ASSERT_EQ(unshare(CLONE_NEWNS|CLONE_NEWUSER), 0); + sprintf(buf, "0 %d 1", uid); + write_file(_metadata, "/proc/self/uid_map", buf); + write_file(_metadata, "/proc/self/setgroups", "deny"); + sprintf(buf, "0 %d 1", gid); + write_file(_metadata, "/proc/self/gid_map", buf); + ASSERT_EQ(mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL), 0); + + strcpy(self->fuse_mountpoint, FUSE_MOUNTPOINT); + + if (!mkdtemp(self->fuse_mountpoint)) + SKIP(return, + "Failed to create FUSE mountpoint %s", + strerror(errno)); + + if (access(FUSECTL_MOUNTPOINT, F_OK)) + SKIP(return, + "FUSE control filesystem not mounted"); + + pid = fork(); + if (pid < 0) + SKIP(return, + "Failed to fork FUSE daemon process: %s", + strerror(errno)); + + if (pid == 0) { + execlp(fuse_mnt_prog, fuse_mnt_prog, self->fuse_mountpoint, NULL); + exit(errno); + } + + waitpid(pid, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + SKIP(return, + "Failed to start FUSE daemon %s", + strerror(WEXITSTATUS(status))); + } + + if (stat(self->fuse_mountpoint, &statbuf)) + SKIP(return, + "Failed to stat FUSE mountpoint %s", + strerror(errno)); + + self->connection = statbuf.st_dev; +} + +FIXTURE_TEARDOWN(fusectl) +{ + umount2(self->fuse_mountpoint, MNT_DETACH); + rmdir(self->fuse_mountpoint); +} + +TEST_F(fusectl, abort) +{ + char path_buf[PATH_MAX]; + int abort_fd, test_fd, ret; + + sprintf(path_buf, "/sys/fs/fuse/connections/%d/abort", self->connection); + + ASSERT_EQ(0, access(path_buf, F_OK)); + + abort_fd = open(path_buf, O_WRONLY); + ASSERT_GE(abort_fd, 0); + + sprintf(path_buf, "%s/test", self->fuse_mountpoint); + + test_fd = open(path_buf, O_RDWR); + ASSERT_GE(test_fd, 0); + + ret = read(test_fd, path_buf, sizeof(path_buf)); + ASSERT_EQ(ret, 0); + + ret = write(test_fd, "test", sizeof("test")); + ASSERT_EQ(ret, sizeof("test")); + + ret = lseek(test_fd, 0, SEEK_SET); + ASSERT_GE(ret, 0); + + ret = write(abort_fd, FUSECTL_TEST_VALUE, sizeof(FUSECTL_TEST_VALUE)); + ASSERT_GT(ret, 0); + + close(abort_fd); + + ret = read(test_fd, path_buf, sizeof(path_buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENOTCONN); +} + +TEST_HARNESS_MAIN From e99f156bff9dfb9244a7c442fa9aac25d0ea211f Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Wed, 12 Aug 2026 13:26:22 -0400 Subject: [PATCH 62/98] selftests/fuse: adapt fuse_mnt to libfuse3 API jira SECO-478 RFE: FUSE_IO_URING On systems with only libfuse3-devel installed (no libfuse2-devel), fuse_mnt.c fails to compile because fuse3 requires API version 30+. Update fuse_mnt.c from FUSE API version 26 to 31: - getattr, truncate: add struct fuse_file_info * parameter - readdir: add enum fuse_readdir_flags parameter - filler calls: add flags argument Also update the Makefile to try pkg-config fuse3 as a fallback when pkg-config fuse is not available. Signed-off-by: Brett Mastbergen --- .../selftests/filesystems/fuse/Makefile | 7 +++++++ .../selftests/filesystems/fuse/fuse_mnt.c | 21 ++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile index 612aad69a93aa..b63f50f656a66 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -9,13 +9,20 @@ include ../../lib.mk VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null) ifeq ($(VAR_CFLAGS),) +# libfuse2-devel not found; try fuse3 which provides v2 compat headers +VAR_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null) +ifeq ($(VAR_CFLAGS),) VAR_CFLAGS := -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse endif +endif VAR_LDLIBS := $(shell pkg-config fuse --libs 2>/dev/null) ifeq ($(VAR_LDLIBS),) +VAR_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null) +ifeq ($(VAR_LDLIBS),) VAR_LDLIBS := -lfuse -pthread endif +endif $(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS) $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_mnt.c b/tools/testing/selftests/filesystems/fuse/fuse_mnt.c index d12b17f30fadc..44a2b5e0fd1dd 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_mnt.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_mnt.c @@ -4,7 +4,7 @@ * Creates a simple FUSE filesystem with a single read-write file (/test) */ -#define FUSE_USE_VERSION 26 +#define FUSE_USE_VERSION 31 #include #include @@ -20,8 +20,10 @@ static char *content; static size_t content_size = 0; static const char test_path[] = "/test"; -static int test_getattr(const char *path, struct stat *st) +static int test_getattr(const char *path, struct stat *st, + struct fuse_file_info *fi) { + (void)fi; memset(st, 0, sizeof(*st)); if (!strcmp(path, "/")) { @@ -41,14 +43,17 @@ static int test_getattr(const char *path, struct stat *st) } static int test_readdir(const char *path, void *buf, fuse_fill_dir_t filler, - off_t offset, struct fuse_file_info *fi) + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) { + (void)flags; + if (strcmp(path, "/")) return -ENOENT; - filler(buf, ".", NULL, 0); - filler(buf, "..", NULL, 0); - filler(buf, test_path + 1, NULL, 0); + filler(buf, ".", NULL, 0, 0); + filler(buf, "..", NULL, 0, 0); + filler(buf, test_path + 1, NULL, 0, 0); return 0; } @@ -107,8 +112,10 @@ static int test_write(const char *path, const char *buf, size_t size, return size; } -static int test_truncate(const char *path, off_t size) +static int test_truncate(const char *path, off_t size, + struct fuse_file_info *fi) { + (void)fi; if (strcmp(path, test_path) != 0) return -ENOENT; From 98830cc0771f197a3f118ee24fe1d2085e632386 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Wed, 12 Aug 2026 11:44:20 -0400 Subject: [PATCH 63/98] selftests/fuse: add ACL_DONT_CACHE regression test jira SECO-478 RFE: FUSE_IO_URING commit-author Amir Goldstein commit 9acb102522b92f24fba6b238b3668a4d9dfcb592 upstream-diff | kselftest_harness.h include uses relative path (../../kselftest_harness.h) because commit e6fbd1759c9e ("selftests: complete kselftest include centralization") is not present in this tree. A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel, __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED, inadvertently enabling the kernel ACL cache for that inode. This test validates the fix that preserves ACL_DONT_CACHE state in forget_cached_acl(). Signed-off-by: Amir Goldstein Signed-off-by: Christian Brauner (cherry picked from commit 9acb102522b92f24fba6b238b3668a4d9dfcb592) Signed-off-by: Brett Mastbergen --- .../selftests/filesystems/fuse/Makefile | 10 + .../filesystems/fuse/fuse_acl_cache_test.c | 347 ++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile index b63f50f656a66..0d0af19e2d059 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -5,6 +5,13 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) TEST_GEN_PROGS := fusectl_test TEST_GEN_FILES := fuse_mnt +# fuse_acl_cache_test requires libfuse3; add it only when the library is present. +ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null) +ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null) +ifneq ($(ACL_CFLAGS),) +TEST_GEN_PROGS += fuse_acl_cache_test +endif + include ../../lib.mk VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null) @@ -26,3 +33,6 @@ endif $(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS) $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS) + +$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS) +$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c new file mode 100644 index 0000000000000..5d8f28271b1ac --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC + * + * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode + * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed + * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls + * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel, + * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED, + * inadvertently enabling the kernel ACL cache for that inode. The next + * getxattr populates the cache. Because fuse_set_acl() skips + * forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to + * the ACL leaves the stale kernel entry in place, and the next getxattr returns + * wrong data without ever reaching the FUSE daemon. + * + * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is + * ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's + * lifetime. + * + * Test outline: + * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated). + * 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE). + * 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called. + * Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled). + * Fixed: ACL_DONT_CACHE preserved. + * 4. lgetxattr -> daemon called, ACL_A returned. + * Buggy: result now cached (ACL_NOT_CACHED -> cached). + * Fixed: result still not cached. + * 5. Daemon switches to ACL_B internally (different size). + * 6. lgetxattr -> should return ACL_B (44 bytes). + * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL. + * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_USE_VERSION 31 +#include + +#include "../../kselftest_harness.h" + +/* ---- ACL binary encoding ------------------------------------------------ */ +/* + * POSIX ACL v2 xattr format (little-endian): + * header: u32 version (= 0x00000002) + * entry: u16 tag | u16 perm | u32 id + * + * Entries must appear in tag-ascending order; named USER/GROUP entries + * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation. + */ + +/* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */ +static const uint8_t acl_a[] = { + 0x02, 0x00, 0x00, 0x00, /* v2 header */ + 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ + 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ + 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ +}; + +/* + * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes. + * A named USER entry requires a MASK; all tags in ascending order. + */ +static const uint8_t acl_b[] = { + 0x02, 0x00, 0x00, 0x00, /* v2 header */ + 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ + 0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */ + 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ + 0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */ + 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ +}; + +/* ---- Shared state (daemon thread <-> test thread) ----------------------- */ + +#define FILE_INO 2 +#define FILE_NAME "testfile" + +struct daemon_state { + pthread_mutex_t lock; + const uint8_t *acl; + size_t acl_size; + int getxattr_count; +}; + +/* + * Global: callbacks are stateless fns so we use a single global. + * Safe because only one test instance runs at a time. + */ +static struct daemon_state g_ds = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +/* ---- FUSE lowlevel callbacks -------------------------------------------- */ + +static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) { + fuse_reply_err(req, ENOENT); + return; + } + struct fuse_entry_param e = {}; + + /* + * Long attr/entry timeouts so that normal stat() calls do not + * expire and trigger forget_all_cached_acls() on their own; + * only the explicit AT_STATX_FORCE_SYNC should trigger it. + */ + e.ino = FILE_INO; + e.generation = 1; + e.attr_timeout = 10.0; + e.entry_timeout = 10.0; + e.attr.st_ino = FILE_INO; + e.attr.st_mode = S_IFREG | 0644; + e.attr.st_nlink = 1; + fuse_reply_entry(req, &e); +} + +static void fs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat st = {}; + + (void)fi; + if (ino == FUSE_ROOT_ID) { + st.st_ino = FUSE_ROOT_ID; + st.st_mode = S_IFDIR | 0755; + st.st_nlink = 2; + } else if (ino == FILE_INO) { + st.st_ino = FILE_INO; + st.st_mode = S_IFREG | 0644; + st.st_nlink = 1; + } else { + fuse_reply_err(req, ENOENT); + return; + } + fuse_reply_attr(req, &st, 10); +} + +static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + if (ino != FILE_INO || + strcmp(name, "system.posix_acl_access") != 0) { + fuse_reply_err(req, ENODATA); + return; + } + + pthread_mutex_lock(&g_ds.lock); + const uint8_t *acl = g_ds.acl; + size_t acl_size = g_ds.acl_size; + g_ds.getxattr_count++; + pthread_mutex_unlock(&g_ds.lock); + + if (size == 0) + fuse_reply_xattr(req, acl_size); + else if (size < acl_size) + fuse_reply_err(req, ERANGE); + else + fuse_reply_buf(req, (const char *)acl, acl_size); +} + +static const struct fuse_lowlevel_ops fs_ops = { + .lookup = fs_lookup, + .getattr = fs_getattr, + .getxattr = fs_getxattr, +}; + +/* ---- Daemon thread ------------------------------------------------------- */ + +static void *run_daemon(void *arg) +{ + fuse_session_loop((struct fuse_session *)arg); + return NULL; +} + +/* ---- kselftest harness --------------------------------------------------- */ + +FIXTURE(acl_cache) { + struct fuse_session *se; + char mountpoint[PATH_MAX]; + char file_path[PATH_MAX]; + pthread_t thread; +}; + +FIXTURE_SETUP(acl_cache) +{ + char *fuse_argv[] = { "fuse_acl_cache_test", NULL }; + struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv); + + g_ds.acl = acl_a; + g_ds.acl_size = sizeof(acl_a); + g_ds.getxattr_count = 0; + + strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX"); + if (!mkdtemp(self->mountpoint)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + snprintf(self->file_path, sizeof(self->file_path), + "%s/" FILE_NAME, self->mountpoint); + + self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL); + if (!self->se) { + rmdir(self->mountpoint); + SKIP(return, "fuse_session_new failed"); + } + + if (fuse_session_mount(self->se, self->mountpoint)) { + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "fuse_session_mount failed " + "(missing fusermount3 or insufficient privileges)"); + } + + if (pthread_create(&self->thread, NULL, run_daemon, self->se)) { + fuse_session_unmount(self->se); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "pthread_create: %s", strerror(errno)); + } + + fuse_opt_free_args(&args); +} + +FIXTURE_TEARDOWN(acl_cache) +{ + fuse_session_exit(self->se); + fuse_session_unmount(self->se); + pthread_join(self->thread, NULL); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); +} + +static int do_force_statx(const char *path) +{ + struct statx stx; + + return statx(AT_FDCWD, path, AT_STATX_FORCE_SYNC, STATX_BASIC_STATS, + &stx); +} + +TEST_F(acl_cache, stale_after_force_sync) +{ + char buf[512]; + ssize_t sz; + int count; + + /* + * Step 1: two getxattr calls before any statx(FORCE_SYNC). + * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED, + * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel + * is never placed and the result is never cached. Both calls must + * reach the daemon, proving ACL_DONT_CACHE suppresses caching. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + ASSERT_EQ(count, 2); + TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), " + "ACL_DONT_CACHE is working", count); + + /* + * Step 2: statx(AT_STATX_FORCE_SYNC). + * fuse_update_get_attr() calls forget_all_cached_acls() before sending + * FUSE_GETATTR. + * Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled) + * Fixed kernel: ACL_DONT_CACHE preserved (no effect) + */ + ASSERT_EQ(do_force_statx(self->file_path), 0); + TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded"); + + /* + * Step 3: getxattr — cache population attempt after the trigger. + * Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl + * called -> ACL_A parsed and stored in the kernel cache. + * Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped -> + * fuse_get_inode_acl called but result not cached. + * Either way the correct ACL_A is returned here. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + ASSERT_EQ(count, 3); + TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), " + "returned correct ACL_A (%zd bytes)", count, sz); + + /* + * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes). + * Simulates an ACL change that fuse_set_acl() would NOT invalidate for + * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case). + * On a fixed kernel the ACL was never cached, so this is moot. + */ + pthread_mutex_lock(&g_ds.lock); + g_ds.acl = acl_b; + g_ds.acl_size = sizeof(acl_b); + pthread_mutex_unlock(&g_ds.lock); + TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b)); + + /* + * Step 5: getxattr — the decisive check. + * Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3. + * Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + if (sz == (ssize_t)sizeof(acl_a)) + TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache " + "(count=%d); ACL_DONT_CACHE corrupted by " + "forget_all_cached_acls()", sz, count); + else + TH_LOG("step 5 OK: daemon reached (count=%d), " + "fresh ACL_B (%zd bytes)", count, sz); + + EXPECT_EQ(sz, (ssize_t)sizeof(acl_b)); + EXPECT_EQ(count, 4); +} + +TEST_HARNESS_MAIN From de9b6345ea4eb22b31555e0bace77256cce3c2ea Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Wed, 12 Aug 2026 11:46:53 -0400 Subject: [PATCH 64/98] selftests/fuse: add FUSE io-uring integration test jira SECO-478 RFE: FUSE_IO_URING Add a kselftest for the FUSE io-uring request dispatch path. A minimal in-memory FUSE daemon runs in a thread, negotiates FUSE_OVER_IO_URING during FUSE_INIT, and handles requests through FUSE_IO_URING_CMD_REGISTER / FUSE_IO_URING_CMD_COMMIT_AND_FETCH. An atomic counter verifies requests are served through io_uring. Test cases: - read_file: read and verify file content - write_and_readback: write data, read it back, compare - readdir: list directory, verify entries - stat_files: stat root and file, verify attributes - concurrent_io: 4 threads doing interleaved reads and writes - requests_via_uring: per-operation verification that read, write, readdir, and stat each produce io_uring requests - sustained_io: write 256KB in 4KB chunks, read back, verify pattern - crash_recovery: fork daemon, SIGKILL with active I/O, verify kernel health - abort_before_ring_ready: negotiate io_uring but never register entries, abort connection, verify no deadlock or leak Tests SKIP when prerequisites are not met (no root, io_uring disabled, enable_uring != Y). Signed-off-by: Brett Mastbergen --- .../selftests/filesystems/fuse/.gitignore | 1 + .../selftests/filesystems/fuse/Makefile | 12 + .../testing/selftests/filesystems/fuse/config | 3 + .../filesystems/fuse/fuse_uring_test.c | 1442 +++++++++++++++++ 4 files changed, 1458 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fuse/config create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_uring_test.c diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore index 3e72e742d08e8..323aa31624df3 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only fuse_mnt +fuse_uring_test fusectl_test diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile index 0d0af19e2d059..ca4f296c2555d 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -5,6 +5,15 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) TEST_GEN_PROGS := fusectl_test TEST_GEN_FILES := fuse_mnt +# fuse_uring_test requires liburing; add it only when the library is present. +URING_CFLAGS := $(shell pkg-config liburing --cflags 2>/dev/null) +URING_LDLIBS := $(shell pkg-config liburing --libs 2>/dev/null) +ifeq ($(URING_LDLIBS),) +$(warning liburing not found; fuse_uring_test will be skipped) +else +TEST_GEN_PROGS += fuse_uring_test +endif + # fuse_acl_cache_test requires libfuse3; add it only when the library is present. ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null) ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null) @@ -36,3 +45,6 @@ $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS) $(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS) $(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS) + +$(OUTPUT)/fuse_uring_test: CFLAGS += $(URING_CFLAGS) -Wextra -Wno-unused-parameter -pthread +$(OUTPUT)/fuse_uring_test: LDLIBS += $(URING_LDLIBS) -lpthread diff --git a/tools/testing/selftests/filesystems/fuse/config b/tools/testing/selftests/filesystems/fuse/config new file mode 100644 index 0000000000000..c4b20b2c2d037 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/config @@ -0,0 +1,3 @@ +CONFIG_FUSE_FS=m +CONFIG_FUSE_IO_URING=y +CONFIG_IO_URING=y diff --git a/tools/testing/selftests/filesystems/fuse/fuse_uring_test.c b/tools/testing/selftests/filesystems/fuse/fuse_uring_test.c new file mode 100644 index 0000000000000..d733d80b63a4a --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_uring_test.c @@ -0,0 +1,1442 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE io-uring selftest + * + * Validates FUSE request dispatch over io_uring by running a minimal + * in-memory filesystem daemon in a thread and exercising it from the + * test thread. The daemon negotiates FUSE_OVER_IO_URING during + * FUSE_INIT, registers ring entries via FUSE_IO_URING_CMD_REGISTER, + * and handles requests through FUSE_IO_URING_CMD_COMMIT_AND_FETCH. + * + * An atomic counter tracks requests served through io_uring so tests + * can verify the kernel is actually using the io_uring path. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* linux/fuse.h and liburing.h after glibc headers to avoid redefinition */ +#include +#include + +#include "../../kselftest_harness.h" + +/* + * FUSE io-uring UAPI definitions. Remove this block once linux/fuse.h + * includes the io-uring structures (added upstream in v6.14). + */ + +#ifndef FUSE_OVER_IO_URING +#define FUSE_OVER_IO_URING (1ULL << 41) +#endif + +#ifndef FUSE_URING_IN_OUT_HEADER_SZ +#define FUSE_URING_IN_OUT_HEADER_SZ 128 +#define FUSE_URING_OP_IN_OUT_SZ 128 + +struct fuse_uring_ent_in_out { + uint64_t flags; + uint64_t commit_id; + uint32_t payload_sz; + uint32_t padding; + uint64_t reserved; +}; + +struct fuse_uring_req_header { + char in_out[FUSE_URING_IN_OUT_HEADER_SZ]; + char op_in[FUSE_URING_OP_IN_OUT_SZ]; + struct fuse_uring_ent_in_out ring_ent_in_out; +}; + +enum fuse_uring_cmd { + FUSE_IO_URING_CMD_REGISTER = 1, + FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2, +}; + +struct fuse_uring_cmd_req { + uint64_t flags; + uint64_t commit_id; + uint16_t qid; + uint8_t padding[6]; +}; +#endif /* FUSE_URING_IN_OUT_HEADER_SZ */ + +/* ---- constants --------------------------------------------------------- */ + +#define HELLO_CONTENT "Hello from FUSE io-uring!\n" +#define HELLO_LEN (sizeof(HELLO_CONTENT) - 1) +#define TESTFILE_MAX (256 * 1024) +#define INODE_ROOT 1 +#define INODE_HELLO 2 +#define INODE_TESTFILE 3 +#define DIRENT_NAME_OFF 24 /* offsetof(struct fuse_dirent, name) */ +#define DIRENT_ALIGN(x) (((x) + 7) & ~(size_t)7) +#define MP_LEN 128 /* max mountpoint path length */ +#define PATH_LEN (MP_LEN + 32) + +/* ---- daemon state (shared between daemon thread and test thread) ------- */ + +struct daemon_state { + int fuse_fd; + struct io_uring ring; + int ring_ready; + int nr_queues; + size_t max_payload_sz; + + /* per-queue entry */ + struct { + struct fuse_uring_req_header *hdr; + void *payload; + struct iovec iov[2]; + } *entries; + + /* synchronisation */ + pthread_mutex_t lock; + pthread_cond_t cond; + atomic_int ready; + atomic_int exiting; + + /* io-uring path verification */ + atomic_int uring_reqs; + + /* in-memory filesystem */ + char *testfile; + size_t testfile_sz; +}; + +/* ---- FUSE request handling --------------------------------------------- */ + +static size_t add_dirent(void *buf, size_t bufsz, size_t off, + uint64_t ino, unsigned type, + const char *name, uint64_t next_off) +{ + size_t nlen = strlen(name); + size_t elen = DIRENT_ALIGN(DIRENT_NAME_OFF + nlen); + struct fuse_dirent *d; + + if (off + elen > bufsz) + return 0; + d = (struct fuse_dirent *)((char *)buf + off); + d->ino = ino; + d->off = next_off; + d->namelen = nlen; + d->type = type; + memcpy(d->name, name, nlen); + if (elen > DIRENT_NAME_OFF + nlen) + memset(d->name + nlen, 0, elen - DIRENT_NAME_OFF - nlen); + return elen; +} + +static void fill_attr(struct fuse_attr *a, uint64_t ino, size_t tf_sz) +{ + memset(a, 0, sizeof(*a)); + a->ino = ino; + a->blksize = 4096; + switch (ino) { + case INODE_ROOT: + a->mode = S_IFDIR | 0755; + a->nlink = 2; + break; + case INODE_HELLO: + a->mode = S_IFREG | 0444; + a->nlink = 1; + a->size = HELLO_LEN; + break; + case INODE_TESTFILE: + a->mode = S_IFREG | 0644; + a->nlink = 1; + a->size = tf_sz; + break; + } +} + +/* + * Handle one FUSE request. Returns payload bytes (>=0) or -errno. + */ +static int handle_request(struct daemon_state *ds, + struct fuse_uring_req_header *hdr, void *payload, + uint32_t payload_sz) +{ + struct fuse_in_header *ih = (void *)hdr->in_out; + + switch (ih->opcode) { + case FUSE_GETATTR: { + struct fuse_attr_out *ao = (void *)payload; + + if (ih->nodeid < INODE_ROOT || ih->nodeid > INODE_TESTFILE) + return -ENOENT; + memset(ao, 0, sizeof(*ao)); + ao->attr_valid = 1; + fill_attr(&ao->attr, ih->nodeid, ds->testfile_sz); + return sizeof(*ao); + } + case FUSE_LOOKUP: { + const char *name = (const char *)payload; + struct fuse_entry_out *eo; + uint64_t ino = 0; + + if (ih->nodeid != INODE_ROOT) + return -ENOENT; + if (payload_sz >= 5 && !memcmp(name, "hello", 5) && + (payload_sz == 5 || name[5] == '\0')) + ino = INODE_HELLO; + else if (payload_sz >= 8 && !memcmp(name, "testfile", 8) && + (payload_sz == 8 || name[8] == '\0')) + ino = INODE_TESTFILE; + else + return -ENOENT; + eo = (void *)payload; + memset(eo, 0, sizeof(*eo)); + eo->nodeid = ino; + eo->generation = 1; + eo->entry_valid = 1; + eo->attr_valid = 1; + fill_attr(&eo->attr, ino, ds->testfile_sz); + return sizeof(*eo); + } + case FUSE_OPEN: + case FUSE_OPENDIR: { + struct fuse_open_out *oo = (void *)payload; + + memset(oo, 0, sizeof(*oo)); + return sizeof(*oo); + } + case FUSE_READ: { + struct fuse_read_in *ri = (void *)hdr->op_in; + const char *src; + size_t src_sz, sz; + + if (ih->nodeid == INODE_HELLO) { + src = HELLO_CONTENT; + src_sz = HELLO_LEN; + } else if (ih->nodeid == INODE_TESTFILE) { + src = ds->testfile; + src_sz = ds->testfile_sz; + } else { + return -ENOENT; + } + if (ri->offset >= src_sz) + return 0; + sz = src_sz - ri->offset; + if (sz > ri->size) + sz = ri->size; + memcpy(payload, src + ri->offset, sz); + return (int)sz; + } + case FUSE_WRITE: { + struct fuse_write_in *wi = (void *)hdr->op_in; + struct fuse_write_out *wo; + size_t sz = wi->size, end; + + if (ih->nodeid != INODE_TESTFILE) + return -EACCES; + if (sz > payload_sz) + sz = payload_sz; + end = wi->offset + sz; + if (end > TESTFILE_MAX) + return -ENOSPC; + memcpy(ds->testfile + wi->offset, payload, sz); + if (end > ds->testfile_sz) + ds->testfile_sz = end; + wo = (void *)payload; + memset(wo, 0, sizeof(*wo)); + wo->size = sz; + return sizeof(*wo); + } + case FUSE_READDIR: { + struct fuse_read_in *ri = (void *)hdr->op_in; + size_t bufsz = ri->size, w = 0, n; + + if (ih->nodeid != INODE_ROOT) + return -ENOENT; + if (bufsz > ds->max_payload_sz) + bufsz = ds->max_payload_sz; + if (ri->offset < 1) { + n = add_dirent(payload, bufsz, w, INODE_ROOT, DT_DIR, ".", 1); + if (n) w += n; + } + if (ri->offset < 2) { + n = add_dirent(payload, bufsz, w, INODE_ROOT, DT_DIR, "..", 2); + if (n) w += n; + } + if (ri->offset < 3) { + n = add_dirent(payload, bufsz, w, INODE_HELLO, DT_REG, "hello", 3); + if (n) w += n; + } + if (ri->offset < 4) { + n = add_dirent(payload, bufsz, w, INODE_TESTFILE, DT_REG, "testfile", 4); + if (n) w += n; + } + return (int)w; + } + case FUSE_STATFS: { + struct fuse_statfs_out *so = (void *)payload; + + memset(so, 0, sizeof(*so)); + so->st.bsize = 4096; + so->st.frsize = 4096; + so->st.namelen = 255; + so->st.blocks = 1024; + so->st.bfree = 512; + so->st.bavail = 512; + return sizeof(*so); + } + case FUSE_RELEASE: + case FUSE_RELEASEDIR: + case FUSE_FLUSH: + case FUSE_FSYNC: + case FUSE_FSYNCDIR: + return 0; + case FUSE_DESTROY: + ds->exiting = 1; + return 0; + default: + return -ENOSYS; + } +} + +/* ---- io_uring SQE helpers ---------------------------------------------- */ + +static int sqe_register(struct io_uring *ring, int fd, + struct iovec *iov, int qid, void *user_data) +{ + struct io_uring_sqe *sqe = io_uring_get_sqe(ring); + struct fuse_uring_cmd_req *cr; + + if (!sqe) + return -ENOSPC; + io_uring_prep_rw(IORING_OP_URING_CMD, sqe, fd, NULL, 0, 0); + sqe->cmd_op = FUSE_IO_URING_CMD_REGISTER; + sqe->addr = (unsigned long)iov; + sqe->len = 2; + sqe->user_data = (uint64_t)(uintptr_t)user_data; + cr = (void *)sqe->cmd; + memset(cr, 0, sizeof(*cr)); + cr->qid = qid; + return 0; +} + +static int sqe_commit_fetch(struct io_uring *ring, int fd, + struct fuse_uring_req_header *hdr, + uint64_t commit_id, int qid, + int32_t error, uint32_t payload_sz, + void *user_data) +{ + struct io_uring_sqe *sqe = io_uring_get_sqe(ring); + struct fuse_uring_cmd_req *cr; + struct fuse_out_header *oh = (void *)hdr->in_out; + + if (!sqe) + return -ENOSPC; + + oh->len = sizeof(*oh); + oh->error = error; + oh->unique = commit_id; + hdr->ring_ent_in_out.commit_id = commit_id; + hdr->ring_ent_in_out.payload_sz = payload_sz; + + io_uring_prep_rw(IORING_OP_URING_CMD, sqe, fd, NULL, 0, 0); + sqe->cmd_op = FUSE_IO_URING_CMD_COMMIT_AND_FETCH; + sqe->user_data = (uint64_t)(uintptr_t)user_data; + cr = (void *)sqe->cmd; + memset(cr, 0, sizeof(*cr)); + cr->commit_id = commit_id; + cr->qid = qid; + return 0; +} + +/* ---- daemon thread ----------------------------------------------------- */ + +static int daemon_init(struct daemon_state *ds) +{ + char *buf; + char reply[sizeof(struct fuse_out_header) + sizeof(struct fuse_init_out)]; + struct fuse_in_header *ih; + struct fuse_init_in *ii; + struct fuse_out_header *oh; + struct fuse_init_out *io; + ssize_t n; + + buf = malloc(FUSE_MIN_READ_BUFFER); + if (!buf) + return -1; + + n = read(ds->fuse_fd, buf, FUSE_MIN_READ_BUFFER); + if (n < (ssize_t)sizeof(struct fuse_in_header)) { + free(buf); + return -1; + } + ih = (void *)buf; + if (ih->opcode != FUSE_INIT) { + free(buf); + return -1; + } + ii = (void *)(buf + sizeof(*ih)); + + if (!(((uint64_t)ii->flags | ((uint64_t)ii->flags2 << 32)) & FUSE_OVER_IO_URING)) { + free(buf); + return -1; + } + + memset(reply, 0, sizeof(reply)); + oh = (void *)reply; + io = (void *)(reply + sizeof(*oh)); + oh->len = sizeof(reply); + oh->unique = ih->unique; + io->major = FUSE_KERNEL_VERSION; + io->minor = FUSE_KERNEL_MINOR_VERSION; + io->max_readahead = ii->max_readahead; + free(buf); + + io->max_write = TESTFILE_MAX; + io->max_pages = TESTFILE_MAX / 4096; + io->max_background = 16; + io->congestion_threshold = 12; + io->time_gran = 1; + io->flags = FUSE_BIG_WRITES | FUSE_INIT_EXT; + io->flags2 = (uint32_t)(FUSE_OVER_IO_URING >> 32); + + n = write(ds->fuse_fd, reply, sizeof(reply)); + if (n != sizeof(reply)) + return -1; + + ds->max_payload_sz = io->max_write; + if (ds->max_payload_sz < FUSE_MIN_READ_BUFFER) + ds->max_payload_sz = FUSE_MIN_READ_BUFFER; + return 0; +} + +static int daemon_setup_ring(struct daemon_state *ds) +{ + struct io_uring_params params = { .flags = IORING_SETUP_SQE128 }; + int i, ret; + + ret = io_uring_queue_init_params(ds->nr_queues * 2 + 4, + &ds->ring, ¶ms); + if (ret < 0) + return ret; + ds->ring_ready = 1; + + ds->testfile = calloc(1, TESTFILE_MAX); + if (!ds->testfile) + return -ENOMEM; + + ds->entries = calloc(ds->nr_queues, sizeof(*ds->entries)); + if (!ds->entries) + return -ENOMEM; + + for (i = 0; i < ds->nr_queues; i++) { + if (posix_memalign((void **)&ds->entries[i].hdr, 4096, + sizeof(*ds->entries[i].hdr))) + return -ENOMEM; + if (posix_memalign(&ds->entries[i].payload, 4096, + ds->max_payload_sz)) + return -ENOMEM; + memset(ds->entries[i].hdr, 0, sizeof(*ds->entries[i].hdr)); + memset(ds->entries[i].payload, 0, ds->max_payload_sz); + ds->entries[i].iov[0].iov_base = ds->entries[i].hdr; + ds->entries[i].iov[0].iov_len = sizeof(*ds->entries[i].hdr); + ds->entries[i].iov[1].iov_base = ds->entries[i].payload; + ds->entries[i].iov[1].iov_len = ds->max_payload_sz; + + ret = sqe_register(&ds->ring, ds->fuse_fd, + ds->entries[i].iov, i, + (void *)(uintptr_t)i); + if (ret) + return ret; + } + return io_uring_submit(&ds->ring); +} + +static void *daemon_loop(void *arg) +{ + struct daemon_state *ds = arg; + struct io_uring_cqe *cqe; + + if (daemon_init(ds)) + goto out; + if (daemon_setup_ring(ds) < 0) + goto out; + + /* signal test thread that we're ready */ + pthread_mutex_lock(&ds->lock); + ds->ready = 1; + pthread_cond_signal(&ds->cond); + pthread_mutex_unlock(&ds->lock); + + while (!ds->exiting) { + if (io_uring_wait_cqe(&ds->ring, &cqe)) + break; + do { + int qid = (int)(uintptr_t)cqe->user_data; + int ret; + + if (cqe->res < 0) { + io_uring_cqe_seen(&ds->ring, cqe); + if (cqe->res == -ENOTCONN || + cqe->res == -ECONNABORTED) + goto out; + continue; + } + + atomic_fetch_add(&ds->uring_reqs, 1); + + ret = handle_request(ds, ds->entries[qid].hdr, + ds->entries[qid].payload, + ds->entries[qid].hdr->ring_ent_in_out.payload_sz); + + if (ret < 0) { + if (sqe_commit_fetch(&ds->ring, ds->fuse_fd, + ds->entries[qid].hdr, + ds->entries[qid].hdr->ring_ent_in_out.commit_id, + qid, ret, 0, + (void *)(uintptr_t)qid)) + goto out; + } else { + if (sqe_commit_fetch(&ds->ring, ds->fuse_fd, + ds->entries[qid].hdr, + ds->entries[qid].hdr->ring_ent_in_out.commit_id, + qid, 0, ret, + (void *)(uintptr_t)qid)) + goto out; + } + + io_uring_cqe_seen(&ds->ring, cqe); + } while (io_uring_peek_cqe(&ds->ring, &cqe) == 0); + + io_uring_submit(&ds->ring); + } +out: + return NULL; +} + +/* ---- daemon lifecycle (used by fixtures) ------------------------------- */ + +static int daemon_start(struct daemon_state *ds, const char *mountpoint) +{ + char opts[256]; + + memset(ds, 0, sizeof(*ds)); + pthread_mutex_init(&ds->lock, NULL); + pthread_cond_init(&ds->cond, NULL); + ds->nr_queues = sysconf(_SC_NPROCESSORS_CONF); + if (ds->nr_queues > 64) + ds->nr_queues = 64; + + ds->fuse_fd = open("/dev/fuse", O_RDWR | O_CLOEXEC); + if (ds->fuse_fd < 0) + return -errno; + + snprintf(opts, sizeof(opts), + "fd=%d,rootmode=40000,user_id=%u,group_id=%u", + ds->fuse_fd, getuid(), getgid()); + if (mount("fuse_uring_test", mountpoint, "fuse", + MS_NOSUID | MS_NODEV, opts)) { + close(ds->fuse_fd); + ds->fuse_fd = -1; + return -errno; + } + + return 0; +} + +static int daemon_wait_ready(struct daemon_state *ds, const char *mountpoint, + pthread_t *thread) +{ + struct timespec ts; + int err; + + if (pthread_create(thread, NULL, daemon_loop, ds)) + return -errno; + + pthread_mutex_lock(&ds->lock); + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 10; + err = 0; + while (!ds->ready && !err) + err = pthread_cond_timedwait(&ds->cond, &ds->lock, &ts); + pthread_mutex_unlock(&ds->lock); + + if (!ds->ready) { + /* Thread was created but daemon failed; clean up */ + ds->exiting = 1; + umount2(mountpoint, MNT_DETACH); + pthread_join(*thread, NULL); + close(ds->fuse_fd); + ds->fuse_fd = -1; + return -ETIMEDOUT; + } + return 0; +} + +static void daemon_stop(struct daemon_state *ds, const char *mountpoint, + pthread_t thread) +{ + int i; + + umount2(mountpoint, MNT_DETACH); + pthread_join(thread, NULL); + if (ds->ring_ready) + io_uring_queue_exit(&ds->ring); + if (ds->entries) { + for (i = 0; i < ds->nr_queues; i++) { + free(ds->entries[i].hdr); + free(ds->entries[i].payload); + } + free(ds->entries); + } + free(ds->testfile); + if (ds->fuse_fd >= 0) + close(ds->fuse_fd); + pthread_mutex_destroy(&ds->lock); + pthread_cond_destroy(&ds->cond); +} + +/* ---- prerequisites and kernel parameter management --------------------- */ + +static int orig_uring_disabled = -1; /* -1 = not saved */ +static int orig_enable_uring = -1; /* -1 = not saved */ + +static int read_sysctl_int(const char *path) +{ + FILE *f; + int val = -1; + + f = fopen(path, "r"); + if (f) { + if (fscanf(f, "%d", &val) != 1) + val = -1; + fclose(f); + } + return val; +} + +static void write_sysctl(const char *path, const char *val) +{ + FILE *f; + + f = fopen(path, "w"); + if (f) { + fputs(val, f); + fclose(f); + } +} + +static int ensure_io_uring_enabled(void) +{ + int val; + + /* enable io_uring if kernel restricts it */ + val = read_sysctl_int("/proc/sys/kernel/io_uring_disabled"); + if (val > 0) { + if (orig_uring_disabled < 0) + orig_uring_disabled = val; + write_sysctl("/proc/sys/kernel/io_uring_disabled", "0"); + if (read_sysctl_int("/proc/sys/kernel/io_uring_disabled") != 0) + return -1; + } + + return 0; +} + +static int ensure_fuse_uring_enabled(void) +{ + FILE *f; + char val; + + if (access("/dev/fuse", R_OK | W_OK)) + return -1; + + f = fopen("/sys/module/fuse/parameters/enable_uring", "r"); + if (!f) + return -1; + val = fgetc(f); + fclose(f); + + if (val != 'Y') { + if (orig_enable_uring < 0) + orig_enable_uring = 0; + write_sysctl("/sys/module/fuse/parameters/enable_uring", "1"); + /* re-check */ + f = fopen("/sys/module/fuse/parameters/enable_uring", "r"); + if (!f) + return -1; + val = fgetc(f); + fclose(f); + if (val != 'Y') + return -1; + } + + return 0; +} + +static void restore_kernel_params(void) +{ + if (orig_enable_uring >= 0) { + write_sysctl("/sys/module/fuse/parameters/enable_uring", + orig_enable_uring ? "1" : "0"); + orig_enable_uring = -1; + } + if (orig_uring_disabled >= 0) { + char buf[16]; + + snprintf(buf, sizeof(buf), "%d", orig_uring_disabled); + write_sysctl("/proc/sys/kernel/io_uring_disabled", buf); + orig_uring_disabled = -1; + } +} + +/* ---- post-test health helpers ------------------------------------------ */ + +/* + * Count entries under /sys/fs/fuse/connections — each corresponds to + * an active fuse_conn. Used to detect connection leaks after teardown. + */ +static int count_fuse_connections(void) +{ + DIR *d = opendir("/sys/fs/fuse/connections"); + struct dirent *de; + int n = 0; + + if (!d) + return -1; + while ((de = readdir(d)) != NULL) { + if (de->d_name[0] == '.') + continue; + n++; + } + closedir(d); + return n; +} + +/* + * Time-bounded umount. Retries every 50 ms up to deadline_ms total. + * Returns 0 on success (or already unmounted), -1 on timeout. + */ +static int umount_with_deadline(const char *mp, unsigned int deadline_ms) +{ + unsigned int slept_ms = 0; + + while (slept_ms <= deadline_ms) { + int rc = umount2(mp, MNT_DETACH); + + if (rc == 0) + return 0; + if (errno == EINVAL || errno == ENOENT) + return 0; /* not mounted — treat as success */ + usleep(50000); + slept_ms += 50; + } + return -1; +} + +/* ---- kselftest fixtures ------------------------------------------------ */ + +FIXTURE(fuse_uring) { + struct daemon_state ds; + char mountpoint[MP_LEN]; + pthread_t thread; + int thread_started; + int conn_baseline; +}; + +FIXTURE_SETUP(fuse_uring) +{ + static int atexit_registered; + + if (getuid() != 0) + SKIP(return, "must run as root"); + if (!atexit_registered) { + atexit(restore_kernel_params); + atexit_registered = 1; + } + if (ensure_io_uring_enabled()) + SKIP(return, "cannot enable io_uring"); + if (ensure_fuse_uring_enabled()) + SKIP(return, "FUSE io-uring not available"); + + strcpy(self->mountpoint, "/tmp/fuse_uring_XXXXXX"); + if (!mkdtemp(self->mountpoint)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + if (daemon_start(&self->ds, self->mountpoint)) { + rmdir(self->mountpoint); + SKIP(return, "daemon_start: %s", strerror(errno)); + } + if (daemon_wait_ready(&self->ds, self->mountpoint, &self->thread)) { + rmdir(self->mountpoint); + SKIP(return, "daemon did not become ready"); + } + self->thread_started = 1; + self->conn_baseline = count_fuse_connections(); + if (self->conn_baseline < 0) + self->conn_baseline = 0; +} + +FIXTURE_TEARDOWN(fuse_uring) +{ + if (!self->thread_started) + return; + daemon_stop(&self->ds, self->mountpoint, self->thread); + rmdir(self->mountpoint); +} + +/* ---- tests ------------------------------------------------------------- */ + +TEST_F(fuse_uring, read_file) +{ + char buf[256]; + char path[PATH_LEN]; + int fd, n; + + snprintf(path, sizeof(path), "%s/hello", self->mountpoint); + fd = open(path, O_RDONLY); + ASSERT_GE(fd, 0); + n = read(fd, buf, sizeof(buf)); + close(fd); + ASSERT_EQ(n, (int)HELLO_LEN); + buf[n] = '\0'; + ASSERT_STREQ(buf, HELLO_CONTENT); +} + +TEST_F(fuse_uring, write_and_readback) +{ + char path[PATH_LEN]; + const char *msg = "io-uring write test"; + char buf[256]; + int fd, n; + + snprintf(path, sizeof(path), "%s/testfile", self->mountpoint); + + fd = open(path, O_WRONLY); + ASSERT_GE(fd, 0); + n = write(fd, msg, strlen(msg)); + close(fd); + ASSERT_EQ(n, (int)strlen(msg)); + + fd = open(path, O_RDONLY); + ASSERT_GE(fd, 0); + n = read(fd, buf, sizeof(buf)); + close(fd); + ASSERT_EQ(n, (int)strlen(msg)); + buf[n] = '\0'; + ASSERT_STREQ(buf, msg); +} + +TEST_F(fuse_uring, readdir) +{ + DIR *d; + struct dirent *de; + int found_hello = 0, found_testfile = 0; + + d = opendir(self->mountpoint); + ASSERT_NE(d, NULL); + while ((de = readdir(d)) != NULL) { + if (!strcmp(de->d_name, "hello")) + found_hello = 1; + if (!strcmp(de->d_name, "testfile")) + found_testfile = 1; + } + closedir(d); + ASSERT_EQ(found_hello, 1); + ASSERT_EQ(found_testfile, 1); +} + +TEST_F(fuse_uring, stat_files) +{ + struct stat st; + char path[PATH_LEN]; + + ASSERT_EQ(stat(self->mountpoint, &st), 0); + ASSERT_TRUE(S_ISDIR(st.st_mode)); + + snprintf(path, sizeof(path), "%s/hello", self->mountpoint); + ASSERT_EQ(stat(path, &st), 0); + ASSERT_TRUE(S_ISREG(st.st_mode)); + ASSERT_EQ(st.st_size, (off_t)HELLO_LEN); +} + +struct conc_ctx { + const char *mountpoint; + int id; +}; + +static void *concurrent_worker(void *arg) +{ + struct conc_ctx *ctx = arg; + char path[PATH_LEN]; + int i; + + for (i = 0; i < 10; i++) { + char buf[128]; + int fd; + + /* readers hit /hello */ + snprintf(path, sizeof(path), "%s/hello", ctx->mountpoint); + fd = open(path, O_RDONLY); + if (fd >= 0) { + read(fd, buf, sizeof(buf)); + close(fd); + } + + /* writers hit /testfile */ + snprintf(path, sizeof(path), "%s/testfile", ctx->mountpoint); + fd = open(path, O_WRONLY); + if (fd >= 0) { + snprintf(buf, sizeof(buf), "thread%d iter%d", ctx->id, i); + write(fd, buf, strlen(buf)); + close(fd); + } + } + return NULL; +} + +#define CONC_THREADS 4 + +TEST_F(fuse_uring, concurrent_io) +{ + pthread_t threads[CONC_THREADS]; + struct conc_ctx ctxs[CONC_THREADS]; + int i; + + for (i = 0; i < CONC_THREADS; i++) { + ctxs[i].mountpoint = self->mountpoint; + ctxs[i].id = i; + pthread_create(&threads[i], NULL, concurrent_worker, &ctxs[i]); + } + for (i = 0; i < CONC_THREADS; i++) + pthread_join(threads[i], NULL); + + /* verify filesystem still works after concurrent access */ + { + char path[PATH_LEN]; + char buf[128]; + int fd, n; + + snprintf(path, sizeof(path), "%s/hello", self->mountpoint); + fd = open(path, O_RDONLY); + ASSERT_GE(fd, 0); + n = read(fd, buf, sizeof(buf)); + close(fd); + ASSERT_EQ(n, (int)HELLO_LEN); + } +} + +/* + * Per-operation io_uring path verification. Reset the daemon's + * request counter before each distinct operation type and verify it + * incremented afterwards. If the kernel silently falls back to + * /dev/fuse for any opcode, the counter stays at zero for that op. + */ +TEST_F(fuse_uring, requests_via_uring) +{ + char path[PATH_LEN]; + char buf[256]; + int fd, n, before, after; + DIR *d; + struct dirent *de; + struct stat st; + + /* LOOKUP + OPEN + READ + RELEASE (via open+read+close) */ + atomic_store(&self->ds.uring_reqs, 0); + snprintf(path, sizeof(path), "%s/hello", self->mountpoint); + fd = open(path, O_RDONLY); + ASSERT_GE(fd, 0); + n = read(fd, buf, sizeof(buf)); + close(fd); + ASSERT_EQ(n, (int)HELLO_LEN); + after = atomic_load(&self->ds.uring_reqs); + ASSERT_GT(after, 0); + TH_LOG("read path: %d io_uring requests (LOOKUP+OPEN+READ+RELEASE)", + after); + + /* WRITE (via open+write+close on testfile) */ + before = atomic_load(&self->ds.uring_reqs); + snprintf(path, sizeof(path), "%s/testfile", self->mountpoint); + fd = open(path, O_WRONLY); + ASSERT_GE(fd, 0); + n = write(fd, "verify", 6); + close(fd); + ASSERT_EQ(n, 6); + after = atomic_load(&self->ds.uring_reqs); + ASSERT_GT(after, before); + TH_LOG("write path: %d new io_uring requests", after - before); + + /* READDIR (via opendir+readdir+closedir) */ + before = atomic_load(&self->ds.uring_reqs); + d = opendir(self->mountpoint); + ASSERT_NE(d, NULL); + while ((de = readdir(d)) != NULL) + ; + closedir(d); + after = atomic_load(&self->ds.uring_reqs); + ASSERT_GT(after, before); + TH_LOG("readdir path: %d new io_uring requests", after - before); + + /* GETATTR (via stat) */ + before = atomic_load(&self->ds.uring_reqs); + snprintf(path, sizeof(path), "%s/hello", self->mountpoint); + ASSERT_EQ(stat(path, &st), 0); + after = atomic_load(&self->ds.uring_reqs); + ASSERT_GT(after, before); + TH_LOG("stat path: %d new io_uring requests", after - before); + + TH_LOG("total io_uring requests: %d", after); +} + +/* + * Sustained I/O: write 256KB in 4KB chunks with a known pattern, then + * read the entire file back and verify every byte. Exercises payload + * buffer reuse across many commit/fetch cycles and catches corruption + * in the io_uring request pipeline. + */ +TEST_F(fuse_uring, sustained_io) +{ + char path[PATH_LEN]; + char wbuf[4096], rbuf[4096]; + int fd, total, off, n, i; + int write_sz = TESTFILE_MAX; + + snprintf(path, sizeof(path), "%s/testfile", self->mountpoint); + + /* write phase: fill file with pattern */ + fd = open(path, O_WRONLY); + ASSERT_GE(fd, 0); + for (off = 0; off < write_sz; off += sizeof(wbuf)) { + /* fill with offset-dependent pattern */ + for (i = 0; i < (int)sizeof(wbuf); i++) + wbuf[i] = (char)((off + i) & 0xff); + n = write(fd, wbuf, sizeof(wbuf)); + ASSERT_EQ(n, (int)sizeof(wbuf)); + } + close(fd); + + /* read phase: verify every byte */ + fd = open(path, O_RDONLY); + ASSERT_GE(fd, 0); + total = 0; + while (total < write_sz) { + n = read(fd, rbuf, sizeof(rbuf)); + ASSERT_GT(n, 0); + for (i = 0; i < n; i++) { + if (rbuf[i] != (char)((total + i) & 0xff)) { + TH_LOG("data mismatch at offset %d: " + "expected 0x%02x got 0x%02x", + total + i, + (total + i) & 0xff, + (unsigned char)rbuf[i]); + ASSERT_EQ(rbuf[i], + (char)((total + i) & 0xff)); + } + } + total += n; + } + close(fd); + ASSERT_EQ(total, write_sz); + TH_LOG("sustained I/O: wrote and verified %d bytes in 4KB chunks", + write_sz); +} + +/* + * Background requests: fill a file, then read it sequentially in small + * chunks to trigger kernel readahead. Readahead generates background + * FUSE requests that flow through fuse_uring_queue_bq_req() and + * fuse_uring_flush_bg() — the code path fixed by 31da059891bd (bg dispatch + * ordering). Verify that all data arrives correctly and that the + * io_uring request count exceeds the minimum expected from foreground + * requests alone, indicating background requests were dispatched. + */ +TEST_F(fuse_uring, background_requests) +{ + char path[PATH_LEN]; + char wbuf[4096], rbuf[512]; + int fd, n, i, off, total; + int before, after, read_calls; + int write_sz = TESTFILE_MAX; + + snprintf(path, sizeof(path), "%s/testfile", self->mountpoint); + + /* fill the file with a known pattern */ + fd = open(path, O_WRONLY); + ASSERT_GE(fd, 0); + for (off = 0; off < write_sz; off += sizeof(wbuf)) { + for (i = 0; i < (int)sizeof(wbuf); i++) + wbuf[i] = (char)((off + i) % 251); + n = write(fd, wbuf, sizeof(wbuf)); + ASSERT_EQ(n, (int)sizeof(wbuf)); + } + close(fd); + + /* + * Sequential read in small chunks (512B) to maximise the + * kernel's readahead window. The kernel will issue background + * FUSE_READ requests ahead of what we ask for. + */ + before = atomic_load(&self->ds.uring_reqs); + fd = open(path, O_RDONLY); + ASSERT_GE(fd, 0); + + total = 0; + read_calls = 0; + while (total < write_sz) { + n = read(fd, rbuf, sizeof(rbuf)); + ASSERT_GT(n, 0); + /* verify pattern */ + for (i = 0; i < n; i++) { + if (rbuf[i] != (char)((total + i) % 251)) { + TH_LOG("bg read mismatch at offset %d", + total + i); + ASSERT_EQ(rbuf[i], + (char)((total + i) % 251)); + } + } + total += n; + read_calls++; + } + close(fd); + after = atomic_load(&self->ds.uring_reqs); + + ASSERT_EQ(total, write_sz); + + /* + * The io_uring request count should reflect both the foreground + * reads and any background readahead the kernel issued. At + * minimum we expect LOOKUP + OPEN + reads + RELEASE. + */ + TH_LOG("background requests: %d read() calls for %d bytes, " + "%d io_uring requests (delta from before)", + read_calls, write_sz, after - before); + ASSERT_GT(after - before, 0); +} + +/* + * Crash recovery: fork a daemon process, drive active I/O against it + * from multiple threads, then SIGKILL the daemon while requests are + * in flight. This exercises the teardown paths that race with + * io_uring task work (the bug class fixed by bea4fe98204b, 952b5d36f6a2, + * 198f45eeb9f7). + */ + +static atomic_int crash_io_running; + +static void *crash_io_worker(void *arg) +{ + const char *mp = arg; + char path[PATH_LEN]; + char buf[128]; + + snprintf(path, sizeof(path), "%s/hello", mp); + while (crash_io_running) { + int fd = open(path, O_RDONLY); + + if (fd >= 0) { + read(fd, buf, sizeof(buf)); + close(fd); + } + snprintf(path, sizeof(path), "%s/testfile", mp); + fd = open(path, O_WRONLY); + if (fd >= 0) { + write(fd, "crash", 5); + close(fd); + } + snprintf(path, sizeof(path), "%s/hello", mp); + } + return NULL; +} + +#define CRASH_IO_THREADS 4 + +TEST_F(fuse_uring, crash_recovery) +{ + char crash_mp[MP_LEN]; + pid_t pid; + int status; + + strcpy(crash_mp, "/tmp/fuse_crash_XXXXXX"); + if (!mkdtemp(crash_mp)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + /* child: run a daemon, handle requests until killed */ + struct daemon_state cds; + pthread_t ct; + + if (daemon_start(&cds, crash_mp)) + _exit(1); + if (daemon_wait_ready(&cds, crash_mp, &ct)) + _exit(1); + pthread_join(ct, NULL); + _exit(0); + } + + /* parent: wait for child's filesystem to appear */ + { + char path[PATH_LEN]; + int fd, i; + + snprintf(path, sizeof(path), "%s/hello", crash_mp); + for (i = 0; i < 50; i++) { + fd = open(path, O_RDONLY); + if (fd >= 0) { + close(fd); + break; + } + usleep(100000); + } + } + + /* start I/O threads hammering the filesystem */ + { + pthread_t io_threads[CRASH_IO_THREADS]; + int i; + + crash_io_running = 1; + for (i = 0; i < CRASH_IO_THREADS; i++) + pthread_create(&io_threads[i], NULL, + crash_io_worker, crash_mp); + + /* let I/O build up, then kill daemon mid-flight */ + usleep(200000); + kill(pid, SIGKILL); + waitpid(pid, &status, 0); + + /* stop I/O threads (they'll get errors now) */ + crash_io_running = 0; + for (i = 0; i < CRASH_IO_THREADS; i++) + pthread_join(io_threads[i], NULL); + } + + /* + * Bounded umount: if async teardown is stuck, this will + * time out rather than silently leaking the connection. + */ + { + int rc, final_conns; + + rc = umount_with_deadline(crash_mp, 6000); + ASSERT_EQ(rc, 0) { + TH_LOG("crash_mp umount timed out after 6s " + "(async teardown may be stuck)"); + } + rmdir(crash_mp); + + /* grace period for delayed_work to drain */ + usleep(200000); + + final_conns = count_fuse_connections(); + TH_LOG("crash_recovery: conns %d -> %d (baseline %d)", + self->conn_baseline, final_conns, + self->conn_baseline); + ASSERT_LE(final_conns, self->conn_baseline + 1); + } + + /* + * The kernel should be healthy. Verify by doing an operation + * on the main fixture's filesystem. + */ + { + struct stat st; + + ASSERT_EQ(stat(self->mountpoint, &st), 0); + TH_LOG("kernel healthy after daemon crash with active I/O"); + } +} + +/* + * Abort before ring ready: negotiate FUSE_OVER_IO_URING in FUSE_INIT + * but never register any io_uring entries, then abort the connection. + * Exercises the deadlock fix (d55011469b41) where blocked allocators would + * hang forever waiting for a ring that never becomes ready, and the + * abort-during-creation fix (c146284c4355) where rings created after abort + * would leak. + */ +TEST_F(fuse_uring, abort_before_ring_ready) +{ + char abort_mp[MP_LEN]; + pid_t pid; + int status; + struct stat st; + + strcpy(abort_mp, "/tmp/fuse_abort_XXXXXX"); + if (!mkdtemp(abort_mp)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + /* + * Child: open /dev/fuse, mount, do FUSE_INIT with + * FUSE_OVER_IO_URING, but never register io_uring + * entries. The ring never becomes ready. Sleep + * until killed — the kernel must handle teardown of + * an io_uring-negotiated connection where the ring + * was never initialised without deadlocking. + */ + struct daemon_state ds; + + memset(&ds, 0, sizeof(ds)); + if (daemon_start(&ds, abort_mp)) + _exit(1); + if (daemon_init(&ds)) + _exit(1); + pause(); + _exit(0); + } + + /* + * Give the child time to complete FUSE_INIT, then kill it. + * This tears down the FUSE connection while fc->io_uring is + * set but the ring was never readied. On a buggy kernel this + * deadlocks in fuse_block_alloc() or leaks resources. + */ + usleep(500000); + kill(pid, SIGKILL); + waitpid(pid, &status, 0); + + { + int rc; + + rc = umount_with_deadline(abort_mp, 6000); + ASSERT_EQ(rc, 0) { + TH_LOG("abort_mp umount timed out after 6s"); + } + rmdir(abort_mp); + } + + /* verify kernel is still healthy */ + ASSERT_EQ(stat(self->mountpoint, &st), 0); + TH_LOG("kernel healthy after teardown of uninitialized io_uring ring"); +} + +#define REG_RACE_CHILDREN 16 + +/* + * Regression test for the race classes fixed by: + * 952b5d36f6a2 fuse-uring: fix race between registration and + * connection abortion + * b70a3aca1693 fuse-uring: Avoid queue->stopped races and set/read + * that value under lock + * d351da750669 fuse-uring: Avoid use-after-free in + * fuse_uring_async_stop_queues (CVE-2026-64261) + * + * Forks N children performing FUSE_INIT with FUSE_OVER_IO_URING + * negotiated but delaying entry registration. Parent kills them + * at staggered offsets — some during FUSE_INIT, others after + * negotiation while in pause(). The crucial window is between + * fuse_uring_create() running under fc->lock and fuse_abort_conn() + * clearing fc->connected. + * + * Delays are deterministic (child_index * 2.5ms) for reproducibility. + * Seed logged via TH_LOG for reruns. + */ +TEST_F(fuse_uring, teardown_under_concurrent_registration) +{ + char mp_bufs[REG_RACE_CHILDREN][MP_LEN]; + pid_t pids[REG_RACE_CHILDREN]; + int i, status, initial_conns, final_conns; + unsigned int seed = (unsigned int)getpid(); + + srandom(seed); + TH_LOG("registration-race seed=%u", seed); + + initial_conns = count_fuse_connections(); + if (initial_conns < 0) + initial_conns = 0; + + for (i = 0; i < REG_RACE_CHILDREN; i++) { + snprintf(mp_bufs[i], sizeof(mp_bufs[i]), + "/tmp/fuse_regrace_%d_XXXXXX", i); + if (!mkdtemp(mp_bufs[i])) { + int j; + + for (j = 0; j < i; j++) { + kill(pids[j], SIGKILL); + waitpid(pids[j], &status, 0); + umount_with_deadline(mp_bufs[j], 1000); + rmdir(mp_bufs[j]); + } + SKIP(return, "mkdtemp: %s", strerror(errno)); + } + + pids[i] = fork(); + ASSERT_GE(pids[i], 0); + + if (pids[i] == 0) { + struct daemon_state ds; + + memset(&ds, 0, sizeof(ds)); + if (daemon_start(&ds, mp_bufs[i])) + _exit(1); + if (daemon_init(&ds)) + _exit(1); + /* + * Deliberately skip ring registration. + * Enter pause() with fc->io_uring=1 but ring + * never initialized. + */ + usleep(i * 2500); /* deterministic stagger */ + pause(); + _exit(0); + } + } + + /* Stage 1: kill even-indexed children early */ + usleep(10000); + for (i = 0; i < REG_RACE_CHILDREN; i += 2) + kill(pids[i], SIGKILL); + + /* Stage 2: kill odd-indexed children slightly later */ + usleep(20000); + for (i = 1; i < REG_RACE_CHILDREN; i += 2) + kill(pids[i], SIGKILL); + + for (i = 0; i < REG_RACE_CHILDREN; i++) + waitpid(pids[i], &status, 0); + + /* Bounded umount for all children */ + { + int leaked = 0; + + for (i = 0; i < REG_RACE_CHILDREN; i++) { + if (umount_with_deadline(mp_bufs[i], 6000) != 0) + leaked++; + rmdir(mp_bufs[i]); + } + ASSERT_EQ(leaked, 0) { + TH_LOG("%d child mounts would not release", leaked); + } + } + + /* Wait for async teardown delayed_work to drain */ + usleep(6000000); + + final_conns = count_fuse_connections(); + TH_LOG("registration race: %d children, conns %d -> %d", + REG_RACE_CHILDREN, initial_conns, final_conns); + ASSERT_EQ(final_conns, initial_conns); + + /* Main fixture mount must still be healthy */ + { + struct stat st; + + ASSERT_EQ(stat(self->mountpoint, &st), 0); + } +} + +TEST_HARNESS_MAIN From 00addc65f0fad162299e52ee8c79aecf0ed8cc1e Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Wed, 9 Sep 2026 11:46:15 +0000 Subject: [PATCH 65/98] sctp: stream: fully roll back denied add-stream state cve CVE-2026-52929 commit-author Wyatt Feng commit a5f8a90ac9f77c678a9781c0a464b635e0d63e49 When ADD_OUT_STREAMS is denied, SCTP only shrinks the queued chunks and then lowers outcnt. That leaves removed stream metadata behind, so a later re-add can reuse a stale ext and hit a null-pointer dereference in the scheduler get path. Fix the rollback by tearing down the removed stream state the same way other stream resizes do. Unschedule the current scheduler state, drop the removed stream ext state with sctp_stream_outq_migrate(), and then reschedule the remaining streams. This keeps scheduler-private RR/FC/PRIO lists consistent while fully rolling back denied outgoing stream additions. Fixes: 637784ade221 ("sctp: introduce priority based stream scheduler") Cc: stable@kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Zhengchuan Liang Reported-by: Xin Liu Signed-off-by: Wyatt Feng Signed-off-by: Ren Wei Acked-by: Xin Long Link: https://patch.msgid.link/d78954ecd94954653ee299400e98d74a03a6f7d3.1780603399.git.bronzed_45_vested@icloud.com Signed-off-by: Jakub Kicinski (cherry picked from commit a5f8a90ac9f77c678a9781c0a464b635e0d63e49) Signed-off-by: Shreeya Patel --- net/sctp/stream.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/sctp/stream.c b/net/sctp/stream.c index 0615e4426341c..af167ca19bc6d 100644 --- a/net/sctp/stream.c +++ b/net/sctp/stream.c @@ -1038,6 +1038,7 @@ struct sctp_chunk *sctp_process_strreset_resp( stsn, rtsn, GFP_ATOMIC); } else if (req->type == SCTP_PARAM_RESET_ADD_OUT_STREAMS) { struct sctp_strreset_addstrm *addstrm; + const struct sctp_sched_ops *sched; __u16 number; addstrm = (struct sctp_strreset_addstrm *)req; @@ -1048,7 +1049,10 @@ struct sctp_chunk *sctp_process_strreset_resp( for (i = number; i < stream->outcnt; i++) SCTP_SO(stream, i)->state = SCTP_STREAM_OPEN; } else { - sctp_stream_shrink_out(stream, number); + sched = sctp_sched_ops_from_stream(stream); + sched->unsched_all(stream); + sctp_stream_outq_migrate(stream, NULL, number); + sched->sched_all(stream); stream->outcnt = number; } From 1a9bfa76ca12377f09f1bf236fff0128551fb03e Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Tue, 15 Sep 2026 13:39:47 -0400 Subject: [PATCH 66/98] mpls: add seqcount to protect the platform_label{,s} pair cve CVE-2026-43042 commit-author Sabrina Dubroca commit 629ec78ef8608d955ce217880cdc3e1873af3a15 upstream-diff Uses a file-scope seqcount_t instead of upstream's per-netns seqcount_mutex_t to avoid a kABI-breaking struct change. Write side uses local_bh_disable() with preempt_disable_nested() for RT safety. Uses rcu_dereference_rtnl() instead of upstream's plain rcu_dereference() to stay lockdep-clean under RTNL. mpls_dump_routes() still runs under RTNL on this tree; the seqcount there is extra hardening. The RCU-protected codepaths (mpls_forward, mpls_dump_routes) can have an inconsistent view of platform_labels vs platform_label in case of a concurrent resize (resize_platform_label_table, under platform_mutex). This can lead to OOB accesses. This patch adds a seqcount, so that we get a consistent snapshot. Note that mpls_label_ok is also susceptible to this, so the check against RTA_DST in rtm_to_route_config, done outside platform_mutex, is not sufficient. This value gets passed to mpls_label_ok once more in both mpls_route_add and mpls_route_del, so there is no issue, but that additional check must not be removed. Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Fixes: 7720c01f3f590 ("mpls: Add a sysctl to control the size of the mpls label table") Fixes: dde1b38e873c ("mpls: Convert mpls_dump_routes() to RCU.") Signed-off-by: Sabrina Dubroca Link: https://patch.msgid.link/cd8fca15e3eb7e212b094064cd83652e20fd9d31.1774284088.git.sd@queasysnail.net Signed-off-by: Jakub Kicinski (cherry picked from commit 629ec78ef8608d955ce217880cdc3e1873af3a15) Signed-off-by: Brett Mastbergen --- net/mpls/af_mpls.c | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 3373b6b34dc7d..63d5ec55afc55 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -38,6 +38,8 @@ #define MPLS_NEIGH_TABLE_UNSPEC (NEIGH_LINK_TABLE + 1) +static seqcount_t mpls_platform_label_seq = SEQCNT_ZERO(mpls_platform_label_seq); + static int label_limit = (1 << 20) - 1; static int ttl_max = 255; @@ -75,16 +77,32 @@ static void rtmsg_lfib(int event, u32 label, struct mpls_route *rt, struct nlmsghdr *nlh, struct net *net, u32 portid, unsigned int nlm_flags); +static struct mpls_route __rcu **mpls_platform_label_rcu(struct net *net, + size_t *platform_labels) +{ + struct mpls_route __rcu **platform_label; + unsigned int sequence; + + do { + sequence = read_seqcount_begin(&mpls_platform_label_seq); + platform_label = rcu_dereference_rtnl(net->mpls.platform_label); + *platform_labels = net->mpls.platform_labels; + } while (read_seqcount_retry(&mpls_platform_label_seq, sequence)); + + return platform_label; +} + static struct mpls_route *mpls_route_input_rcu(struct net *net, unsigned index) { - struct mpls_route *rt = NULL; + struct mpls_route __rcu **platform_label; + size_t platform_labels; - if (index < net->mpls.platform_labels) { - struct mpls_route __rcu **platform_label = - rcu_dereference_rtnl(net->mpls.platform_label); - rt = rcu_dereference_rtnl(platform_label[index]); - } - return rt; + platform_label = mpls_platform_label_rcu(net, &platform_labels); + + if (index < platform_labels) + return rcu_dereference_rtnl(platform_label[index]); + + return NULL; } bool mpls_output_possible(const struct net_device *dev) @@ -2190,8 +2208,7 @@ static int mpls_dump_routes(struct sk_buff *skb, struct netlink_callback *cb) if (index < MPLS_LABEL_FIRST_UNRESERVED) index = MPLS_LABEL_FIRST_UNRESERVED; - platform_label = rtnl_dereference(net->mpls.platform_label); - platform_labels = net->mpls.platform_labels; + platform_label = mpls_platform_label_rcu(net, &platform_labels); if (filter.filter_set) flags |= NLM_F_DUMP_FILTERED; @@ -2576,8 +2593,14 @@ static int resize_platform_label_table(struct net *net, size_t limit) } /* Update the global pointers */ + local_bh_disable(); + preempt_disable_nested(); + write_seqcount_begin(&mpls_platform_label_seq); net->mpls.platform_labels = limit; rcu_assign_pointer(net->mpls.platform_label, labels); + write_seqcount_end(&mpls_platform_label_seq); + preempt_enable_nested(); + local_bh_enable(); rtnl_unlock(); From 56c113f201cead4314638429b3ea77e71d24f3b3 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Mon, 14 Jul 2025 14:20:39 -0700 Subject: [PATCH 67/98] crypto: ecdh - implement FIPS PCT commit - commit-source https://build.opensuse.org/public/source/SUSE:SLE-15-SP6:GA/kernel-source/patches.suse.tar.bz2 commit-patch-path patches.suse/crypto-ecdh-implement-FIPS-PCT.patch SP800-56Arev3, 5.6.2.1.4 ("Owner Assurance of Pair-wise Consistency") requires that a pair-wise consistency check needs to be conducted on a keypair. A pair-wise consistency test (PCT) is meant to ensure that a some provided public key is indeed associated with the given private one. As the kernel's ECDH implementation always computes the public key from the private one, this is guaranteed already as per the API. However, in the course of the certification process, there had been a lengthy discussion regarding this topic, with the result that a PCT is nonetheless mandatory. As the only user of the in-kernel ECDH is bluetooth, performance certainly isn't super critical. Simply implement a PCT for ECDH and move on. As mandated by SP800-56Arev3, 5.6.2.1.4, the PCT involves recomputing the public key and comparing it against the one under test. Signed-off-by: Nicolai Stange Signed-off-by: Jeremy Allison --- crypto/ecdh.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crypto/ecdh.c b/crypto/ecdh.c index 72cfd15901563..e069c7bb51164 100644 --- a/crypto/ecdh.c +++ b/crypto/ecdh.c @@ -11,6 +11,7 @@ #include #include #include +#include struct ecdh_ctx { unsigned int curve_id; @@ -99,6 +100,36 @@ static int ecdh_compute_value(struct kpp_request *req) ctx->private_key, public_key); buf = public_key; nbytes = public_key_sz; + + /* + * SP800-56Arev3, 5.6.2.1.4: ("Owner Assurance of + * Pair-wise Consistency"): recompute the public key + * and check if the results match. + */ + if (fips_enabled) { + u64 *public_key_pct; + + if (ret < 0) + goto free_all; + + public_key_pct = kmalloc(public_key_sz, GFP_KERNEL); + if (!public_key_pct) { + ret = -ENOMEM; + goto free_all; + } + + ret = ecc_make_pub_key(ctx->curve_id, ctx->ndigits, + ctx->private_key, + public_key_pct); + if (ret < 0) { + kfree(public_key_pct); + goto free_all; + } + + if (memcmp(public_key, public_key_pct, public_key_sz)) + panic("ECDH PCT failed in FIPS mode"); + kfree(public_key_pct); + } } if (ret < 0) From dc34199884a8b815c3f35abd1ac11d5964528e4e Mon Sep 17 00:00:00 2001 From: Jason Rodriguez Date: Mon, 30 Sep 2024 12:57:14 -0400 Subject: [PATCH 68/98] crypto: essiv - Zeroize keys on exit in essiv_aead_setkey() In essiv_aead_setkey(), use the same logic as crypto_authenc_esn_setkey() to zeroize keys on exit. [Sultan: touched up commit message] Signed-off-by: Jason Rodriguez --- crypto/essiv.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crypto/essiv.c b/crypto/essiv.c index 1c00c3324058c..b715ec33e3eaf 100644 --- a/crypto/essiv.c +++ b/crypto/essiv.c @@ -114,13 +114,16 @@ static int essiv_aead_setkey(struct crypto_aead *tfm, const u8 *key, crypto_shash_update(desc, keys.enckey, keys.enckeylen) ?: crypto_shash_finup(desc, keys.authkey, keys.authkeylen, salt); if (err) - return err; + goto out; crypto_cipher_clear_flags(tctx->essiv_cipher, CRYPTO_TFM_REQ_MASK); crypto_cipher_set_flags(tctx->essiv_cipher, crypto_aead_get_flags(tfm) & CRYPTO_TFM_REQ_MASK); - return crypto_cipher_setkey(tctx->essiv_cipher, salt, + err = crypto_cipher_setkey(tctx->essiv_cipher, salt, crypto_shash_digestsize(tctx->hash)); +out: + memzero_explicit(&keys, sizeof(keys)); + return err; } static int essiv_aead_setauthsize(struct crypto_aead *tfm, From 3b8f720029970169b659aa742b11c7b656b49dac Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Wed, 11 Jun 2025 14:16:35 -0700 Subject: [PATCH 69/98] crypto: drbg - Align buffers to at least a cache line None of the ciphers used by the DRBG have an alignment requirement; thus, they all return 0 from .crypto_init, resulting in inconsistent alignment across all buffers. Align all buffers to at least a cache line to improve performance. This is especially useful when multiple DRBG instances are used, since it prevents false sharing of cache lines between the different instances. Signed-off-by: Sultan Alsawaf --- crypto/drbg.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crypto/drbg.c b/crypto/drbg.c index 20321c8140e0d..250afd830b898 100644 --- a/crypto/drbg.c +++ b/crypto/drbg.c @@ -1292,6 +1292,12 @@ static inline int drbg_alloc_state(struct drbg_state *drbg) if (ret < 0) goto err; + /* + * Align to at least a cache line for better performance. This also + * prevents false sharing of cache lines between different instances. + */ + ret = max(ret, L1_CACHE_BYTES - 1); + drbg->Vbuf = kmalloc(drbg_statelen(drbg) + ret, GFP_KERNEL); if (!drbg->Vbuf) { ret = -ENOMEM; From 6af126b32963194ff4a5aecc231faf2521209a89 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Wed, 18 Jun 2025 23:42:08 -0700 Subject: [PATCH 70/98] mm/gup: introduce pin_user_pages_fast_only() Like pin_user_pages_fast(), but with the internal-only FOLL_FAST_ONLY flag. This complements the get_user_pages*() API, which already has get_user_pages_fast_only(). Signed-off-by: Sultan Alsawaf --- include/linux/mm.h | 2 ++ mm/gup.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index d2acffe4c8009..501f0212ac0e7 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -2616,6 +2616,8 @@ int get_user_pages_fast(unsigned long start, int nr_pages, unsigned int gup_flags, struct page **pages); int pin_user_pages_fast(unsigned long start, int nr_pages, unsigned int gup_flags, struct page **pages); +int pin_user_pages_fast_only(unsigned long start, int nr_pages, + unsigned int gup_flags, struct page **pages); void folio_add_pin(struct folio *folio); int account_locked_vm(struct mm_struct *mm, unsigned long pages, bool inc); diff --git a/mm/gup.c b/mm/gup.c index 960e4401320aa..737eceda0c6bf 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -3539,6 +3539,34 @@ int pin_user_pages_fast(unsigned long start, int nr_pages, } EXPORT_SYMBOL_GPL(pin_user_pages_fast); +/** + * pin_user_pages_fast_only() - pin user pages in memory + * @start: starting user address + * @nr_pages: number of pages from start to pin + * @gup_flags: flags modifying pin behaviour + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long. + * + * Like pin_user_pages_fast() except it's IRQ-safe in that it won't fall back to + * the regular GUP. + * + * If the architecture does not support this function, simply return with no + * pages pinned. + * + * Careful, careful! COW breaking can go either way, so a non-write + * access can get ambiguous page results. If you call this function without + * 'write' set, you'd better be sure that you're ok with that ambiguity. + */ +int pin_user_pages_fast_only(unsigned long start, int nr_pages, + unsigned int gup_flags, struct page **pages) +{ + if (!is_valid_gup_args(pages, NULL, &gup_flags, + FOLL_PIN | FOLL_FAST_ONLY)) + return -EINVAL; + return gup_fast_fallback(start, nr_pages, gup_flags, pages); +} +EXPORT_SYMBOL_GPL(pin_user_pages_fast_only); + /** * pin_user_pages_remote() - pin pages of a remote process * From 02fe5d7c4eeac18267c1f2071e6d417e77e46783 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 24 Jun 2025 15:16:34 -0700 Subject: [PATCH 71/98] crypto: rng - Convert crypto_default_rng_refcnt into an unsigned int There is no reason this refcount should be a signed int. Convert it to an unsigned int, thereby also making it less likely to ever overflow. Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index 3c9b158ac2b07..d90e07f6c966f 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -31,7 +31,7 @@ static struct crypto_rng *crypto_reseed_rng; static ____cacheline_aligned_in_smp DEFINE_MUTEX(crypto_default_rng_lock); struct crypto_rng *crypto_default_rng; EXPORT_SYMBOL_GPL(crypto_default_rng); -static int crypto_default_rng_refcnt; +static unsigned int crypto_default_rng_refcnt; int crypto_rng_reset(struct crypto_rng *tfm, const u8 *seed, unsigned int slen) { @@ -157,7 +157,7 @@ void crypto_put_default_rng(void) EXPORT_SYMBOL_GPL(crypto_put_default_rng); #if defined(CONFIG_CRYPTO_RNG) || defined(CONFIG_CRYPTO_RNG_MODULE) -static int crypto_del_rng(struct crypto_rng **rngp, int *refcntp, +static int crypto_del_rng(struct crypto_rng **rngp, unsigned int *refcntp, struct mutex *lock) { int err = -EBUSY; From ce9d08d5c5366e24f89c120dc6503c25b27b15fd Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 24 Jun 2025 15:31:00 -0700 Subject: [PATCH 72/98] crypto: rng - Fix priority inversions due to mutex locks Since crypto_devrandom_read_iter() is invoked directly by user tasks and is accessible by every task in the system, there are glaring priority inversions on crypto_reseed_rng_lock and crypto_default_rng_lock. Tasks of arbitrary scheduling priority access crypto_devrandom_read_iter(). When a low-priority task owns one of the mutex locks, higher-priority tasks waiting on that mutex lock are stalled until the low-priority task is done. Fix the priority inversions by converting the mutex locks into rt_mutex locks which have PI support. Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index d90e07f6c966f..41aeae0e6bf90 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include @@ -26,9 +26,9 @@ #include "internal.h" -static ____cacheline_aligned_in_smp DEFINE_MUTEX(crypto_reseed_rng_lock); +static ____cacheline_aligned_in_smp DEFINE_RT_MUTEX(crypto_reseed_rng_lock); static struct crypto_rng *crypto_reseed_rng; -static ____cacheline_aligned_in_smp DEFINE_MUTEX(crypto_default_rng_lock); +static ____cacheline_aligned_in_smp DEFINE_RT_MUTEX(crypto_default_rng_lock); struct crypto_rng *crypto_default_rng; EXPORT_SYMBOL_GPL(crypto_default_rng); static unsigned int crypto_default_rng_refcnt; @@ -138,11 +138,11 @@ int crypto_get_default_rng(void) { int err; - mutex_lock(&crypto_default_rng_lock); + rt_mutex_lock(&crypto_default_rng_lock); err = crypto_get_rng(&crypto_default_rng); if (!err) crypto_default_rng_refcnt++; - mutex_unlock(&crypto_default_rng_lock); + rt_mutex_unlock(&crypto_default_rng_lock); return err; } @@ -150,19 +150,19 @@ EXPORT_SYMBOL_GPL(crypto_get_default_rng); void crypto_put_default_rng(void) { - mutex_lock(&crypto_default_rng_lock); + rt_mutex_lock(&crypto_default_rng_lock); crypto_default_rng_refcnt--; - mutex_unlock(&crypto_default_rng_lock); + rt_mutex_unlock(&crypto_default_rng_lock); } EXPORT_SYMBOL_GPL(crypto_put_default_rng); #if defined(CONFIG_CRYPTO_RNG) || defined(CONFIG_CRYPTO_RNG_MODULE) static int crypto_del_rng(struct crypto_rng **rngp, unsigned int *refcntp, - struct mutex *lock) + struct rt_mutex *lock) { int err = -EBUSY; - mutex_lock(lock); + rt_mutex_lock(lock); if (refcntp && *refcntp) goto out; @@ -172,7 +172,7 @@ static int crypto_del_rng(struct crypto_rng **rngp, unsigned int *refcntp, err = 0; out: - mutex_unlock(lock); + rt_mutex_unlock(lock); return err; } @@ -257,7 +257,7 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) * a separate mutex (drbg->drbg_mutex) around the * reseed-and-generate operation. */ - mutex_lock(&crypto_reseed_rng_lock); + rt_mutex_lock(&crypto_reseed_rng_lock); /* If crypto_default_rng is not set, it will be seeded * at creation in __crypto_get_default_rng and thus no @@ -268,7 +268,7 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) ret = crypto_get_rng(&crypto_reseed_rng); if (ret) { - mutex_unlock(&crypto_reseed_rng_lock); + rt_mutex_unlock(&crypto_reseed_rng_lock); return ret; } @@ -307,7 +307,7 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) } if (reseed) - mutex_unlock(&crypto_reseed_rng_lock); + rt_mutex_unlock(&crypto_reseed_rng_lock); else crypto_put_default_rng(); memzero_explicit(tmp, sizeof(tmp)); From 96934389be7633715bfc7ce0f3f9c602d76b4a30 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Fri, 21 Aug 2026 11:17:39 -0700 Subject: [PATCH 73/98] crypto: sha1 - Add FIPS known answer test for lib/crypto path The kernel has two SHA-1 code paths: the crypto API (crypto/sha1_generic.c), which is covered by testmgr's FIPS self-tests at algorithm registration time, and the library implementation (lib/crypto/sha1.c), which provides sha1_init() and sha1_transform() directly to in-kernel callers that bypass the crypto API entirely. When booting with fips=1, the library path was not independently validated, meaning any corruption or miscompilation of the lib/crypto SHA-1 code would go undetected. This is a gap in FIPS 140 coverage since several subsystems use the library functions directly. Add a known answer test that runs at subsys_initcall time when CONFIG_CRYPTO_FIPS is enabled and fips_enabled is set. The test computes SHA-1 over the NIST one-block test message "abc" (pre-padded to a full 64-byte SHA-1 input block) using the library's own sha1_init() and sha1_transform() functions, and compares the result against the known digest a9993e364706816aba3e25717850c26c9cd0d89d. On mismatch the kernel panics, consistent with FIPS 140 self-test failure handling elsewhere in the tree (e.g. the 6.18 HMAC-SHA-1 KAT in the same file and DRBG self-tests in crypto/). The test vector data and expected digest are marked __initconst so they are freed after boot, and the workspace buffer is scrubbed with memzero_explicit() after use. No header, Kconfig, or Makefile changes are required: SHA1_BLOCK_SIZE, SHA1_DIGEST_WORDS, and SHA1_WORKSPACE_WORDS are already provided by include/crypto/sha1.h, and CONFIG_CRYPTO_FIPS already exists in crypto/Kconfig. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/crypto/sha1.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/crypto/sha1.c b/lib/crypto/sha1.c index ebb60519ae939..955617749e71a 100644 --- a/lib/crypto/sha1.c +++ b/lib/crypto/sha1.c @@ -137,5 +137,51 @@ void sha1_init(__u32 *buf) } EXPORT_SYMBOL(sha1_init); +#if defined(CONFIG_CRYPTO_FIPS) && !defined(__DISABLE_EXPORTS) +#include +#include + +static int __init sha1_mod_init(void) +{ + if (fips_enabled) { + /* + * FIPS known answer test for SHA-1. + * Input: NIST one-block message "abc" (pre-padded). + * Expected digest: a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d + */ + static const u8 fips_input[SHA1_BLOCK_SIZE] __initconst = { + 0x61, 0x62, 0x63, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + }; + static const __u32 fips_digest[SHA1_DIGEST_WORDS] __initconst = { + 0xa9993e36, 0x4706816a, 0xba3e2571, + 0x7850c26c, 0x9cd0d89d, + }; + __u32 digest[SHA1_DIGEST_WORDS]; + __u32 workspace[SHA1_WORKSPACE_WORDS]; + + sha1_init(digest); + sha1_transform(digest, (const char *)fips_input, workspace); + memzero_explicit(workspace, sizeof(workspace)); + + if (memcmp(digest, fips_digest, sizeof(digest)) != 0) + panic("sha1: FIPS self-test failed\n"); + } + return 0; +} +subsys_initcall(sha1_mod_init); + +static void __exit sha1_mod_exit(void) +{ +} +module_exit(sha1_mod_exit); +#endif + MODULE_DESCRIPTION("SHA-1 Algorithm"); MODULE_LICENSE("GPL"); From 8ab7bf1e8017bad1b19bc0f43b93a6159081159d Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Fri, 21 Aug 2026 11:28:06 -0700 Subject: [PATCH 74/98] crypto: sha256 - Add FIPS known answer test for lib/crypto path The kernel has two SHA-256 code paths: the crypto API (crypto/sha256_generic.c), which is covered by testmgr's FIPS self-tests at algorithm registration time, and the library implementation (lib/crypto/sha256.c), which provides sha256_init(), sha256_update(), sha256_final(), and the sha256() one-shot function directly to in-kernel callers that bypass the crypto API entirely. When booting with fips=1, the library path was not independently validated, meaning any corruption or miscompilation of the lib/crypto SHA-256 code would go undetected. This is a gap in FIPS 140 coverage since several subsystems use the library functions directly. Add a known answer test that runs at subsys_initcall time when CONFIG_CRYPTO_FIPS is enabled and fips_enabled is set. The test uses the sha256() one-shot function to hash the NIST one-block test message "abc", exercising the full sha256_init/update/final path including the underlying sha256_transform compression function. The result is compared against the known digest ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad. On mismatch the kernel panics, consistent with FIPS 140 self-test failure handling elsewhere in the tree. Unlike the SHA-1 library KAT (which could only test the raw transform since the 6.12 lib/crypto/sha1.c lacks update/final), this test covers the complete hash pipeline including padding and finalization logic. The test vector data and expected digest are marked __initconst so they are freed after boot. No header, Kconfig, or Makefile changes are required. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/crypto/sha256.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/crypto/sha256.c b/lib/crypto/sha256.c index 04c1f2557e6c2..58490a67be245 100644 --- a/lib/crypto/sha256.c +++ b/lib/crypto/sha256.c @@ -165,5 +165,43 @@ void sha256(const u8 *data, unsigned int len, u8 *out) } EXPORT_SYMBOL(sha256); +#if defined(CONFIG_CRYPTO_FIPS) && !defined(__DISABLE_EXPORTS) +#include +#include + +static int __init sha256_mod_init(void) +{ + if (fips_enabled) { + /* + * FIPS known answer test for SHA-256. + * Input: NIST one-block message "abc". + * Expected digest: + * ba7816bf 8f01cfea 414140de 5dae2223 + * b00361a3 96177a9c b410ff61 f20015ad + */ + static const u8 fips_input[] __initconst = { 'a', 'b', 'c' }; + static const u8 fips_digest[SHA256_DIGEST_SIZE] __initconst = { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad, + }; + u8 out[SHA256_DIGEST_SIZE]; + + sha256(fips_input, sizeof(fips_input), out); + + if (memcmp(out, fips_digest, sizeof(out)) != 0) + panic("sha256: FIPS self-test failed\n"); + } + return 0; +} +subsys_initcall(sha256_mod_init); + +static void __exit sha256_mod_exit(void) +{ +} +module_exit(sha256_mod_exit); +#endif + MODULE_DESCRIPTION("SHA-256 Algorithm"); MODULE_LICENSE("GPL"); From ba20357571a9b1dc2d6478457b64ce45272eda3f Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 16 Apr 2026 17:23:45 -0700 Subject: [PATCH 75/98] When in fips mode, self-test errors must panic. Requested by the lab. Signed-off-by: Jeremy Allison --- lib/crypto/aesgcm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/crypto/aesgcm.c b/lib/crypto/aesgcm.c index 6bba6473fdf34..1aa5b325935b9 100644 --- a/lib/crypto/aesgcm.c +++ b/lib/crypto/aesgcm.c @@ -12,6 +12,7 @@ #include #include +#include static void aesgcm_encrypt_block(const struct crypto_aes_ctx *ctx, void *dst, const void *src) @@ -702,6 +703,8 @@ static int __init libaesgcm_init(void) if (aesgcm_expandkey(&ctx, aesgcm_tv[i].key, aesgcm_tv[i].klen, aesgcm_tv[i].clen - plen)) { pr_err("aesgcm_expandkey() failed on vector %d\n", i); + if (fips_enabled) + panic("aesgcm_expandkey() failed on vector %d\n", i); return -ENODEV; } @@ -710,6 +713,8 @@ static int __init libaesgcm_init(void) aesgcm_tv[i].iv, aesgcm_tv[i].ctext + plen) || memcmp(buf, aesgcm_tv[i].ptext, plen)) { pr_err("aesgcm_decrypt() #1 failed on vector %d\n", i); + if (fips_enabled) + panic("aesgcm_decrypt() #1 failed on vector %d\n", i); return -ENODEV; } @@ -718,6 +723,8 @@ static int __init libaesgcm_init(void) aesgcm_tv[i].alen, aesgcm_tv[i].iv, tagbuf); if (memcmp(buf, aesgcm_tv[i].ctext, plen)) { pr_err("aesgcm_encrypt() failed on vector %d\n", i); + if (fips_enabled) + panic("aesgcm_encrypt() failed on vector %d\n", i); return -ENODEV; } @@ -726,6 +733,8 @@ static int __init libaesgcm_init(void) aesgcm_tv[i].alen, aesgcm_tv[i].iv, tagbuf) || memcmp(buf, aesgcm_tv[i].ptext, plen)) { pr_err("aesgcm_decrypt() #2 failed on vector %d\n", i); + if (fips_enabled) + panic("aesgcm_decrypt() #2 failed on vector %d\n", i); return -ENODEV; } } From dd33b139eb0e2af49ff25c5c64253fd4fe334403 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 16 Apr 2026 17:24:36 -0700 Subject: [PATCH 76/98] When in fips mode, self-test errors must panic. Requested by the lab. Signed-off-by: Jeremy Allison --- lib/crypto/aescfb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/crypto/aescfb.c b/lib/crypto/aescfb.c index 749dc1258a44b..3072750c39080 100644 --- a/lib/crypto/aescfb.c +++ b/lib/crypto/aescfb.c @@ -11,6 +11,7 @@ #include #include +#include static void aescfb_encrypt_block(const struct crypto_aes_ctx *ctx, void *dst, const void *src) @@ -220,6 +221,8 @@ static int __init libaescfb_init(void) if (aes_expandkey(&ctx, aescfb_tv[i].key, aescfb_tv[i].klen)) { pr_err("aes_expandkey() failed on vector %d\n", i); + if (fips_enabled) + panic("aes_expandkey() failed on vector %d\n", i); return -ENODEV; } @@ -227,6 +230,8 @@ static int __init libaescfb_init(void) aescfb_tv[i].iv); if (memcmp(buf, aescfb_tv[i].ctext, aescfb_tv[i].len)) { pr_err("aescfb_encrypt() #1 failed on vector %d\n", i); + if (fips_enabled) + panic("aescfb_encrypt() #1 failed on vector %d\n", i); return -ENODEV; } @@ -234,6 +239,8 @@ static int __init libaescfb_init(void) aescfb_decrypt(&ctx, buf, buf, aescfb_tv[i].len, aescfb_tv[i].iv); if (memcmp(buf, aescfb_tv[i].ptext, aescfb_tv[i].len)) { pr_err("aescfb_decrypt() failed on vector %d\n", i); + if (fips_enabled) + panic("aescfb_decrypt() failed on vector %d\n", i); return -ENODEV; } @@ -241,6 +248,8 @@ static int __init libaescfb_init(void) aescfb_encrypt(&ctx, buf, buf, aescfb_tv[i].len, aescfb_tv[i].iv); if (memcmp(buf, aescfb_tv[i].ctext, aescfb_tv[i].len)) { pr_err("aescfb_encrypt() #2 failed on vector %d\n", i); + if (fips_enabled) + panic("aescfb_encrypt() #2 failed on vector %d\n", i); return -ENODEV; } From d880704d17ca0ead747211630efea756358e4de5 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 16 Apr 2026 20:05:57 -0700 Subject: [PATCH 77/98] In crypto_kdf108_ctr_generate() there is no minimum length check for the digest to be generated - it must be at least 112 bits. Requested by the lab. Signed-off-by: Jeremy Allison --- crypto/kdf_sp800108.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crypto/kdf_sp800108.c b/crypto/kdf_sp800108.c index c3f9938e1ad27..50f90a16d7ddd 100644 --- a/crypto/kdf_sp800108.c +++ b/crypto/kdf_sp800108.c @@ -25,6 +25,10 @@ int crypto_kdf108_ctr_generate(struct crypto_shash *kmd, int err = 0; u8 *dst_orig = dst; + /* SP800-131Arev2: minimum 112-bit output length */ + if (fips_enabled && dlen < 112 / 8) + return -EINVAL; + desc->tfm = kmd; while (dlen) { From 6fb00963cba70685f2e0a58a307ef114c27659d2 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Mon, 24 Aug 2026 13:41:53 -0700 Subject: [PATCH 78/98] crypto: Kconfig - Make CRYPTO_FIPS depend on the DRBG being built-in When FIPS mode is enabled (via fips=1), there is an absolute need for the DRBG to be available. This is at odds with the fact that the DRBG can be built as a module when in FIPS mode, leaving critical RNG functionality at the whims of userspace. Userspace could simply rmmod the DRBG module, or not provide it at all and thus a different stdrng algorithm could be used without anyone noticing. Additionally, when running a FIPS-enabled userspace, modprobe itself may perform a getrandom() syscall _before_ loading a given module. As a result, there's a possible deadlock scenario where the RNG core (crypto/rng.c) initializes _before_ the DRBG, thereby installing its getrandom() override without having an stdrng algorithm available. Then, when userspace calls getrandom() which redirects to the override in crypto/rng.c, crypto_alloc_rng("stdrng") invokes the UMH (modprobe) to load the DRBG (which is aliased to stdrng). And *then* that modprobe invocation gets stuck at getrandom() because there's no stdrng algorithm available! There are too many risks that come with allowing the DRBG and RNG core to be modular for FIPS mode. Therefore, make CRYPTO_FIPS require the DRBG to be built-in, which in turn makes the DRBG require the RNG core to be built-in. That way, it's guaranteed for these drivers to be built-in when running in FIPS mode. Also clean up the CRYPTO_FIPS option name and remove the CRYPTO_ANSI_CPRNG dependency since it's obsolete for FIPS now. Signed-off-by: Sultan Alsawaf Signed-off-by: Jonathan Maple Signed-off-by: Jeremy Allison --- crypto/Kconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/Kconfig b/crypto/Kconfig index f6f2cd8c9d1c1..2f2246c9c5242 100644 --- a/crypto/Kconfig +++ b/crypto/Kconfig @@ -24,12 +24,12 @@ if CRYPTO menu "Crypto core or helper" config CRYPTO_FIPS - bool "FIPS 200 compliance" - depends on (CRYPTO_ANSI_CPRNG || CRYPTO_DRBG) && !CRYPTO_MANAGER_DISABLE_TESTS + bool "FIPS compliance" + depends on CRYPTO_DRBG=y && !CRYPTO_MANAGER_DISABLE_TESTS depends on (MODULE_SIG || !MODULES) help This option enables the fips boot option which is - required if you want the system to operate in a FIPS 200 + required if you want the system to operate in a FIPS certification. You should say no unless you know what this is. From 1fbab9aeb77f66e48138bc9e0aeb732689dcccc1 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Thu, 2 Oct 2025 17:45:39 +0800 Subject: [PATCH 79/98] crypto: rng - Ensure set_ent is always present cve CVE-2025-40109 commit c0d36727bf39bb16ef0a67ed608e279535ebf0da upstream. Ensure that set_ent is always set since only drbg provides it. Fixes: 77ebdabe8de7 ("crypto: af_alg - add extra parameters for DRBG interface") Reported-by: Yiqi Sun Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- crypto/rng.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crypto/rng.c b/crypto/rng.c index 41aeae0e6bf90..e8e9e2bfdf03d 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -187,6 +187,11 @@ int crypto_del_default_rng(void) EXPORT_SYMBOL_GPL(crypto_del_default_rng); #endif +static void rng_default_set_ent(struct crypto_rng *tfm, const u8 *data, + unsigned int len) +{ +} + int crypto_register_rng(struct rng_alg *alg) { struct crypto_alg *base = &alg->base; @@ -198,6 +203,9 @@ int crypto_register_rng(struct rng_alg *alg) base->cra_flags &= ~CRYPTO_ALG_TYPE_MASK; base->cra_flags |= CRYPTO_ALG_TYPE_RNG; + if (!alg->set_ent) + alg->set_ent = rng_default_set_ent; + return crypto_register_alg(base); } EXPORT_SYMBOL_GPL(crypto_register_rng); From 79c8e8063d51a1c6d3897afa70c1152eabc01c76 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 5 Mar 2026 11:19:36 -0800 Subject: [PATCH 80/98] crypto: rng - Override drivers/char/random in FIPS mode commit-author Herbert Xu commit 8b0beca705b3877e24cccdd672422c66bbd75635 commit-source https://gitlab.com/cki-project/kernel-ark Upstream Status: RHEL only Restore the changes to use the crypto RNG in drivers/char/random which were reverted after 5.18. This reverts commit 297bcb88233101e8d5062729ff3a5f989bad1c3b. This also brings the code up-to-date with respect to centos-stream commit 9de3a7339793d3c516b9305a8854267156f90c53 so that changes that were made after the kernel-ark revert have been brought in. Signed-off-by: Herbert Xu Signed-off-by: Jeremy Allison --- include/linux/crypto.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/crypto.h b/include/linux/crypto.h index cd78d16ee5d65..8c7f5331ba4fa 100644 --- a/include/linux/crypto.h +++ b/include/linux/crypto.h @@ -133,7 +133,8 @@ #define CRYPTO_TFM_REQ_FORBID_WEAK_KEYS 0x00000100 #define CRYPTO_TFM_REQ_MAY_SLEEP 0x00000200 #define CRYPTO_TFM_REQ_MAY_BACKLOG 0x00000400 -#define CRYPTO_TFM_REQ_NEED_RESEED 0x00000800 +#define CRYPTO_TFM_REQ_ON_STACK 0x00000800 +#define CRYPTO_TFM_REQ_NEED_RESEED 0x00001000 #define CRYPTO_TFM_FIPS_COMPLIANCE 0x80000000 From 578b9ac2b89ad0ff816642db53862163c2659ecc Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 27 Jun 2025 19:06:18 -0700 Subject: [PATCH 81/98] crypto: rng - Implement fast per-CPU DRBG instances When the kernel is booted with fips=1, the RNG exposed to userspace is hijacked away from the CRNG and redirects to crypto_devrandom_read_iter(), which utilizes the DRBG. Notably, crypto_devrandom_read_iter() maintains just two global DRBG instances _for the entire system_, and the two instances serve separate request types: one instance for GRND_RANDOM requests (crypto_reseed_rng), and one instance for non-GRND_RANDOM requests (crypto_default_rng). So in essence, for requests of a single type, there is just one global RNG for all CPUs in the entire system, which scales _very_ poorly. To make matters worse, the temporary buffer used to ferry data between the DRBG and userspace is woefully small at only 256 bytes, which doesn't do a good job of maximizing throughput from the DRBG. This results in lost performance when userspace requests >256 bytes; it is observed that DRBG throughput improves by 70% on an i9-13900H when the buffer size is increased to 4096 bytes (one page). Going beyond the size of one page up to the DRBG maximum request limit of 65536 bytes produces diminishing returns of only 3% improved throughput in comparison. And going below the size of one page produces progressively less throughput at each power of 2: there's a 5% loss going from 4096 bytes to 2048 bytes and a 9% loss going from 2048 bytes to 1024 bytes. Thus, this implements per-CPU DRBG instances utilizing a page-sized buffer for each CPU to utilize the DRBG itself more effectively. On top of that, for non-GRND_RANDOM requests, the DRBG's operations now occur under a local lock that disables preemption on non-PREEMPT_RT kernels, which not only keeps each CPU's DRBG instance isolated from another, but also improves temporal cache locality while the DRBG actively generates a new string of random bytes. Prefaulting one user destination page at a time is also employed to prevent a DRBG instance from getting blocked on page faults, thereby maximizing the use of the DRBG so that the only bottleneck is the DRBG itself. Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 523 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 442 insertions(+), 81 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index e8e9e2bfdf03d..84ddb1531d67b 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -6,6 +6,9 @@ * * Copyright (c) 2008 Neil Horman * Copyright (c) 2015 Herbert Xu + * + * Copyright (C) 2025 Ctrl IQ, Inc. + * Author: Sultan Alsawaf */ #include @@ -26,13 +29,39 @@ #include "internal.h" -static ____cacheline_aligned_in_smp DEFINE_RT_MUTEX(crypto_reseed_rng_lock); -static struct crypto_rng *crypto_reseed_rng; static ____cacheline_aligned_in_smp DEFINE_RT_MUTEX(crypto_default_rng_lock); struct crypto_rng *crypto_default_rng; EXPORT_SYMBOL_GPL(crypto_default_rng); static unsigned int crypto_default_rng_refcnt; +/* + * Per-CPU RNG instances are only used by crypto_devrandom_rng. The global RNG, + * crypto_default_rng, is only used directly by other drivers. + * + * Per-CPU instances of the DRBG are efficient because the DRBG itself supports + * an arbitrary number of instances and can be seeded on a per-CPU basis. + * + * Specifically, the DRBG is seeded by the CRNG and the Jitter RNG. The CRNG is + * globally accessible and is already per-CPU. And while the Jitter RNG _isn't_ + * per-CPU, creating a DRBG instance also creates a Jitter RNG instance; + * therefore, per-CPU DRBG instances implies per-CPU Jitter RNG instances. + */ +struct cpu_rng_inst { + local_lock_t lock; + struct rt_mutex mlock; + struct crypto_rng *rng; + void *page; +}; + +static DEFINE_PER_CPU_ALIGNED(struct cpu_rng_inst, pcpu_default_rng) = { + .lock = INIT_LOCAL_LOCK(pcpu_default_rng.lock), + .mlock = __RT_MUTEX_INITIALIZER(pcpu_default_rng.mlock) +}; +static DEFINE_PER_CPU_ALIGNED(struct cpu_rng_inst, pcpu_reseed_rng) = { + /* The reseed instances don't use the local lock */ + .mlock = __RT_MUTEX_INITIALIZER(pcpu_reseed_rng.mlock) +}; + int crypto_rng_reset(struct crypto_rng *tfm, const u8 *seed, unsigned int slen) { u8 *buf = NULL; @@ -157,34 +186,54 @@ void crypto_put_default_rng(void) EXPORT_SYMBOL_GPL(crypto_put_default_rng); #if defined(CONFIG_CRYPTO_RNG) || defined(CONFIG_CRYPTO_RNG_MODULE) -static int crypto_del_rng(struct crypto_rng **rngp, unsigned int *refcntp, - struct rt_mutex *lock) -{ - int err = -EBUSY; +#define down_read_del_pcpu_rwsem() down_read(&del_pcpu_rwsem) +#define up_read_del_pcpu_rwsem() up_read(&del_pcpu_rwsem) +static DECLARE_RWSEM(del_pcpu_rwsem); - rt_mutex_lock(lock); - if (refcntp && *refcntp) - goto out; - - crypto_free_rng(*rngp); - *rngp = NULL; +static void crypto_del_pcpu_rng(struct cpu_rng_inst __percpu *pcri) +{ + int cpu; - err = 0; + for_each_possible_cpu(cpu) { + struct cpu_rng_inst *cri = per_cpu_ptr(pcri, cpu); -out: - rt_mutex_unlock(lock); - - return err; + if (cri->rng) { + crypto_free_rng(cri->rng); + cri->rng = NULL; + } + } } int crypto_del_default_rng(void) { - return crypto_del_rng(&crypto_default_rng, &crypto_default_rng_refcnt, - &crypto_default_rng_lock) ?: - crypto_del_rng(&crypto_reseed_rng, NULL, - &crypto_reseed_rng_lock); + bool busy; + + rt_mutex_lock(&crypto_default_rng_lock); + if (!(busy = crypto_default_rng_refcnt)) { + crypto_free_rng(crypto_default_rng); + crypto_default_rng = NULL; + } + rt_mutex_unlock(&crypto_default_rng_lock); + if (busy) + return -EBUSY; + + if (!down_write_trylock(&del_pcpu_rwsem)) + return -EBUSY; + + crypto_del_pcpu_rng(&pcpu_default_rng); + crypto_del_pcpu_rng(&pcpu_reseed_rng); + up_write(&del_pcpu_rwsem); + + return 0; } EXPORT_SYMBOL_GPL(crypto_del_default_rng); +#else +static inline void down_read_del_pcpu_rwsem(void) +{ +} +static inline void up_read_del_pcpu_rwsem(void) +{ +} #endif static void rng_default_set_ent(struct crypto_rng *tfm, const u8 *data, @@ -245,81 +294,345 @@ void crypto_unregister_rngs(struct rng_alg *algs, int count) } EXPORT_SYMBOL_GPL(crypto_unregister_rngs); -static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) +/* + * On non-PREEMPT_RT kernels, local locks disable preemption. When there's no + * rng allocated, one must be allocated by calling crypto_get_rng(), which can + * sleep. Therefore, crypto_get_rng() cannot be called under local_lock(), so if + * our CPU's RNG instance doesn't have an rng allocated, we drop the local lock + * and take a mutex lock instead. After the local lock is dropped, the current + * task can be freely migrated to another CPU, which means that calling + * local_lock() again might not result in the same instance getting locked as + * before. That's why this function exists: to loop on calling local_lock() and + * allocating an rng as needed with crypto_get_rng() until the current CPU's + * instance is found to have an rng allocated. If crypto_get_rng() ever fails, + * this function returns an error even if there are instances for other CPUs + * which _do_ have an rng allocated. + */ +static __always_inline struct cpu_rng_inst * +lock_default_rng(struct crypto_rng **rng) __acquires(&cri->lock) { - struct crypto_rng *rng; - u8 tmp[256]; - ssize_t ret; + struct cpu_rng_inst __percpu *pcri = &pcpu_default_rng; + struct cpu_rng_inst *cri; + int ret; + + while (1) { + local_lock(&pcri->lock); + cri = this_cpu_ptr(pcri); + /* + * cri->rng may have transitioned from non-NULL to NULL, but + * underneath down_read_del_pcpu_rwsem() it can only transition + * from NULL to non-NULL. This may occur on a different CPU, + * thus cri->rng must be read atomically to prevent data races; + * this elides mlock by pairing with the WRITE_ONCE() in the + * slow path below. + * + * + * And if cri->rng is non-NULL, then it is good to go. To avoid + * data races due to load speculation on torn cri->rng loads + * _after_ the NULL check, one of the following is required: + * 1. smp_acquire__after_ctrl_dep() in the if-statement + * 2. All cri->rng reads are performed with READ_ONCE() + * 3. cri->rng is never read again outside this function + * + * Option #3 yields the best performance, so this function + * provides the rng pointer as an output for the caller to use. + */ + *rng = READ_ONCE(cri->rng); + if (likely(*rng)) + return cri; + + /* + * Slow path: there's no rng currently allocated to this instance. + * Release the local lock and acquire this instance's mlock to + * perform the allocation. + * + * Note that this task may be migrated to a different CPU now! + */ + local_unlock(&cri->lock); + rt_mutex_lock(&cri->mlock); + if (!cri->rng) { + struct crypto_rng *new_rng = NULL; + + ret = crypto_get_rng(&new_rng); + if (ret) { + rt_mutex_unlock(&cri->mlock); + break; + } - if (unlikely(!iov_iter_count(iter))) - return 0; + /* + * Pairs with READ_ONCE() above, because we might not be + * on the same CPU anymore as when we first got `cri`. + */ + WRITE_ONCE(cri->rng, new_rng); + } + rt_mutex_unlock(&cri->mlock); + } - if (reseed) { - u32 flags = 0; + /* + * Even if this task got migrated to another CPU that _does_ have an rng + * allocated, just bail out if crypto_get_rng() ever fails in order to + * avoid looping forever. + */ + return ERR_PTR(ret); +} - /* If reseeding is requested, acquire a lock on - * crypto_reseed_rng so it is not swapped out until - * the initial random bytes are generated. - * - * The algorithm implementation is also protected with - * a separate mutex (drbg->drbg_mutex) around the - * reseed-and-generate operation. +static __always_inline struct cpu_rng_inst * +lock_reseed_rng(struct crypto_rng **rng) __acquires(&cri->mlock) +{ + struct cpu_rng_inst __percpu *pcri = &pcpu_reseed_rng; + struct cpu_rng_inst *cri; + int ret; + + /* + * Use whichever CPU this task is currently running on, knowing full + * well that the task can freely migrate to other CPUs. The reseed RNG + * requires holding a lock across the entire devrandom read, so that + * another task cannot extract entropy from the same seed. In other + * words, when reseeding is requested, reseeding must be done every time + * every time mlock is acquired. + */ + cri = raw_cpu_ptr(pcri); + rt_mutex_lock(&cri->mlock); + if (likely(cri->rng)) { + /* + * Since this rng instance wasn't just allocated, it needs to be + * explicitly reseeded. New rng instances are seeded on creation + * in crypto_get_rng() and thus don't need explicit reseeding. */ - rt_mutex_lock(&crypto_reseed_rng_lock); + crypto_tfm_set_flags(crypto_rng_tfm(cri->rng), + CRYPTO_TFM_REQ_NEED_RESEED); + } else { + ret = crypto_get_rng(&cri->rng); + if (ret) { + rt_mutex_unlock(&cri->mlock); + return ERR_PTR(ret); + } + } - /* If crypto_default_rng is not set, it will be seeded - * at creation in __crypto_get_default_rng and thus no - * reseeding is needed. - */ - if (crypto_reseed_rng) - flags |= CRYPTO_TFM_REQ_NEED_RESEED; + *rng = cri->rng; + return cri; +} - ret = crypto_get_rng(&crypto_reseed_rng); - if (ret) { - rt_mutex_unlock(&crypto_reseed_rng_lock); - return ret; +#define lock_local_rng(rng, reseed) \ + ({ (reseed) ? lock_reseed_rng(rng) : lock_default_rng(rng); }) + +#define unlock_local_rng(cri, reseed) \ +do { \ + if (reseed) \ + rt_mutex_unlock(&(cri)->mlock); \ + else \ + local_unlock(&(cri)->lock); \ +} while (0) + +static __always_inline void +clear_rng_page(struct cpu_rng_inst *cri, size_t count) +{ + /* For zeroing a whole page, clear_page() is faster than memset() */ + count < PAGE_SIZE ? memset(cri->page, 0, count) : clear_page(cri->page); +} + +static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) +{ + /* lock_local_rng() puts us in atomic context for !reseed on non-RT */ + const bool atomic = !reseed && !IS_ENABLED(CONFIG_PREEMPT_RT); + const bool user_no_reseed = !reseed && user_backed_iter(iter); + size_t ulen, page_dirty_len = 0; + struct cpu_rng_inst *cri; + struct crypto_rng *rng; + void __user *uaddr; + struct page *upage; + ssize_t ret = 0; + + if (unlikely(!iov_iter_count(iter))) + return 0; + + /* Set up the starting user destination address and length */ + if (user_no_reseed) { + if (iter_is_ubuf(iter)) { + uaddr = iter->ubuf + iter->iov_offset; + ulen = iov_iter_count(iter); + } else if (iter_is_iovec(iter)) { + uaddr = iter_iov_addr(iter); + ulen = iter_iov_len(iter); + } else { + /* + * ITER_UBUF and ITER_IOVEC are the only user-backed + * iters. Bug out if a new user-backed iter appears. + */ + BUG(); } + } - rng = crypto_reseed_rng; - crypto_tfm_set_flags(crypto_rng_tfm(rng), flags); - } else { - ret = crypto_get_default_rng(); - if (ret) - return ret; - rng = crypto_default_rng; + /* Prevent rngs from getting deleted from per-CPU RNG instances */ + down_read_del_pcpu_rwsem(); +restart: + /* + * Pin the user page backing the current user destination address, + * potentially prefaulting to allocate a page for the destination. By + * prefaulting without the RNG lock held, the DRBG won't be blocked by + * time spent on page faults for this task, and thus the DRBG can still + * be used by other tasks. + */ + if (user_no_reseed && pin_user_pages_fast((unsigned long)uaddr, 1, + FOLL_WRITE, &upage) != 1) + goto up_rwsem; + + cri = lock_local_rng(&rng, reseed); + if (IS_ERR(cri)) { + if (!ret) + ret = PTR_ERR(cri); + goto unpin_upage; } - for (;;) { - size_t i, copied; + while (1) { + size_t copied, i = min(iov_iter_count(iter), PAGE_SIZE); + bool resched_without_lock = false; int err; - i = min_t(size_t, iov_iter_count(iter), sizeof(tmp)); - err = crypto_rng_get_bytes(rng, tmp, i); + /* + * Generate up to one page at a time, and align to a page + * boundary so we only need to pin one user page at a time. + */ + if (user_no_reseed) + i = min3(i, PAGE_SIZE - offset_in_page(uaddr), ulen); + + /* + * On non-PREEMPT_RT kernels, local locks disable preemption. + * The DRBG's generate() function has a mutex lock, which could + * mean that we'll schedule while atomic if the mutex lock + * sleeps. However, that will never happen if we ensure that + * there's never any contention on the DRBG's mutex lock while + * we're atomic! Our local lock ensures calls to the DRBG are + * always serialized, so there's no contention from here. And + * the DRBG only uses its mutex lock from one other path, when + * an instance of the DRBG is freshly allocated, which we only + * do from crypto_get_rng(). So the DRBG's mutex lock is + * guaranteed to not have contention when we call generate() and + * thus it'll never sleep here. And of course, nothing else in + * generate() ever sleeps. + */ + err = crypto_rng_get_bytes(rng, cri->page, i); if (err) { - ret = ret ?: err; + if (!ret) + ret = err; break; } - copied = copy_to_iter(tmp, i, iter); - ret += copied; + /* + * Record the number of bytes used in cri->page and either copy + * directly to the user address without faulting, or copy to the + * iter which is always backed by kernel memory when !reseed && + * !user_backed_iter(). When reseed == true, the iter may be + * backed by user memory, but we copy to it with the possibility + * of page faults anyway because we need to hold the lock across + * the entire call; this is why a mutex is used instead of a + * local lock for the reseed RNG, to permit sleeping without + * yielding the DRBG instance. + */ + page_dirty_len = max(i, page_dirty_len); + if (user_no_reseed) { + err = copy_to_user_nofault(uaddr, cri->page, i); + if (err >= 0) { + iov_iter_advance(iter, i - err); + ret += i - err; + } + if (err) + break; + } else { + /* + * We know that copying from cri->page is safe, so use + * _copy_to_iter() directly to skip check_copy_size(). + */ + copied = _copy_to_iter(cri->page, i, iter); + ret += copied; + if (copied != i) + break; + } - if (!iov_iter_count(iter) || copied != i) + /* + * Quit when either the requested number of bytes have been + * generated or there is a pending signal. + */ + if (!iov_iter_count(iter) || signal_pending(current)) break; - BUILD_BUG_ON(PAGE_SIZE % sizeof(tmp) != 0); - if (ret % PAGE_SIZE == 0) { - if (signal_pending(current)) - break; - cond_resched(); + /* Compute the next user destination address and length */ + if (user_no_reseed) { + ulen -= i; + if (likely(ulen)) { + uaddr += i; + } else { + /* + * This path is only reachable by ITER_IOVEC + * because ulen is initialized to the request + * size for ITER_UBUF, and therefore ITER_UBUF + * will always quit at the iov_iter_count() + * check above before ulen can become zero. + * + * iter->iov_offset is guaranteed to be zero + * here, so iter_iov_{addr|len}() isn't needed. + */ + uaddr = iter_iov(iter)->iov_base; + ulen = iter_iov(iter)->iov_len; + } + + unpin_user_page(upage); } + + /* + * Reschedule right now if needed and we're not atomic. If we're + * atomic, then we must first drop the lock to reschedule. + */ + if (need_resched()) { + if (atomic) + resched_without_lock = true; + else + cond_resched(); + } + + /* + * Optimistically try to pin the next user page without + * faulting, so we don't need to clear cri->page and drop the + * lock on every iteration. If this fails, we fall back to + * pinning with the option to prefault. + */ + if (user_no_reseed && !resched_without_lock && + pin_user_pages_fast_only((unsigned long)uaddr, 1, + FOLL_WRITE, &upage) == 1) + continue; + + /* + * Restart if either rescheduling is needed (and requires + * dropping the lock since we're atomic) or the optimistic page + * pinning attempt failed. + * + * This always implies `reseed == false`, so unlock_local_rng() + * can just be passed `false` for reseed to eliminate a branch. + */ + if (resched_without_lock || user_no_reseed) { + /* + * Clear the buffer of our latest random bytes before + * unlocking and potentially migrating CPUs, in which + * case we wouldn't have the same `cri` anymore. + */ + clear_rng_page(cri, page_dirty_len); + unlock_local_rng(cri, false); + page_dirty_len = 0; + if (resched_without_lock) + cond_resched(); + goto restart; + } } - if (reseed) - rt_mutex_unlock(&crypto_reseed_rng_lock); - else - crypto_put_default_rng(); - memzero_explicit(tmp, sizeof(tmp)); - return ret ? ret : -EFAULT; + if (page_dirty_len) + clear_rng_page(cri, page_dirty_len); + unlock_local_rng(cri, reseed); +unpin_upage: + if (user_no_reseed) + unpin_user_page(upage); +up_rwsem: + up_read_del_pcpu_rwsem(); + return ret ? ret : -EFAULT; } static const struct random_extrng crypto_devrandom_rng = { @@ -327,23 +640,71 @@ static const struct random_extrng crypto_devrandom_rng = { .owner = THIS_MODULE, }; -static int __init crypto_rng_init(void) +static void free_pcpu_inst(struct cpu_rng_inst __percpu *pcri) { - int err; + int cpu; - if (fips_enabled) { - err = crypto_get_default_rng(); - if (err) - return err; - crypto_put_default_rng(); - random_register_extrng(&crypto_devrandom_rng); + for_each_possible_cpu(cpu) { + struct cpu_rng_inst *cri = per_cpu_ptr(pcri, cpu); + + if (cri->rng) + crypto_free_rng(cri->rng); + + free_page((unsigned long)cri->page); + } +} + +static int __init alloc_pcpu_inst(struct cpu_rng_inst __percpu *pcri) +{ + int cpu; + + for_each_possible_cpu(cpu) { + struct cpu_rng_inst *cri = per_cpu_ptr(pcri, cpu); + + cri->page = (void *)__get_free_page(GFP_KERNEL); + if (!cri->page) + goto err_page_alloc; + + local_lock_init(&cri->lock); } + return 0; + +err_page_alloc: + while (cpu--) + free_page((unsigned long)per_cpu_ptr(pcri, cpu)->page); + return -ENOMEM; +} + +static int __init crypto_rng_init(void) +{ + int ret; + + if (!fips_enabled) + return 0; + + ret = alloc_pcpu_inst(&pcpu_default_rng); + if (ret) + return ret; + + ret = alloc_pcpu_inst(&pcpu_reseed_rng); + if (ret) + goto free_pcpu_default; + + random_register_extrng(&crypto_devrandom_rng); + return 0; + +free_pcpu_default: + free_pcpu_inst(&pcpu_default_rng); + return ret; } static void __exit crypto_rng_exit(void) { random_unregister_extrng(); + + free_pcpu_inst(&pcpu_default_rng); + free_pcpu_inst(&pcpu_reseed_rng); } late_initcall(crypto_rng_init); From 3339dd5a2baa7f5be1a5d74ea5d019fcad40da93 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 1 Aug 2025 15:19:15 -0700 Subject: [PATCH 82/98] random: Restrict extrng registration to init time It is technically a risk to permit extrng registration by modules after kernel init completes. Since there is only one user of the extrng interface and it is imperative that it is the _only_ registered extrng for FIPS compliance, restrict the extrng registration interface to only permit registration during kernel init and only from built-in drivers. This also eliminates the risks associated with the extrng interface itself being designed to solely accommodate a single registration, which would therefore permit the registered extrng to be overridden or even removed by an unrelated module. Signed-off-by: Sultan Alsawaf Signed-off-by: Jonathan Maple --- crypto/rng.c | 12 +------- drivers/char/random.c | 70 ++++++++---------------------------------- include/linux/random.h | 17 +++++----- 3 files changed, 20 insertions(+), 79 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index 84ddb1531d67b..551236f4278c5 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -636,8 +636,7 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) } static const struct random_extrng crypto_devrandom_rng = { - .extrng_read_iter = crypto_devrandom_read_iter, - .owner = THIS_MODULE, + .extrng_read_iter = crypto_devrandom_read_iter }; static void free_pcpu_inst(struct cpu_rng_inst __percpu *pcri) @@ -699,16 +698,7 @@ static int __init crypto_rng_init(void) return ret; } -static void __exit crypto_rng_exit(void) -{ - random_unregister_extrng(); - - free_pcpu_inst(&pcpu_default_rng); - free_pcpu_inst(&pcpu_reseed_rng); -} - late_initcall(crypto_rng_init); -module_exit(crypto_rng_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Random Number Generator"); diff --git a/drivers/char/random.c b/drivers/char/random.c index 5c09d6b360623..cb5a4205e16e6 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -51,7 +51,6 @@ #include #include #include -#include #include #include #include @@ -341,7 +340,7 @@ static void crng_fast_key_erasure(u8 key[CHACHA_KEY_SIZE], /* * Hook for external RNG. */ -static const struct random_extrng __rcu *extrng; +static const struct random_extrng *extrng __ro_after_init; /* * This function returns a ChaCha state that you may use for generating @@ -999,18 +998,12 @@ void __init add_bootloader_randomness(const void *buf, size_t len) credit_init_bits(len * 8); } -void random_register_extrng(const struct random_extrng *rng) +void __init random_register_extrng(const struct random_extrng *rng) { - rcu_assign_pointer(extrng, rng); + /* Don't allow the registered extrng to be overridden */ + BUG_ON(extrng); + extrng = rng; } -EXPORT_SYMBOL_GPL(random_register_extrng); - -void random_unregister_extrng(void) -{ - RCU_INIT_POINTER(extrng, NULL); - synchronize_rcu(); -} -EXPORT_SYMBOL_GPL(random_unregister_extrng); #if IS_ENABLED(CONFIG_VMGENID) static BLOCKING_NOTIFIER_HEAD(vmfork_chain); @@ -1421,7 +1414,6 @@ SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags { struct iov_iter iter; int ret; - const struct random_extrng *rng; if (flags & ~(GRND_NONBLOCK | GRND_RANDOM | GRND_INSECURE)) return -EINVAL; @@ -1433,19 +1425,11 @@ SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; - rcu_read_lock(); - rng = rcu_dereference(extrng); - if (rng && !try_module_get(rng->owner)) - rng = NULL; - rcu_read_unlock(); - - if (rng) { + if (extrng) { ret = import_ubuf(ITER_DEST, ubuf, len, &iter); if (unlikely(ret)) return ret; - ret = rng->extrng_read_iter(&iter, !!(flags & GRND_RANDOM)); - module_put(rng->owner); - return ret; + return extrng->extrng_read_iter(&iter, !!(flags & GRND_RANDOM)); } if (!crng_ready() && !(flags & GRND_INSECURE)) { @@ -1616,52 +1600,24 @@ static int random_fasync(int fd, struct file *filp, int on) static int random_open(struct inode *inode, struct file *filp) { - const struct random_extrng *rng; - - rcu_read_lock(); - rng = rcu_dereference(extrng); - if (rng && !try_module_get(rng->owner)) - rng = NULL; - rcu_read_unlock(); - - if (!rng) - return 0; - - filp->f_op = &extrng_random_fops; - filp->private_data = rng->owner; + if (extrng) + filp->f_op = &extrng_random_fops; return 0; } static int urandom_open(struct inode *inode, struct file *filp) { - const struct random_extrng *rng; + if (extrng) + filp->f_op = &extrng_urandom_fops; - rcu_read_lock(); - rng = rcu_dereference(extrng); - if (rng && !try_module_get(rng->owner)) - rng = NULL; - rcu_read_unlock(); - - if (!rng) - return 0; - - filp->f_op = &extrng_urandom_fops; - filp->private_data = rng->owner; - - return 0; -} - -static int extrng_release(struct inode *inode, struct file *filp) -{ - module_put(filp->private_data); return 0; } static ssize_t extrng_read_iter(struct kiocb *kiocb, struct iov_iter *iter) { - return rcu_dereference_raw(extrng)->extrng_read_iter(iter, false); + return extrng->extrng_read_iter(iter, false); } const struct file_operations random_fops = { @@ -1698,7 +1654,6 @@ static const struct file_operations extrng_random_fops = { .compat_ioctl = compat_ptr_ioctl, .fasync = random_fasync, .llseek = noop_llseek, - .release = extrng_release, .splice_read = copy_splice_read, .splice_write = iter_file_splice_write, }; @@ -1711,7 +1666,6 @@ static const struct file_operations extrng_urandom_fops = { .compat_ioctl = compat_ptr_ioctl, .fasync = random_fasync, .llseek = noop_llseek, - .release = extrng_release, .splice_read = copy_splice_read, .splice_write = iter_file_splice_write, }; diff --git a/include/linux/random.h b/include/linux/random.h index 8a52424fd0d50..53b79b68a9546 100644 --- a/include/linux/random.h +++ b/include/linux/random.h @@ -9,13 +9,6 @@ #include -struct iov_iter; - -struct random_extrng { - ssize_t (*extrng_read_iter)(struct iov_iter *iter, bool reseed); - struct module *owner; -}; - struct notifier_block; void add_device_randomness(const void *buf, size_t len); @@ -164,11 +157,15 @@ int random_prepare_cpu(unsigned int cpu); int random_online_cpu(unsigned int cpu); #endif -void random_register_extrng(const struct random_extrng *rng); -void random_unregister_extrng(void); - #ifndef MODULE extern const struct file_operations random_fops, urandom_fops; + +struct iov_iter; +struct random_extrng { + ssize_t (*extrng_read_iter)(struct iov_iter *iter, bool reseed); +}; + +void __init random_register_extrng(const struct random_extrng *rng); #endif #endif /* _LINUX_RANDOM_H */ From c680a8971fe6e8ab57687f96bfb6168e82923ed0 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 25 Jul 2025 14:32:37 -0700 Subject: [PATCH 83/98] crypto: rng - Only allow the DRBG to register as "stdrng" in FIPS mode In FIPS mode, the DRBG must take precedence over all stdrng algorithms. The only problem standing in the way of this is that a different stdrng algorithm could get registered and utilized before the DRBG is registered, and since crypto_alloc_rng() only allocates an stdrng algorithm when there's no existing allocation, this means that it's possible for the wrong stdrng algorithm to remain in use indefinitely. This issue is also often impossible to observe from userspace; an RNG other than the DRBG could be used somewhere in the kernel and userspace would be none the wiser. To ensure this can never happen, only allow stdrng instances from the DRBG to be registered when running in FIPS mode. This works since the previous commit forces the DRBG to be built into the kernel when CONFIG_CRYPTO_FIPS is enabled, so the DRBG's presence is guaranteed when fips_enabled is true. Signed-off-by: Sultan Alsawaf Signed-off-by: Jonathan Maple --- crypto/rng.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crypto/rng.c b/crypto/rng.c index 551236f4278c5..5e601af94ca3f 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -33,6 +33,7 @@ static ____cacheline_aligned_in_smp DEFINE_RT_MUTEX(crypto_default_rng_lock); struct crypto_rng *crypto_default_rng; EXPORT_SYMBOL_GPL(crypto_default_rng); static unsigned int crypto_default_rng_refcnt; +static bool drbg_registered __ro_after_init; /* * Per-CPU RNG instances are only used by crypto_devrandom_rng. The global RNG, @@ -248,6 +249,19 @@ int crypto_register_rng(struct rng_alg *alg) if (alg->seedsize > PAGE_SIZE / 8) return -EINVAL; + /* + * In FIPS mode, the DRBG must take precedence over all other "stdrng" + * algorithms. Therefore, forbid registration of a non-DRBG stdrng in + * FIPS mode. All of the DRBG's driver names are prefixed with "drbg_". + * This also stops new stdrng instances from getting registered after it + * is known that the DRBG is registered, so a new module can't come in + * and pretend to be the DRBG. And when CONFIG_CRYPTO_FIPS is enabled, + * the DRBG is built into the kernel directly; it can't be a module. + */ + if (fips_enabled && !strcmp(base->cra_name, "stdrng") && + (drbg_registered || strncmp(base->cra_driver_name, "drbg_", 5))) + return -EINVAL; + base->cra_type = &crypto_rng_type; base->cra_flags &= ~CRYPTO_ALG_TYPE_MASK; base->cra_flags |= CRYPTO_ALG_TYPE_RNG; @@ -275,6 +289,18 @@ int crypto_register_rngs(struct rng_alg *algs, int count) goto err; } + /* + * Track when the DRBG is registered in FIPS mode. The DRBG calls + * crypto_register_rngs() to register its stdrng instances, and since + * crypto_register_rng() only allows stdrng instances from the DRBG in + * FIPS mode, a successful stdrng registration means it was the DRBG. + * Just check the first alg in the array to see if it's called "stdrng", + * since all of the DRBG's algorithms are named "stdrng". Once + * drbg_registered is set to true, this if-statement is always false. + */ + if (fips_enabled && !strcmp(algs->base.cra_name, "stdrng")) + drbg_registered = true; + return 0; err: From 06a00c32934a32f53253476f2bdca55e5e62305c Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 23 Jun 2026 13:41:31 -0700 Subject: [PATCH 84/98] crypto: rng - Fix tabs vs spaces in the per-CPU DRBG code The 6.18.y version of "crypto: rng - Implement fast per-CPU DRBG instances" picked up a few spots that use spaces where the ciqlts9_6 tree's version [1] uses tabs: the lock_default_rng() comment's numbered list, the line continuations in the unlock_local_rng() macro, and a brace in crypto_devrandom_read_iter(). A nearby block comment was also missing the space before its '*'. Fix them up. [1] https://github.com/ctrliq/kernel-src-tree/commit/b0c560a37518cad09488bab031e1c6e817d65cb8 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index 5e601af94ca3f..5fa9b680e3257 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -356,9 +356,9 @@ lock_default_rng(struct crypto_rng **rng) __acquires(&cri->lock) * And if cri->rng is non-NULL, then it is good to go. To avoid * data races due to load speculation on torn cri->rng loads * _after_ the NULL check, one of the following is required: - * 1. smp_acquire__after_ctrl_dep() in the if-statement - * 2. All cri->rng reads are performed with READ_ONCE() - * 3. cri->rng is never read again outside this function + * 1. smp_acquire__after_ctrl_dep() in the if-statement + * 2. All cri->rng reads are performed with READ_ONCE() + * 3. cri->rng is never read again outside this function * * Option #3 yields the best performance, so this function * provides the rng pointer as an output for the caller to use. @@ -443,11 +443,11 @@ lock_reseed_rng(struct crypto_rng **rng) __acquires(&cri->mlock) ({ (reseed) ? lock_reseed_rng(rng) : lock_default_rng(rng); }) #define unlock_local_rng(cri, reseed) \ -do { \ - if (reseed) \ - rt_mutex_unlock(&(cri)->mlock); \ - else \ - local_unlock(&(cri)->lock); \ +do { \ + if (reseed) \ + rt_mutex_unlock(&(cri)->mlock); \ + else \ + local_unlock(&(cri)->lock); \ } while (0) static __always_inline void @@ -482,9 +482,9 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) ulen = iter_iov_len(iter); } else { /* - * ITER_UBUF and ITER_IOVEC are the only user-backed - * iters. Bug out if a new user-backed iter appears. - */ + * ITER_UBUF and ITER_IOVEC are the only user-backed + * iters. Bug out if a new user-backed iter appears. + */ BUG(); } } @@ -647,7 +647,7 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) if (resched_without_lock) cond_resched(); goto restart; - } + } } if (page_dirty_len) From ae9cd3947caad18fa918dc86c51b2b69c064c2df Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 23 Jun 2026 13:09:41 -0700 Subject: [PATCH 85/98] crypto: rng - Make the per-CPU DRBG instances permanent The per-CPU DRBG instances used to be torn down by crypto_del_default_rng() under del_pcpu_rwsem, and the read path took that rwsem as a reader to keep an instance from being freed out from under it. That machinery only made sense back when the extrng override could be unregistered and the DRBG could be built as a module. That's no longer the case. extrng registration is now restricted to init time from built-in drivers, and FIPS mode requires the DRBG to be built-in, so the registered DRBG can't be unregistered or swapped out. As such, the per-CPU instances are never torn down, and the deletion rwsem just adds a lock to the hot read path for no reason. Drop the deletion rwsem and crypto_del_pcpu_rng(), and stop freeing the per-CPU instances in crypto_del_default_rng(). Since the instances are permanent now, allocate their pages once at init time with __GFP_NOFAIL and ditch free_pcpu_inst() along with the failure paths in crypto_rng_init(); failing to install the RNG override in FIPS mode would be catastrophic, so the setup isn't allowed to fail anyway. This brings the per-CPU DRBG implementation in line with the ciqlts9_6 tree's version of "crypto: rng - Implement fast per-CPU DRBG instances" [1]. [1] https://github.com/ctrliq/kernel-src-tree/commit/b0c560a37518cad09488bab031e1c6e817d65cb8 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 106 +++++++++------------------------------------------ 1 file changed, 18 insertions(+), 88 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index 5fa9b680e3257..da4d179116121 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -187,24 +187,6 @@ void crypto_put_default_rng(void) EXPORT_SYMBOL_GPL(crypto_put_default_rng); #if defined(CONFIG_CRYPTO_RNG) || defined(CONFIG_CRYPTO_RNG_MODULE) -#define down_read_del_pcpu_rwsem() down_read(&del_pcpu_rwsem) -#define up_read_del_pcpu_rwsem() up_read(&del_pcpu_rwsem) -static DECLARE_RWSEM(del_pcpu_rwsem); - -static void crypto_del_pcpu_rng(struct cpu_rng_inst __percpu *pcri) -{ - int cpu; - - for_each_possible_cpu(cpu) { - struct cpu_rng_inst *cri = per_cpu_ptr(pcri, cpu); - - if (cri->rng) { - crypto_free_rng(cri->rng); - cri->rng = NULL; - } - } -} - int crypto_del_default_rng(void) { bool busy; @@ -215,26 +197,10 @@ int crypto_del_default_rng(void) crypto_default_rng = NULL; } rt_mutex_unlock(&crypto_default_rng_lock); - if (busy) - return -EBUSY; - if (!down_write_trylock(&del_pcpu_rwsem)) - return -EBUSY; - - crypto_del_pcpu_rng(&pcpu_default_rng); - crypto_del_pcpu_rng(&pcpu_reseed_rng); - up_write(&del_pcpu_rwsem); - - return 0; + return busy ? -EBUSY : 0; } EXPORT_SYMBOL_GPL(crypto_del_default_rng); -#else -static inline void down_read_del_pcpu_rwsem(void) -{ -} -static inline void up_read_del_pcpu_rwsem(void) -{ -} #endif static void rng_default_set_ent(struct crypto_rng *tfm, const u8 *data, @@ -345,13 +311,10 @@ lock_default_rng(struct crypto_rng **rng) __acquires(&cri->lock) local_lock(&pcri->lock); cri = this_cpu_ptr(pcri); /* - * cri->rng may have transitioned from non-NULL to NULL, but - * underneath down_read_del_pcpu_rwsem() it can only transition - * from NULL to non-NULL. This may occur on a different CPU, - * thus cri->rng must be read atomically to prevent data races; - * this elides mlock by pairing with the WRITE_ONCE() in the - * slow path below. - * + * cri->rng can only transition from NULL to non-NULL. This may + * occur on a different CPU, thus cri->rng must be read + * atomically to prevent data races; this elides mlock by + * pairing with the WRITE_ONCE() in the slow path below. * * And if cri->rng is non-NULL, then it is good to go. To avoid * data races due to load speculation on torn cri->rng loads @@ -489,8 +452,6 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) } } - /* Prevent rngs from getting deleted from per-CPU RNG instances */ - down_read_del_pcpu_rwsem(); restart: /* * Pin the user page backing the current user destination address, @@ -501,7 +462,7 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) */ if (user_no_reseed && pin_user_pages_fast((unsigned long)uaddr, 1, FOLL_WRITE, &upage) != 1) - goto up_rwsem; + goto exit; cri = lock_local_rng(&rng, reseed); if (IS_ERR(cri)) { @@ -656,72 +617,41 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) unpin_upage: if (user_no_reseed) unpin_user_page(upage); -up_rwsem: - up_read_del_pcpu_rwsem(); - return ret ? ret : -EFAULT; +exit: + return ret ? ret : -EFAULT; } static const struct random_extrng crypto_devrandom_rng = { .extrng_read_iter = crypto_devrandom_read_iter }; -static void free_pcpu_inst(struct cpu_rng_inst __percpu *pcri) +static void __init alloc_pcpu_inst(struct cpu_rng_inst __percpu *pcri) { int cpu; for_each_possible_cpu(cpu) { struct cpu_rng_inst *cri = per_cpu_ptr(pcri, cpu); - if (cri->rng) - crypto_free_rng(cri->rng); - - free_page((unsigned long)cri->page); - } -} - -static int __init alloc_pcpu_inst(struct cpu_rng_inst __percpu *pcri) -{ - int cpu; - - for_each_possible_cpu(cpu) { - struct cpu_rng_inst *cri = per_cpu_ptr(pcri, cpu); - - cri->page = (void *)__get_free_page(GFP_KERNEL); - if (!cri->page) - goto err_page_alloc; - + cri->page = (void *)__get_free_page(GFP_KERNEL | __GFP_NOFAIL); local_lock_init(&cri->lock); } - - return 0; - -err_page_alloc: - while (cpu--) - free_page((unsigned long)per_cpu_ptr(pcri, cpu)->page); - return -ENOMEM; } static int __init crypto_rng_init(void) { - int ret; - if (!fips_enabled) return 0; - ret = alloc_pcpu_inst(&pcpu_default_rng); - if (ret) - return ret; - - ret = alloc_pcpu_inst(&pcpu_reseed_rng); - if (ret) - goto free_pcpu_default; - + /* + * Never fail to register the RNG override in FIPS mode because failure + * would result in the system quietly booting without the FIPS-mandated + * RNG installed. This would be catastrophic for FIPS compliance, hence + * the RNG override setup is *not* allowed to fail. + */ + alloc_pcpu_inst(&pcpu_default_rng); + alloc_pcpu_inst(&pcpu_reseed_rng); random_register_extrng(&crypto_devrandom_rng); return 0; - -free_pcpu_default: - free_pcpu_inst(&pcpu_default_rng); - return ret; } late_initcall(crypto_rng_init); From 0157b699fc3b69720d2db84eb72488d9023cbd8b Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Mon, 22 Jun 2026 18:40:11 -0700 Subject: [PATCH 86/98] crypto: rng - Skip leading zero-length iovec segments The fast per-CPU DRBG path computes its initial user destination address straight from the iov_iter. For an ITER_IOVEC iter it reads iter_iov_addr() and iter_iov_len() of the current segment, but when the iovec leads with one or more zero-length segments, the current segment is one of those empty entries. iter_iov_addr() then hands back the base of an empty segment, which is whatever userspace put there: its base can be NULL or some other unwritable address, since a zero-length segment is never actually touched. Right after the setup, that address is prefaulted, and on a bogus base it fails. A failed prefault on the very first address is treated as fatal, so the whole read bails out to -EFAULT even though there are perfectly good non-empty segments later in the iovec. This is reachable with something as simple as readv() on /dev/urandom where the first iovec entry is {NULL, 0}. Fix it by advancing the iterator by zero before reading the first address. The iovec advance loop walks past every leading empty segment and stops at the first non-empty one, and there's guaranteed to be such a segment because iov_iter_count() is nonzero at this point. Empty segments that crop up mid-stream are already skipped by the per-copy advance, so this only needs to run once during setup. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crypto/rng.c b/crypto/rng.c index da4d179116121..acdd761ab2a00 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -441,6 +441,16 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) uaddr = iter->ubuf + iter->iov_offset; ulen = iov_iter_count(iter); } else if (iter_is_iovec(iter)) { + /* + * Skip any leading zero-length segments first, since + * uaddr would otherwise point at an empty segment's + * base (which may be NULL or unwritable). Advancing by + * zero walks past every leading empty segment (the + * iovec advance loop stops at the first non-empty one), + * and there's guaranteed to be a non-empty one because + * iov_iter_count() is nonzero above. + */ + iov_iter_advance(iter, 0); uaddr = iter_iov_addr(iter); ulen = iter_iov_len(iter); } else { From 13b074212024e478b1919c3a90cafa2b876f5a0b Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Mon, 22 Jun 2026 23:47:14 -0700 Subject: [PATCH 87/98] crypto: rng - Fix spurious EFAULT when the destination PTE is zapped While GUP pinning makes it possible to pin the page _backing_ a user address, it *doesn't* pin the page table entry (PTE) for that mapping. This means the pinned physical page can be separated from the user address it was backing, and even back a _different_ user address within the same process. PTE zapping naturally happens during memory reclaim when memory pressure is elevated, and can even be done directly by userspace via madvise(MADV_DONTNEED). Since the optimized per-CPU DRBG loop assumes copy_to_user_nofault() will always succeed on a GUP-pinned page, it immediately bails out when the nofault copy actually *does* fail for the reasons described above. This results in either fewer than requested random bytes copied or, more seriously, a spurious EFAULT returned to userspace when no random bytes were copied. As it turns out, there's no way to pin a PTE. That means it's not possible to guarantee a 100% success rate for the copy_to_user_nofault() attempt. Fix this by handling copy_to_user_nofault() errors correctly with a fall back to a faultable copy attempt outside of the RNG lock. In order to guarantee forward progress for the caller, an on-stack bounce buffer is used to copy up to 256 bytes of the generated random bytes whenever this happens rather than discarding the whole thing. There's no need to use GUP pinning anymore since there's no use for having a page pinned without pinning a PTE to go along with that page, hence the page pinning is eliminated which saves a software page table walk that was performed for _at least_ every destination page. Reported-by: Kun Yi Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 146 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 51 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index acdd761ab2a00..aed8221c0f09d 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -420,16 +420,30 @@ clear_rng_page(struct cpu_rng_inst *cri, size_t count) count < PAGE_SIZE ? memset(cri->page, 0, count) : clear_page(cri->page); } +static __always_inline void +no_reseed_clear_unlock(struct cpu_rng_inst *cri, size_t *page_dirty_len) +{ + /* + * Clear the buffer of our latest random bytes before unlocking and + * potentially migrating CPUs, in which case we wouldn't have the same + * `cri` anymore. + */ + clear_rng_page(cri, *page_dirty_len); + unlock_local_rng(cri, false); + *page_dirty_len = 0; +} + static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) { /* lock_local_rng() puts us in atomic context for !reseed on non-RT */ const bool atomic = !reseed && !IS_ENABLED(CONFIG_PREEMPT_RT); const bool user_no_reseed = !reseed && user_backed_iter(iter); + /* Align the bounce buffer to a word to prevent alignment traps */ + u8 bounce[SZ_256] __aligned(sizeof(long)); size_t ulen, page_dirty_len = 0; struct cpu_rng_inst *cri; struct crypto_rng *rng; - void __user *uaddr; - struct page *upage; + u8 __user *uaddr; ssize_t ret = 0; if (unlikely(!iov_iter_count(iter))) @@ -464,21 +478,39 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) restart: /* - * Pin the user page backing the current user destination address, - * potentially prefaulting to allocate a page for the destination. By - * prefaulting without the RNG lock held, the DRBG won't be blocked by - * time spent on page faults for this task, and thus the DRBG can still - * be used by other tasks. + * Prefault the current user destination address in case the page for + * that virtual address is not resident. This is done by simply writing + * a single zero byte to the destination, which forces a COW if the page + * isn't resident, leaving us with a writable mapping. By prefaulting + * without the RNG lock held, the DRBG won't be blocked by time spent on + * page faults for this task, and thus the DRBG can still be used by + * other tasks. + * + * Notably, this doesn't keep the page from getting evicted later on; + * it's just an optimistic prefault under the assumption that the page + * won't be evicted before we copy random bytes into it, which is often + * but _not always_ the case. + * + * While GUP pinning makes it possible to pin the page _backing_ a user + * address, it *doesn't* pin the page table entry (PTE) for that + * mapping. This means the pinned physical page can be separated from + * the user address it was backing, and even back a _different_ user + * address within the same process. PTE zapping naturally happens during + * memory reclaim when memory pressure is elevated, and can even be done + * directly by userspace via madvise(MADV_DONTNEED). Since there's no + * way to pin a PTE, that means it's not possible to guarantee a page + * will be resident when copy_to_user_nofault() runs under the RNG lock + * below; in other words, there's no way to guarantee a 100% success + * rate for the copy_to_user_nofault() attempt. */ - if (user_no_reseed && pin_user_pages_fast((unsigned long)uaddr, 1, - FOLL_WRITE, &upage) != 1) + if (user_no_reseed && put_user((u8)0, uaddr)) goto exit; cri = lock_local_rng(&rng, reseed); if (IS_ERR(cri)) { if (!ret) ret = PTR_ERR(cri); - goto unpin_upage; + goto exit; } while (1) { @@ -488,7 +520,7 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) /* * Generate up to one page at a time, and align to a page - * boundary so we only need to pin one user page at a time. + * boundary to track page residence one page at a time. */ if (user_no_reseed) i = min3(i, PAGE_SIZE - offset_in_page(uaddr), ulen); @@ -525,33 +557,56 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) * the entire call; this is why a mutex is used instead of a * local lock for the reseed RNG, to permit sleeping without * yielding the DRBG instance. + * + * From this point on, err != 0 means the nofault copy failed. */ page_dirty_len = max(i, page_dirty_len); - if (user_no_reseed) { - err = copy_to_user_nofault(uaddr, cri->page, i); - if (err >= 0) { - iov_iter_advance(iter, i - err); - ret += i - err; - } - if (err) - break; + if (user_no_reseed && + !(err = copy_to_user_nofault(uaddr, cri->page, i))) { + iov_iter_advance(iter, i); + copied = i; } else { + const void *src = cri->page; + /* - * We know that copying from cri->page is safe, so use - * _copy_to_iter() directly to skip check_copy_size(). + * Fall back to a faultable copy attempt when the + * non-faulting attempt failed, likely because the PTE + * was zapped. To ensure forward progress, some of the + * generated bytes are retained in an on-stack bounce + * buffer and copied out rather than just discarding all + * of it and going back to `restart`. */ - copied = _copy_to_iter(cri->page, i, iter); - ret += copied; - if (copied != i) - break; + if (unlikely(err)) { + i = min(i, sizeof(bounce)); + memcpy(bounce, cri->page, i); + no_reseed_clear_unlock(cri, &page_dirty_len); + src = bounce; + } + + /* + * We know that copying from cri->page (or the bounce + * buffer) is safe, so use _copy_to_iter() directly to + * skip check_copy_size(). + */ + copied = _copy_to_iter(src, i, iter); + + /* Sanitize the on-stack bounce buffer if it was used */ + if (err) + memzero_explicit(bounce, i); } + ret += copied; /* - * Quit when either the requested number of bytes have been - * generated or there is a pending signal. + * Quit when the copy fell short, the requested number of bytes + * have been generated, or there is a pending signal. */ - if (!iov_iter_count(iter) || signal_pending(current)) + if (copied != i || !iov_iter_count(iter) || + signal_pending(current)) { + /* Skip the unlock when already unlocked */ + if (unlikely(err)) + goto exit; break; + } /* Compute the next user destination address and length */ if (user_no_reseed) { @@ -572,8 +627,6 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) uaddr = iter_iov(iter)->iov_base; ulen = iter_iov(iter)->iov_len; } - - unpin_user_page(upage); } /* @@ -588,33 +641,27 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) } /* - * Optimistically try to pin the next user page without - * faulting, so we don't need to clear cri->page and drop the - * lock on every iteration. If this fails, we fall back to - * pinning with the option to prefault. + * Optimistically check if the next user page is resident and + * writable with a dummy copy_to_user_nofault() attempt to write + * a single zero byte. If this fails, fall back to prefaulting + * outside of the lock. */ - if (user_no_reseed && !resched_without_lock && - pin_user_pages_fast_only((unsigned long)uaddr, 1, - FOLL_WRITE, &upage) == 1) + if (!err && user_no_reseed && !resched_without_lock && + !copy_to_user_nofault(uaddr, &(u8){ 0 }, 1)) continue; /* * Restart if either rescheduling is needed (and requires - * dropping the lock since we're atomic) or the optimistic page - * pinning attempt failed. + * dropping the lock since we're atomic) or the destination page + * wasn't resident when copy_to_user_nofault() was attempted. * * This always implies `reseed == false`, so unlock_local_rng() - * can just be passed `false` for reseed to eliminate a branch. + * can just be passed `false` for reseed to eliminate a branch, + * hence using no_reseed_clear_unlock(). */ if (resched_without_lock || user_no_reseed) { - /* - * Clear the buffer of our latest random bytes before - * unlocking and potentially migrating CPUs, in which - * case we wouldn't have the same `cri` anymore. - */ - clear_rng_page(cri, page_dirty_len); - unlock_local_rng(cri, false); - page_dirty_len = 0; + if (!err) + no_reseed_clear_unlock(cri, &page_dirty_len); if (resched_without_lock) cond_resched(); goto restart; @@ -624,9 +671,6 @@ static ssize_t crypto_devrandom_read_iter(struct iov_iter *iter, bool reseed) if (page_dirty_len) clear_rng_page(cri, page_dirty_len); unlock_local_rng(cri, reseed); -unpin_upage: - if (user_no_reseed) - unpin_user_page(upage); exit: return ret ? ret : -EFAULT; } From 88ad2ff1d42dc457fbdaefa7834941ad3a613f7f Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 23 Jun 2026 11:33:01 -0700 Subject: [PATCH 88/98] Revert "mm/gup: reintroduce pin_user_pages_fast_only()" This reverts commit ef467f3c43338f72f4173c98b15bc8021f79c3a1. This helper is no longer used by the FIPS-mode RNG, which was the motivation for reintroducing it. Remove it. Signed-off-by: Sultan Alsawaf --- include/linux/mm.h | 2 -- mm/gup.c | 28 ---------------------------- 2 files changed, 30 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 501f0212ac0e7..d2acffe4c8009 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -2616,8 +2616,6 @@ int get_user_pages_fast(unsigned long start, int nr_pages, unsigned int gup_flags, struct page **pages); int pin_user_pages_fast(unsigned long start, int nr_pages, unsigned int gup_flags, struct page **pages); -int pin_user_pages_fast_only(unsigned long start, int nr_pages, - unsigned int gup_flags, struct page **pages); void folio_add_pin(struct folio *folio); int account_locked_vm(struct mm_struct *mm, unsigned long pages, bool inc); diff --git a/mm/gup.c b/mm/gup.c index 737eceda0c6bf..960e4401320aa 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -3539,34 +3539,6 @@ int pin_user_pages_fast(unsigned long start, int nr_pages, } EXPORT_SYMBOL_GPL(pin_user_pages_fast); -/** - * pin_user_pages_fast_only() - pin user pages in memory - * @start: starting user address - * @nr_pages: number of pages from start to pin - * @gup_flags: flags modifying pin behaviour - * @pages: array that receives pointers to the pages pinned. - * Should be at least nr_pages long. - * - * Like pin_user_pages_fast() except it's IRQ-safe in that it won't fall back to - * the regular GUP. - * - * If the architecture does not support this function, simply return with no - * pages pinned. - * - * Careful, careful! COW breaking can go either way, so a non-write - * access can get ambiguous page results. If you call this function without - * 'write' set, you'd better be sure that you're ok with that ambiguity. - */ -int pin_user_pages_fast_only(unsigned long start, int nr_pages, - unsigned int gup_flags, struct page **pages) -{ - if (!is_valid_gup_args(pages, NULL, &gup_flags, - FOLL_PIN | FOLL_FAST_ONLY)) - return -EINVAL; - return gup_fast_fallback(start, nr_pages, gup_flags, pages); -} -EXPORT_SYMBOL_GPL(pin_user_pages_fast_only); - /** * pin_user_pages_remote() - pin pages of a remote process * From de070a462bc7e3bec4d94358533916d10e10b541 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 6 Oct 2026 17:22:26 -0700 Subject: [PATCH 89/98] local_lock: Move this_cpu_ptr() notation from internal to main header commit-author Sebastian Andrzej Siewior commit 7ff495e26a39f3e7a3d4058df59b5b6d6f943cab local_lock.h is the main header for the local_lock_t type and provides wrappers around internal functions prefixed with __ in local_lock_internal.h. Move the this_cpu_ptr() dereference of the variable from the internal to the main header. Since it is all macro implemented, this_cpu_ptr() will still happen within the preempt/ IRQ disabled section. This frees the internal implementation (__) to be used on local_lock_t types which are local variables and must not be accessed via this_cpu_ptr(). Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Acked-by: Waiman Long Acked-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/all/20250630075138.3448715-2-bigeasy@linutronix.de (cherry picked from commit 7ff495e26a39f3e7a3d4058df59b5b6d6f943cab) Signed-off-by: Sultan Alsawaf --- include/linux/local_lock.h | 20 +++++++++---------- include/linux/local_lock_internal.h | 30 ++++++++++++++--------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/linux/local_lock.h b/include/linux/local_lock.h index 16a2ee4f8310b..2ba8464195244 100644 --- a/include/linux/local_lock.h +++ b/include/linux/local_lock.h @@ -13,13 +13,13 @@ * local_lock - Acquire a per CPU local lock * @lock: The lock variable */ -#define local_lock(lock) __local_lock(lock) +#define local_lock(lock) __local_lock(this_cpu_ptr(lock)) /** * local_lock_irq - Acquire a per CPU local lock and disable interrupts * @lock: The lock variable */ -#define local_lock_irq(lock) __local_lock_irq(lock) +#define local_lock_irq(lock) __local_lock_irq(this_cpu_ptr(lock)) /** * local_lock_irqsave - Acquire a per CPU local lock, save and disable @@ -28,19 +28,19 @@ * @flags: Storage for interrupt flags */ #define local_lock_irqsave(lock, flags) \ - __local_lock_irqsave(lock, flags) + __local_lock_irqsave(this_cpu_ptr(lock), flags) /** * local_unlock - Release a per CPU local lock * @lock: The lock variable */ -#define local_unlock(lock) __local_unlock(lock) +#define local_unlock(lock) __local_unlock(this_cpu_ptr(lock)) /** * local_unlock_irq - Release a per CPU local lock and enable interrupts * @lock: The lock variable */ -#define local_unlock_irq(lock) __local_unlock_irq(lock) +#define local_unlock_irq(lock) __local_unlock_irq(this_cpu_ptr(lock)) /** * local_unlock_irqrestore - Release a per CPU local lock and restore @@ -49,7 +49,7 @@ * @flags: Interrupt flags to restore */ #define local_unlock_irqrestore(lock, flags) \ - __local_unlock_irqrestore(lock, flags) + __local_unlock_irqrestore(this_cpu_ptr(lock), flags) /** * local_lock_init - Runtime initialize a lock instance @@ -64,7 +64,7 @@ * locking constrains it will _always_ fail to acquire the lock in NMI or * HARDIRQ context on PREEMPT_RT. */ -#define local_trylock(lock) __local_trylock(lock) +#define local_trylock(lock) __local_trylock(this_cpu_ptr(lock)) /** * local_trylock_irqsave - Try to acquire a per CPU local lock, save and disable @@ -77,7 +77,7 @@ * HARDIRQ context on PREEMPT_RT. */ #define local_trylock_irqsave(lock, flags) \ - __local_trylock_irqsave(lock, flags) + __local_trylock_irqsave(this_cpu_ptr(lock), flags) DEFINE_GUARD(local_lock, local_lock_t __percpu*, local_lock(_T), @@ -91,10 +91,10 @@ DEFINE_LOCK_GUARD_1(local_lock_irqsave, local_lock_t __percpu, unsigned long flags) #define local_lock_nested_bh(_lock) \ - __local_lock_nested_bh(_lock) + __local_lock_nested_bh(this_cpu_ptr(_lock)) #define local_unlock_nested_bh(_lock) \ - __local_unlock_nested_bh(_lock) + __local_unlock_nested_bh(this_cpu_ptr(_lock)) DEFINE_GUARD(local_lock_nested_bh, local_lock_t __percpu*, local_lock_nested_bh(_T), diff --git a/include/linux/local_lock_internal.h b/include/linux/local_lock_internal.h index 8d5ac16a9b179..d80b5306a2c0c 100644 --- a/include/linux/local_lock_internal.h +++ b/include/linux/local_lock_internal.h @@ -99,14 +99,14 @@ do { \ local_trylock_t *tl; \ local_lock_t *l; \ \ - l = (local_lock_t *)this_cpu_ptr(lock); \ + l = (local_lock_t *)(lock); \ tl = (local_trylock_t *)l; \ _Generic((lock), \ - __percpu local_trylock_t *: ({ \ + local_trylock_t *: ({ \ lockdep_assert(tl->acquired == 0); \ WRITE_ONCE(tl->acquired, 1); \ }), \ - __percpu local_lock_t *: (void)0); \ + local_lock_t *: (void)0); \ local_lock_acquire(l); \ } while (0) @@ -133,7 +133,7 @@ do { \ local_trylock_t *tl; \ \ preempt_disable(); \ - tl = this_cpu_ptr(lock); \ + tl = (lock); \ if (READ_ONCE(tl->acquired)) { \ preempt_enable(); \ tl = NULL; \ @@ -150,7 +150,7 @@ do { \ local_trylock_t *tl; \ \ local_irq_save(flags); \ - tl = this_cpu_ptr(lock); \ + tl = (lock); \ if (READ_ONCE(tl->acquired)) { \ local_irq_restore(flags); \ tl = NULL; \ @@ -167,15 +167,15 @@ do { \ local_trylock_t *tl; \ local_lock_t *l; \ \ - l = (local_lock_t *)this_cpu_ptr(lock); \ + l = (local_lock_t *)(lock); \ tl = (local_trylock_t *)l; \ local_lock_release(l); \ _Generic((lock), \ - __percpu local_trylock_t *: ({ \ + local_trylock_t *: ({ \ lockdep_assert(tl->acquired == 1); \ WRITE_ONCE(tl->acquired, 0); \ }), \ - __percpu local_lock_t *: (void)0); \ + local_lock_t *: (void)0); \ } while (0) #define __local_unlock(lock) \ @@ -199,11 +199,11 @@ do { \ #define __local_lock_nested_bh(lock) \ do { \ lockdep_assert_in_softirq(); \ - local_lock_acquire(this_cpu_ptr(lock)); \ + local_lock_acquire((lock)); \ } while (0) #define __local_unlock_nested_bh(lock) \ - local_lock_release(this_cpu_ptr(lock)) + local_lock_release((lock)) #else /* !CONFIG_PREEMPT_RT */ @@ -227,7 +227,7 @@ typedef spinlock_t local_trylock_t; #define __local_lock(__lock) \ do { \ migrate_disable(); \ - spin_lock(this_cpu_ptr((__lock))); \ + spin_lock((__lock)); \ } while (0) #define __local_lock_irq(lock) __local_lock(lock) @@ -241,7 +241,7 @@ typedef spinlock_t local_trylock_t; #define __local_unlock(__lock) \ do { \ - spin_unlock(this_cpu_ptr((__lock))); \ + spin_unlock((__lock)); \ migrate_enable(); \ } while (0) @@ -252,12 +252,12 @@ typedef spinlock_t local_trylock_t; #define __local_lock_nested_bh(lock) \ do { \ lockdep_assert_in_softirq_func(); \ - spin_lock(this_cpu_ptr(lock)); \ + spin_lock((lock)); \ } while (0) #define __local_unlock_nested_bh(lock) \ do { \ - spin_unlock(this_cpu_ptr((lock))); \ + spin_unlock((lock)); \ } while (0) #define __local_trylock(lock) \ @@ -268,7 +268,7 @@ do { \ __locked = 0; \ } else { \ migrate_disable(); \ - __locked = spin_trylock(this_cpu_ptr((lock))); \ + __locked = spin_trylock((lock)); \ if (!__locked) \ migrate_enable(); \ } \ From 7ad96576017531d50f6388beffb5e9099827ed97 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Wed, 12 Aug 2026 16:24:24 -0700 Subject: [PATCH 90/98] crypto: rng - Fix double percpu offset in local_unlock() calls local_unlock() is supposed to take a percpu pointer, which it then offsets for the current CPU. The problem is that the per-CPU DRBG code passes local_unlock() a pointer that has *already been offset for the current CPU*, so the percpu offset gets added twice. `pcri` is the percpu pointer and `cri` is the offset `pcri` pointer for the current CPU; passing `cri` to local_unlock() results in the current CPU's offset getting added again and thus the resulting pointer is garbage. This went unnoticed at runtime because local_unlock() only dereferences that pointer on PREEMPT_RT or CONFIG_DEBUG_LOCK_ALLOC=y kernels. The pointer is never actually used otherwise. This also went unnoticed at compile time because, for x86_64, __percpu only expands to something on GCC 14+; __percpu is otherwise an empty macro. So there's no pointer type mismatch detected at compile time on older GCC versions. On GCC 14+, the following compile error occurs: ../crypto/rng.c: In function 'lock_default_rng': ../include/linux/percpu-defs.h:221:45: error: initialization from pointer to non-enclosed address space Fix it by using __local_unlock() instead, which eliminates the duplicate per-CPU offset addition. While using local_unlock() with `pcri` also works, using `cri` with __local_unlock() is leaner because it doesn't need to redo the offset addition, saving an instruction on kernels that actually use the pointer. Signed-off-by: Sultan Alsawaf --- crypto/rng.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/rng.c b/crypto/rng.c index aed8221c0f09d..f757195dcd634 100644 --- a/crypto/rng.c +++ b/crypto/rng.c @@ -337,7 +337,7 @@ lock_default_rng(struct crypto_rng **rng) __acquires(&cri->lock) * * Note that this task may be migrated to a different CPU now! */ - local_unlock(&cri->lock); + __local_unlock(&cri->lock); rt_mutex_lock(&cri->mlock); if (!cri->rng) { struct crypto_rng *new_rng = NULL; @@ -410,7 +410,7 @@ do { \ if (reseed) \ rt_mutex_unlock(&(cri)->mlock); \ else \ - local_unlock(&(cri)->lock); \ + __local_unlock(&(cri)->lock); \ } while (0) static __always_inline void From 9f79fd462ec82a46406171cf68fca430f8d51422 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Wed, 26 Aug 2026 14:24:56 -0700 Subject: [PATCH 91/98] configs: Set CRYPTO_FIPS_CUSTOM_VERSION Add CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y, CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" Requested by lab. Signed-off-by: Jeremy Allison --- configs/kernel-aarch64-64k-debug-rhel.config | 3 ++- configs/kernel-aarch64-64k-rhel.config | 3 ++- configs/kernel-aarch64-debug-rhel.config | 3 ++- configs/kernel-aarch64-rhel.config | 3 ++- configs/kernel-aarch64-rt-64k-debug-rhel.config | 3 ++- configs/kernel-aarch64-rt-64k-rhel.config | 3 ++- configs/kernel-aarch64-rt-debug-rhel.config | 3 ++- configs/kernel-aarch64-rt-rhel.config | 3 ++- configs/kernel-ppc64le-debug-rhel.config | 3 ++- configs/kernel-ppc64le-rhel.config | 3 ++- configs/kernel-s390x-debug-rhel.config | 3 ++- configs/kernel-s390x-rhel.config | 3 ++- configs/kernel-s390x-zfcpdump-rhel.config | 3 ++- configs/kernel-x86_64-debug-rhel.config | 3 ++- configs/kernel-x86_64-rhel.config | 3 ++- configs/kernel-x86_64-rt-debug-rhel.config | 3 ++- configs/kernel-x86_64-rt-rhel.config | 3 ++- 17 files changed, 34 insertions(+), 17 deletions(-) diff --git a/configs/kernel-aarch64-64k-debug-rhel.config b/configs/kernel-aarch64-64k-debug-rhel.config index f3faa3d7dd1b8..74d4ea0357d52 100644 --- a/configs/kernel-aarch64-64k-debug-rhel.config +++ b/configs/kernel-aarch64-64k-debug-rhel.config @@ -1284,7 +1284,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-aarch64-64k-rhel.config b/configs/kernel-aarch64-64k-rhel.config index 5f470f09028db..7bd7548ba83c3 100644 --- a/configs/kernel-aarch64-64k-rhel.config +++ b/configs/kernel-aarch64-64k-rhel.config @@ -1284,7 +1284,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-aarch64-debug-rhel.config b/configs/kernel-aarch64-debug-rhel.config index 64f4b0a27a691..1720ba2026ba2 100644 --- a/configs/kernel-aarch64-debug-rhel.config +++ b/configs/kernel-aarch64-debug-rhel.config @@ -1282,7 +1282,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-aarch64-rhel.config b/configs/kernel-aarch64-rhel.config index 5e99da15a0831..dd43475b24ffa 100644 --- a/configs/kernel-aarch64-rhel.config +++ b/configs/kernel-aarch64-rhel.config @@ -1282,7 +1282,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-aarch64-rt-64k-debug-rhel.config b/configs/kernel-aarch64-rt-64k-debug-rhel.config index b9dad62152aed..99fadd51da827 100644 --- a/configs/kernel-aarch64-rt-64k-debug-rhel.config +++ b/configs/kernel-aarch64-rt-64k-debug-rhel.config @@ -1285,7 +1285,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-aarch64-rt-64k-rhel.config b/configs/kernel-aarch64-rt-64k-rhel.config index 358794debfae8..724e9199270be 100644 --- a/configs/kernel-aarch64-rt-64k-rhel.config +++ b/configs/kernel-aarch64-rt-64k-rhel.config @@ -1285,7 +1285,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-aarch64-rt-debug-rhel.config b/configs/kernel-aarch64-rt-debug-rhel.config index 7a0d5c21e335b..efda3e150f88f 100644 --- a/configs/kernel-aarch64-rt-debug-rhel.config +++ b/configs/kernel-aarch64-rt-debug-rhel.config @@ -1283,7 +1283,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-aarch64-rt-rhel.config b/configs/kernel-aarch64-rt-rhel.config index 9689925d7f700..226c1b19d515b 100644 --- a/configs/kernel-aarch64-rt-rhel.config +++ b/configs/kernel-aarch64-rt-rhel.config @@ -1283,7 +1283,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-ppc64le-debug-rhel.config b/configs/kernel-ppc64le-debug-rhel.config index 13e50f79c0117..24c7b6cdf2605 100644 --- a/configs/kernel-ppc64le-debug-rhel.config +++ b/configs/kernel-ppc64le-debug-rhel.config @@ -1045,7 +1045,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-ppc64le-rhel.config b/configs/kernel-ppc64le-rhel.config index c14817c4b65b8..355bf6a015b75 100644 --- a/configs/kernel-ppc64le-rhel.config +++ b/configs/kernel-ppc64le-rhel.config @@ -1045,7 +1045,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-s390x-debug-rhel.config b/configs/kernel-s390x-debug-rhel.config index 4612cb7922f4d..e45def33f6ba9 100644 --- a/configs/kernel-s390x-debug-rhel.config +++ b/configs/kernel-s390x-debug-rhel.config @@ -1041,7 +1041,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-s390x-rhel.config b/configs/kernel-s390x-rhel.config index 40a2b54fc955e..959a8db8b34f8 100644 --- a/configs/kernel-s390x-rhel.config +++ b/configs/kernel-s390x-rhel.config @@ -1041,7 +1041,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-s390x-zfcpdump-rhel.config b/configs/kernel-s390x-zfcpdump-rhel.config index 990151517043b..e9a45b30b5f8d 100644 --- a/configs/kernel-s390x-zfcpdump-rhel.config +++ b/configs/kernel-s390x-zfcpdump-rhel.config @@ -1042,7 +1042,8 @@ CONFIG_CRYPTO_ECHAINIV=y # CONFIG_CRYPTO_ECRDSA is not set # CONFIG_CRYPTO_ESSIV is not set CONFIG_CRYPTO_FCRYPT=y -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-x86_64-debug-rhel.config b/configs/kernel-x86_64-debug-rhel.config index 9acaed39d2317..6d7c1a0cc8a8d 100644 --- a/configs/kernel-x86_64-debug-rhel.config +++ b/configs/kernel-x86_64-debug-rhel.config @@ -1099,7 +1099,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-x86_64-rhel.config b/configs/kernel-x86_64-rhel.config index 9338ba7f8b268..813bd7ef8bbef 100644 --- a/configs/kernel-x86_64-rhel.config +++ b/configs/kernel-x86_64-rhel.config @@ -1099,7 +1099,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-x86_64-rt-debug-rhel.config b/configs/kernel-x86_64-rt-debug-rhel.config index b2333db6a99f7..5f1f815db0535 100644 --- a/configs/kernel-x86_64-rt-debug-rhel.config +++ b/configs/kernel-x86_64-rt-debug-rhel.config @@ -1100,7 +1100,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y diff --git a/configs/kernel-x86_64-rt-rhel.config b/configs/kernel-x86_64-rt-rhel.config index ef2e800124764..e162fa31ab362 100644 --- a/configs/kernel-x86_64-rt-rhel.config +++ b/configs/kernel-x86_64-rt-rhel.config @@ -1100,7 +1100,8 @@ CONFIG_CRYPTO_ECHAINIV=m # CONFIG_CRYPTO_ECRDSA is not set CONFIG_CRYPTO_ESSIV=m CONFIG_CRYPTO_FCRYPT=m -# CONFIG_CRYPTO_FIPS_CUSTOM_VERSION is not set +CONFIG_CRYPTO_FIPS_CUSTOM_VERSION=y +CONFIG_CRYPTO_FIPS_VERSION="ciq.6.12.20260826" CONFIG_CRYPTO_FIPS_NAME="Linux Kernel Cryptographic API" CONFIG_CRYPTO_FIPS=y CONFIG_CRYPTO_GCM=y From ddf3a03bda522801189ac189d09552a5bf7d3609 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Wed, 26 Aug 2026 18:40:32 -0700 Subject: [PATCH 92/98] configs: Alter JITTERENTROPY_MEMSIZE Unset CRYPTO_JITTERENTROPY_MEMSIZE_2, set CRYPTO_JITTERENTROPY_MEMSIZE_128. CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKS=512 CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKSIZE=256 Requested by lab. Signed-off-by: Jeremy Allison --- configs/kernel-aarch64-64k-debug-rhel.config | 4 ++-- configs/kernel-aarch64-64k-rhel.config | 4 ++-- configs/kernel-aarch64-debug-rhel.config | 4 ++-- configs/kernel-aarch64-rhel.config | 4 ++-- configs/kernel-aarch64-rt-64k-debug-rhel.config | 4 ++-- configs/kernel-aarch64-rt-64k-rhel.config | 4 ++-- configs/kernel-aarch64-rt-debug-rhel.config | 4 ++-- configs/kernel-aarch64-rt-rhel.config | 4 ++-- configs/kernel-ppc64le-debug-rhel.config | 4 ++-- configs/kernel-ppc64le-rhel.config | 4 ++-- configs/kernel-s390x-debug-rhel.config | 4 ++-- configs/kernel-s390x-rhel.config | 4 ++-- configs/kernel-s390x-zfcpdump-rhel.config | 4 ++-- configs/kernel-x86_64-debug-rhel.config | 4 ++-- configs/kernel-x86_64-rhel.config | 4 ++-- configs/kernel-x86_64-rt-debug-rhel.config | 4 ++-- configs/kernel-x86_64-rt-rhel.config | 4 ++-- 17 files changed, 34 insertions(+), 34 deletions(-) diff --git a/configs/kernel-aarch64-64k-debug-rhel.config b/configs/kernel-aarch64-64k-debug-rhel.config index 74d4ea0357d52..d54503fa6b579 100644 --- a/configs/kernel-aarch64-64k-debug-rhel.config +++ b/configs/kernel-aarch64-64k-debug-rhel.config @@ -1295,8 +1295,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-aarch64-64k-rhel.config b/configs/kernel-aarch64-64k-rhel.config index 7bd7548ba83c3..63c69dce59d74 100644 --- a/configs/kernel-aarch64-64k-rhel.config +++ b/configs/kernel-aarch64-64k-rhel.config @@ -1295,8 +1295,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-aarch64-debug-rhel.config b/configs/kernel-aarch64-debug-rhel.config index 1720ba2026ba2..2273d0fac4881 100644 --- a/configs/kernel-aarch64-debug-rhel.config +++ b/configs/kernel-aarch64-debug-rhel.config @@ -1293,8 +1293,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-aarch64-rhel.config b/configs/kernel-aarch64-rhel.config index dd43475b24ffa..d4e44633b11ff 100644 --- a/configs/kernel-aarch64-rhel.config +++ b/configs/kernel-aarch64-rhel.config @@ -1293,8 +1293,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-aarch64-rt-64k-debug-rhel.config b/configs/kernel-aarch64-rt-64k-debug-rhel.config index 99fadd51da827..7dfb453abff76 100644 --- a/configs/kernel-aarch64-rt-64k-debug-rhel.config +++ b/configs/kernel-aarch64-rt-64k-debug-rhel.config @@ -1296,8 +1296,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-aarch64-rt-64k-rhel.config b/configs/kernel-aarch64-rt-64k-rhel.config index 724e9199270be..00d3045264c40 100644 --- a/configs/kernel-aarch64-rt-64k-rhel.config +++ b/configs/kernel-aarch64-rt-64k-rhel.config @@ -1296,8 +1296,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-aarch64-rt-debug-rhel.config b/configs/kernel-aarch64-rt-debug-rhel.config index efda3e150f88f..d8f7f2beb2c98 100644 --- a/configs/kernel-aarch64-rt-debug-rhel.config +++ b/configs/kernel-aarch64-rt-debug-rhel.config @@ -1294,8 +1294,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-aarch64-rt-rhel.config b/configs/kernel-aarch64-rt-rhel.config index 226c1b19d515b..3e9f9e4d7d34d 100644 --- a/configs/kernel-aarch64-rt-rhel.config +++ b/configs/kernel-aarch64-rt-rhel.config @@ -1294,8 +1294,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-ppc64le-debug-rhel.config b/configs/kernel-ppc64le-debug-rhel.config index 24c7b6cdf2605..6cdcd62d166b6 100644 --- a/configs/kernel-ppc64le-debug-rhel.config +++ b/configs/kernel-ppc64le-debug-rhel.config @@ -1055,8 +1055,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-ppc64le-rhel.config b/configs/kernel-ppc64le-rhel.config index 355bf6a015b75..e0217bb0c91a3 100644 --- a/configs/kernel-ppc64le-rhel.config +++ b/configs/kernel-ppc64le-rhel.config @@ -1055,8 +1055,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-s390x-debug-rhel.config b/configs/kernel-s390x-debug-rhel.config index e45def33f6ba9..e8b2bb7e40b42 100644 --- a/configs/kernel-s390x-debug-rhel.config +++ b/configs/kernel-s390x-debug-rhel.config @@ -1053,8 +1053,8 @@ CONFIG_CRYPTO_HMAC_S390=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-s390x-rhel.config b/configs/kernel-s390x-rhel.config index 959a8db8b34f8..a67c0c78c90b9 100644 --- a/configs/kernel-s390x-rhel.config +++ b/configs/kernel-s390x-rhel.config @@ -1053,8 +1053,8 @@ CONFIG_CRYPTO_HMAC_S390=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-s390x-zfcpdump-rhel.config b/configs/kernel-s390x-zfcpdump-rhel.config index e9a45b30b5f8d..61bd6210098a0 100644 --- a/configs/kernel-s390x-zfcpdump-rhel.config +++ b/configs/kernel-s390x-zfcpdump-rhel.config @@ -1054,8 +1054,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-x86_64-debug-rhel.config b/configs/kernel-x86_64-debug-rhel.config index 6d7c1a0cc8a8d..1ff4d7a435afd 100644 --- a/configs/kernel-x86_64-debug-rhel.config +++ b/configs/kernel-x86_64-debug-rhel.config @@ -1110,8 +1110,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-x86_64-rhel.config b/configs/kernel-x86_64-rhel.config index 813bd7ef8bbef..4e87a4a76bda0 100644 --- a/configs/kernel-x86_64-rhel.config +++ b/configs/kernel-x86_64-rhel.config @@ -1110,8 +1110,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-x86_64-rt-debug-rhel.config b/configs/kernel-x86_64-rt-debug-rhel.config index 5f1f815db0535..dff75ead3d1d7 100644 --- a/configs/kernel-x86_64-rt-debug-rhel.config +++ b/configs/kernel-x86_64-rt-debug-rhel.config @@ -1111,8 +1111,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set diff --git a/configs/kernel-x86_64-rt-rhel.config b/configs/kernel-x86_64-rt-rhel.config index e162fa31ab362..c78467ed24948 100644 --- a/configs/kernel-x86_64-rt-rhel.config +++ b/configs/kernel-x86_64-rt-rhel.config @@ -1111,8 +1111,8 @@ CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_HW=y # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_1024 is not set -# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128 is not set -CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2=y +CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_128=y +# CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_2 is not set # CONFIG_CRYPTO_JITTERENTROPY_MEMSIZE_8192 is not set CONFIG_CRYPTO_JITTERENTROPY_OSR=3 # CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set From afb0895cc2e211d2ed02d5b4fe0f9f55e8349e48 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Fri, 11 Sep 2026 09:23:18 -0700 Subject: [PATCH 93/98] dh_safe_prime_set_secret misses the check SP800-56Arev3, 5.6.2.1.2 that an externally provided private key is within the valid range 1 <= key <= q-1. Requested by lab. Signed-off-by: Jeremy Allison Co-Authored-By: Claude Opus 4.8 (1M context) --- crypto/dh.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/crypto/dh.c b/crypto/dh.c index e13258c3d1d1a..f8d57fc577f9d 100644 --- a/crypto/dh.c +++ b/crypto/dh.c @@ -479,6 +479,60 @@ static void *dh_safe_prime_gen_privkey(const struct dh_safe_prime *safe_prime, return ERR_PTR(err); } +/* + * SP800-56Ar3, sec. 5.6.2.1.2: obtain owner assurance that an externally + * provided private key is within the valid range 1 <= key <= q - 1, with + * q = (p - 1) / 2 for the safe-prime groups. + * + * The upper bound is q - 1 rather than this implementation's generation + * bound of 2^N - 1 (see dh_safe_prime_gen_privkey()): per SP800-56Ar3 + * sec. 5.6.1.1.1 a compliant generator may pick any N up to log2(q) + 1, + * so an externally generated key can legitimately be as large as q - 1. + * q - 1 is also the only bound derivable from the group alone. + */ +static int dh_safe_prime_check_privkey(const struct dh_safe_prime *safe_prime, + const struct dh *params) +{ + MPI x, p, q; + int ret; + + x = mpi_read_raw_data(params->key, params->key_size); + if (!x) + return -EINVAL; + + p = mpi_read_raw_data(safe_prime->p, safe_prime->p_size); + if (!p) { + ret = -EINVAL; + goto out_free_x; + } + + q = mpi_alloc(mpi_get_nlimbs(p)); + if (!q) { + ret = -ENOMEM; + goto out_free_p; + } + + /* + * p is an odd (safe) prime, so (p - 1) / 2 == p >> 1; no need to + * explicitly subtract one before shifting, cf. dh_is_pubkey_valid(). + */ + ret = mpi_rshift(q, p, 1); + if (ret) + goto out_free_q; + + /* Reject key < 1 (i.e. key == 0) or key > q - 1 (i.e. key >= q). */ + if (mpi_cmp_ui(x, 1) < 0 || mpi_cmp(x, q) >= 0) + ret = -EINVAL; + +out_free_q: + mpi_free(q); +out_free_p: + mpi_free(p); +out_free_x: + mpi_free(x); + return ret; +} + static int dh_safe_prime_set_secret(struct crypto_kpp *tfm, const void *buffer, unsigned int len) { @@ -509,6 +563,15 @@ static int dh_safe_prime_set_secret(struct crypto_kpp *tfm, const void *buffer, if (IS_ERR(key)) return PTR_ERR(key); params.key = key; + } else if (fips_enabled) { + /* + * A locally generated key is in range by construction; an + * externally provided one must be validated per + * SP800-56Ar3 sec. 5.6.2.1.2. + */ + err = dh_safe_prime_check_privkey(inst_ctx->safe_prime, ¶ms); + if (err) + return err; } buf_size = crypto_dh_key_len(¶ms); From eb7227dd1ca40b218fe25a176e118ea2145b9054 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 13 Oct 2026 16:31:03 -0400 Subject: [PATCH 94/98] exec: Cleanup POSIX timers right after de_thread() cve CVE-2026-98260 commit-author Hyunwoo Kim commit acb03d3881818581052924a9bbbe92b8741ed448 commit-source-sha 75aa08b93c65766040f9ca99f41ac85ad22596b6 commit-source linux-6.18.y upstream-diff | Context due to missing unrelated change: af7bb0d2ca4 ("exec: fix the racy usage of fs_struct->in_exec") commit acb03d3881818581052924a9bbbe92b8741ed448 upstream. A per-thread CPU timer holds a reference to the PID of the thread it is attached to and, while it is armed, its node is queued in that thread's posix_cputimers. The task is looked up by that PID. When a non-leader thread exec()s, de_thread() changes which task owns that PID. pid_task(timer->it.cpu.pid, PIDTYPE_PID) then returns NULL, but the node is still queued on tsk, which is alive. timer_lock_sighand() takes a failed lookup to mean that the node is already dequeued, so it has nothing to undo. begin_new_exec() calls posix_cpu_timers_exit(me) right after exec_task_namespaces() and that removes the leftover node, so the state normally stays invisible. But bprm->point_of_no_return is set before de_thread(), so if unshare_files(), set_mm_exe_file(), exec_mmap() or exec_task_namespaces() fails, the task dies before it gets there. exit_itimers() then frees the k_itimer while its node is still queued, and reaping tsk later erases that freed node from the rbtree. In short: the non-leader thread B the parent timer_create(CLOCK_THREAD_CPUTIME_ID) timer_settime() arm_timer() // the node is queued on B execve() de_thread(B) exchange_tids(B, leader) // B's PID now belongs to the leader release_task(leader) __exit_signal(leader) posix_cpu_timers_exit(leader) // cleans leader's queue, not B's __unhash_process(leader) // that PID has no task anymore exec_mmap() mmap_read_lock_killable(old_mm) kill(B, SIGKILL) // -EINTR get_signal() do_exit() exit_itimers() posix_timer_delete() posix_cpu_timer_del() posix_timer_unhash_and_free() // freed while still queued wait4() release_task(B) posix_cpu_timers_exit(B) cleanup_timerqueue() timerqueue_del() // use-after-free Move the POSIX timer cleanup right after de_thread() before any of the later failure conditions brings the task into do_exit(). [ tglx: Move the cleanup right after de_thread() ] Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") Signed-off-by: Hyunwoo Kim Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/ao7Q8miiuLAPVnWv@v4bel Link: https://patch.msgid.link/20260911090541.627712075@kernel.org Signed-off-by: Greg Kroah-Hartman (cherry picked from commit 75aa08b93c65766040f9ca99f41ac85ad22596b6) Signed-off-by: Jonathan Maple --- fs/exec.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 2c6e61139ac78..7cb7a1b283957 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1203,6 +1203,17 @@ void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec) perf_event_comm(tsk, exec); } +static void posixtimer_exec(struct task_struct *me) +{ +#ifdef CONFIG_POSIX_TIMERS + spin_lock_irq(&me->sighand->siglock); + posix_cpu_timers_exit(me); + spin_unlock_irq(&me->sighand->siglock); + exit_itimers(me); + flush_itimer_signals(); +#endif +} + /* * Calling this is the point of no return. None of the failures will be * seen by userspace since either the process is already taking a fatal @@ -1239,6 +1250,15 @@ int begin_new_exec(struct linux_binprm * bprm) if (retval) goto out; + /* + * This must be done here to ensure that POSIX CPU timers which were + * armed on the current task are dequeued from me::posix_cputimers. + * Otherwise in case of a TID switch the deletion of the related POSIX + * timer would not remove an enqueued timer because the TID lookup + * of the old TID fails. + */ + posixtimer_exec(me); + /* * Cancel any io_uring activity across execve */ @@ -1277,14 +1297,6 @@ int begin_new_exec(struct linux_binprm * bprm) if (retval) goto out_unlock; -#ifdef CONFIG_POSIX_TIMERS - spin_lock_irq(&me->sighand->siglock); - posix_cpu_timers_exit(me); - spin_unlock_irq(&me->sighand->siglock); - exit_itimers(me); - flush_itimer_signals(); -#endif - /* * Make the signal table private. */ From 6c47ef30d33a128bde833489eb6c4f03b4c31072 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 13 Oct 2026 17:03:10 -0400 Subject: [PATCH 95/98] posix-timers: Target group sigqueue to current task only if not exiting cve-pre CVE-2026-98256 commit-author Frederic Weisbecker commit 63dffecfba3eddcf67a8f76d80e0c141f93d44a5 A sigqueue belonging to a posix timer, which target is not a specific thread but a whole thread group, is preferrably targeted to the current task if it is part of that thread group. However nothing prevents a posix timer event from queueing such a sigqueue from a reaped yet running task. The interruptible code space between exit_notify() and the final call to schedule() is enough for posix_timer_fn() hrtimer to fire. If that happens while the current task is part of the thread group target, it is proposed to handle it but since its sighand pointer may have been cleared already, the sigqueue is dropped even if there are other tasks running within the group that could handle it. As a result posix timers with thread group wide target may miss signals when some of their threads are exiting. Fix this with verifying that the current task hasn't been through exit_notify() before proposing it as a preferred target so as to ensure that its sighand is still here and stable. complete_signal() might still reconsider the choice and find a better target within the group if current has passed retarget_shared_pending() already. Fixes: bcb7ee79029d ("posix-timers: Prefer delivery of signals to the current thread") Reported-by: Anthony Mallet Suggested-by: Oleg Nesterov Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Acked-by: Oleg Nesterov Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20241122234811.60455-1-frederic@kernel.org Closes: https://lore.kernel.org/all/26411.57288.238690.681680@gargle.gargle.HOWL (cherry picked from commit 63dffecfba3eddcf67a8f76d80e0c141f93d44a5) Signed-off-by: Jonathan Maple --- kernel/signal.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index 575e2450666d8..fd2a7e76004d8 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1967,14 +1967,15 @@ static void posixtimer_queue_sigqueue(struct sigqueue *q, struct task_struct *t, * * Where type is not PIDTYPE_PID, signals must be delivered to the * process. In this case, prefer to deliver to current if it is in - * the same thread group as the target process, which avoids - * unnecessarily waking up a potentially idle task. + * the same thread group as the target process and its sighand is + * stable, which avoids unnecessarily waking up a potentially idle task. */ static inline struct task_struct *posixtimer_get_target(struct k_itimer *tmr) { struct task_struct *t = pid_task(tmr->it_pid, tmr->it_pid_type); - if (t && tmr->it_pid_type != PIDTYPE_PID && same_thread_group(t, current)) + if (t && tmr->it_pid_type != PIDTYPE_PID && + same_thread_group(t, current) && !current->exit_state) t = current; return t; } From b13bcdab92f3744cd3bb17a0d726196c04e59118 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 13 Oct 2026 17:17:13 -0400 Subject: [PATCH 96/98] signal: Prevent exec() race cve CVE-2026-98256 commit-author Thomas Gleixner commit d2710c8d938ae6a825a6463158e6e6f31eac792a commit-source-sha 621c365739828900f32a0c1cdfe5387528a62176 commit-source linux-6.18.y upstream-diff | Conflict in exit_signals() due to missing commit: 2b1642b8810 ("signal: Move MMCID exit out of sighand lock") however this is part of a much larger rewrite of sched/mmcid https://lore.kernel.org/all/20251119172549.706439391@linutronix.de/ Too invasive to backport. Keeping sched_mm_cid_exit_signals(tsk) at the top of the new scoped_guard() prior to tsk->flags |= PF_EXITING keep the original ordering of both the now defunct early-exit path and the locked path. [ Upstream commit d2710c8d938ae6a825a6463158e6e6f31eac792a ] Hyunwoo debugged the following KASAN UAF splat: BUG: KASAN: slab-use-after-free in __send_signal_locked+0xb27/0xba0 Write of size 8 at addr ffff888007ed80c8 by task poc/79 ... Call Trace: __send_signal_locked+0xb27/0xba0 do_send_sig_info+0xa7/0x160 do_send_specific+0x76/0xa0 __x64_sys_tgkill+0x193/0x270 ... Allocated by task 80: do_timer_create+0x1a4/0x1030 __x64_sys_timer_create+0x145/0x190 ... Freed by task 12: kmem_cache_free_bulk+0x1f8/0x4a0 kvfree_rcu_bulk+0x14f/0x1c0 kfree_rcu_work+0x128/0x1a0 ... Last potentially related work creation: kvfree_call_rcu+0x39/0x390 __flush_itimer_signals+0x211/0x320 flush_itimer_signals+0x47/0x90 begin_new_exec+0xa6b/0x28c0 It turned out that this happens with a non-leader exec() as Hyunwoo explained: de_thread() calls exchange_tids() before release_task(leader), so the struct pid held by a SIGEV_THREAD_ID timer created against the leader's tid now points to the thread which called execve(). pid_task() returns that thread and lock_task_sighand() on it succeeds. If the timer signal is blocked, its sigqueue stays queued on the leader's task::pending. The next expiry of that timer can then run while release_task() flushes the queue. posixtimer_send_sigqueue() checks whether the sigqueue is already queued with a plain list_empty(), which only reads list_head::next. list_del_init() is not atomic and INIT_LIST_HEAD() stores list_head::next before list_head::prev, so the check can pass in between. list_add_tail() queues the entry on the task::pending of the live thread, and the list_head::prev store from the flush then overwrites the list_head::prev link that list_add_tail() has just set. __flush_itimer_signals() does not undo that either. With list_head::prev pointing at the entry itself, its list_del_init() only stores the same values again, so the entry is not removed from the list. It is still there after the last reference is dropped and the timer is freed by RCU, and the list_add_tail() of a later tgkill() follows that list_head::prev into the freed timer. This problem surfaced with the recent commit which moved the sigqueue flush out of the sighand lock held region. Hyonwoo proposed to fix this by using list_del_init_careful(), but that just papers over the problem. After some disucssions and various attempts to solve it, Eric pointed out that there is no reason to flush task::pending late in release_task() and it should be done in exit_signals() already. As nothing can collect and deliver signals which are queued in a dying task's pending queue, there is no reason to delay it further. But it has to be ensured that no signals can be queued into it after that point. exit_signals() sets PF_EXITING in task::flags, which can be used as an indicator for this. Cure it by: - Preventing signal queueing for task private signals (PIDTYPE_PID) when the task has PF_EXITING set in __send_signal_locked() and in posixtimer_send_sigqueue(). - Protecting the unlocked setting of PF_EXITING in exit_signals() for the task group empty and the group exit case with sighand lock - Flushing task::pending signals right there. Optimize that by moving the whole pending list to an on-stack list head under sighand lock and free the signals without the lock held. There has been quite some discussion about the lockless flush and the non-leader exec case on weakly ordered systems. The problem is that a third party which tries to send a posix timer signal relies on the PID lookup to find the target task and that lookup might result in the new leader when the signal was originaly directed to the old leader. In case that the signal was queued on the old leader then the lockless flush raised a concern over the following situation: old_leader new_leader third party A: flush_list() // list_del_init() stores to sigqueue LOCK (tasklist) old_leader->exit_state = EXIT_ZOMBIE; B: UNLOCK (tasklist) C: LOCK (tasklist) if (old_leader->exit_state) transfer_tids() D: store PID posix_timer_send_sigqueue() // Observes #D so t = new_leader E: t = get_target() F: LOCK (sighand) G: if (list_empty(sigqueue)) list_add(sigqueue) The concern was that the third party might observe #D but not observe #A and therefore would proceed to #G while the list_del() stores (#A) in flush_list() are not visible yet, which could result in list corruption. That would be possible if looking at it solely from a RELEASE+ACQUIRE ordering point of view, but B-C is a UNLOCK+LOCK hand-over, which is not the same as RELEASE+ACQUIRE: RELEASE+ACQUIRE: RCpc, only the CPUs involved agree on the ordering UNLOCK+LOCK: RCtso, the hand-over is store-ordering As B-C is UNLOCK+LOCK, which is RCtso and that does impose store order, A stores must happen before the D store. Combine with E-F, which has a data dependency from the LOAD to the LOCK and thereby constraints later LOADs, those sigqueue loads in G that come after F must in fact observe the A stores. Fixes: fb3bbcfe344e ("exit: change the release_task() paths to call flush_sigqueue() lockless") Reported-by: Hyunwoo Kim Debugged-by: Hyunwoo Kim Suggested-by: "Eric W. Biederman" Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911090541.572536604@kernel.org Closes: https://patch.msgid.link/aok1rdkBgZsynHZB@v4bel Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman (cherry picked from commit 621c365739828900f32a0c1cdfe5387528a62176) Signed-off-by: Jonathan Maple --- kernel/exit.c | 11 ++--- kernel/signal.c | 116 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 91 insertions(+), 36 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 1ab844a573474..8fe1ce3fb0ea5 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -280,12 +280,13 @@ void release_task(struct task_struct *p) put_pid(thread_pid); release_thread(p); /* - * This task was already removed from the process/thread/pid lists - * and lock_task_sighand(p) can't succeed. Nobody else can touch - * ->pending or, if group dead, signal->shared_pending. We can call - * flush_sigqueue() lockless. + * This task was already removed from the process/thread/pid lists and + * lock_task_sighand(p) can't succeed. If it's the group leader then + * flush tsk->signal->shared_pending. tsk->pending has been flushed + * already in exit_signals(). Nothing else can touch + * signal->shared_pending anymore, so flush_sigqueue() can be invoked + * lockless. */ - flush_sigqueue(&p->pending); if (thread_group_leader(p)) flush_sigqueue(&p->signal->shared_pending); diff --git a/kernel/signal.c b/kernel/signal.c index fd2a7e76004d8..561dfef818014 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -474,18 +474,42 @@ static void __sigqueue_free(struct sigqueue *q) kmem_cache_free(sigqueue_cachep, q); } -void flush_sigqueue(struct sigpending *queue) +/* + * flush_sigqueue_list() can only be invoked without holding sighand::siglock in + * the following cases: + * + * 1) When flushing task::pending _after_ setting task::flags PF_EXITING + * + * All functions which try to send a signal to @task will observe PF_EXITING + * and drop the signal. + * + * 2) When flushing task::signal::shared_pending _after_ the last task in a + * thread group was unhashed and task::sighand is NULL. + * + * Nothing can queue a signal anymore because sighand is NULL. + */ +static void flush_sigqueue_list(struct list_head *head) { - struct sigqueue *q; + struct sigqueue *q, *tmp; - sigemptyset(&queue->signal); - while (!list_empty(&queue->list)) { - q = list_entry(queue->list.next, struct sigqueue , list); + list_for_each_entry_safe(q, tmp, head, list) { list_del_init(&q->list); __sigqueue_free(q); } } +void flush_sigqueue(struct sigpending *queue) +{ + sigemptyset(&queue->signal); + flush_sigqueue_list(&queue->list); +} + +static void sigqueue_dequeue_pending(struct sigpending *queue, struct list_head *head) +{ + sigemptyset(&queue->signal); + list_splice_init(&queue->list, head); +} + /* * Flush all pending signals for this kthread. */ @@ -1038,6 +1062,21 @@ static inline bool legacy_queue(struct sigpending *signals, int sig) return (sig < SIGRTMIN) && sigismember(&signals->signal, sig); } +/* + * When PF_EXITING is set the task is on the way out and has t::pending + * flushed already. Prevent queueing of PIDTYPE_PID signals as they would + * be leaked. + */ +static inline bool task_can_queue_signal(struct task_struct *t, enum pid_type type) +{ + lockdep_assert_held(&t->sighand->siglock); + + if (!(t->flags & PF_EXITING)) + return true; + + return type != PIDTYPE_PID; +} + static int __send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type, bool force) { @@ -1049,6 +1088,10 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info, lockdep_assert_held(&t->sighand->siglock); result = TRACE_SIGNAL_IGNORED; + + if (!task_can_queue_signal(t, type)) + goto ret; + if (!prepare_signal(sig, t, force)) goto ret; @@ -1975,11 +2018,25 @@ static inline struct task_struct *posixtimer_get_target(struct k_itimer *tmr) struct task_struct *t = pid_task(tmr->it_pid, tmr->it_pid_type); if (t && tmr->it_pid_type != PIDTYPE_PID && - same_thread_group(t, current) && !current->exit_state) + same_thread_group(t, current) && !(current->flags & PF_EXITING)) t = current; return t; } +/* + * Find the target task for the POSIX timer signal and prevent that a + * PIDTYPE_PID signal is queued on a task which has PF_EXITING set. + */ +static inline struct task_struct *posixtimer_get_unignore_target(struct k_itimer *tmr) +{ + struct task_struct *t = posixtimer_get_target(tmr); + + if (t && task_can_queue_signal(t, tmr->it_pid_type)) + return t; + + return NULL; +} + void posixtimer_send_sigqueue(struct k_itimer *tmr) { struct sigqueue *q = &tmr->sigq; @@ -1997,6 +2054,9 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) if (!likely(lock_task_sighand(t, &flags))) return; + if (!task_can_queue_signal(t, tmr->it_pid_type)) + goto unlock; + /* * Update @tmr::sigqueue_seq for posix timer signals with sighand * locked to prevent a race against dequeue_signal(). @@ -2088,6 +2148,7 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) result = TRACE_SIGNAL_DELIVERED; out: trace_signal_generate(sig, &q->info, t, tmr->it_pid_type != PIDTYPE_PID, result); +unlock: unlock_task_sighand(t, &flags); } @@ -2143,7 +2204,7 @@ static void posixtimer_sig_unignore(struct task_struct *tsk, int sig) * has exited by now, drop the reference count. */ guard(rcu)(); - target = posixtimer_get_target(tmr); + target = posixtimer_get_unignore_target(tmr); if (target) posixtimer_queue_sigqueue(&tmr->sigq, target, tmr->it_pid_type); else @@ -3125,44 +3186,37 @@ static void retarget_shared_pending(struct task_struct *tsk, sigset_t *which) void exit_signals(struct task_struct *tsk) { + LIST_HEAD(sigq_list); int group_stop = 0; - sigset_t unblocked; /* * @tsk is about to have PF_EXITING set - lock out users which - * expect stable threadgroup. + * expect a stable threadgroup. */ cgroup_threadgroup_change_begin(tsk); - if (thread_group_empty(tsk) || (tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + scoped_guard(spinlock_irq, &tsk->sighand->siglock) { sched_mm_cid_exit_signals(tsk); tsk->flags |= PF_EXITING; - cgroup_threadgroup_change_end(tsk); - return; - } - spin_lock_irq(&tsk->sighand->siglock); - /* - * From now this task is not visible for group-wide signals, - * see wants_signal(), do_signal_stop(). - */ - sched_mm_cid_exit_signals(tsk); - tsk->flags |= PF_EXITING; + sigqueue_dequeue_pending(&tsk->pending, &sigq_list); - cgroup_threadgroup_change_end(tsk); + if (task_sigpending(tsk) && !thread_group_empty(tsk) && + !(tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + sigset_t unblocked = tsk->blocked; - if (!task_sigpending(tsk)) - goto out; + signotset(&unblocked); + retarget_shared_pending(tsk, &unblocked); - unblocked = tsk->blocked; - signotset(&unblocked); - retarget_shared_pending(tsk, &unblocked); + if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && + task_participate_group_stop(tsk)) + group_stop = CLD_STOPPED; + } + } - if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && - task_participate_group_stop(tsk)) - group_stop = CLD_STOPPED; -out: - spin_unlock_irq(&tsk->sighand->siglock); + cgroup_threadgroup_change_end(tsk); + + flush_sigqueue_list(&sigq_list); /* * If group stop has completed, deliver the notification. This From 37b0be20411904c0132311e482e2472553ed6d1e Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Fri, 17 Oct 2025 12:52:16 -0400 Subject: [PATCH 97/98] github actions: Use reusable validate kernel commits workflow Simplifies the workflow to use the reusable workflow defined in main branch. This reduces duplication and makes the workflow easier to maintain across multiple branches. The workflow was renamed because it now includes validation over and above just checking for upstream fixes Signed-off-by: Jonathan Maple --- .github/workflows/validate-kernel-commits.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/validate-kernel-commits.yml diff --git a/.github/workflows/validate-kernel-commits.yml b/.github/workflows/validate-kernel-commits.yml new file mode 100644 index 0000000000000..c74434336e251 --- /dev/null +++ b/.github/workflows/validate-kernel-commits.yml @@ -0,0 +1,10 @@ +name: Validate Kernel Commits + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + check: + uses: ctrliq/kernel-src-tree/.github/workflows/validate-kernel-commits.yml@main + secrets: inherit From f5d19914a94ccf357b090e6ce81655ecca84bf02 Mon Sep 17 00:00:00 2001 From: Brett Mastbergen Date: Fri, 16 Oct 2026 11:19:12 -0400 Subject: [PATCH 98/98] ipv4: validate checksum_start before completing checksum cve CVE-2026-99999 commit-author Michael S. Tsirkin commit 6785011f8b16abf40b1e9d8b2e332232b3e68822 If a packet with bad checksum metadata gets into the ipv4 stack, skb_checksum_help can corrupt the network header and cause a bunch of mischief. This was discovered and reported by Paulos, and has been reporoduced by others independently since. We really shouldn't allow such packets in, but as a defence in depth measure, let's also check before we complete the checksum. A more complete validation at input is forthcoming, but needs more work. Cc: stable@vger.kernel.org Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Fixes: bfd5f4a3d605 ("packet: Add GSO/csum offload support.") Reported-by: Paulos Yibelo Closes: https://lore.kernel.org/netdev/20260922030310.8684-2-habte.yibelo@gmail.com/ Signed-off-by: Michael S. Tsirkin Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/0a012b4923e189c4c593ef4f471e5ff0edbe9030.1791412497.git.mst@redhat.com Signed-off-by: Jakub Kicinski (cherry picked from commit 6785011f8b16abf40b1e9d8b2e332232b3e68822) Signed-off-by: Brett Mastbergen --- include/net/ip.h | 7 +++++++ net/bridge/netfilter/nf_conntrack_bridge.c | 10 +++++++--- net/ipv4/ip_output.c | 10 +++++++--- net/netfilter/nfnetlink_queue.c | 7 +++++++ net/netfilter/xt_CHECKSUM.c | 6 +++++- net/xfrm/xfrm_output.c | 16 +++++++++++----- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index 7e7db511c9630..01c7ed96585ee 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -74,6 +74,13 @@ static inline unsigned int ip_hdrlen(const struct sk_buff *skb) return ip_hdr(skb)->ihl * 4; } +static inline int ip_check_csum_start(const struct sk_buff *skb) +{ + if (unlikely(skb->csum_start < skb->network_header + ip_hdrlen(skb))) + return -EINVAL; + return 0; +} + struct ipcm_cookie { struct sockcm_cookie sockc; __be32 addr; diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 6482de4d87509..10127b7588efa 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -40,9 +40,13 @@ static int nf_br_ip_fragment(struct net *net, struct sock *sk, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto blackhole; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto blackhole; + } iph = ip_hdr(skb); diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index 7625bcc856155..780d0a6e50958 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -779,9 +779,13 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto fail; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto fail; + } /* * Point into the IP datagram header. diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index 508276f9e034a..bf12e5bc417ca 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -653,6 +654,12 @@ static int nfqnl_put_bridge(struct nf_queue_entry *entry, struct sk_buff *skb) static int nf_queue_checksum_help(struct sk_buff *entskb) { + if (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + if (skb_csum_is_sctp(entskb)) return skb_crc32c_csum_help(entskb); diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index 9d99f5a3d1764..1fb0f8118404d 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -15,6 +15,7 @@ #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin "); @@ -25,8 +26,11 @@ MODULE_ALIAS("ip6t_CHECKSUM"); static unsigned int checksum_tg(struct sk_buff *skb, const struct xt_action_param *par) { - if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) + if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) { + if (xt_family(par) == NFPROTO_IPV4 && ip_check_csum_start(skb)) + return NF_DROP; skb_checksum_help(skb); + } return XT_CONTINUE; } diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index 71bfbb6760f7e..15469d664f5f9 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -816,16 +816,22 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) } if (skb->ip_summed == CHECKSUM_PARTIAL) { - err = skb_checksum_help(skb); - if (err) { - XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); - kfree_skb(skb); - return err; + if (skb->protocol == htons(ETH_P_IP)) { + err = ip_check_csum_start(skb); + if (err) + goto error; } + err = skb_checksum_help(skb); + if (err) + goto error; } out: return xfrm_output2(net, sk, skb); +error: + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; } EXPORT_SYMBOL_GPL(xfrm_output);