fix CVE-2026-93243. Lorenzo Stoakes (ARM) (2): [Backport] mm/secretmem: properly account locked pages [Backport] perf: Track the perf mlock gift separately Wupeng Ma (1): [Huawei] perf: Fix kabi breakage of struct user_struct include/linux/sched/user.h | 13 ++- kernel/events/core.c | 12 +-- mm/secretmem.c | 117 ++++++++++++++++++++-- tools/testing/selftests/mm/memfd_secret.c | 30 +----- 4 files changed, 123 insertions(+), 49 deletions(-) -- 2.43.0
From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org> stable inclusion from stable-v6.6.160 commit 51fba083e454f901947d6a335eeff671171d4714 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/20015 CVE: CVE-2026-93243 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- [ Upstream commit 97d34aa65c29cca85e3e9050f4c936389b38a054 ] secretmem accounts folios by treating memory as if it were mlock()'d and thus limited by the RLIMIT_MEMLOCK limit. However the folios are unevictable and remain so until the inode is evicted, eliminating usual mlock() semantics - mapping folios then unmapping them does not clear their unevictable state, since it depends on AS_UNEVICTABLE, not PG_mlocked. A user can therefore easily work around the RLIMIT_MEMLOCK limit - simply map then unmap and VmLck no longer counts the secretmem range. Worse, folios are not accounted in the process's RSS, meaning the OOM killer won't know to kill the process. Repeatedly mapping/unmapping (or forking) can then result in the consumption of all available system memory with unevictable folios and cause system instability. A secretmem fd can be passed between processes and over fork so a per-process limit simply does not make sense, so follow the precedent set by io_uring, perf, skbuff, iommufd and xdp by tracking the number of locked pages in user_struct->locked_vm. Since the scope tracked is actually inode lifetime, the RLIMIT_MEMLOCK applies per-user not per-process, so it doesn't make sense to bypass for users with CAP_IPC_LOCK, therefore remove this bypass. There is simply no reason to carry on marking the mapping as mlock()'d since it's misleading and the lifecycle is now correctly handled, so remove this too. Note that secretmem does not support any form of truncation (including hole punching) and the folios are unreclaimable, so the folios need only be accounted on fault and unaccounted on inode destruction. __secretmem_account_pages() is more or less a duplicate of the code that io_uring etc. use, but since this is a bug fix that needs backporting, defer any de-duplication efforts to a follow-up. test_mlock_limit() asserts mlock_future_ok() on mmap(), however this has been removed, so remove the test altogether for the fix. A new test will be sent separately for upstream. Link: https://lore.kernel.org/20260826-secretmem-accounting-v3-1-94cb04399510@kern... Fixes: 1507f51255c9 ("mm: introduce memfd_secret system call to create "secret" memory areas") Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org> Reported-by: Daehyeon Ko <4ncienth@gmail.com> Closes: https://lore.kernel.org/linux-mm/20260813225328.2010303-1-4ncienth@gmail.com... Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org> Acked-by: David Hildenbrand (Arm) <david@kernel.org> Tested-by: Daehyeon Ko <4ncienth@gmail.com> Cc: Alexei Starovoitov <ast@kernel.org> Cc: David Hildenbrand <david@kernel.org> Cc: David S. Miller <davem@davemloft.net> Cc: Hagen Paul Pfeifer <hagen@jauu.net> Cc: Jakub Kacinski <kuba@kernel.org> Cc: James Bottomley <james.bottomley@HansenPartnership.com> Cc: Jesper Dangaard Brouer <hawk@kernel.org> Cc: John Fastabend <john.fastabend@gmail.com> Cc: Liam R. Howlett <liam@infradead.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Stanislav Fomichev <sdf@fomichev.me> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Vlastimil Babka <vbabka@kernel.org> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> [ adapted secretmem changes to Linux 6.6’s older APIs. ] Signed-off-by: Sasha Levin <sashal@kernel.org> Conflicts: mm/secretmem.c [Commit 97d34aa65c29 targets mainline's anon_inode_make_secure_inode(); this tree still creates the inode with alloc_anon_inode() plus security_inode_init_security_anon(), so keep that path and initialize the secretmem private state after it. The memfd_secret selftest hunk is dropped because selftests/mm cannot be built by this tree's kernel Makefile environment.] Signed-off-by: Wupeng Ma <mawupeng1@huawei.com> --- include/linux/sched/user.h | 3 +- mm/secretmem.c | 117 ++++++++++++++++++++-- tools/testing/selftests/mm/memfd_secret.c | 30 +----- 3 files changed, 110 insertions(+), 40 deletions(-) diff --git a/include/linux/sched/user.h b/include/linux/sched/user.h index 4cc52698e214e..8d7e5521f7cdd 100644 --- a/include/linux/sched/user.h +++ b/include/linux/sched/user.h @@ -25,7 +25,8 @@ struct user_struct { #if defined(CONFIG_PERF_EVENTS) || defined(CONFIG_BPF_SYSCALL) || \ defined(CONFIG_NET) || defined(CONFIG_IO_URING) || \ - defined(CONFIG_VFIO_PCI_ZDEV_KVM) || IS_ENABLED(CONFIG_IOMMUFD) + defined(CONFIG_VFIO_PCI_ZDEV_KVM) || IS_ENABLED(CONFIG_IOMMUFD) || \ + defined(CONFIG_SECRETMEM) atomic_long_t locked_vm; #endif #ifdef CONFIG_WATCH_QUEUE diff --git a/mm/secretmem.c b/mm/secretmem.c index 987def80d8106..8c29d41e35c56 100644 --- a/mm/secretmem.c +++ b/mm/secretmem.c @@ -18,6 +18,8 @@ #include <linux/secretmem.h> #include <linux/set_memory.h> #include <linux/sched/signal.h> +#include <linux/sched/user.h> +#include <linux/cred.h> #include <uapi/linux/magic.h> @@ -47,10 +49,69 @@ bool secretmem_active(void) return !!atomic_read(&secretmem_users); } +struct secretmem_inode_state { + struct user_struct *user; + atomic_long_t nr_pages_accounted; +}; + +static bool __secretmem_account_pages(struct user_struct *user, + unsigned long nr_pages) +{ + unsigned long page_limit, cur_pages, new_pages; + + if (!nr_pages) + return true; + + page_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; + + cur_pages = atomic_long_read(&user->locked_vm); + do { + new_pages = cur_pages + nr_pages; + if (new_pages > page_limit) + return false; + } while (!atomic_long_try_cmpxchg(&user->locked_vm, + &cur_pages, new_pages)); + return true; +} + +static bool secretmem_account_folio(struct secretmem_inode_state *state, + struct folio *folio) +{ + const unsigned long nr_pages = folio_nr_pages(folio); + + if (!__secretmem_account_pages(state->user, nr_pages)) + return false; + + atomic_long_add(nr_pages, &state->nr_pages_accounted); + return true; +} + +static void __secretmem_unaccount_pages(struct secretmem_inode_state *state, + unsigned long nr_pages) +{ + atomic_long_sub(nr_pages, &state->user->locked_vm); + atomic_long_sub(nr_pages, &state->nr_pages_accounted); +} + +static void secretmem_unaccount_folio(struct secretmem_inode_state *state, + struct folio *folio) +{ + __secretmem_unaccount_pages(state, folio_nr_pages(folio)); +} + +static void secretmem_unaccount_all_folios(struct secretmem_inode_state *state) +{ + const unsigned long nr_pages_accounted = + atomic_long_read(&state->nr_pages_accounted); + + __secretmem_unaccount_pages(state, nr_pages_accounted); +} + static vm_fault_t secretmem_fault(struct vm_fault *vmf) { struct address_space *mapping = vmf->vma->vm_file->f_mapping; struct inode *inode = file_inode(vmf->vma->vm_file); + struct secretmem_inode_state *state = inode->i_private; pgoff_t offset = vmf->pgoff; gfp_t gfp = vmf->gfp_mask; unsigned long addr; @@ -73,9 +134,16 @@ static vm_fault_t secretmem_fault(struct vm_fault *vmf) goto out; } + if (!secretmem_account_folio(state, folio)) { + folio_put(folio); + ret = VM_FAULT_SIGBUS; + goto out; + } + page = &folio->page; err = set_direct_map_invalid_noflush(page); if (err) { + secretmem_unaccount_folio(state, folio); folio_put(folio); ret = vmf_error(err); goto out; @@ -84,6 +152,7 @@ static vm_fault_t secretmem_fault(struct vm_fault *vmf) __folio_mark_uptodate(folio); err = filemap_add_folio(mapping, folio, offset, gfp); if (unlikely(err)) { + secretmem_unaccount_folio(state, folio); /* * If a split of large page was required, it * already happened when we marked the page invalid @@ -114,23 +183,30 @@ static const struct vm_operations_struct secretmem_vm_ops = { .fault = secretmem_fault, }; +static void secretmem_destroy_inode_priv(struct inode *inode) +{ + struct secretmem_inode_state *state = inode->i_private; + + secretmem_unaccount_all_folios(state); + free_uid(state->user); + kfree(state); + inode->i_private = NULL; +} + static int secretmem_release(struct inode *inode, struct file *file) { atomic_dec(&secretmem_users); + secretmem_destroy_inode_priv(inode); + return 0; } static int secretmem_mmap(struct file *file, struct vm_area_struct *vma) { - unsigned long len = vma->vm_end - vma->vm_start; - if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0) return -EINVAL; - if (!mlock_future_ok(vma->vm_mm, vma->vm_flags | VM_LOCKED, len)) - return -EAGAIN; - - vm_flags_set(vma, VM_LOCKED | VM_DONTDUMP); + vm_flags_set(vma, VM_DONTDUMP); vma->vm_ops = &secretmem_vm_ops; return 0; @@ -190,6 +266,19 @@ static const struct inode_operations secretmem_iops = { static struct vfsmount *secretmem_mnt; +static int secretmem_init_inode_priv(struct inode *inode) +{ + struct secretmem_inode_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->user = get_uid(current_user()); + inode->i_private = state; + return 0; +} + static struct file *secretmem_file_create(unsigned long flags) { struct file *file; @@ -213,10 +302,17 @@ static struct file *secretmem_file_create(unsigned long flags) goto err_free_inode; } + + err = secretmem_init_inode_priv(inode); + if (err) + goto err_free_inode; + file = alloc_file_pseudo(inode, secretmem_mnt, "secretmem", O_RDWR, &secretmem_fops); - if (IS_ERR(file)) - goto err_free_inode; + if (IS_ERR(file)) { + err = PTR_ERR(file); + goto err_free_priv; + } mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER); mapping_set_unevictable(inode->i_mapping); @@ -229,10 +325,11 @@ static struct file *secretmem_file_create(unsigned long flags) inode->i_size = 0; return file; - +err_free_priv: + secretmem_destroy_inode_priv(inode); err_free_inode: iput(inode); - return file; + return ERR_PTR(err); } SYSCALL_DEFINE1(memfd_secret, unsigned int, flags) diff --git a/tools/testing/selftests/mm/memfd_secret.c b/tools/testing/selftests/mm/memfd_secret.c index 9b298f6a04b37..348cc976726bb 100644 --- a/tools/testing/selftests/mm/memfd_secret.c +++ b/tools/testing/selftests/mm/memfd_secret.c @@ -56,33 +56,6 @@ static void test_file_apis(int fd) pass("file IO is blocked as expected\n"); } -static void test_mlock_limit(int fd) -{ - size_t len; - char *mem; - - len = mlock_limit_cur; - if (len % page_size != 0) - len = (len/page_size) * page_size; - - mem = mmap(NULL, len, prot, mode, fd, 0); - if (mem == MAP_FAILED) { - fail("unable to mmap secret memory\n"); - return; - } - munmap(mem, len); - - len = mlock_limit_max * 2; - mem = mmap(NULL, len, prot, mode, fd, 0); - if (mem != MAP_FAILED) { - fail("unexpected mlock limit violation\n"); - munmap(mem, len); - return; - } - - pass("mlock limit is respected\n"); -} - static void try_process_vm_read(int fd, int pipefd[2]) { struct iovec liov, riov; @@ -258,7 +231,7 @@ static void prepare(void) strerror(errno)); } -#define NUM_TESTS 4 +#define NUM_TESTS 3 int main(int argc, char *argv[]) { @@ -278,7 +251,6 @@ int main(int argc, char *argv[]) strerror(errno)); } - test_mlock_limit(fd); test_file_apis(fd); test_process_vm_read(fd); test_ptrace(fd); -- 2.43.0
From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org> stable inclusion from stable-v6.18.59 commit b67fb3955b07dc86765155fc308f69e5636bf511 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/20015 CVE: CVE-2026-93243 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- commit 78436f3882fb8d848c823bf8cf754988202c5ca5 upstream. The locked_vm field in struct user_struct was originally introduced by commit 789f90fcf6b0 ("perf_counter: per user mlock gift") to provide an mlock-like budget for perf measured against mm->pinned_vm (since 2011). perf grants each user a 'gift' of perf_event_mlock_kb * online CPUs of buffer pages which is charged to user->locked_vm without any RLIMIT_MEMLOCK check. Only pages beyond the gift are checked against RLIMIT_MEMLOCK, per-mm, via mm->pinned_vm, which makes it different from the standard mlock() check, which is per-mm and made against RLIMIT_MEMLOCK. However, since this was introduced, a number of other components have utilised this field where a shared resource needed to be limited against RLIMIT_MEMLOCK. The other users are currently MSG_ZEROCOPY, io_uring, AF_XDP, iommufd, s390 KVM zPCI and most recently, secretmem in commit 97d34aa65c29 ("mm/secretmem: properly account locked pages"). This field is shared between all of these, but because the gift is not checked against RLIMIT_MEMLOCK, user->locked_vm alone can exceed it - 16 CPUs * 516 KiB is already more than the default 8 MiB - after which every other user of the field fails unconditionally. This was reported by a user who saw secretmem failing while running a simultaneous perf record. Resolve the issue by simply giving perf its own field separate from the rest. BPF used user->locked_vm up until commit 80ee81e0403c ("bpf: Eliminate rlimit-based memory accounting infra for bpf maps") which landed in 5.11. Thus also eliminate the now-defunct ifdef for CONFIG_BPF_SYSCALL for user->locked_vm. This issue exists between all of the components which use user->locked_vm and perf, however secretmem is the first user who has caused a report over many years and has been backported, so target the fix at that. Fixes: 97d34aa65c29 ("mm/secretmem: properly account locked pages") Closes: https://lore.kernel.org/20261002175651.811343-1-ameer.hamza@truenas.com Reported-by: Ameer Hamza <ameer.hamza@truenas.com> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261003-perf-locked-vm-fix-v1-1-d214dcaf0d76@kerne... Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Wupeng Ma <mawupeng1@huawei.com> Conflicts: kernel/events/core.c [Context conflicts: replace locked_vm with perf_mlock] Signed-off-by: Wupeng Ma <mawupeng1@huawei.com> --- include/linux/sched/user.h | 8 +++++--- kernel/events/core.c | 12 ++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/include/linux/sched/user.h b/include/linux/sched/user.h index 8d7e5521f7cdd..ec262b605437c 100644 --- a/include/linux/sched/user.h +++ b/include/linux/sched/user.h @@ -23,11 +23,13 @@ struct user_struct { struct hlist_node uidhash_node; kuid_t uid; -#if defined(CONFIG_PERF_EVENTS) || defined(CONFIG_BPF_SYSCALL) || \ - defined(CONFIG_NET) || defined(CONFIG_IO_URING) || \ +#if defined(CONFIG_NET) || defined(CONFIG_IO_URING) || \ defined(CONFIG_VFIO_PCI_ZDEV_KVM) || IS_ENABLED(CONFIG_IOMMUFD) || \ defined(CONFIG_SECRETMEM) - atomic_long_t locked_vm; + atomic_long_t locked_vm; /* pinned pages charged to RLIMIT_MEMLOCK */ +#endif +#ifdef CONFIG_PERF_EVENTS + atomic_long_t perf_mlock; /* perf buffer pages within perf_event_mlock_kb */ #endif #ifdef CONFIG_WATCH_QUEUE atomic_t nr_watches; /* The number of watches this user currently has */ diff --git a/kernel/events/core.c b/kernel/events/core.c index 99a3ca49a312d..9a5cfac01e495 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -6403,7 +6403,7 @@ static void perf_mmap_close(struct vm_area_struct *vma) perf_pmu_output_stop(event); /* now it's safe to free the pages */ - atomic_long_sub(rb->aux_nr_pages - rb->aux_mmap_locked, &mmap_user->locked_vm); + atomic_long_sub(rb->aux_nr_pages - rb->aux_mmap_locked, &mmap_user->perf_mlock); atomic64_sub(rb->aux_mmap_locked, &vma->vm_mm->pinned_vm); /* this has to be the last one */ @@ -6478,7 +6478,7 @@ static void perf_mmap_close(struct vm_area_struct *vma) */ atomic_long_sub((size >> PAGE_SHIFT) + 1 - mmap_locked, - &mmap_user->locked_vm); + &mmap_user->perf_mlock); atomic64_sub(mmap_locked, &vma->vm_mm->pinned_vm); free_uid(mmap_user); @@ -6640,11 +6640,11 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma) */ user_lock_limit *= num_online_cpus(); - user_locked = atomic_long_read(&user->locked_vm); + user_locked = atomic_long_read(&user->perf_mlock); /* * sysctl_perf_event_mlock may have changed, so that - * user->locked_vm > user_lock_limit + * user->perf_mlock > user_lock_limit */ if (user_locked > user_lock_limit) user_locked = user_lock_limit; @@ -6652,7 +6652,7 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma) if (user_locked > user_lock_limit) { /* - * charge locked_vm until it hits user_lock_limit; + * charge perf_mlock until it hits user_lock_limit; * charge the rest from pinned_vm */ extra = user_locked - user_lock_limit; @@ -6704,7 +6704,7 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma) unlock: if (!ret) { - atomic_long_add(user_extra, &user->locked_vm); + atomic_long_add(user_extra, &user->perf_mlock); atomic64_add(extra, &vma->vm_mm->pinned_vm); atomic_inc(&event->mmap_count); -- 2.43.0
hulk inclusion category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/20015 CVE: CVE-2026-93243 -------------------------------- Backport commit e0e763a87ea9 ("[Backport] perf: Track the perf mlock gift separately") adds atomic_long_t perf_mlock to struct user_struct right after locked_vm. When CONFIG_PERF_EVENTS is enabled, this shifts every following member and grows the structure from 136 to 144 bytes, changing the checksum of the exported free_uid() from 0x3d886965 to 0xacf17fe5 and breaking kABI for out-of-tree modules. struct user_struct has no KABI_RESERVE slot on this branch, so it cannot be recovered the way it is done on 5.10. Instead move perf_mlock to the end of the structure and wrap it in KABI_EXTEND(), which keeps the new member hidden from genksyms and therefore leaves the checksum of every symbol computed from this structure untouched. This is the same approach already used elsewhere in this tree (struct disk_stats, struct hrtimer, struct pipe_inode_info). The tail extension does not affect the original layout: all pre-existing members keep their offsets and the size of the original part stays 136 bytes, so modules built against the previous layout still work. perf_mlock is only referenced by name in kernel/events/core.c, so no other code needs to be updated. Fixes: b67fb3955b07 ("[Backport] perf: Track the perf mlock gift separately") Signed-off-by: Wupeng Ma <mawupeng1@huawei.com> --- include/linux/sched/user.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/linux/sched/user.h b/include/linux/sched/user.h index ec262b605437c..2cc27a8f6eed8 100644 --- a/include/linux/sched/user.h +++ b/include/linux/sched/user.h @@ -7,6 +7,7 @@ #include <linux/percpu_counter.h> #include <linux/refcount.h> #include <linux/ratelimit.h> +#include <linux/kabi.h> /* * Some day this will be a full-fledged user tracking system.. @@ -28,15 +29,16 @@ struct user_struct { defined(CONFIG_SECRETMEM) atomic_long_t locked_vm; /* pinned pages charged to RLIMIT_MEMLOCK */ #endif -#ifdef CONFIG_PERF_EVENTS - atomic_long_t perf_mlock; /* perf buffer pages within perf_event_mlock_kb */ -#endif #ifdef CONFIG_WATCH_QUEUE atomic_t nr_watches; /* The number of watches this user currently has */ #endif /* Miscellaneous per-user rate limit */ struct ratelimit_state ratelimit; + +#ifdef CONFIG_PERF_EVENTS + KABI_EXTEND(atomic_long_t perf_mlock) /* perf buffer pages within perf_event_mlock_kb */ +#endif }; extern int uids_sysfs_init(void); -- 2.43.0
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://atomgit.com/openeuler/kernel/merge_requests/30533 邮件列表地址:https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/7SB... FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://atomgit.com/openeuler/kernel/merge_requests/30533 Mailing list address: https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/7SB...
participants (2)
-
patchwork bot -
Wupeng Ma