A secretmem inode can outlive its original file. An O_PATH descriptor opened through /proc/self/fd keeps the inode and its unevictable folios alive after the mapping and original descriptor are closed. The file release path nevertheless drops the locked-memory charge, allowing the same user to reuse RLIMIT_MEMLOCK while those folios remain allocated. Move private-state and locked-memory accounting cleanup to inode eviction, after truncating the page cache. Keep secretmem_users active over the same lifetime so that the hibernation guard covers retained secret pages. Let iput() handle file-creation failures and tolerate an inode with no private state. Fixes: 97d34aa65c29 ("mm/secretmem: properly account locked pages") Signed-off-by: Baek Kyu, Kim --- On an arm64 QEMU guest with an 8 KiB RLIMIT_MEMLOCK, an O_PATH reference retained two secretmem pages. The baseline dropped the charge to zero and allowed a further one-page fault; the patch kept both pages charged and rejected that fault until the last path reference closed. The Image was built with KASAN and lockdep. Hibernation, other architectures, and long-running concurrency were not tested. mm/secretmem.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/mm/secretmem.c b/mm/secretmem.c index 384f5cfc457f..fa8be360bd2a 100644 --- a/mm/secretmem.c +++ b/mm/secretmem.c @@ -185,18 +185,14 @@ static void secretmem_destroy_inode_priv(struct inode *inode) { struct secretmem_inode_state *state = inode->i_private; + if (!state) + return; + 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_prepare(struct vm_area_desc *desc) @@ -216,7 +212,6 @@ bool vma_is_secretmem(struct vm_area_struct *vma) } static const struct file_operations secretmem_fops = { - .release = secretmem_release, .mmap_prepare = secretmem_mmap_prepare, }; @@ -274,6 +269,7 @@ static int secretmem_init_inode_priv(struct inode *inode) state->user = get_uid(current_user()); inode->i_private = state; + atomic_inc(&secretmem_users); return 0; } @@ -296,7 +292,7 @@ static struct file *secretmem_file_create(unsigned long flags) O_RDWR | O_LARGEFILE, &secretmem_fops); if (IS_ERR(file)) { err = PTR_ERR(file); - goto err_free_priv; + goto err_free_inode; } mapping_set_gfp_mask(inode->i_mapping, GFP_USER); @@ -309,11 +305,7 @@ static struct file *secretmem_file_create(unsigned long flags) inode->i_mode |= S_IFREG; inode->i_size = 0; - atomic_inc(&secretmem_users); - return file; -err_free_priv: - secretmem_destroy_inode_priv(inode); err_free_inode: iput(inode); return ERR_PTR(err); @@ -335,6 +327,18 @@ SYSCALL_DEFINE1(memfd_secret, unsigned int, flags) return FD_ADD(flags & O_CLOEXEC, secretmem_file_create(flags)); } +static void secretmem_evict_inode(struct inode *inode) +{ + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + secretmem_destroy_inode_priv(inode); +} + +static const struct super_operations secretmem_super_ops = { + .statfs = simple_statfs, + .evict_inode = secretmem_evict_inode, +}; + static int secretmem_init_fs_context(struct fs_context *fc) { struct pseudo_fs_context *ctx; @@ -342,6 +346,7 @@ static int secretmem_init_fs_context(struct fs_context *fc) ctx = init_pseudo(fc, SECRETMEM_MAGIC); if (!ctx) return -ENOMEM; + ctx->ops = &secretmem_super_ops; return 0; } -- 2.55.0