[Devel] [PATCH RHEL10 COMMIT] proc connector: pin task VE for the exit event notification

Konstantin Khorenko khorenko at virtuozzo.com
Wed Aug 26 16:09:13 MSK 2026


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.10.vz10
------>
commit 5d93a033f4ceafc086a81687ac873c236e2f2d9e
Author: Vasileios Almpanis <vasileios.almpanis at virtuozzo.com>
Date:   Tue Aug 25 16:19:54 2026 +0000

    proc connector: pin task VE for the exit event notification
    
    The exit event is reported after exit_notify(), so the parent might have
    been woken up and reaped the exiting task via wait() -> release_task()
    -> exit_ve_namespace(), which resets tsk->task_ve to ve0. If that wins
    the race against the exiting task then the event will only be delivered
    to the host VE listeners and the in-VE listeners will be skipped. For a
    task inside a container this means the listener never receives an exit
    event.
    
    The issue is caught by the LTP suite_kernel_misc.exec.cn_pec_sh test:
    pec_listener terminates upon receiving the exit event of the pid given
    via -p. When the lost exit event is a child's one, the test fails:
    
      cn_pec 3 TFAIL: Event was not detected by the event listener:
                      exit pid: 58388 exit_code: 0 exit_signal: 17
    
    and when it is the event generator's own exit event, the listener
    polls the netlink socket forever and the test hangs until the LTP
    timeout kills it (~8h on coverage kernels):
    
    22:51:37 cn_pec 2 TINFO: Testing exec event (nevents=10)
    07:11:37 Test timed out, sending SIGTERM!
    
    The race was captured using kprobes on the connector send path:
      p:cnp/pexit proc_exit_connector task=$arg1:x64
      p:cnp/vexit exit_ve_namespace task=$arg1:x64
      p:cnp/pevcve proc_event_connector_ve what=$arg3:u32
      r:cnp/cnsend cn_netlink_send_mult_ve ret=$retval:s64
    
    A normal exit looks like:
      pexit -> pevcve(ve) -> cnsend ret=0 -> pevcve(ve0)
    
    The lost event (task 0xffff89a506d73980 is the exiting child, reaped by
    its parent pid 465820 in between):
    
     465831 [001] pexit: (proc_exit_connector) task=0xffff89a506d73980
     465820 [002] vexit: (exit_ve_namespace) task=0xffff89a506d73980
     465831 [001] pevcve: (proc_event_connector_ve) what=2147483648
    
    Only one proc_event_connector_ve() call fires (ve0, no listeners) and
    cn_netlink_send_mult_ve() is never reached: the event is dropped.
    
    This is the same race window that commit c565cc211694 ("proc
    connector: report proper pid/tgid of an exited process") closed for
    the task pid/tgid, but nothing pins the VE.
    
    Reproducer (fails within ~50 iterations in a CT on a coverage kernel):
    
      cd /opt/ltp/testcases/bin/
      export PATH=$PATH:/opt/ltp/testcases/bin
      for i in $(seq 1 1024); do cn_pec.sh >/dev/null 2>&1 || break; done
    
    Solve this the same way. Pin the VE in do_exit() before exit_notify()
    is called and use it in proc_exit_connector() instead of re-reading
    task->task_ve.
    
    https://virtuozzo.atlassian.net/browse/VSTOR-140421
    Fixes: 95fa2f096b72 ("ve: Introduce VE namespace")
    Feature: ve: ve generic structures
    Signed-off-by: Vasileios Almpanis <vasileios.almpanis at virtuozzo.com>
    Reviewed-by: Konstantin Khorenko <khorenko at virtuozzo.com>
---
 drivers/connector/cn_proc.c | 10 ++++++++--
 include/linux/cn_proc.h     |  8 ++++++--
 kernel/exit.c               |  5 ++++-
 3 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
index 6084308085489..8d8c538cc3cd4 100644
--- a/drivers/connector/cn_proc.c
+++ b/drivers/connector/cn_proc.c
@@ -401,9 +401,15 @@ static bool fill_exit_event(struct proc_event *ev, struct ve_struct *ve,
 	return true;
 }
 
-void proc_exit_connector(struct task_struct *task, struct pids *pids)
+void proc_exit_connector(struct task_struct *task, struct pids *pids,
+			 struct ve_struct *ve)
 {
-	proc_event_connector(task, PROC_EVENT_EXIT, (long)pids, fill_exit_event);
+	if (!ve_is_super(ve))
+		proc_event_connector_ve(task, ve, PROC_EVENT_EXIT, (long)pids,
+					fill_exit_event);
+
+	proc_event_connector_ve(task, get_ve0(), PROC_EVENT_EXIT, (long)pids,
+				fill_exit_event);
 }
 
 /*
diff --git a/include/linux/cn_proc.h b/include/linux/cn_proc.h
index 9701c13d82dfc..aa606192b5c2e 100644
--- a/include/linux/cn_proc.h
+++ b/include/linux/cn_proc.h
@@ -19,6 +19,8 @@
 
 #include <uapi/linux/cn_proc.h>
 
+struct ve_struct;
+
 /*
  * The struct is used solely for pinning task pids for proc connector
  * notification on process exit.
@@ -36,7 +38,8 @@ void proc_sid_connector(struct task_struct *task);
 void proc_ptrace_connector(struct task_struct *task, int which_id);
 void proc_comm_connector(struct task_struct *task);
 void proc_coredump_connector(struct task_struct *task);
-void proc_exit_connector(struct task_struct *task, struct pids *pids);
+void proc_exit_connector(struct task_struct *task, struct pids *pids,
+			 struct ve_struct *ve);
 #else
 static inline void proc_fork_connector(struct task_struct *task)
 {}
@@ -61,7 +64,8 @@ static inline void proc_ptrace_connector(struct task_struct *task,
 static inline void proc_coredump_connector(struct task_struct *task)
 {}
 
-static inline void proc_exit_connector(struct task_struct *task, struct pids *pids)
+static inline void proc_exit_connector(struct task_struct *task, struct pids *pids,
+				       struct ve_struct *ve)
 {}
 #endif	/* CONFIG_PROC_EVENTS */
 #endif	/* CN_PROC_H */
diff --git a/kernel/exit.c b/kernel/exit.c
index 448a734270a7f..94d9bddae2b86 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -936,6 +936,7 @@ void __noreturn do_exit(long code)
 	struct task_struct *tsk = current;
 	int group_dead;
 	struct pids pids;
+	struct ve_struct *ve;
 
 	WARN_ON(irqs_disabled());
 
@@ -1021,8 +1022,10 @@ void __noreturn do_exit(long code)
 	exit_tasks_rcu_start();
 	pids.pid = get_pid(task_pid(tsk));
 	pids.tgid = get_pid(task_tgid(tsk));
+	ve = get_task_ve(tsk);
 	exit_notify(tsk, group_dead);
-	proc_exit_connector(tsk, &pids);
+	proc_exit_connector(tsk, &pids, ve);
+	put_ve(ve);
 	put_pid(pids.tgid);
 	put_pid(pids.pid);
 	mpol_put_task_policy(tsk);


More information about the Devel mailing list