[RFC PATCH 1/1] netlink: Add Netlink process event for cgroup migration

From: Prakash Sangappa

Date: Tue Apr 07 2026 - 13:24:26 EST


Introduce a netlink process event that gets generated
when a task migrates between cgroup. The process event
includes the task's pid,tgid and the initiator process
pid,tgid along with the destination cgroup id.

Signed-off-by: Prakash Sangappa <prakash.sangappa@xxxxxxxxxx>
---
drivers/connector/cn_proc.c | 28 ++++++++++++++++++++++++++++
include/linux/cn_proc.h | 3 +++
include/uapi/linux/cn_proc.h | 14 ++++++++++++--
kernel/cgroup/cgroup-v1.c | 7 ++++++-
kernel/cgroup/cgroup.c | 5 ++++-
5 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
index 0056ab81fbc3..4a17572ae171 100644
--- a/drivers/connector/cn_proc.c
+++ b/drivers/connector/cn_proc.c
@@ -19,6 +19,7 @@

#include <linux/cn_proc.h>
#include <linux/local_lock.h>
+#include <linux/cgroup.h>

/*
* Size of a cn_msg followed by a proc_event structure. Since the
@@ -355,6 +356,33 @@ void proc_exit_connector(struct task_struct *task)
send_msg(msg);
}

+void proc_cgroup_migrate_connector(struct task_struct *task, struct cgroup *cgrp)
+{
+ struct cn_msg *msg;
+ struct proc_event *ev;
+ __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+
+ if (atomic_read(&proc_event_num_listeners) < 1)
+ return;
+
+ msg = buffer_to_cn_msg(buffer);
+ ev = (struct proc_event *)msg->data;
+ memset(&ev->event_data, 0, sizeof(ev->event_data));
+ ev->timestamp_ns = ktime_get_ns();
+ ev->what = PROC_EVENT_CGRP_MIGRATE;
+ ev->event_data.cgrp.process_pid = task->pid;
+ ev->event_data.cgrp.process_tgid = task->tgid;
+ ev->event_data.cgrp.initiator_pid = current->pid;
+ ev->event_data.cgrp.initiator_tgid = current->tgid;
+ ev->event_data.cgrp.cgroup_id = cgroup_id(cgrp);
+
+ memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id));
+ msg->ack = 0; /* not used */
+ msg->len = sizeof(*ev);
+ msg->flags = 0; /* not used */
+ send_msg(msg);
+}
+
/*
* Send an acknowledgement message to userspace
*
diff --git a/include/linux/cn_proc.h b/include/linux/cn_proc.h
index 1d5b02a96c46..9b16a0456af6 100644
--- a/include/linux/cn_proc.h
+++ b/include/linux/cn_proc.h
@@ -28,6 +28,7 @@ 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);
+void proc_cgroup_migrate_connector(struct task_struct *task, struct cgroup *cgrp);
#else
static inline void proc_fork_connector(struct task_struct *task)
{}
@@ -54,5 +55,7 @@ static inline void proc_coredump_connector(struct task_struct *task)

static inline void proc_exit_connector(struct task_struct *task)
{}
+static inline void proc_cgrp_migrate_connector(struct task_struct *task)
+{}
#endif /* CONFIG_PROC_EVENTS */
#endif /* CN_PROC_H */
diff --git a/include/uapi/linux/cn_proc.h b/include/uapi/linux/cn_proc.h
index 18e3745b86cd..c202d7fdab28 100644
--- a/include/uapi/linux/cn_proc.h
+++ b/include/uapi/linux/cn_proc.h
@@ -33,7 +33,8 @@ enum proc_cn_mcast_op {
#define PROC_EVENT_ALL (PROC_EVENT_FORK | PROC_EVENT_EXEC | PROC_EVENT_UID | \
PROC_EVENT_GID | PROC_EVENT_SID | PROC_EVENT_PTRACE | \
PROC_EVENT_COMM | PROC_EVENT_NONZERO_EXIT | \
- PROC_EVENT_COREDUMP | PROC_EVENT_EXIT)
+ PROC_EVENT_COREDUMP | PROC_EVENT_EXIT | \
+ PROC_EVENT_CGRP_MIGRATE)

/*
* If you add an entry in proc_cn_event, make sure you add it in
@@ -51,7 +52,8 @@ enum proc_cn_event {
PROC_EVENT_SID = 0x00000080,
PROC_EVENT_PTRACE = 0x00000100,
PROC_EVENT_COMM = 0x00000200,
- /* "next" should be 0x00000400 */
+ PROC_EVENT_CGRP_MIGRATE = 0x00000400,
+ /* "next" should be 0x00000800 */
/* "last" is the last process event: exit,
* while "next to last" is coredumping event
* before that is report only if process dies
@@ -153,6 +155,14 @@ struct proc_event {
__kernel_pid_t parent_tgid;
} exit;

+ struct cgrp_proc_event {
+ __kernel_pid_t process_pid;
+ __kernel_pid_t process_tgid;
+ __kernel_pid_t initiator_pid;
+ __kernel_pid_t initiator_tgid;
+ __u64 cgroup_id;
+ } cgrp;
+
} event_data;
};

diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c
index a4337c9b5287..9b07c9ad9b43 100644
--- a/kernel/cgroup/cgroup-v1.c
+++ b/kernel/cgroup/cgroup-v1.c
@@ -16,6 +16,8 @@
#include <linux/pid_namespace.h>
#include <linux/cgroupstats.h>
#include <linux/fs_parser.h>
+#include <linux/cn_proc.h>
+

#include <trace/events/cgroup.h>

@@ -147,8 +149,11 @@ int cgroup_transfer_tasks(struct cgroup *to, struct cgroup *from)

if (task) {
ret = cgroup_migrate(task, false, &mgctx);
- if (!ret)
+ if (!ret) {
+ proc_cgroup_migrate_connector(task, to);
TRACE_CGROUP_PATH(transfer_tasks, to, task, false);
+ }
+
put_task_struct(task);
}
} while (task && !ret);
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 01fc2a93f3ef..4cac29d5c1b5 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -59,6 +59,7 @@
#include <linux/nstree.h>
#include <linux/irq_work.h>
#include <net/sock.h>
+#include <linux/cn_proc.h>

#define CREATE_TRACE_POINTS
#include <trace/events/cgroup.h>
@@ -3040,8 +3041,10 @@ int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,

cgroup_migrate_finish(&mgctx);

- if (!ret)
+ if (!ret) {
+ proc_cgroup_migrate_connector(leader, dst_cgrp);
TRACE_CGROUP_PATH(attach_task, dst_cgrp, leader, threadgroup);
+ }

return ret;
}
--
2.43.7