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

Submitted by Pavel Tikhomirov on July 4, 2017, 9:08 a.m.

Details

Message ID 20170704090809.8127-9-ptikhomirov@virtuozzo.com
State New
Series "rework pgid restore for pidnses"
Headers show

Commit Message

Pavel Tikhomirov July 4, 2017, 9:08 a.m.
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.

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

Patch hide | download patch | download mbox

diff --git a/criu/cr-restore.c b/criu/cr-restore.c
index 4083f2a..15c1aab 100644
--- a/criu/cr-restore.c
+++ b/criu/cr-restore.c
@@ -1462,6 +1462,7 @@  static inline int fork_with_pid(struct pstree_item *item)
 		pr_perror("Can't fork for %d", pid);
 		goto err_unlock;
 	}
+	rsti(item)->forked = 1;
 
 err_unlock:
 	unlock_last_pid();
@@ -1626,7 +1627,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);
 	}
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;