From 70509805a5ce1c565b37fc8c81ad9cb725af0126 Mon Sep 17 00:00:00 2001 From: Roxana Nicolescu Date: Thu, 12 Mar 2026 14:46:21 +0100 Subject: [PATCH 01/15] 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 649df1b597b5edc96f727f5f0f57bd8fde135cb5 Mon Sep 17 00:00:00 2001 From: Roxana Nicolescu Date: Thu, 26 Mar 2026 13:05:48 +0100 Subject: [PATCH 02/15] 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 1728c2bec6ef869e5060af8cb2f88c0a0c918107 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Mon, 11 May 2026 18:01:20 -0400 Subject: [PATCH 03/15] 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 6d1df03e9676036b77e98ee75673989b2fbbdcf5 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Mon, 11 May 2026 18:02:54 -0400 Subject: [PATCH 04/15] 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 6317c2e58b19a60e43c3dbab4624ef38250deaaa Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Mon, 9 Jun 2025 15:49:43 -0400 Subject: [PATCH 05/15] 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 1c22a2f55d73e50a2b8cc36ace377b871c329bc1 Mon Sep 17 00:00:00 2001 From: Roxana Nicolescu Date: Tue, 17 Feb 2026 16:12:43 +0100 Subject: [PATCH 06/15] 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 2b75594cc8fd3eabacc7e8c81136c0a588d4361b Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 31 Mar 2026 15:25:55 -0400 Subject: [PATCH 07/15] 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 ab26204359f31..b26eaf8fe9483 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 4848a54f03e5b201109e740fafc6877f068d9ac3 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 31 Mar 2026 15:34:16 -0400 Subject: [PATCH 08/15] 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 1f64ae6d7a69e..e0c0000d3bebe 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1887,6 +1887,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) { @@ -1915,6 +1928,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 711a3e6183db1627744e35df2cbd2e0c76ac6fba Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 31 Mar 2026 16:31:50 -0400 Subject: [PATCH 09/15] 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 e0c0000d3bebe..43c61b0a8294a 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2005,7 +2005,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 7f0f0ea8ce8c4711ab9b2e49ac16df447879987e Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Mon, 4 May 2026 10:12:39 +0000 Subject: [PATCH 10/15] 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 43c61b0a8294a..a9de694d48fcd 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" @@ -2469,6 +2470,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, @@ -2481,6 +2493,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 d70f7b8973c29d1252bba11a94f334abf8ba4176 Mon Sep 17 00:00:00 2001 From: Shreeya Patel Date: Mon, 4 May 2026 10:14:42 +0000 Subject: [PATCH 11/15] 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 f4f3d063d8e09c471e6d2f52aee6603056d9b479 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 8 May 2026 13:57:14 +0000 Subject: [PATCH 12/15] rxrpc: Also unshare DATA/RESPONSE packets when paged frags are present cve CVE-2026-43500 commit-author Hyunwoo Kim commit - commit-source https://lore.kernel.org/all/af2kdW2F1gJ9U-Gg@v4bel upstream-diff | The conn_event.c hunk is dropped entirely. Upstream wraps the conn->security->verify_response() call inside a new rxrpc_verify_response() function that copies non-linear skbs before in-place decryption. This kernel doesn't have that wrapper; the security op is called directly from rxrpc_process_event(), so there is no call site to patch. Additionally, the rxkad_verify_response() implementation in this tree already pulls the response and ticket out via skb_copy_bits() into kmalloc'd local buffers and decrypts those buffers (not the skb backing pages), so the RESPONSE-packet vector that v3 closes upstream is not reachable here. The call_event.c hunk applies as-is. The DATA-packet handler in rxrpc_input_call_event() and the RESPONSE handler in rxrpc_verify_response() copy the skb to a linear one before calling into the security ops only when skb_cloned() is true. An skb that is not cloned but still carries externally-owned paged fragments (e.g. SKBFL_SHARED_FRAG set by splice() into a UDP socket via __ip_append_data, or a chained skb_has_frag_list()) falls through to the in-place decryption path, which binds the frag pages directly into the AEAD/skcipher SGL via skb_to_sgvec(). Extend the gate to also unshare when skb_has_frag_list() or skb_has_shared_frag() is true. This catches the splice-loopback vector and other externally-shared frag sources while preserving the zero-copy fast path for skbs whose frags are kernel-private (e.g. NIC page_pool RX, GRO). The OOM/trace handling already in place is reused. Fixes: d0d5c0cd1e71 ("rxrpc: Use skb_unshare() rather than skb_cow_data()") Cc: stable@vger.kernel.org Signed-off-by: Hyunwoo Kim (cherry picked from commit 544687651fe57721c5e4e76380ed8ef8fdfdc98b) Signed-off-by: Shreeya Patel --- net/rxrpc/call_event.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c index fdd683261226c..2b19b252225e5 100644 --- a/net/rxrpc/call_event.c +++ b/net/rxrpc/call_event.c @@ -334,7 +334,9 @@ bool rxrpc_input_call_event(struct rxrpc_call *call) if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA && sp->hdr.securityIndex != 0 && - skb_cloned(skb)) { + (skb_cloned(skb) || + skb_has_frag_list(skb) || + skb_has_shared_frag(skb))) { /* Unshare the packet so that it can be * modified by in-place decryption. */ From e041e613c8fd12863eb0c559ae464432d0a7a976 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 9 Jun 2026 13:03:58 -0400 Subject: [PATCH 13/15] KVM: arm64: Reassign nested_mmus array behind mmu_lock cve CVE-2026-46317 commit-author Hyunwoo Kim commit 70543358fa08e0f7cebc3447c3b70fe97ad7aaa8 kvm->arch.nested_mmus[] is walked under kvm->mmu_lock, including from the MMU notifier path (kvm_unmap_gfn_range() -> kvm_nested_s2_unmap()), which can run at any time. kvm_vcpu_init_nested() reallocates the array and frees the old buffer while holding only kvm->arch.config_lock, so such a walker can reference the freed array. Allocate the new array outside of mmu_lock, as the allocation can sleep. Under the lock, copy the existing entries, fix up the back pointers and reassign the array. Free the old buffer after dropping the lock, as kvfree() can sleep as well. Fixes: 4f128f8e1aaac ("KVM: arm64: nv: Support multiple nested Stage-2 mmu structures") Signed-off-by: Hyunwoo Kim Reviewed-by: Oliver Upton Link: https://patch.msgid.link/aiKIVVeIr1aAB1yp@v4bel Signed-off-by: Marc Zyngier Cc: stable@vger,kernel.org (cherry picked from commit 70543358fa08e0f7cebc3447c3b70fe97ad7aaa8) Signed-off-by: Jonathan Maple --- arch/arm64/kvm/nested.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index e298921e79aa4..b1f0350baf50d 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -89,21 +89,28 @@ int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) * again, and there is no reason to affect the whole VM for this. */ num_mmus = atomic_read(&kvm->online_vcpus) * S2_MMU_PER_VCPU; - tmp = kvrealloc(kvm->arch.nested_mmus, - size_mul(sizeof(*kvm->arch.nested_mmus), num_mmus), - GFP_KERNEL_ACCOUNT | __GFP_ZERO); - if (!tmp) - return -ENOMEM; - swap(kvm->arch.nested_mmus, tmp); + if (num_mmus > kvm->arch.nested_mmus_size) { + tmp = kvcalloc(num_mmus, sizeof(*tmp), GFP_KERNEL_ACCOUNT); + if (!tmp) + return -ENOMEM; - /* - * If we went through a realocation, adjust the MMU back-pointers in - * the previously initialised kvm_pgtable structures. - */ - if (kvm->arch.nested_mmus != tmp) - for (int i = 0; i < kvm->arch.nested_mmus_size; i++) - kvm->arch.nested_mmus[i].pgt->mmu = &kvm->arch.nested_mmus[i]; + write_lock(&kvm->mmu_lock); + + if (kvm->arch.nested_mmus_size) { + memcpy(tmp, kvm->arch.nested_mmus, + size_mul(sizeof(*tmp), kvm->arch.nested_mmus_size)); + + for (int i = 0; i < kvm->arch.nested_mmus_size; i++) + tmp[i].pgt->mmu = &tmp[i]; + } + + swap(kvm->arch.nested_mmus, tmp); + + write_unlock(&kvm->mmu_lock); + + kvfree(tmp); + } for (int i = kvm->arch.nested_mmus_size; !ret && i < num_mmus; i++) ret = init_nested_s2_mmu(kvm, &kvm->arch.nested_mmus[i]); From 6c98cb848e0f972f56cf25566667fee2119927a9 Mon Sep 17 00:00:00 2001 From: Jonathan Maple Date: Tue, 9 Jun 2026 13:08:59 -0400 Subject: [PATCH 14/15] KVM: arm64: Take the SRCU lock for page table walks in fault injection and AT emulation bugfix aarch64 kvm commit-author Hyunwoo Kim commit f2ca45b50d4216c9cc7ffabf50d9ad1932209251 walk_s1() and kvm_walk_nested_s2() expect to be called while holding kvm->srcu to guard against memslot changes. While this is generally the case, __kvm_at_s12() and __kvm_find_s1_desc_level() call into the respective walkers without taking kvm->srcu. Fix by acquiring kvm->srcu prior to the table walk in both instances. Cc: stable@vger.kernel.org Fixes: 50f77dc87f13 ("KVM: arm64: Populate level on S1PTW SEA injection") Fixes: be04cebf3e78 ("KVM: arm64: nv: Add emulation of AT S12E{0,1}{R,W}") Suggested-by: Oliver Upton Signed-off-by: Hyunwoo Kim Reviewed-by: Oliver Upton Link: https://patch.msgid.link/aiAZfdeyanIvP8SD@v4bel Signed-off-by: Marc Zyngier (cherry picked from commit f2ca45b50d4216c9cc7ffabf50d9ad1932209251) Signed-off-by: Jonathan Maple --- arch/arm64/kvm/at.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/at.c b/arch/arm64/kvm/at.c index be26d5aa668c3..e6de6aac6ede2 100644 --- a/arch/arm64/kvm/at.c +++ b/arch/arm64/kvm/at.c @@ -1528,7 +1528,8 @@ void __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr) /* Do the stage-2 translation */ ipa = (par & GENMASK_ULL(47, 12)) | (vaddr & GENMASK_ULL(11, 0)); out.esr = 0; - ret = kvm_walk_nested_s2(vcpu, ipa, &out); + scoped_guard(srcu, &vcpu->kvm->srcu) + ret = kvm_walk_nested_s2(vcpu, ipa, &out); if (ret < 0) return; @@ -1623,7 +1624,8 @@ int __kvm_find_s1_desc_level(struct kvm_vcpu *vcpu, u64 va, u64 ipa, int *level) } /* Walk the guest's PT, looking for a match along the way */ - ret = walk_s1(vcpu, &wi, &wr, va); + scoped_guard(srcu, &vcpu->kvm->srcu) + ret = walk_s1(vcpu, &wi, &wr, va); switch (ret) { case -EINTR: /* We interrupted the walk on a match, return the level */ From 5757f89fa975cded81622e00bccb1232de73434b Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 23 Jul 2026 15:46:14 -0700 Subject: [PATCH 15/15] net: openvswitch: reject oversized nested action attrs commit-author Asim Viladi Oglu Manizada commit 3f1f755366687d051174739fb99f7d560202f60b Open vSwitch stores generated flow actions as nlattrs, whose nla_len field is u16. Commit a1e64addf3ff ("net: openvswitch: remove misbehaving actions length check") allowed the total sw_flow_actions stream to grow beyond 64 KiB, which is valid, but also removed the last guard preventing a generated nested action attribute from exceeding U16_MAX. An oversized generated container can thus be closed with a truncated nla_len. A later dump or teardown then walks a structurally different stream than the one that was validated. In particular, an oversized nested CLONE/CT action may cause subsequent bytes in the generated stream to be interpreted as independent actions. Keep the larger total-action-stream behavior, but make nested action close reject generated containers that do not fit in nla_len, and return the error through all callers. For recursive SAMPLE, CLONE, DEC_TTL, and CHECK_PKT_LEN builders, trim resource-owning action-list tails in reverse construction order before discarding failed wrappers, so resources copied into the rejected tails are released before the wrappers are removed. Most failed outer wrappers are discarded by truncating actions_len after child resources have been released. CHECK_PKT_LEN also trims its parent after branch resources are gone. SET/TUNNEL close failures unwind their known tun_dst ownership directly, and SET_TO_MASKED has no external ownership and truncates on close failure. Fixes: a1e64addf3ff ("net: openvswitch: remove misbehaving actions length check") Cc: stable@vger.kernel.org Assisted-by: avom-custom-harness:gpt-5.5-qwen3.6-mod-mix Signed-off-by: Asim Viladi Oglu Manizada Reviewed-by: Eelco Chaudron Reviewed-by: Aaron Conole Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20260706094336.38639-1-manizada@pm.me Signed-off-by: Paolo Abeni (cherry picked from commit 3f1f755366687d051174739fb99f7d560202f60b) Signed-off-by: Sultan Alsawaf --- net/openvswitch/flow_netlink.c | 201 +++++++++++++++++++++++++-------- 1 file changed, 157 insertions(+), 44 deletions(-) diff --git a/net/openvswitch/flow_netlink.c b/net/openvswitch/flow_netlink.c index 865d5e5a0784f..fb957f7a15d6d 100644 --- a/net/openvswitch/flow_netlink.c +++ b/net/openvswitch/flow_netlink.c @@ -2533,13 +2533,56 @@ static inline int add_nested_action_start(struct sw_flow_actions **sfa, return used; } -static inline void add_nested_action_end(struct sw_flow_actions *sfa, - int st_offset) +static inline int add_nested_action_end(struct sw_flow_actions *sfa, + int st_offset) { - struct nlattr *a = (struct nlattr *) ((unsigned char *)sfa->actions + - st_offset); + struct nlattr *a; + u32 attr_len; + + if (WARN_ON_ONCE(st_offset < 0 || + (u32)st_offset > sfa->actions_len)) + return -EINVAL; + + attr_len = sfa->actions_len - (u32)st_offset; + if (WARN_ON_ONCE(attr_len < NLA_HDRLEN)) + return -EINVAL; - a->nla_len = sfa->actions_len - st_offset; + if (attr_len > U16_MAX) + return -EMSGSIZE; + + a = (struct nlattr *)((u8 *)sfa->actions + st_offset); + a->nla_len = attr_len; + return 0; +} + +/* Free the generated action-list tail at @start and truncate it. + * If @nested, @start points to its containing nlattr header. + */ +static void ovs_nla_trim(struct sw_flow_actions *sfa, int start, bool nested) +{ + const struct nlattr *actions; + u32 len; + + if (start < 0) + return; + + if (WARN_ON_ONCE((u32)start > sfa->actions_len)) + return; + + actions = (const struct nlattr *)((u8 *)sfa->actions + start); + len = sfa->actions_len - (u32)start; + + if (nested) { + if (len < NLA_HDRLEN) + goto out; + + actions = (const struct nlattr *)((u8 *)actions + NLA_HDRLEN); + len -= NLA_HDRLEN; + } + + ovs_nla_free_nested_actions(actions, len); +out: + sfa->actions_len = start; } static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, @@ -2559,6 +2602,7 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr, const struct nlattr *attrs[OVS_SAMPLE_ATTR_MAX + 1]; const struct nlattr *probability, *actions; const struct nlattr *a; + int actions_start; int rem, start, err; struct sample_arg arg; @@ -2602,18 +2646,27 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr, err = ovs_nla_add_action(sfa, OVS_SAMPLE_ATTR_ARG, &arg, sizeof(arg), log); if (err) - return err; + goto err; + actions_start = (*sfa)->actions_len; err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, actions_start, false); +err: + (*sfa)->actions_len = start; + return err; } static int validate_and_copy_dec_ttl(struct net *net, @@ -2661,18 +2714,31 @@ static int validate_and_copy_dec_ttl(struct net *net, return start; action_start = add_nested_action_start(sfa, OVS_DEC_TTL_ATTR_ACTION, log); - if (action_start < 0) - return action_start; + if (action_start < 0) { + err = action_start; + goto err; + } err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; + + err = add_nested_action_end(*sfa, action_start); + if (err) + goto err_free; - add_nested_action_end(*sfa, action_start); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, action_start, true); +err: + (*sfa)->actions_len = start; + return err; } static int validate_and_copy_clone(struct net *net, @@ -2683,6 +2749,7 @@ static int validate_and_copy_clone(struct net *net, u32 mpls_label_count, bool log, bool last, u32 depth) { + int actions_start; int start, err; u32 exec; @@ -2698,17 +2765,26 @@ static int validate_and_copy_clone(struct net *net, err = ovs_nla_add_action(sfa, OVS_CLONE_ATTR_EXEC, &exec, sizeof(exec), log); if (err) - return err; + goto err; + actions_start = (*sfa)->actions_len; err = __ovs_nla_copy_actions(net, attr, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, actions_start, false); +err: + (*sfa)->actions_len = start; + return err; } void ovs_match_init(struct sw_flow_match *match, @@ -2800,20 +2876,20 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, tun_dst = metadata_dst_alloc(key.tun_opts_len, METADATA_IP_TUNNEL, GFP_KERNEL); - if (!tun_dst) - return -ENOMEM; + if (!tun_dst) { + err = -ENOMEM; + goto err; + } err = dst_cache_init(&tun_dst->u.tun_info.dst_cache, GFP_KERNEL); - if (err) { - dst_release((struct dst_entry *)tun_dst); - return err; - } + if (err) + goto err_free_tun_dst; a = __add_action(sfa, OVS_KEY_ATTR_TUNNEL_INFO, NULL, sizeof(*ovs_tun), log); if (IS_ERR(a)) { - dst_release((struct dst_entry *)tun_dst); - return PTR_ERR(a); + err = PTR_ERR(a); + goto err_free_tun_dst; } ovs_tun = nla_data(a); @@ -2834,8 +2910,16 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, ip_tunnel_info_opts_set(tun_info, TUN_METADATA_OPTS(&key, key.tun_opts_len), key.tun_opts_len, dst_opt_type); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (WARN_ON_ONCE(err)) + goto err_free_tun_dst; + + return 0; +err_free_tun_dst: + dst_release((struct dst_entry *)tun_dst); +err: + (*sfa)->actions_len = start; return err; } @@ -3010,7 +3094,7 @@ static int validate_set(const struct nlattr *a, /* Convert non-masked non-tunnel set actions to masked set actions. */ if (!masked && key_type != OVS_KEY_ATTR_TUNNEL) { - int start, len = key_len * 2; + int err, start, len = key_len * 2; struct nlattr *at; *skip_copy = true; @@ -3022,8 +3106,11 @@ static int validate_set(const struct nlattr *a, return start; at = __add_action(sfa, key_type, NULL, len, log); - if (IS_ERR(at)) - return PTR_ERR(at); + if (IS_ERR(at)) { + err = PTR_ERR(at); + (*sfa)->actions_len = start; + return err; + } memcpy(nla_data(at), nla_data(ovs_key), key_len); /* Key. */ memset(nla_data(at) + key_len, 0xff, key_len); /* Mask. */ @@ -3033,7 +3120,11 @@ static int validate_set(const struct nlattr *a, mask->ipv6_label &= htonl(0x000FFFFF); } - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (WARN_ON_ONCE(err)) { + (*sfa)->actions_len = start; + return err; + } } return 0; @@ -3079,7 +3170,8 @@ static int validate_and_copy_check_pkt_len(struct net *net, const struct nlattr *acts_if_greater, *acts_if_lesser_eq; struct nlattr *a[OVS_CHECK_PKT_LEN_ATTR_MAX + 1]; struct check_pkt_len_arg arg; - int nested_acts_start; + int greater_acts_start = -1; + int lesser_acts_start = -1; int start, err; err = nla_parse_deprecated_strict(a, OVS_CHECK_PKT_LEN_ATTR_MAX, @@ -3114,37 +3206,58 @@ static int validate_and_copy_check_pkt_len(struct net *net, err = ovs_nla_add_action(sfa, OVS_CHECK_PKT_LEN_ATTR_ARG, &arg, sizeof(arg), log); if (err) - return err; + goto err_free; - nested_acts_start = add_nested_action_start(sfa, - OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL, log); - if (nested_acts_start < 0) - return nested_acts_start; + lesser_acts_start = + add_nested_action_start(sfa, + OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL, + log); + if (lesser_acts_start < 0) { + err = lesser_acts_start; + goto err_free; + } err = __ovs_nla_copy_actions(net, acts_if_lesser_eq, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, nested_acts_start); + err = add_nested_action_end(*sfa, lesser_acts_start); + if (err) + goto err_free; - nested_acts_start = add_nested_action_start(sfa, - OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER, log); - if (nested_acts_start < 0) - return nested_acts_start; + greater_acts_start = + add_nested_action_start(sfa, + OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER, + log); + if (greater_acts_start < 0) { + err = greater_acts_start; + goto err_free; + } err = __ovs_nla_copy_actions(net, acts_if_greater, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; + + err = add_nested_action_end(*sfa, greater_acts_start); + if (err) + goto err_free; - add_nested_action_end(*sfa, nested_acts_start); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, greater_acts_start, true); + ovs_nla_trim(*sfa, lesser_acts_start, true); + ovs_nla_trim(*sfa, start, false); + return err; } static int validate_psample(const struct nlattr *attr)