diff --git a/include/linux/remote_mapping.h b/include/linux/remote_mapping.h
new file mode 100644
index 0000000..ad0995d
--- /dev/null
+++ b/include/linux/remote_mapping.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _REMOTE_MAPPING_H
+#define _REMOTE_MAPPING_H
+
+#include <linux/mm.h>
+#include <linux/mmu_notifier.h>
+#include <linux/rbtree.h>
+#include <linux/srcu.h>
+#include <linux/rwsem.h>
+
+struct remote_file_context {
+	struct srcu_struct mm_srcu;
+	spinlock_t mm_lock;
+	struct mm_struct __rcu *mm;
+	struct mmu_notifier mn;
+
+	// interval tree for mapped ranges
+	struct rb_root_cached rb_root;
+	struct rw_semaphore tree_lock;
+};
+
+// describes a mapped range
+// mirror VMA points here
+struct remote_vma_context {
+	// al information about the mapped interval is found in the VMA
+	struct vm_area_struct *vma;
+
+	// interval tree link
+	struct rb_node target_rb;
+	unsigned long rb_subtree_last;
+};
+
+#endif /* _REMOTE_MAPPING_H */
diff --git a/include/uapi/linux/remote_mapping.h b/include/uapi/linux/remote_mapping.h
new file mode 100644
index 0000000..eb0eec3
--- /dev/null
+++ b/include/uapi/linux/remote_mapping.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef __UAPI_REMOTE_MAPPING_H__
+#define __UAPI_REMOTE_MAPPING_H__
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define REMOTE_PROC_MAP	_IOW('r', 0x01, int)
+// TODO: also ioctl for pidfd
+
+#endif /* __UAPI_REMOTE_MAPPING_H__ */
diff --git a/mm/Kconfig b/mm/Kconfig
index ab80933..c10dd5c 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -739,4 +739,13 @@ config ARCH_HAS_HUGEPD
 config MAPPING_DIRTY_HELPERS
         bool
 
+config REMOTE_MAPPING
+        bool "Remote memory mapping"
+        depends on MMU && MMU_NOTIFIER
+        default n
+
+        help
+          Allows a given process to map pages of another process in its own
+          address space.
+
 endmenu
diff --git a/mm/Makefile b/mm/Makefile
index 1937cc2..595f1a8c 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -108,3 +108,4 @@ obj-$(CONFIG_ZONE_DEVICE) += memremap.o
 obj-$(CONFIG_HMM_MIRROR) += hmm.o
 obj-$(CONFIG_MEMFD_CREATE) += memfd.o
 obj-$(CONFIG_MAPPING_DIRTY_HELPERS) += mapping_dirty_helpers.o
