Revision 462a8e08e0e6287e5ce13187257edbf24213ed03 authored by David Chen on 09 February 2023, 17:48:28 UTC, committed by Linus Torvalds on 12 February 2023, 18:30:05 UTC
When we upgraded our kernel, we started seeing some page corruption like the following consistently: BUG: Bad page state in process ganesha.nfsd pfn:1304ca page:0000000022261c55 refcount:0 mapcount:-128 mapping:0000000000000000 index:0x0 pfn:0x1304ca flags: 0x17ffffc0000000() raw: 0017ffffc0000000 ffff8a513ffd4c98 ffffeee24b35ec08 0000000000000000 raw: 0000000000000000 0000000000000001 00000000ffffff7f 0000000000000000 page dumped because: nonzero mapcount CPU: 0 PID: 15567 Comm: ganesha.nfsd Kdump: loaded Tainted: P B O 5.10.158-1.nutanix.20221209.el7.x86_64 #1 Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 04/05/2016 Call Trace: dump_stack+0x74/0x96 bad_page.cold+0x63/0x94 check_new_page_bad+0x6d/0x80 rmqueue+0x46e/0x970 get_page_from_freelist+0xcb/0x3f0 ? _cond_resched+0x19/0x40 __alloc_pages_nodemask+0x164/0x300 alloc_pages_current+0x87/0xf0 skb_page_frag_refill+0x84/0x110 ... Sometimes, it would also show up as corruption in the free list pointer and cause crashes. After bisecting the issue, we found the issue started from commit e320d3012d25 ("mm/page_alloc.c: fix freeing non-compound pages"): if (put_page_testzero(page)) free_the_page(page, order); else if (!PageHead(page)) while (order-- > 0) free_the_page(page + (1 << order), order); So the problem is the check PageHead is racy because at this point we already dropped our reference to the page. So even if we came in with compound page, the page can already be freed and PageHead can return false and we will end up freeing all the tail pages causing double free. Fixes: e320d3012d25 ("mm/page_alloc.c: fix freeing non-compound pages") Link: https://lore.kernel.org/lkml/BYAPR02MB448855960A9656EEA81141FC94D99@BYAPR02MB4488.namprd02.prod.outlook.com/ Cc: Andrew Morton <akpm@linux-foundation.org> Cc: stable@vger.kernel.org Signed-off-by: Chunwei Chen <david.chen@nutanix.com> Reviewed-by: Vlastimil Babka <vbabka@suse.cz> Reviewed-by: Matthew Wilcox (Oracle) <willy@infradead.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent f339c25
usermode_driver.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* umd - User mode driver support
*/
#include <linux/shmem_fs.h>
#include <linux/pipe_fs_i.h>
#include <linux/mount.h>
#include <linux/fs_struct.h>
#include <linux/task_work.h>
#include <linux/usermode_driver.h>
static struct vfsmount *blob_to_mnt(const void *data, size_t len, const char *name)
{
struct file_system_type *type;
struct vfsmount *mnt;
struct file *file;
ssize_t written;
loff_t pos = 0;
type = get_fs_type("tmpfs");
if (!type)
return ERR_PTR(-ENODEV);
mnt = kern_mount(type);
put_filesystem(type);
if (IS_ERR(mnt))
return mnt;
file = file_open_root_mnt(mnt, name, O_CREAT | O_WRONLY, 0700);
if (IS_ERR(file)) {
kern_unmount(mnt);
return ERR_CAST(file);
}
written = kernel_write(file, data, len, &pos);
if (written != len) {
int err = written;
if (err >= 0)
err = -ENOMEM;
filp_close(file, NULL);
kern_unmount(mnt);
return ERR_PTR(err);
}
fput(file);
/* Flush delayed fput so exec can open the file read-only */
flush_delayed_fput();
task_work_run();
return mnt;
}
/**
* umd_load_blob - Remember a blob of bytes for fork_usermode_driver
* @info: information about usermode driver
* @data: a blob of bytes that can be executed as a file
* @len: The lentgh of the blob
*
*/
int umd_load_blob(struct umd_info *info, const void *data, size_t len)
{
struct vfsmount *mnt;
if (WARN_ON_ONCE(info->wd.dentry || info->wd.mnt))
return -EBUSY;
mnt = blob_to_mnt(data, len, info->driver_name);
if (IS_ERR(mnt))
return PTR_ERR(mnt);
info->wd.mnt = mnt;
info->wd.dentry = mnt->mnt_root;
return 0;
}
EXPORT_SYMBOL_GPL(umd_load_blob);
/**
* umd_unload_blob - Disassociate @info from a previously loaded blob
* @info: information about usermode driver
*
*/
int umd_unload_blob(struct umd_info *info)
{
if (WARN_ON_ONCE(!info->wd.mnt ||
!info->wd.dentry ||
info->wd.mnt->mnt_root != info->wd.dentry))
return -EINVAL;
kern_unmount(info->wd.mnt);
info->wd.mnt = NULL;
info->wd.dentry = NULL;
return 0;
}
EXPORT_SYMBOL_GPL(umd_unload_blob);
static int umd_setup(struct subprocess_info *info, struct cred *new)
{
struct umd_info *umd_info = info->data;
struct file *from_umh[2];
struct file *to_umh[2];
int err;
/* create pipe to send data to umh */
err = create_pipe_files(to_umh, 0);
if (err)
return err;
err = replace_fd(0, to_umh[0], 0);
fput(to_umh[0]);
if (err < 0) {
fput(to_umh[1]);
return err;
}
/* create pipe to receive data from umh */
err = create_pipe_files(from_umh, 0);
if (err) {
fput(to_umh[1]);
replace_fd(0, NULL, 0);
return err;
}
err = replace_fd(1, from_umh[1], 0);
fput(from_umh[1]);
if (err < 0) {
fput(to_umh[1]);
replace_fd(0, NULL, 0);
fput(from_umh[0]);
return err;
}
set_fs_pwd(current->fs, &umd_info->wd);
umd_info->pipe_to_umh = to_umh[1];
umd_info->pipe_from_umh = from_umh[0];
umd_info->tgid = get_pid(task_tgid(current));
return 0;
}
static void umd_cleanup(struct subprocess_info *info)
{
struct umd_info *umd_info = info->data;
/* cleanup if umh_setup() was successful but exec failed */
if (info->retval)
umd_cleanup_helper(umd_info);
}
/**
* umd_cleanup_helper - release the resources which were allocated in umd_setup
* @info: information about usermode driver
*/
void umd_cleanup_helper(struct umd_info *info)
{
fput(info->pipe_to_umh);
fput(info->pipe_from_umh);
put_pid(info->tgid);
info->tgid = NULL;
}
EXPORT_SYMBOL_GPL(umd_cleanup_helper);
/**
* fork_usermode_driver - fork a usermode driver
* @info: information about usermode driver (shouldn't be NULL)
*
* Returns either negative error or zero which indicates success in
* executing a usermode driver. In such case 'struct umd_info *info'
* is populated with two pipes and a tgid of the process. The caller is
* responsible for health check of the user process, killing it via
* tgid, and closing the pipes when user process is no longer needed.
*/
int fork_usermode_driver(struct umd_info *info)
{
struct subprocess_info *sub_info;
const char *argv[] = { info->driver_name, NULL };
int err;
if (WARN_ON_ONCE(info->tgid))
return -EBUSY;
err = -ENOMEM;
sub_info = call_usermodehelper_setup(info->driver_name,
(char **)argv, NULL, GFP_KERNEL,
umd_setup, umd_cleanup, info);
if (!sub_info)
goto out;
err = call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC);
out:
return err;
}
EXPORT_SYMBOL_GPL(fork_usermode_driver);
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...