From 034f732a1af4ce295d993e6951decc4898967dd3 Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Thu, 18 Jan 2024 22:13:03 +0100 Subject: initial checkin --- tests/test_simple.py | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 tests/test_simple.py (limited to 'tests') diff --git a/tests/test_simple.py b/tests/test_simple.py new file mode 100644 index 0000000..e0cb66e --- /dev/null +++ b/tests/test_simple.py @@ -0,0 +1,164 @@ +# Copyright 2024 Helmut Grohne +# SPDX-License-Identifier: GPL-3 + +import functools +import os +import pathlib +import socket +import unittest + +import pytest + +import linuxnamespaces + + +def allow_fork_exit(function): + @functools.wraps(function) + def wrapped(*args, **kwargs): + mainpid = os.getpid() + try: + return function(*args, **kwargs) + except SystemExit as sysexit: + if sysexit.code or os.getpid() == mainpid: + raise + + # We're supposed to successfully exit from a child process. If we + # were to return or raise here, pytest would record success or + # failure. Instead we hide this process from pytest. + os._exit(0) + return pytest.mark.forked(wrapped) + +class IDAllocationTest(unittest.TestCase): + def test_idalloc(self) -> None: + alloc = linuxnamespaces.IDAllocation() + alloc.add_range(1, 2) + alloc.add_range(5, 4) + self.assertIn(alloc.find(3), (5, 6)) + self.assertIn(alloc.allocate(3), (5, 6)) + self.assertRaises(ValueError, alloc.find, 3) + self.assertRaises(ValueError, alloc.allocate, 3) + self.assertEqual(alloc.find(2), 1) + + def test_merge(self) -> None: + alloc = linuxnamespaces.IDAllocation() + alloc.add_range(1, 2) + alloc.add_range(3, 2) + self.assertIn(alloc.allocate(3), (1, 2)) + + +class UnshareTest(unittest.TestCase): + @pytest.mark.forked + def test_unshare_user(self) -> None: + overflowuid = int(pathlib.Path("/proc/sys/fs/overflowuid").read_text()) + idmap = linuxnamespaces.IDMapping(0, os.getuid(), 1) + linuxnamespaces.unshare(linuxnamespaces.CloneFlags.NEWUSER) + self.assertEqual(os.getuid(), overflowuid) + linuxnamespaces.newuidmap(-1, [idmap], False) + self.assertEqual(os.getuid(), 0) + # UID 1 is not mapped. + self.assertRaises(OSError, os.setuid, 1) + + @allow_fork_exit + def test_mount_proc(self) -> None: + idmap = linuxnamespaces.IDMapping(0, os.getuid(), 1) + linuxnamespaces.unshare( + linuxnamespaces.CloneFlags.NEWUSER + | linuxnamespaces.CloneFlags.NEWNS + | linuxnamespaces.CloneFlags.NEWPID + ) + linuxnamespaces.newuidmap(-1, [idmap], False) + @linuxnamespaces.run_in_fork + def setup() -> None: + self.assertEqual(os.getpid(), 1) + linuxnamespaces.mount("proc", "/proc", "proc") + setup() + + @pytest.mark.forked + def test_sethostname(self) -> None: + self.assertRaises(socket.error, socket.sethostname, "example") + linuxnamespaces.unshare( + linuxnamespaces.CloneFlags.NEWUSER + | linuxnamespaces.CloneFlags.NEWUTS + ) + socket.sethostname("example") + + @pytest.mark.forked + def test_populate_dev(self) -> None: + uidmap = linuxnamespaces.IDMapping(0, os.getuid(), 1) + gidmap = linuxnamespaces.IDMapping(0, os.getgid(), 1) + linuxnamespaces.unshare( + linuxnamespaces.CloneFlags.NEWUSER + | linuxnamespaces.CloneFlags.NEWNS + ) + pathlib.Path("/proc/self/setgroups").write_text("deny") + linuxnamespaces.newuidmap(-1, [uidmap], False) + linuxnamespaces.newgidmap(-1, [gidmap], False) + linuxnamespaces.mount("tmpfs", "/mnt", "tmpfs", data="mode=0755") + os.mkdir("/mnt/dev") + linuxnamespaces.populate_dev("/", "/mnt", pidns=False) + self.assertTrue(os.access("/mnt/dev/null", os.W_OK)) + pathlib.Path("/mnt/dev/null").write_text("") + + +class UnshareIdmapTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.uidalloc = linuxnamespaces.IDAllocation.loadsubid("uid") + self.gidalloc = linuxnamespaces.IDAllocation.loadsubid("gid") + try: + self.uidalloc.find(65536) + self.gidalloc.find(65536) + except ValueError: + self.skipTest("insufficient /etc/sub?id allocation") + + @allow_fork_exit + def test_unshare_user_idmap(self) -> None: + overflowuid = int(pathlib.Path("/proc/sys/fs/overflowuid").read_text()) + uidmap = linuxnamespaces.IDMapping( + 0, self.uidalloc.allocate(65536), 65536 + ) + self.assertNotEqual(os.getuid(), uidmap.outerstart) + gidmap = linuxnamespaces.IDMapping( + 0, self.gidalloc.allocate(65536), 65536 + ) + pid = os.getpid() + @linuxnamespaces.run_in_fork + def setup() -> None: + linuxnamespaces.newgidmap(pid, [gidmap]) + linuxnamespaces.newuidmap(pid, [uidmap]) + linuxnamespaces.unshare(linuxnamespaces.CloneFlags.NEWUSER) + setup() + self.assertEqual(os.getuid(), overflowuid) + os.setuid(0) + self.assertEqual(os.getuid(), 0) + os.setuid(1) + self.assertEqual(os.getuid(), 1) + + @allow_fork_exit + def test_populate_dev(self) -> None: + uidmap = linuxnamespaces.IDMapping( + 0, self.uidalloc.allocate(65536), 65536 + ) + self.assertNotEqual(os.getuid(), uidmap.outerstart) + gidmap = linuxnamespaces.IDMapping( + 0, self.gidalloc.allocate(65536), 65536 + ) + pid = os.getpid() + @linuxnamespaces.run_in_fork + def setup() -> None: + linuxnamespaces.newgidmap(pid, [gidmap]) + linuxnamespaces.newuidmap(pid, [uidmap]) + linuxnamespaces.unshare( + linuxnamespaces.CloneFlags.NEWUSER + | linuxnamespaces.CloneFlags.NEWNS + | linuxnamespaces.CloneFlags.NEWPID + ) + setup() + os.setreuid(0, 0) + os.setregid(0, 0) + linuxnamespaces.mount("tmpfs", "/mnt", "tmpfs") + os.mkdir("/mnt/dev") + @linuxnamespaces.run_in_fork + def test() -> None: + linuxnamespaces.populate_dev("/", "/mnt") + test() -- cgit v1.2.3