Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Christian Brauner 3364 100.00% 7 100.00%
Total 3364 7


// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/nsfs.h>
#include <sys/capability.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../kselftest_harness.h"
#include "../filesystems/utils.h"
#include "wrappers.h"

/*
 * Test that unprivileged users can only see namespaces they're currently in.
 * Create a namespace, drop privileges, verify we can only see our own namespaces.
 */
TEST(listns_unprivileged_current_only)
{
	struct ns_id_req req = {
		.size = sizeof(req),
		.spare = 0,
		.ns_id = 0,
		.ns_type = CLONE_NEWNET,
		.spare2 = 0,
		.user_ns_id = 0,
	};
	__u64 ns_ids[100];
	ssize_t ret;
	int pipefd[2];
	pid_t pid;
	int status;
	bool found_ours;
	int unexpected_count;

	ASSERT_EQ(pipe(pipefd), 0);

	pid = fork();
	ASSERT_GE(pid, 0);

	if (pid == 0) {
		int fd;
		__u64 our_netns_id;
		bool found_ours;
		int unexpected_count;

		close(pipefd[0]);

		/* Create user namespace to be unprivileged */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Create a network namespace */
		if (unshare(CLONE_NEWNET) < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Get our network namespace ID */
		fd = open("/proc/self/ns/net", O_RDONLY);
		if (fd < 0) {
			close(pipefd[1]);
			exit(1);
		}

		if (ioctl(fd, NS_GET_ID, &our_netns_id) < 0) {
			close(fd);
			close(pipefd[1]);
			exit(1);
		}
		close(fd);

		/* Now we're unprivileged - list all network namespaces */
		ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
		if (ret < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* We should only see our own network namespace */
		found_ours = false;
		unexpected_count = 0;

		for (ssize_t i = 0; i < ret; i++) {
			if (ns_ids[i] == our_netns_id) {
				found_ours = true;
			} else {
				/* This is either init_net (which we can see) or unexpected */
				unexpected_count++;
			}
		}

		/* Send results to parent */
		write(pipefd[1], &found_ours, sizeof(found_ours));
		write(pipefd[1], &unexpected_count, sizeof(unexpected_count));
		close(pipefd[1]);
		exit(0);
	}

	/* Parent */
	close(pipefd[1]);

	found_ours = false;
	unexpected_count = 0;
	read(pipefd[0], &found_ours, sizeof(found_ours));
	read(pipefd[0], &unexpected_count, sizeof(unexpected_count));
	close(pipefd[0]);

	waitpid(pid, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));
	ASSERT_EQ(WEXITSTATUS(status), 0);

	/* Child should have seen its own namespace */
	ASSERT_TRUE(found_ours);

	TH_LOG("Unprivileged child saw its own namespace, plus %d others (likely init_net)",
			unexpected_count);
}

/*
 * Test that users with CAP_SYS_ADMIN in a user namespace can see
 * all namespaces owned by that user namespace.
 */
TEST(listns_cap_sys_admin_in_userns)
{
	struct ns_id_req req = {
		.size = sizeof(req),
		.spare = 0,
		.ns_id = 0,
		.ns_type = 0,  /* All types */
		.spare2 = 0,
		.user_ns_id = 0,  /* Will be set to our created user namespace */
	};
	__u64 ns_ids[100];
	int pipefd[2];
	pid_t pid;
	int status;
	bool success;
	ssize_t count;

	ASSERT_EQ(pipe(pipefd), 0);

	pid = fork();
	ASSERT_GE(pid, 0);

	if (pid == 0) {
		int fd;
		__u64 userns_id;
		ssize_t ret;
		int min_expected;
		bool success;

		close(pipefd[0]);

		/* Create user namespace - we'll have CAP_SYS_ADMIN in it */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Get the user namespace ID */
		fd = open("/proc/self/ns/user", O_RDONLY);
		if (fd < 0) {
			close(pipefd[1]);
			exit(1);
		}

		if (ioctl(fd, NS_GET_ID, &userns_id) < 0) {
			close(fd);
			close(pipefd[1]);
			exit(1);
		}
		close(fd);

		/* Create several namespaces owned by this user namespace */
		unshare(CLONE_NEWNET);
		unshare(CLONE_NEWUTS);
		unshare(CLONE_NEWIPC);

		/* List namespaces owned by our user namespace */
		req.user_ns_id = userns_id;
		ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
		if (ret < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/*
		 * We have CAP_SYS_ADMIN in this user namespace,
		 * so we should see all namespaces owned by it.
		 * That includes: net, uts, ipc, and the user namespace itself.
		 */
		min_expected = 4;
		success = (ret >= min_expected);

		write(pipefd[1], &success, sizeof(success));
		write(pipefd[1], &ret, sizeof(ret));
		close(pipefd[1]);
		exit(0);
	}

	/* Parent */
	close(pipefd[1]);

	success = false;
	count = 0;
	read(pipefd[0], &success, sizeof(success));
	read(pipefd[0], &count, sizeof(count));
	close(pipefd[0]);

	waitpid(pid, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));
	ASSERT_EQ(WEXITSTATUS(status), 0);

	ASSERT_TRUE(success);
	TH_LOG("User with CAP_SYS_ADMIN saw %zd namespaces owned by their user namespace",
			count);
}

/*
 * Test that users cannot see namespaces from unrelated user namespaces.
 * Create two sibling user namespaces, verify they can't see each other's
 * owned namespaces.
 */
TEST(listns_cannot_see_sibling_userns_namespaces)
{
	int pipefd[2];
	pid_t pid1, pid2;
	int status;
	__u64 netns_a_id;
	int pipefd2[2];
	bool found_sibling_netns;

	ASSERT_EQ(pipe(pipefd), 0);

	/* Fork first child - creates user namespace A */
	pid1 = fork();
	ASSERT_GE(pid1, 0);

	if (pid1 == 0) {
		int fd;
		__u64 netns_a_id;
		char buf;

		close(pipefd[0]);

		/* Create user namespace A */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Create network namespace owned by user namespace A */
		if (unshare(CLONE_NEWNET) < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Get network namespace ID */
		fd = open("/proc/self/ns/net", O_RDONLY);
		if (fd < 0) {
			close(pipefd[1]);
			exit(1);
		}

		if (ioctl(fd, NS_GET_ID, &netns_a_id) < 0) {
			close(fd);
			close(pipefd[1]);
			exit(1);
		}
		close(fd);

		/* Send namespace ID to parent */
		write(pipefd[1], &netns_a_id, sizeof(netns_a_id));

		/* Keep alive for sibling to check */
		read(pipefd[1], &buf, 1);
		close(pipefd[1]);
		exit(0);
	}

	/* Parent reads namespace A ID */
	close(pipefd[1]);
	netns_a_id = 0;
	read(pipefd[0], &netns_a_id, sizeof(netns_a_id));

	TH_LOG("User namespace A created network namespace with ID %llu",
	       (unsigned long long)netns_a_id);

	/* Fork second child - creates user namespace B */
	ASSERT_EQ(pipe(pipefd2), 0);

	pid2 = fork();
	ASSERT_GE(pid2, 0);

	if (pid2 == 0) {
		struct ns_id_req req = {
			.size = sizeof(req),
			.spare = 0,
			.ns_id = 0,
			.ns_type = CLONE_NEWNET,
			.spare2 = 0,
			.user_ns_id = 0,
		};
		__u64 ns_ids[100];
		ssize_t ret;
		bool found_sibling_netns;

		close(pipefd[0]);
		close(pipefd2[0]);

		/* Create user namespace B (sibling to A) */
		if (setup_userns() < 0) {
			close(pipefd2[1]);
			exit(1);
		}

		/* Try to list all network namespaces */
		ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);

		found_sibling_netns = false;
		if (ret > 0) {
			for (ssize_t i = 0; i < ret; i++) {
				if (ns_ids[i] == netns_a_id) {
					found_sibling_netns = true;
					break;
				}
			}
		}

		/* We should NOT see the sibling's network namespace */
		write(pipefd2[1], &found_sibling_netns, sizeof(found_sibling_netns));
		close(pipefd2[1]);
		exit(0);
	}

	/* Parent reads result from second child */
	close(pipefd2[1]);
	found_sibling_netns = false;
	read(pipefd2[0], &found_sibling_netns, sizeof(found_sibling_netns));
	close(pipefd2[0]);

	/* Signal first child to exit */
	close(pipefd[0]);

	/* Wait for both children */
	waitpid(pid2, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));

	waitpid(pid1, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));

	/* Second child should NOT have seen first child's namespace */
	ASSERT_FALSE(found_sibling_netns);
	TH_LOG("User namespace B correctly could not see sibling namespace A's network namespace");
}

