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
next prev 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox