From 854320a4b82f876a2e88b8451ea816cb6237939c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 1 Jun 2024 10:13:14 +0200 Subject: [PATCH 1/2] tests: add test for unsafe name file copy QubesOS/qubes-issues#8332 --- qubes/tests/integ/vm_qrexec_gui.py | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/qubes/tests/integ/vm_qrexec_gui.py b/qubes/tests/integ/vm_qrexec_gui.py index 011ca9cff..8d1d70623 100644 --- a/qubes/tests/integ/vm_qrexec_gui.py +++ b/qubes/tests/integ/vm_qrexec_gui.py @@ -783,6 +783,49 @@ def test_130_qrexec_filemove_disk_full(self): self.loop.run_until_complete(self.testvm1.run_for_stdio( 'test -f /tmp/testfile')) + def test_140_qrexec_filecopy_unsafe_name(self): + self.loop.run_until_complete(asyncio.gather( + self.testvm1.start(), + self.testvm2.start())) + + # emoji are "unsafe" + name = "test-\U0001f605" + self.loop.run_until_complete(self.testvm1.run_for_stdio( + f"cp /etc/passwd /tmp/{name}")) + with self.qrexec_policy('qubes.Filecopy+', self.testvm1, self.testvm2), \ + self.qrexec_policy('qubes.Filecopy+allow-unsafe-characters', self.testvm1, self.testvm2, allow=False): + with self.assertRaises(subprocess.CalledProcessError): + self.loop.run_until_complete( + self.testvm1.run_for_stdio( + f"qvm-copy-to-vm {self.testvm2!s} /tmp/{name}")) + + try: + self.loop.run_until_complete(self.testvm2.run_for_stdio( + f"! test -e /home/user/QubesIncoming/{self.testvm1!s}/{name}")) + except subprocess.CalledProcessError: + self.fail('file with "unsafe" name was copied') + + # try again with changed policy + with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2): + try: + self.loop.run_until_complete( + self.testvm1.run_for_stdio( + f"qvm-copy-to-vm {self.testvm2!s} /tmp/{name}")) + except subprocess.CalledProcessError as e: + self.fail(f"qvm-copy-to-vm failed: {e.stderr}") + + try: + self.loop.run_until_complete(self.testvm2.run_for_stdio( + f"diff /etc/passwd /home/user/QubesIncoming/{self.testvm1!s}/{name}")) + except subprocess.CalledProcessError: + self.fail('file differs') + + try: + self.loop.run_until_complete(self.testvm1.run_for_stdio( + f"test -f /tmp/{name}")) + except subprocess.CalledProcessError: + self.fail('source file got removed') + def test_200_timezone(self): """Test whether timezone setting is properly propagated to the VM""" if "whonix" in self.template: From 0aa13823fe9c11845d1fe29e48c52289319cbd50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 1 Jun 2024 10:16:54 +0200 Subject: [PATCH 2/2] tests: add test for unsafe symlinks Symlinks pointing outside of copied directory should be denied in both filecopy modes QubesOS/qubes-issues#8332 --- qubes/tests/integ/vm_qrexec_gui.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/qubes/tests/integ/vm_qrexec_gui.py b/qubes/tests/integ/vm_qrexec_gui.py index 8d1d70623..11d02e962 100644 --- a/qubes/tests/integ/vm_qrexec_gui.py +++ b/qubes/tests/integ/vm_qrexec_gui.py @@ -826,6 +826,35 @@ def test_140_qrexec_filecopy_unsafe_name(self): except subprocess.CalledProcessError: self.fail('source file got removed') + def test_141_qrexec_filecopy_unsafe_symlink(self): + self.loop.run_until_complete(asyncio.gather( + self.testvm1.start(), + self.testvm2.start())) + + # symlinks are not allowed in either mode + name = "test" + self.loop.run_until_complete(self.testvm1.run_for_stdio( + f"ln -s /etc/passwd /tmp/{name}")) + self.loop.run_until_complete(self.testvm1.run_for_stdio( + f"ln -s ../etc/passwd /tmp/{name}2")) + with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2): + with self.assertRaises(subprocess.CalledProcessError): + self.loop.run_until_complete( + self.testvm1.run_for_stdio( + f"qvm-copy-to-vm {self.testvm2!s} /tmp/{name}")) + with self.assertRaises(subprocess.CalledProcessError): + self.loop.run_until_complete( + self.testvm1.run_for_stdio( + f"qvm-copy-to-vm {self.testvm2!s} /tmp/{name}2")) + + try: + self.loop.run_until_complete(self.testvm2.run_for_stdio( + f"! test -e /home/user/QubesIncoming/{self.testvm1!s}/{name}")) + self.loop.run_until_complete(self.testvm2.run_for_stdio( + f"! test -e /home/user/QubesIncoming/{self.testvm1!s}/{name}2")) + except subprocess.CalledProcessError: + self.fail('file with "unsafe" symlink was copied') + def test_200_timezone(self): """Test whether timezone setting is properly propagated to the VM""" if "whonix" in self.template: