All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
To: Konstantin Khorenko <khorenko@virtuozzo.com>
Cc: devel@openvz.org
Subject: [Devel] [PATCH RFC PATCH hci-8.1 2/4] exec: protect preserved memory against surviving shared-mm tasks
Date: Fri, 09 Oct 2026 17:00:05 +0300	[thread overview]
Message-ID: <20261009-vfio_v1-v1-2-1901cd6c3c74@virtuozzo.com> (raw)
In-Reply-To: <20261009-vfio_v1-v1-0-1901cd6c3c74@virtuozzo.com>

Reserve the marked mm before exec becomes irreversible. Reject tasks
outside the execing thread group while allowing inspection references
and sibling threads that de_thread will remove.

Track CLONE_VM creations from copy_mm until publication or failure so
admission cannot miss an unpublished task. Reject new shared-mm clones
while the reservation is held, and release it on mm switch or exec failure.
Use kABI reserve slots and a spare binprm bit to preserve existing layouts.

https://virtuozzo.atlassian.net/browse/VSTOR-103860

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 fs/exec.c                | 88 +++++++++++++++++++++++++++++++++++++++++++++---
 include/linux/binfmts.h  |  2 ++
 include/linux/mm_types.h |  5 +--
 kernel/fork.c            | 34 +++++++++++++++++++
 4 files changed, 122 insertions(+), 7 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index e861a54b491f..5b2f9c3b0d06 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -978,6 +978,79 @@ ssize_t read_code(struct file *file, unsigned long addr, loff_t pos, size_t len)
 EXPORT_SYMBOL(read_code);
 #endif
 
+/* The reservation owns no mm reference; release it before dropping old_mm. */
+static void release_preserved_mem(struct linux_binprm *bprm, struct mm_struct *mm)
+{
+	if (!bprm->preserving_mem)
+		return;
+	spin_lock(&mm->page_table_lock);
+	mm->exec_keep_active = false;
+	spin_unlock(&mm->page_table_lock);
+	bprm->preserving_mem = false;
+}
+
+/*
+ * Reserve the marked address space before exec becomes irreversible.
+ * Inspection references do not constitute shared execution: mmap_lock
+ * serializes those readers with the mapping transfer. Only tasks outside
+ * our group survive de_thread() and must be rejected here.
+ */
+static int reserve_preserved_mem(struct linux_binprm *bprm)
+{
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *vma;
+	struct task_struct *p, *t;
+	bool preserve = false;
+	int ret = 0;
+
+	if (!bprm->accepts_preserved_mem || !mm)
+		return 0;
+
+	if (mmap_read_lock_killable(mm))
+		return -EINTR;
+	{
+		VMA_ITERATOR(vmi, mm, 0);
+
+		for_each_vma(vmi, vma) {
+			if (vma->vm_flags & VM_EXEC_KEEP) {
+				preserve = true;
+				break;
+			}
+		}
+	}
+	mmap_read_unlock(mm);
+	if (!preserve)
+		return 0;
+
+	/* A clone is either counted here or already visible in the task list. */
+	spin_lock(&mm->page_table_lock);
+	if (mm->exec_keep_active || mm->exec_keep_clones)
+		ret = -EBUSY;
+	else
+		mm->exec_keep_active = true;
+	spin_unlock(&mm->page_table_lock);
+	if (ret)
+		return ret;
+	bprm->preserving_mem = true;
+
+	read_lock(&tasklist_lock);
+	for_each_process_thread(p, t) {
+		if (same_thread_group(current, t))
+			continue;
+		task_lock(t);
+		if (t->mm == mm)
+			ret = -EBUSY;
+		task_unlock(t);
+		if (ret)
+			goto out;
+	}
+out:
+	read_unlock(&tasklist_lock);
+	if (ret)
+		release_preserved_mem(bprm, mm);
+	return ret;
+}
+
 static int vma_dup_some(struct mm_struct *old_mm, struct mm_struct *new_mm)
 {
 	struct vm_area_struct *vma;
@@ -1028,7 +1101,7 @@ static int exec_mmap(struct linux_binprm *bprm)
 			up_write(&tsk->signal->exec_update_lock);
 			return ret;
 		}
-		if (bprm->accepts_preserved_mem) {
+		if (bprm->preserving_mem) {
 			ret = vma_dup_some(old_mm, mm);
 			if (ret) {
 				mmap_read_unlock(old_mm);
@@ -1067,6 +1140,7 @@ static int exec_mmap(struct linux_binprm *bprm)
 		BUG_ON(active_mm != old_mm);
 		setmax_mm_hiwater_rss(&tsk->signal->maxrss, old_mm);
 		mm_update_next_owner(old_mm);
+		release_preserved_mem(bprm, old_mm);
 		mmput(old_mm);
 		return 0;
 	}
@@ -1260,10 +1334,9 @@ void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec)
 }
 
 /*
- * Calling this is the point of no return. None of the failures will be
- * seen by userspace since either the process is already taking a fatal
- * signal (via de_thread() or coredump), or will have SEGV raised
- * (after exec_mmap()) by search_binary_handler (see below).
+ * Complete the recoverable checks before setting point_of_no_return.
+ * Subsequent failures cannot return to the old image: either a fatal
+ * signal is already pending, or bprm_execve() raises SIGSEGV.
  */
 int begin_new_exec(struct linux_binprm * bprm)
 {
@@ -1275,6 +1348,10 @@ int begin_new_exec(struct linux_binprm * bprm)
 	if (retval)
 		return retval;
 
+	retval = reserve_preserved_mem(bprm);
+	if (retval)
+		return retval;
+
 	/*
 	 * This tracepoint marks the point before flushing the old exec where
 	 * the current task is still unchanged, but errors are fatal (point of
@@ -1527,6 +1604,7 @@ static void do_close_execat(struct file *file)
 
 static void free_bprm(struct linux_binprm *bprm)
 {
+	release_preserved_mem(bprm, current->mm);
 	if (bprm->mm) {
 		acct_arg_size(bprm, 0);
 		mmput(bprm->mm);
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 9f03c39498e9..7c4defdc184f 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -51,6 +51,8 @@ struct linux_binprm {
 #ifdef __alpha__
 	unsigned int taso:1;
 #endif
+	/* Uses spare bits without changing the existing binprm layout. */
+	RH_KABI_EXCLUDE(unsigned int preserving_mem:1)
 	struct file *executable; /* Executable to pass to the interpreter */
 	struct file *interpreter;
 	struct file *file;
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index cf50c74516b0..0a74c377f64c 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1091,8 +1091,9 @@ struct mm_struct {
 		} lru_gen;
 #endif /* CONFIG_LRU_GEN_WALKS_MMU */
 	} __randomize_layout;