/*
 * Test permission checking with LISTNS_CURRENT_USER.
 * Verify that listing with LISTNS_CURRENT_USER respects permissions.
 */
TEST(listns_current_user_permissions)
{
	int pipefd[2];
	pid_t pid;
	int status;
	bool success;
	ssize_t count;

	ASSERT_EQ(pipe(pipefd), 0);

	pid = fork();
	ASSERT_GE(pid, 0);

	if (pid == 0) {
		struct ns_id_req req = {
			.size = sizeof(req),
			.spare = 0,
			.ns_id = 0,
			.ns_type = 0,
			.spare2 = 0,
			.user_ns_id = LISTNS_CURRENT_USER,
		};
		__u64 ns_ids[100];
		ssize_t ret;
		bool success;

		close(pipefd[0]);

		/* Create user namespace */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Create some namespaces owned by this user namespace */
		if (unshare(CLONE_NEWNET) < 0) {
			close(pipefd[1]);
			exit(1);
		}

		if (unshare(CLONE_NEWUTS) < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* List with LISTNS_CURRENT_USER - should see our owned namespaces */
		ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);

		success = (ret >= 3);  /* At least user, net, uts */
		write(pipefd[1], &success, sizeof(success));
		write(pipefd[1], &ret, sizeof(ret));
		close(pipefd[1]);
		exit(0);
	}

	/* Parent */
	close(pipefd[1]);

	success = false;
	count = 0;
	read(pipefd[0], &success, sizeof(success));
	read(pipefd[0], &count, sizeof(count));
	close(pipefd[0]);

	waitpid(pid, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));
	ASSERT_EQ(WEXITSTATUS(status), 0);

	ASSERT_TRUE(success);
	TH_LOG("LISTNS_CURRENT_USER returned %zd namespaces", count);
}

