Repository: opencontainers/runc
File: libcontainer/nsenter/nsexec.c (C)

Code before:

void join_namespaces(char *nsspec)
{
	nsset_t to_join = 0, joined = 0;
	struct namespace_t *ns_list;
	size_t ns_len;

	/*
	 * We have to open the file descriptors first, since after we join the
	 * mnt or user namespaces we might no longer be able to access the
	 * paths.
	 */
	to_join = __open_namespaces(nsspec, &ns_list, &ns_len);

	/*
	 * We first try to join all non-userns namespaces to join any namespaces
	 * that we might not be able to join once we switch credentials to the
	 * container's userns. We then join the user namespace, and then try to
	 * join any remaining namespaces (this last step is needed for rootless
	 * containers -- we don't get setns(2) permissions until we join the userns
	 * and get CAP_SYS_ADMIN).
	 *
	 * Splitting the joins this way is necessary for containers that are
	 * configured to join some externally-created namespace but are also
	 * configured to join an unrelated user namespace.
	 *
	 * This is similar to what nsenter(1) seems to do in practice.
	 */
	joined |= __join_namespaces(to_join & ~(joined | CLONE_NEWUSER), ns_list, ns_len);
	joined |= __join_namespaces(CLONE_NEWUSER, ns_list, ns_len);
	joined |= __join_namespaces(to_join & ~(joined | CLONE_NEWUSER), ns_list, ns_len);

	/* Verify that we joined all of the namespaces. */
	__close_namespaces(to_join, joined, ns_list, ns_len);
}

static inline int sane_kill(pid_t pid, int signum)
{
	if (pid > 0)
		return kill(pid, signum);
	else
		return 0;
}

void try_unshare(int flags, const char *msg)
{
	write_log(DEBUG, "unshare %s", msg);
	/*
	 * Kernels prior to v4.3 may return EINVAL on unshare when another process
	 * reads runc's /proc/$PID/status or /proc/$PID/maps. To work around this,
	 * retry on EINVAL a few times.

Review comment (bug):
Maybe for rootless container, all `to_join` ns paths will be joined fail in the first step? So it will introduce some unnecessary syscalls for rootless container? From this aspect, please look #4491 to check whether it will be more simple to fix the related issue in go code or not?

Code after:
	nsset_t to_join = 0, joined = 0;
	struct namespace_t *ns_list;
	size_t ns_len;

	/*
	 * We have to open the file descriptors first, since after we join the
	 * mnt or user namespaces we might no longer be able to access the
	 * paths.
	 */
	to_join = __open_namespaces(nsspec, &ns_list, &ns_len);

	/*
	 * We first try to join all non-userns namespaces to join any namespaces
	 * that we might not be able to join once we switch credentials to the
	 * container's userns. We then join the user namespace, and then try to
	 * join any remaining namespaces (this last step is needed for rootless
	 * containers -- we don't get setns(2) permissions until we join the userns
	 * and get CAP_SYS_ADMIN).
	 *
	 * Splitting the joins this way is necessary for containers that are
	 * configured to join some externally-created namespace but are also
	 * configured to join an unrelated user namespace.
	 *
	 * This is similar to what nsenter(1) seems to do in practice.
	 */
	joined |= __join_namespaces(to_join & ~(joined | CLONE_NEWUSER), ns_list, ns_len);
	joined |= __join_namespaces(CLONE_NEWUSER, ns_list, ns_len);
	joined |= __join_namespaces(to_join & ~(joined | CLONE_NEWUSER), ns_list, ns_len);

	/* Verify that we joined all of the namespaces. */
	__close_namespaces(to_join, joined, ns_list, ns_len);
}

static inline int sane_kill(pid_t pid, int signum)
{
	if (pid > 0)
		return kill(pid, signum);
	else
		return 0;
}

void try_unshare(int flags, const char *msg)
{
	write_log(DEBUG, "unshare %s", msg);
	/*
	 * Kernels prior to v4.3 may return EINVAL on unshare when another process
	 * reads runc's /proc/$PID/status or /proc/$PID/maps. To work around this,
	 * retry on EINVAL a few times.
	 */
	int retries = 5;
	for (; retries > 0; retries--) {