-	RH_KABI_RESERVE(1)
-	RH_KABI_RESERVE(2)
+	/* Shared-mm clone publication, serialized by page_table_lock. */
+	RH_KABI_USE(1, unsigned int exec_keep_clones)
+	RH_KABI_USE(2, bool exec_keep_active)
 	RH_KABI_RESERVE(3)
 	RH_KABI_RESERVE(4)
 	/*
diff --git a/kernel/fork.c b/kernel/fork.c
index 00d9b4febd85..bd8020af2037 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1270,6 +1270,8 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
 	mt_init_flags(&mm->mm_mt, MM_MT_FLAGS);
 	mt_set_external_lock(&mm->mm_mt, &mm->mmap_lock);
 	atomic_set(&mm->mm_users, 1);
+	mm->exec_keep_clones = 0;
+	mm->exec_keep_active = false;
 	atomic_set(&mm->mm_count, 1);
 	seqcount_init(&mm->write_protect_seq);
 	mmap_init_lock(mm);
@@ -1710,9 +1712,32 @@ static struct mm_struct *dup_mm(struct task_struct *tsk,
 	return NULL;
 }
 
+/* Keep unpublished CLONE_VM tasks visible to preserving-exec admission. */
+static int exec_keep_clone_begin(struct mm_struct *mm)
+{
+	int ret = 0;
+
+	spin_lock(&mm->page_table_lock);
+	if (mm->exec_keep_active)
+		ret = -EBUSY;
+	else
+		mm->exec_keep_clones++;
+	spin_unlock(&mm->page_table_lock);
+	return ret;
+}
+
+static void exec_keep_clone_end(struct mm_struct *mm)
+{
+	spin_lock(&mm->page_table_lock);
+	if (!WARN_ON_ONCE(!mm->exec_keep_clones))
+		mm->exec_keep_clones--;
+	spin_unlock(&mm->page_table_lock);
+}
+
 static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
 {
 	struct mm_struct *mm, *oldmm;
+	int ret;
 
 	tsk->min_flt = tsk->maj_flt = 0;
 	tsk->nvcsw = tsk->nivcsw = 0;
@@ -1734,6 +1759,9 @@ static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
 		return 0;
 
 	if (clone_flags & CLONE_VM) {
+		ret = exec_keep_clone_begin(oldmm);
+		if (ret)
+			return ret;
 		mmget(oldmm);
 		mm = oldmm;
 	} else {
@@ -2638,6 +2666,10 @@ __latent_entropy struct task_struct *copy_process(
 	syscall_tracepoint_update(p);
 	write_unlock_irq(&tasklist_lock);
 
+	/* Admission can now find this shared-mm task in the task list. */
+	if ((clone_flags & CLONE_VM) && p->mm)
+		exec_keep_clone_end(p->mm);
+
 	if (pidfile)
 		fd_install(pidfd, pidfile);
 
@@ -2679,6 +2711,8 @@ __latent_entropy struct task_struct *copy_process(
 	exit_ve_namespace(p);
 bad_fork_cleanup_mm:
 	if (p->mm) {
+		if (clone_flags & CLONE_VM)
+			exec_keep_clone_end(p->mm);
 		mm_clear_owner(p->mm, p);
 		mmput(p->mm);
 	}

-- 
2.47.1

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

  parent reply	other threads:[~2026-10-09 14:02 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-09 14:00 [Devel] [PATCH RFC hci-8.1 0/4] mm: keep DMA-pinned MADV_DOEXEC memory working across exec Andrey Drobyshev
2026-10-09 14:00 ` [Devel] [PATCH RFC PATCH hci-8.1 1/4] mm: support moving anonymous page tables between address spaces Andrey Drobyshev
2026-10-09 15:02   ` no-reply
2026-10-09 14:00 ` Andrey Drobyshev [this message]
2026-10-09 14:57   ` [Devel] [PATCH RFC PATCH hci-8.1 2/4] exec: protect preserved memory against surviving shared-mm tasks no-reply
2026-10-09 14:00 ` [Devel] [PATCH RFC PATCH hci-8.1 3/4] mm: move MADV_DOEXEC memory instead of copying it Andrey Drobyshev
2026-10-09 15:08   ` no-reply
2026-10-09 14:00 ` [Devel] [PATCH RFC PATCH hci-8.1 4/4] selftests/mm: test memory preservation across exec Andrey Drobyshev
2026-10-09 15:07   ` no-reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261009-vfio_v1-v1-2-1901cd6c3c74@virtuozzo.com \
    --to=andrey.drobyshev@virtuozzo.com \
    --cc=devel@openvz.org \
    --cc=khorenko@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.