/*
 * Test that CAP_SYS_ADMIN in parent user namespace allows seeing
 * child user namespace's owned namespaces.
 */
TEST(listns_parent_userns_cap_sys_admin)
{
	int pipefd[2];
	pid_t pid;
	int status;
	bool found_child_userns;
	ssize_t count;

	ASSERT_EQ(pipe(pipefd), 0);

	pid = fork();
	ASSERT_GE(pid, 0);

	if (pid == 0) {
		int fd;
		__u64 parent_userns_id;
		__u64 child_userns_id;
		struct ns_id_req req;
		__u64 ns_ids[100];
		ssize_t ret;
		bool found_child_userns;

		close(pipefd[0]);

		/* Create parent user namespace - we have CAP_SYS_ADMIN in it */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Get parent user namespace ID */
		fd = open("/proc/self/ns/user", O_RDONLY);
		if (fd < 0) {
			close(pipefd[1]);
			exit(1);
		}

		if (ioctl(fd, NS_GET_ID, &parent_userns_id) < 0) {
			close(fd);
			close(pipefd[1]);
			exit(1);
		}
		close(fd);

		/* Create child user namespace */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Get child user namespace ID */
		fd = open("/proc/self/ns/user", O_RDONLY);
		if (fd < 0) {
			close(pipefd[1]);
			exit(1);
		}

		if (ioctl(fd, NS_GET_ID, &child_userns_id) < 0) {
			close(fd);
			close(pipefd[1]);
			exit(1);
		}
		close(fd);

		/* Create namespaces owned by child user namespace */
		if (unshare(CLONE_NEWNET) < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* List namespaces owned by parent user namespace */
		req.size = sizeof(req);
		req.spare = 0;
		req.ns_id = 0;
		req.ns_type = 0;
		req.spare2 = 0;
		req.user_ns_id = parent_userns_id;

		ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);

		/* Should see child user namespace in the list */
		found_child_userns = false;
		if (ret > 0) {
			for (ssize_t i = 0; i < ret; i++) {
				if (ns_ids[i] == child_userns_id) {
					found_child_userns = true;
					break;
				}
			}
		}

		write(pipefd[1], &found_child_userns, sizeof(found_child_userns));
		write(pipefd[1], &ret, sizeof(ret));
		close(pipefd[1]);
		exit(0);
	}

	/* Parent */
	close(pipefd[1]);

	found_child_userns = false;
	count = 0;
	read(pipefd[0], &found_child_userns, sizeof(found_child_userns));
	read(pipefd[0], &count, sizeof(count));
	close(pipefd[0]);

	waitpid(pid, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));
	ASSERT_EQ(WEXITSTATUS(status), 0);

	ASSERT_TRUE(found_child_userns);
	TH_LOG("Process with CAP_SYS_ADMIN in parent user namespace saw child user namespace (total: %zd)",
			count);
}

/*
 * Test that we can see user namespaces we have CAP_SYS_ADMIN inside of.
 * This is different from seeing namespaces owned by a user namespace.
 */
