[CRIU] [PATCH v2 08/10] pstree: add set_pgid function to check everything before real syscall

Pavel Tikhomirov ptikhomirov at virtuozzo.com
Fri Jul 7 10:34:05 MSK 2017


set_pgid(curr, item, pgid, ...)

1) pgid must be valid
2) pgid should not be set already
3) curr should be parent of item or same task
4) curr should see pgid.leader
(curr should be in same pidns with leader(on curr's top pidns level))
5) item should not be a session leader
6) all tasks should be forked
7) if pgid.leader != item, pgid.leader.pgid whould be pgid
8) all tasks should share same session at these moment

These one is a bit too general for usecases we have now, but better
collect all restrictions for arbitrary setpgid in one place.

v2: set forked flag in task itself before pgrp_set so that
set_pgid can see these change in self and leader

Signed-off-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
---
 criu/cr-restore.c       |  4 ++-
 criu/include/pstree.h   |  1 +
 criu/include/rst_info.h |  1 +
 criu/pstree.c           | 91 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/criu/cr-restore.c b/criu/cr-restore.c
index 7b4528b..1179606 100644
--- a/criu/cr-restore.c
+++ b/criu/cr-restore.c
@@ -1634,7 +1634,7 @@ static void restore_pgid(void)
 	}
 
 	pr_info("\twill call setpgid, mine pgid is %d\n", pgid);
-	if (setpgid(0, my_pgid) != 0) {
+	if (set_pgid(current, vpgid(current))) {
 		pr_perror("Can't restore pgid (%d/%d->%d)", vpid(current), pgid, vpgid(current));
 		exit(1);
 	}
@@ -1739,6 +1739,8 @@ static int restore_task_with_children(void *_arg)
 
 	current = ca->item;
 
+	rsti(current)->forked = 1;
+
 	current->pid->real = get_self_real_pid();
 	pr_debug("PID: real %d virt %d\n", current->pid->real, vpid(current));
 	if (current->pid->real < 0)
diff --git a/criu/include/pstree.h b/criu/include/pstree.h
index 3817694..037bf14 100644
--- a/criu/include/pstree.h
+++ b/criu/include/pstree.h
@@ -91,6 +91,7 @@ static inline bool task_alive(struct pstree_item *i)
 }
 
 extern int is_session_leader(struct pstree_item *item);
+extern int set_pgid(struct pstree_item *item, pid_t pgid);
 extern int get_free_pids(struct ns_id *ns, pid_t *pids);
 extern void free_pstree_item(struct pstree_item *item);
 extern void free_pstree(struct pstree_item *root_item);
diff --git a/criu/include/rst_info.h b/criu/include/rst_info.h
index c1f8fdf..de68457 100644
--- a/criu/include/rst_info.h
+++ b/criu/include/rst_info.h
@@ -68,6 +68,7 @@ struct rst_info {
 
 	int			curr_sid;
 	int			curr_pgid;
+	int			forked;
 };
 
 extern struct task_entries *task_entries;
diff --git a/criu/pstree.c b/criu/pstree.c
index 1d2ac2f..ddce59f 100644
--- a/criu/pstree.c
+++ b/criu/pstree.c
@@ -1206,6 +1206,97 @@ static int can_receive_pgid(struct pstree_item *item)
 	return 0;
 }
 
+static int check_set_pgid(struct pstree_item *curr, struct pstree_item *item,
+		pid_t pgid, pid_t *pitem_pid, pid_t *pnew_pgid)
+{
+	struct pstree_item *leader;
+	pid_t item_pid, new_pgid;
+
+	/* Invalid pgid */
+	if (pgid <= 0) {
+		pr_err("Can't setpgid %d: bad pgid %d\n", vpid(item), pgid);
+		return 1;
+	}
+
+	/* Pgid is already set */
+	if (pgid == rsti(item)->curr_pgid) {
+		pr_err("Can't setpgid %d: pgid %d is already set\n", vpid(item), pgid);
+		return 1;
+	}
+
+	leader = pstree_item_by_virt(pgid);
+	BUG_ON(!leader || leader->pid->state == TASK_UNDEF);
+
+	/* We must be item or parent of item */
+	if (item != curr && item->parent != curr) {
+		pr_err("Can't setpgid %d: item is not %d and not it's child\n", vpid(item), vpid(curr));
+		return 1;
+	}
+
+	item_pid = get_relative_pid(curr, item);
+	/* We always see item's pid from curr thanks to previous check */
+	BUG_ON(!item_pid);
+
+	/* We must see the leader in our pidns to setpgid to it's group */
+	new_pgid = get_relative_pid(curr, leader);
+	if (!new_pgid) {
+		pr_err("Can't setpgid %d: %d is not visible from %d\n", vpid(item), vpid(leader), vpid(curr));
+		return 1;
+	}
+
+	/* Item should not be session leader */
+	if (rsti(item)->curr_sid == vpid(item)) {
+		pr_err("Can't setpgid %d: item is session leader\n", vpid(item));
+		return 1;
+	}
+
+	/* Everybody should be already forked */
+	if (!rsti(item)->forked || !rsti(leader)->forked) {
+		pr_err("Can't setpgid %d: item or leader is not alive\n", vpid(item));
+		return 1;
+	}
+
+	/* Leader should be the group leader already */
+	if (item != leader && rsti(leader)->curr_pgid != pgid) {
+		pr_err("Can't setpgid %d: leader does not have the right pgid %d/%d\n",
+				vpid(item), rsti(leader)->curr_pgid, pgid);
+		return 1;
+	}
+
+	/* Everybody should be in a same session */
+	if (rsti(curr)->curr_sid != rsti(item)->curr_sid || rsti(item)->curr_sid != rsti(leader)->curr_sid) {
+		pr_err("Can't setpgid %d: sessions does not match %d/%d/%d\n", vpid(item),
+				rsti(item)->curr_sid, rsti(leader)->curr_sid, rsti(curr)->curr_sid);
+		return 1;
+	}
+
+	if (pitem_pid)
+		*pitem_pid = item_pid;
+	if (pnew_pgid)
+		*pnew_pgid = new_pgid;
+	return 0;
+}
+
+/* From current task try to setpgid task item to pgid */
+int set_pgid(struct pstree_item *item, pid_t pgid)
+{
+	pid_t item_pid, new_pgid, old_pgid;
+
+	BUG_ON(pgid == 0);
+
+	if (check_set_pgid(current, item, pgid, &item_pid, &new_pgid))
+		return 1;
+
+	old_pgid = getpgid(item_pid);
+	pr_info("\twill call setpgid for %d, old pgid is %d, new pgid is %d\n", vpid(item), old_pgid, new_pgid);
+	if (setpgid(item_pid, new_pgid) != 0) {
+		pr_perror("Can't restore pgid (%d/%d->%d)", vpid(item), rsti(item)->curr_pgid, vpgid(current));
+		return 1;
+	}
+	rsti(item)->curr_pgid = pgid;
+	return 0;
+}
+
 static struct pstree_item *get_helper(int sid, unsigned int id, struct list_head *helpers)
 {
 	struct pstree_item *helper;
-- 
2.9.4



More information about the CRIU mailing list