mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
120 lines
5.6 KiB
Python
120 lines
5.6 KiB
Python
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
SOURCE = Path(__file__).resolve().parents[1] / "linuxfs/preload/net.c"
|
|
|
|
|
|
@unittest.skipUnless(sys.platform.startswith("linux") and shutil.which("cc"), "needs Linux and a C compiler")
|
|
class CdpGuardTest(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.build = tempfile.TemporaryDirectory()
|
|
cls.library = Path(cls.build.name) / "net.so"
|
|
subprocess.run(["cc", "-shared", "-fPIC", "-O2", str(SOURCE), str(SOURCE.parent / "binfmt.c"), str(SOURCE.parent / "webhelper.c"), "-x", "c", "-", "-ldl",
|
|
"-o", str(cls.library)],
|
|
input=b"int bl_udevmon_stand_in(int fd) { return 0; }", check=True, capture_output=True)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.build.cleanup()
|
|
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.registry = Path(self.temp.name) / "net"
|
|
self.registry.mkdir()
|
|
|
|
def port(self, ipv6=False):
|
|
with socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET) as s:
|
|
s.bind(("::1" if ipv6 else "127.0.0.1", 0))
|
|
return s.getsockname()[1]
|
|
|
|
def env(self, port):
|
|
return dict(os.environ, LD_PRELOAD=str(self.library), BL_CDP_GUARD=str(port), BL_SYSV_DIR=self.temp.name)
|
|
|
|
def test_guarded_ipv4_bind_is_loopback_only(self):
|
|
port = self.port()
|
|
program = "import socket; s=socket.socket(); s.bind(('0.0.0.0', %d)); print(s.getsockname()[0])" % port
|
|
result = subprocess.run([sys.executable, "-c", program], env=self.env(port), check=True, capture_output=True, text=True)
|
|
self.assertEqual("127.0.0.1", result.stdout.strip())
|
|
|
|
def test_guarded_ipv6_bind_is_loopback_only(self):
|
|
if not socket.has_ipv6:
|
|
self.skipTest("needs IPv6")
|
|
port = self.port(True)
|
|
program = "import socket; s=socket.socket(socket.AF_INET6); s.bind(('::', %d)); print(s.getsockname()[0])" % port
|
|
result = subprocess.run([sys.executable, "-c", program], env=self.env(port), check=True, capture_output=True, text=True)
|
|
self.assertEqual("::1", result.stdout.strip())
|
|
|
|
def test_other_listeners_are_unchanged(self):
|
|
port = self.port()
|
|
program = "import socket; s=socket.socket(); s.bind(('0.0.0.0', %d)); print(s.getsockname()[0])" % port
|
|
result = subprocess.run([sys.executable, "-c", program], env=self.env(0), check=True, capture_output=True, text=True)
|
|
self.assertEqual("0.0.0.0", result.stdout.strip())
|
|
|
|
def connect(self, address, registered, stale=False, wrong_destination=False):
|
|
port = self.port()
|
|
program = "import socket; s=socket.socket(); s.bind(('0.0.0.0', %d)); s.listen(); print('ready', flush=True); c,_=s.accept(); c.sendall(b'accepted')" % port
|
|
server = subprocess.Popen([sys.executable, "-u", "-c", program], env=self.env(port), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
try:
|
|
self.assertEqual(b"ready\n", server.stdout.readline())
|
|
with socket.socket() as client:
|
|
client.settimeout(3)
|
|
client.bind((address, 0))
|
|
if registered:
|
|
peer = client.getsockname()[1]
|
|
inode = os.fstat(client.fileno()).st_ino + (1 if stale else 0)
|
|
destination = port + (1 if wrong_destination else 0)
|
|
(self.registry / ("p%d" % peer)).write_text("%d 1 %d %d %d %d %d\n" %
|
|
(os.getpid(), os.getuid(), destination, client.fileno(), inode, socket.AF_INET))
|
|
client.connect(("127.0.0.1", port))
|
|
try:
|
|
return client.recv(8)
|
|
except ConnectionResetError:
|
|
return b""
|
|
finally:
|
|
server.kill()
|
|
server.wait()
|
|
server.stdout.close()
|
|
server.stderr.close()
|
|
|
|
def test_registered_guest_loopback_peer_is_accepted(self):
|
|
self.assertEqual(b"accepted", self.connect("127.0.0.1", True))
|
|
|
|
def test_unregistered_local_peer_is_refused(self):
|
|
self.assertEqual(b"", self.connect("127.0.0.1", False))
|
|
|
|
def test_matching_port_record_cannot_authorize_another_address(self):
|
|
self.assertEqual(b"", self.connect("127.0.0.2", True))
|
|
|
|
def test_stale_socket_record_does_not_authorize_a_reused_port(self):
|
|
self.assertEqual(b"", self.connect("127.0.0.1", True, stale=True))
|
|
|
|
def test_record_for_another_destination_does_not_authorize_cdp(self):
|
|
self.assertEqual(b"", self.connect("127.0.0.1", True, wrong_destination=True))
|
|
|
|
def test_preloaded_guest_connect_still_works(self):
|
|
port = self.port()
|
|
program = "import socket; s=socket.socket(); s.bind(('0.0.0.0', %d)); s.listen(); print('ready', flush=True); c,_=s.accept(); c.sendall(b'accepted')" % port
|
|
server = subprocess.Popen([sys.executable, "-u", "-c", program], env=self.env(port), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
try:
|
|
self.assertEqual(b"ready\n", server.stdout.readline())
|
|
client = "import socket; s=socket.create_connection(('127.0.0.1', %d), timeout=3); print(s.recv(8).decode())" % port
|
|
result = subprocess.run([sys.executable, "-c", client], env=self.env(port), check=True, capture_output=True, text=True, timeout=5)
|
|
self.assertEqual("accepted", result.stdout.strip())
|
|
finally:
|
|
server.kill()
|
|
server.wait()
|
|
server.stdout.close()
|
|
server.stderr.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|