+obj-$(CONFIG_REMOTE_MAPPING) += remote_mapping.o
diff --git a/mm/remote_mapping.c b/mm/remote_mapping.c
new file mode 100644
index 0000000..358b1f5
--- /dev/null
+++ b/mm/remote_mapping.c
@@ -0,0 +1,615 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Remote memory mapping.
+ *
+ * Copyright (C) 2017-2018 Bitdefender S.R.L.
+ *
+ * Author:
+ *   Mircea Cirjaliu <mcirjaliu@bitdefender.com>
+ */
+#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/rmap.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/rbtree.h>
+#include <linux/pagemap.h>
+#include <linux/swap.h>
+#include <linux/swapops.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/mm.h>
+#include <linux/pid.h>
+#include <linux/oom.h>
+#include <linux/huge_mm.h>
+#include <linux/mmu_notifier.h>
+#include <linux/sched/mm.h>
+#include <linux/interval_tree_generic.h>
+#include <linux/refcount.h>
+#include <linux/debugfs.h>
+#include <linux/miscdevice.h>
+#include <linux/remote_mapping.h>
+#include <uapi/linux/remote_mapping.h>
+#include <asm/pgalloc.h>
+#include <linux/mman.h>
+#include <linux/pfn_t.h>
+#include <linux/errno.h>
+#include <linux/lockdep.h>
+
+#include "internal.h"
+
+#include <linux/kgdb.h>
+
+#define ASSERT(exp) BUG_ON(!(exp))
+
+
+static inline unsigned long ctx_start(struct remote_vma_context *ctx)
+{
+	return ctx->vma->vm_pgoff << PAGE_SHIFT;
+}
+
+static inline unsigned long ctx_end(struct remote_vma_context *ctx)
+{
+	return (ctx->vma->vm_pgoff << PAGE_SHIFT) +
+		(ctx->vma->vm_end - ctx->vma->vm_start);
+}
+
+static inline unsigned long range_start(struct remote_vma_context *ctx)
+{
+	return ctx_start(ctx) + 1;
+}
+
+static inline unsigned long range_last(struct remote_vma_context *ctx)
+{
+	return ctx_end(ctx) - 1;
+}
+
+INTERVAL_TREE_DEFINE(struct remote_vma_context, target_rb, unsigned long,
+	rb_subtree_last, range_start, range_last,
+	static inline, range_interval_tree)
+
+#define range_tree_foreach(ctx, root, start, last)	\
+	for (ctx = range_interval_tree_iter_first(root, start, last);\
+	     ctx; ctx = range_interval_tree_iter_next(ctx, start, last))
+
+static inline bool
+range_interval_tree_overlaps(struct remote_vma_context *ctx,
+			     struct remote_file_context *fctx)
+{
+	struct remote_vma_context *iter;
+
+	range_tree_foreach(iter, &fctx->rb_root, ctx_start(ctx), ctx_end(ctx))
+		return true;
+
+	return false;
+}
+
+static int
+mirror_invalidate_range_start(struct mmu_notifier *mn,
+				const struct mmu_notifier_range *range)
+{
+	struct remote_file_context *fctx =
+		container_of(mn, struct remote_file_context, mn);
+	struct remote_vma_context *ctx;
+	unsigned long src_start, src_end;
+	unsigned long vma_start, vma_end;
+
+	/* quick filter - we only map pages from anon VMAs */
+	if (!vma_is_anonymous(range->vma))
+		return 0;
+
+	/*
+	 * If ctx + VMA were found here, then the VMA + its address space
+	 * haven't been unmapped. See comments in mirror_vm_close().
+	 */
+	down_read(&fctx->tree_lock);
+
+	range_tree_foreach(ctx, &fctx->rb_root, range->start, range->end) {
+		pr_debug("%s: %lx-%lx found %lx-%lx\n",
+			__func__, range->start, range->end,
+			ctx_start(ctx), ctx_end(ctx));
+
+		// intersect these intervals (source process address range)
+		src_start = max(range->start, ctx_start(ctx));
+		src_end = min(range->end, ctx_end(ctx));
+
+		// translate to destination process address range
+		vma_start = ctx->vma->vm_start + (src_start - ctx_start(ctx));
+		vma_end = ctx->vma->vm_end + (src_end - ctx_end(ctx));
+
+		zap_vma_ptes(ctx->vma, vma_start, vma_end - vma_start);
+	}
+
+	up_read(&fctx->tree_lock);
+
+	return 0;
+}
+
+/* get notified when source MM is shutting down, so we avoid faulting in vain */
+static void
+mirror_release(struct mmu_notifier *mn, struct mm_struct *mm)
+{
+	struct remote_file_context *fctx =
+		container_of(mn, struct remote_file_context, mn);
+
+	spin_lock(&fctx->mm_lock);
+	rcu_assign_pointer(fctx->mm, NULL);
+	spin_unlock(&fctx->mm_lock);
+
+	/* delay address space closing until local faults finish */
+	synchronize_srcu(&fctx->mm_srcu);
+}
+
+static const struct mmu_notifier_ops mirror_notifier_ops = {
+	.invalidate_range_start = mirror_invalidate_range_start,
+	.release = mirror_release,
+};
+
+
+static void remote_file_context_init(struct remote_file_context *ctx)
+{
+	ctx->mm = NULL;
+	ctx->mn.ops = &mirror_notifier_ops;
+	init_srcu_struct(&ctx->mm_srcu);
+	spin_lock_init(&ctx->mm_lock);
+
+	ctx->rb_root = RB_ROOT_CACHED;
+	init_rwsem(&ctx->tree_lock);
+}
+
+static struct remote_file_context *remote_file_context_alloc(void)
+{
+	struct remote_file_context *ctx;
+
+	ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
+	if (ctx)
+		remote_file_context_init(ctx);
+
+	return ctx;
+}
+
+static void remote_file_context_free(struct remote_file_context *ctx)
+{
+	kfree(ctx);
+}
+
+
+static void remote_vma_context_init(struct remote_vma_context *ctx)
+{
+	ctx->vma = NULL;
+}
+
+static struct remote_vma_context *remote_vma_context_alloc(void)
+{
+	struct remote_vma_context *ctx;
+
+	ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
+	if (ctx)
+		remote_vma_context_init(ctx);
+
+	return ctx;
+}
+
+static void remote_vma_context_free(struct remote_vma_context *ctx)
+{
+	kfree(ctx);
+}
+
+
+static struct page *mm_remote_get_page(struct mm_struct *req_mm,
+	unsigned long req_hva, unsigned int flags)
+{
+	struct page *req_page = NULL;
+	long nrpages;
+
+	might_sleep();
+
+	flags |= FOLL_ANON | FOLL_MIGRATION;
+
+	/* get host page corresponding to requested address */
+	nrpages = get_user_pages_remote(NULL, req_mm, req_hva, 1,
+					flags, &req_page, NULL, NULL);
+	if (unlikely(nrpages == 0)) {
+		pr_err("no page for req_hva %016lx\n", req_hva);
+		return ERR_PTR(-ENOENT);
+	} else if (IS_ERR_VALUE(nrpages)) {
+		pr_err("get_user_pages_remote() failed: %d\n", (int)nrpages);
+		return ERR_PTR(nrpages);
+	}
+
+	/* limit introspection to anon memory (this also excludes zero-page) */
+	if (!PageAnon(req_page)) {
+		put_page(req_page);
+		pr_err("page at req_hva %016lx not anon\n", req_hva);
+		return ERR_PTR(-EINVAL);
+	}
+
+	return req_page;
+}
+
+static int mirror_dev_open(struct inode *inode, struct file *file)
+{
+	struct remote_file_context *fctx;
+
+	fctx = remote_file_context_alloc();
+	if (!fctx)
+		return -ENOMEM;
+	file->private_data = fctx;
+
+	return 0;
+}
+
+static int do_remote_proc_map(struct file *file, int pid)
+{
+	struct task_struct *req_task;
+	struct mm_struct *req_mm;
+	struct remote_file_context *fctx = file->private_data;
+	int result;
+
+	/* this function may race with mirror_release() notifier */
+	spin_lock(&fctx->mm_lock);
+	if (fctx->mm) {
+		spin_unlock(&fctx->mm_lock);
+		return -EALREADY;
+	}
+	spin_unlock(&fctx->mm_lock);
+
+	// find task
+	req_task = find_get_task_by_vpid(pid);
+	if (!req_task)
+		return -ESRCH;
+
+	// find + get mm
+	req_mm = get_task_mm(req_task);
+	put_task_struct(req_task);
+	if (!req_mm)
+		return -EINVAL;
+
+	/* there should be no mirror VMA faults at the moment */
+	spin_lock(&fctx->mm_lock);
+	rcu_assign_pointer(fctx->mm, req_mm);
+	spin_unlock(&fctx->mm_lock);
+
+	// register MMU notifier
+	result = mmu_notifier_register(&fctx->mn, req_mm);
+	if (result) {
+		mmput(req_mm);
+		pr_err("unable to register MMU notifier\n");
+
+		return result;
+	}
+
+	mmput(req_mm);
+
+	return 0;
+}
+
+static long mirror_dev_ioctl(struct file *file, unsigned int ioctl,
+	unsigned long arg)
+{
+	long result;
+
+	switch (ioctl) {
+	case REMOTE_PROC_MAP: {
+		int pid = (int)arg;
+
+		result = do_remote_proc_map(file, pid);
+		break;
+	}
+
+	default:
+		pr_err("ioctl %d not implemented\n", ioctl);
+		result = -ENOTTY;
+	}
+
+	return result;
+}
+
+static int mirror_dev_release(struct inode *inode, struct file *file)
+{
+	struct remote_file_context *fctx = file->private_data;
+	struct mm_struct *src_mm = NULL;
+
+	/* this function may race with mirror_release() notifier */
+	spin_lock(&fctx->mm_lock);
+	if (fctx->mm) {
+		mmgrab(fctx->mm);
+		src_mm = fctx->mm;
+	}
+	spin_unlock(&fctx->mm_lock);
+
+	/* attempt unregistering if pointer found to be valid */
+	if (src_mm) {
+		mmu_notifier_unregister(&fctx->mn, fctx->mm);
+		mmdrop(src_mm);
+	}
+
+	/*
+	 * the synchronization inside mmu_notifier_unregister() makes sure no
+	 * notifier will run after the call
+	 */
+	remote_file_context_free(fctx);
+
+	return 0;
+}
+
+/*
+ * We end up here if the local PMD is NULL.
+ * Doesn't matter if the address is aligned to huge page boundary or not.
+ * We look for a huge page mapped at the target equivalent address and try to
+ * map it in our page tables without splitting it.
+ */
+static vm_fault_t
+mirror_vm_hugefault(struct vm_fault *vmf, enum page_entry_size pe_size)
+{
+	struct vm_area_struct *vma = vmf->vma;
+	struct file *file = vma->vm_file;
+	struct remote_file_context *fctx = file->private_data;
+	unsigned long req_address;
+	unsigned int gup_flags;
+	struct page *req_page;
+	vm_fault_t result;
+	struct mm_struct *src_mm;
+	int idx;
+
+	pr_debug("%s: pe_size %d, address %016lx\n",
+		__func__, pe_size, vmf->address);
+
+	/* No support for anonymous transparent PUD pages yet */
+	if (pe_size == PE_SIZE_PUD)
+		return VM_FAULT_FALLBACK;
+
+	idx = srcu_read_lock(&fctx->mm_srcu);
+
+	/* check if source mm still exists */
+	src_mm = srcu_dereference(fctx->mm, &fctx->mm_srcu);
+	if (!src_mm) {
+		result = VM_FAULT_SIGBUS;
+		goto out;
+	}
+
+	/* attempt near-deadlock situation */
+	if (!down_read_trylock(&src_mm->mmap_sem)) {
+		srcu_read_unlock(&fctx->mm_srcu, idx);
+		up_read(&current->mm->mmap_sem);
+
+		return VM_FAULT_RETRY;
+	}
+
+	/* set GUP flags depending on the VMA */
+	gup_flags = FOLL_HUGE;
+	if (vma->vm_flags & VM_WRITE)
+		gup_flags |= FOLL_WRITE | FOLL_FORCE;
+
+	req_address = vmf->pgoff << PAGE_SHIFT;
+	req_page = mm_remote_get_page(src_mm, req_address, gup_flags);
+
+	/* check for validity of the page */
+	if (IS_ERR_OR_NULL(req_page)) {
+		up_read(&src_mm->mmap_sem);
+
+		if (PTR_ERR(req_page) == -ERESTARTSYS) {
+			srcu_read_unlock(&fctx->mm_srcu, idx);
+			up_read(&current->mm->mmap_sem);
+
+			return VM_FAULT_RETRY;
+		}
+
+		result = VM_FAULT_FALLBACK;
+		goto out;
+	}
+
+	/* shouldn't reach this case, but check anyway */
+	if (unlikely(!PageCompound(req_page))) {
+		result = VM_FAULT_FALLBACK;
+		goto out_page;
+	}
+
+	result = vmf_insert_pfn_pmd(vmf, page_to_pfn_t(req_page),
+				    vmf->flags & FAULT_FLAG_WRITE);
+
+out_page:
+	put_page(req_page);
+	up_read(&src_mm->mmap_sem);
+
+out:
+	srcu_read_unlock(&fctx->mm_srcu, idx);
+
+	return result;
+}
+
+static vm_fault_t mirror_vm_fault(struct vm_fault *vmf)
+{
+	struct vm_area_struct *vma = vmf->vma;
+	struct file *file = vma->vm_file;
+	struct remote_file_context *fctx = file->private_data;
+	unsigned long req_address;
+	unsigned int gup_flags;
+	struct page *req_page;
+	vm_fault_t result;
+	struct mm_struct *src_mm;
+	int idx;
+
+	pr_debug("%s: address %016lx\n", __func__, vmf->address);
+
+	idx = srcu_read_lock(&fctx->mm_srcu);
+
+	/* check if source mm still exists */
+	src_mm = srcu_dereference(fctx->mm, &fctx->mm_srcu);
+	if (!src_mm) {
+		result = VM_FAULT_SIGBUS;
+		goto out;
+	}
+
+	/* attempt near-deadlock situation */
+	if (!down_read_trylock(&src_mm->mmap_sem)) {
+		srcu_read_unlock(&fctx->mm_srcu, idx);
+		up_read(&current->mm->mmap_sem);
+
+		return VM_FAULT_RETRY;
+	}
+
+	/* set GUP flags depending on the VMA */
+	gup_flags = FOLL_SPLIT;
+	if (vma->vm_flags & VM_WRITE)
+		gup_flags |= FOLL_WRITE | FOLL_FORCE;
+
+	req_address = vmf->pgoff << PAGE_SHIFT;
+	req_page = mm_remote_get_page(src_mm, req_address, gup_flags);
+
+	/* check for validity of the page */
+	if (IS_ERR_OR_NULL(req_page)) {
+		up_read(&src_mm->mmap_sem);
+
+		if (PTR_ERR(req_page) == -ERESTARTSYS ||
+		    PTR_ERR(req_page) == -EBUSY) {
+			srcu_read_unlock(&fctx->mm_srcu, idx);
+			up_read(&current->mm->mmap_sem);
+
+			return VM_FAULT_RETRY;
+		}
+
+		result = VM_FAULT_SIGBUS;
+		goto out;
+	}
+
+	result = vmf_insert_pfn(vmf->vma, vmf->address, page_to_pfn(req_page));
+
+//out_page:
+	put_page(req_page);
+	up_read(&src_mm->mmap_sem);
+
+out:
+	srcu_read_unlock(&fctx->mm_srcu, idx);
+
+	return result;
+}
+
+/*
+ * This is called in remove_vma() at the end of __do_munmap() after the address
+ * space has been unmapped and the page tables have been freed.
+ */
+static void mirror_vm_close(struct vm_area_struct *vma)
+{
+	struct remote_vma_context *ctx = vma->vm_private_data;
+	struct remote_file_context *fctx = vma->vm_file->private_data;
+
+	pr_debug("%s: %016lx - %016lx (%lu bytes)\n",
+		__func__, vma->vm_start, vma->vm_end,
+		vma->vm_end - vma->vm_start);
+
+	/* will wait for any running invalidate notifiers to finish */
+	down_write(&fctx->tree_lock);
+	range_interval_tree_remove(ctx, &fctx->rb_root);
+	up_write(&fctx->tree_lock);
+
+	remote_vma_context_free(ctx);
+	vma->vm_private_data = NULL;
+}
+
+// this will prevent partial unmap of destination VMA
+static int mirror_vm_split(struct vm_area_struct *area, unsigned long addr)
+{
+	return -EINVAL;
+}
+
+static const struct vm_operations_struct mirror_vm_ops = {
+	.close = mirror_vm_close,
+	.fault = mirror_vm_fault,
+	.huge_fault = mirror_vm_hugefault,
+	.split = mirror_vm_split,
+};
+
+
+static int mirror_dev_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct remote_file_context *fctx = file->private_data;
+	struct remote_vma_context *ctx;
+
+	pr_debug("%s: %016lx - %016lx (%lu bytes)\n",
+		__func__, vma->vm_start, vma->vm_end,
+		vma->vm_end - vma->vm_start);
+
+	if (!(vma->vm_flags & VM_SHARED))
+		return -EINVAL;
+
+	/* prepare the context */
+	ctx = remote_vma_context_alloc();
+	if (!ctx)
+		return -ENOMEM;
+
+	vma->vm_private_data = ctx;
+	ctx->vma = vma;
+
+	down_write(&fctx->tree_lock);
+	if (range_interval_tree_overlaps(ctx, fctx)) {
+		up_write(&fctx->tree_lock);
+
+		pr_err("part of range already mirrored\n");
+		remote_vma_context_free(ctx);
+		return -EALREADY;
+	}
+
+	range_interval_tree_insert(ctx, &fctx->rb_root);
+	up_write(&fctx->tree_lock);
+
+	/* set basic VMA properties */
+	vma->vm_flags |= VM_DONTCOPY | VM_DONTDUMP | VM_PFNMAP;
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+	vma->vm_ops = &mirror_vm_ops;
+
+	return 0;
+}
+
+/*
+ * We must have the same alignment relative to a huge page boundary
+ * as the target VMA or requested address
+ */
+static unsigned long
+mirror_get_unmapped_area(struct file *file, const unsigned long addr0,
+	const unsigned long len, const unsigned long pgoff,
+	const unsigned long flags)
+{
+	struct vm_unmapped_area_info info;
+	unsigned long address = pgoff << PAGE_SHIFT;
+	bool huge_align = !(address & ~HPAGE_PMD_MASK);
+
+	pr_debug("%s: len %lu, pgoff 0x%016lx, %s alignment.\n",
+		__func__, len, pgoff, huge_align ? "PMD" : "page");
+
+	info.flags = VM_UNMAPPED_AREA_TOPDOWN;
+	info.length = len;
+	info.low_limit = PAGE_SIZE;
+	info.high_limit = get_mmap_base(0);
+	info.align_mask = ~HPAGE_PMD_MASK;
+	info.align_offset = address & ~HPAGE_PMD_MASK;
+
+	address = vm_unmapped_area(&info);
+
+	pr_debug("%s: address 0x%016lx\n", __func__, address);
+
+	return address;
+}
+
+static const struct file_operations mirror_ops = {
+	.open = mirror_dev_open,
+	.unlocked_ioctl = mirror_dev_ioctl,
+	.compat_ioctl = mirror_dev_ioctl,
+	.get_unmapped_area = mirror_get_unmapped_area,
+	.llseek = no_llseek,
+	.mmap = mirror_dev_mmap,
+	.release = mirror_dev_release,
+};
+
+static struct miscdevice mirror_dev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "mirror-proc",
+	.fops = &mirror_ops,
+};
+
+builtin_misc_device(mirror_dev);
+
