[RFC 1/3] x86/vdso: create vdso file, use it for mapping

Dmitry Safonov
2016-08-25T16:00:39Z

I added here a new in-kernel fs with ramfs-like options.
Created vdso file in this fs (yet for testing, only 64-bit vdso).
Mapped this file to process's mm on setup_additional_pages.
Just for testing purpose it's done only for specific UID.

Signed-off-by: Dmitry Safonov <dsafonov@virtuozzo.com>
---
arch/x86/entry/vdso/vma.c | 146 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 140 insertions(+), 6 deletions(-)

diff --git a/arch/x86/entry/vdso/vma.c b/arch/x86/entry/vdso/vma.c
index f840766659a8..e83830e422ae 100644
--- a/arch/x86/entry/vdso/vma.c
+++ b/arch/x86/entry/vdso/vma.c
@@ -13,6 +13,10 @@
#include <linux/elf.h>
#include <linux/cpu.h>
#include <linux/ptrace.h>
+#include <linux/pagemap.h>
+#include <linux/mount.h>
+#include <linux/ramfs.h>
+#include <linux/file.h>
#include <asm/pvclock.h>
#include <asm/vgtod.h>
#include <asm/proto.h>
@@ -35,6 +39,9 @@ void __init init_vdso_image(const struct vdso_image *image)
image->alt_len));
}

+static struct vfsmount *vdso_mnt;
+struct file *vdso_file_64;
+
struct linux_binprm;

/*
@@ -217,12 +224,31 @@ static int map_vdso(const struct vdso_image *image, bool calculate_addr)
/*
* MAYWRITE to allow gdb to COW and set breakpoints
*/
- vma = _install_special_mapping(mm,
- text_start,
- image->size,
- VM_READ|VM_EXEC|
- VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
- &vdso_mapping);
+ if (__kuid_val(task_uid(current)) == 1001) {
+ unsigned long n_addr = mmap_region(vdso_file_64, text_start,
+ image->size, VM_READ|VM_EXEC|
+ VM_DONTEXPAND|VM_SOFTDIRTY|
+ VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC, 0);
+ if (text_start != n_addr) {
+ pr_err("Failed to mmap vdso file at %lx, mmap_region returned %lx\n",
+ text_start, n_addr);
+ goto old_way;
+ }
+ vma = find_vma(mm, text_start);
+ if (IS_ERR(vma) || vma->vm_start != text_start) {
+ pr_err("Failed to find vdso mapped vma at %lx\n",
+ text_start);
+ goto old_way;
+ }
+ } else {
+old_way:
+ vma = _install_special_mapping(mm,
+ text_start,
+ image->size,
+ VM_READ|VM_EXEC|
+ VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
+ &vdso_mapping);
+ }

if (IS_ERR(vma)) {
ret = PTR_ERR(vma);
@@ -336,6 +362,109 @@ static int vgetcpu_online(unsigned int cpu)
return smp_call_function_single(cpu, vgetcpu_cpu_init, NULL, 1);
}

+static __init int add_vdso_pages_to_page_cache(
+ const struct vdso_image *vdso_image, struct inode *inode)
+{
+ unsigned long i;
+ int ret;
+
+ for (i = 0; i < (vdso_image->size / PAGE_SIZE); i++) {
+ struct page *page = virt_to_page(vdso_image->data +
+ (i << PAGE_SHIFT));
+ int ret;
+
+ __SetPageLocked(page);
+ ret = add_to_page_cache_locked(page, inode->i_mapping,
+ i, __GFP_REPEAT);
+ __SetPageUptodate(page);
+ __ClearPageLocked(page);
+ if (unlikely(ret))
+ goto put_pages;
+ }
+ return 0;
+
+put_pages:
+ while (i > 0)
+ put_page(virt_to_page(vdso_image->data + (i << PAGE_SHIFT)));
+ return ret;
+}
+
+static char *vdso_vma_name(struct dentry *dentry, char *buffer, int buflen)
+{
+ return "[vdso]";
+}
+static const struct dentry_operations vdso_dops = {
+ .d_dname = vdso_vma_name,
+};
+
+static __init struct file *init_vdso_file(const struct vdso_image *vdso_image,
+ const char *name)
+{
+ struct super_block *sb;
+ struct qstr name_str;
+ struct inode *inode;
+ struct path path;
+ struct file *res;
+
+ if (IS_ERR(vdso_mnt))
+ return ERR_CAST(vdso_mnt);
+ sb = vdso_mnt->mnt_sb;
+
+ name_str.hash = 0;
+ name_str.len = strlen(name);
+ name_str.name = name;
+
+ res = ERR_PTR(-ENOMEM);
+ path.mnt = mntget(vdso_mnt);
+ path.dentry = d_alloc_pseudo(sb, &name_str);
+ if (!path.dentry)
+ goto put_path;
+ d_set_d_op(path.dentry, &vdso_dops);
+
+ res = ERR_PTR(-ENOSPC);
+ inode = ramfs_get_inode(sb, NULL, S_IFREG | S_IRUGO | S_IXUGO, 0);
+ if (!inode)
+ goto put_path;
+
+ inode->i_flags |= S_PRIVATE;
+ d_instantiate(path.dentry, inode);
+ inode->i_size = vdso_image->size;
+
+ res = ERR_PTR(add_vdso_pages_to_page_cache(vdso_image, inode));
+ if (IS_ERR(res))
+ goto put_path;
+
+ res = alloc_file(&path, FMODE_READ, &ramfs_file_operations);
+ if (!IS_ERR(res))
+ return res;
+
+put_path:
+ path_put(&path);
+ return res;
+}
+
+
+static struct file_system_type vdso_fs_type = {
+ .name = "vdsofs",
+ .mount = ramfs_mount,
+ .kill_sb = kill_litter_super,
+};
+
+static int __init init_vdso_fs(void)
+{
+ int ret;
+
+ ret = register_filesystem(&vdso_fs_type);
+ if (ret)
+ return ret;
+
+ vdso_mnt = kern_mount(&vdso_fs_type);
+ if (IS_ERR(vdso_mnt))
+ return PTR_ERR(vdso_mnt);
+ return 0;
+}
+
+/* XXX: replace BUG_ON with return to old-way vdso handling */
static int __init init_vdso(void)
{
init_vdso_image(&vdso_image_64);
@@ -344,6 +473,11 @@ static int __init init_vdso(void)
init_vdso_image(&vdso_image_x32);
#endif

+ BUG_ON(init_vdso_fs());
+
+ vdso_file_64 = init_vdso_file(&vdso_image_64, "vdso_image_64");
+ BUG_ON(IS_ERR(vdso_file_64));
+
/* notifier priority > KVM */
return cpuhp_setup_state(CPUHP_AP_X86_VDSO_VMA_ONLINE,
"AP_X86_VDSO_VMA_ONLINE", vgetcpu_online, NULL);
--
2.9.0