TEST(listns_cap_sys_admin_inside_userns)
{
	int pipefd[2];
	pid_t pid;
	int status;
	bool found_ours;

	ASSERT_EQ(pipe(pipefd), 0);

	pid = fork();
	ASSERT_GE(pid, 0);

	if (pid == 0) {
		int fd;
		__u64 our_userns_id;
		struct ns_id_req req;
		__u64 ns_ids[100];
		ssize_t ret;
		bool found_ours;

		close(pipefd[0]);

		/* Create user namespace - we have CAP_SYS_ADMIN inside it */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Get our user namespace ID */
		fd = open("/proc/self/ns/user", O_RDONLY);
		if (fd < 0) {
			close(pipefd[1]);
			exit(1);
		}

		if (ioctl(fd, NS_GET_ID, &our_userns_id) < 0) {
			close(fd);
			close(pipefd[1]);
			exit(1);
		}
		close(fd);

		/* List all user namespaces globally */
		req.size = sizeof(req);
		req.spare = 0;
		req.ns_id = 0;
		req.ns_type = CLONE_NEWUSER;
		req.spare2 = 0;
		req.user_ns_id = 0;

		ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);

		/* We should be able to see our own user namespace */
		found_ours = false;
		if (ret > 0) {
			for (ssize_t i = 0; i < ret; i++) {
				if (ns_ids[i] == our_userns_id) {
					found_ours = true;
					break;
				}
			}
		}

		write(pipefd[1], &found_ours, sizeof(found_ours));
		close(pipefd[1]);
		exit(0);
	}

	/* Parent */
	close(pipefd[1]);

	found_ours = false;
	read(pipefd[0], &found_ours, sizeof(found_ours));
	close(pipefd[0]);

	waitpid(pid, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));
	ASSERT_EQ(WEXITSTATUS(status), 0);

	ASSERT_TRUE(found_ours);
	TH_LOG("Process can see user namespace it has CAP_SYS_ADMIN inside of");
}

/*
 * Test that dropping CAP_SYS_ADMIN restricts what we can see.
 */
TEST(listns_drop_cap_sys_admin)
{
	cap_t caps;
	cap_value_t cap_list[1] = { CAP_SYS_ADMIN };

	/* This test needs to start with CAP_SYS_ADMIN */
	caps = cap_get_proc();
	if (!caps) {
		SKIP(return, "Cannot get capabilities");
	}

	cap_flag_value_t cap_val;
	if (cap_get_flag(caps, CAP_SYS_ADMIN, CAP_EFFECTIVE, &cap_val) < 0) {
		cap_free(caps);
		SKIP(return, "Cannot check CAP_SYS_ADMIN");
	}

	if (cap_val != CAP_SET) {
		cap_free(caps);
		SKIP(return, "Test needs CAP_SYS_ADMIN to start");
	}
	cap_free(caps);

	int pipefd[2];
	pid_t pid;
	int status;
	bool correct;
	ssize_t count_before, count_after;

	ASSERT_EQ(pipe(pipefd), 0);

	pid = fork();
	ASSERT_GE(pid, 0);

	if (pid == 0) {
		struct ns_id_req req = {
			.size = sizeof(req),
			.spare = 0,
			.ns_id = 0,
			.ns_type = CLONE_NEWNET,
			.spare2 = 0,
			.user_ns_id = LISTNS_CURRENT_USER,
		};
		__u64 ns_ids_before[100];
		ssize_t count_before;
		__u64 ns_ids_after[100];
		ssize_t count_after;
		bool correct;

		close(pipefd[0]);

		/* Create user namespace */
		if (setup_userns() < 0) {
			close(pipefd[1]);
			exit(1);
		}

		/* Count namespaces with CAP_SYS_ADMIN */
		count_before = sys_listns(&req, ns_ids_before, ARRAY_SIZE(ns_ids_before), 0);

		/* Drop CAP_SYS_ADMIN */
		caps = cap_get_proc();
		if (caps) {
			cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_CLEAR);
			cap_set_flag(caps, CAP_PERMITTED, 1, cap_list, CAP_CLEAR);
			cap_set_proc(caps);
			cap_free(caps);
		}

		/* Ensure we can't regain the capability */
		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

		/* Count namespaces without CAP_SYS_ADMIN */
		count_after = sys_listns(&req, ns_ids_after, ARRAY_SIZE(ns_ids_after), 0);

		/* Without CAP_SYS_ADMIN, we should see same or fewer namespaces */
		correct = (count_after <= count_before);

		write(pipefd[1], &correct, sizeof(correct));
		write(pipefd[1], &count_before, sizeof(count_before));
		write(pipefd[1], &count_after, sizeof(count_after));
		close(pipefd[1]);
		exit(0);
	}

	/* Parent */
	close(pipefd[1]);

	correct = false;
	count_before = 0;
	count_after = 0;
	read(pipefd[0], &correct, sizeof(correct));
	read(pipefd[0], &count_before, sizeof(count_before));
	read(pipefd[0], &count_after, sizeof(count_after));
	close(pipefd[0]);

	waitpid(pid, &status, 0);
	ASSERT_TRUE(WIFEXITED(status));
	ASSERT_EQ(WEXITSTATUS(status), 0);

	ASSERT_TRUE(correct);
	TH_LOG("With CAP_SYS_ADMIN: %zd namespaces, without: %zd namespaces",
			count_before, count_after);
}

TEST_HARNESS_MAIN