From edc80dec7603b4f77a69aaa642c7159850cc827d Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 13 Apr 2024 15:48:02 -0400 Subject: [PATCH 1/2] Check for dom0 messages in more agent tests Use a utility function for doing so, instead of open-coding the checks. Some code goes away. --- qrexec/tests/socket/agent.py | 71 +++++++++++++++--------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/qrexec/tests/socket/agent.py b/qrexec/tests/socket/agent.py index 8aaa5fbc..94dfb133 100644 --- a/qrexec/tests/socket/agent.py +++ b/qrexec/tests/socket/agent.py @@ -47,6 +47,15 @@ class TestAgentBase(unittest.TestCase): target_domain = 43 target_port = 1024 + def check_dom0(self, dom0): + self.assertEqual( + dom0.recv_message(), + ( + qrexec.MSG_CONNECTION_TERMINATED, + struct.pack("&2') + target, dom0 = self.execute('echo "stdout"; echo "stderr" >&2') target.send_message(qrexec.MSG_DATA_STDIN, b"") messages = target.recv_all_messages() @@ -812,7 +793,7 @@ def test_stdin_stderr(self): ) def test_pass_stdin(self): - target = self.execute("cat") + target, dom0 = self.execute("cat") target.send_message(qrexec.MSG_DATA_STDIN, b"data 1") self.assertEqual( @@ -834,15 +815,16 @@ def test_pass_stdin(self): (qrexec.MSG_DATA_EXIT_CODE, b"\0\0\0\0"), ], ) + self.check_dom0(dom0) def test_close_stdin_early(self): # Make sure that we cover the error on writing stdin into living # process. - target = self.execute( + target, dom0 = self.execute( """ read exec <&- -echo closed stdin +echo "closed stdin" sleep 1 """ ) @@ -862,6 +844,7 @@ def test_close_stdin_early(self): (qrexec.MSG_DATA_EXIT_CODE, b"\0\0\0\0"), ], ) + self.check_dom0(dom0) def test_buffer_stdin(self): # Test to trigger WRITE_STDIN_BUFFERED. @@ -877,7 +860,7 @@ def test_buffer_stdin(self): fifo = os.path.join(self.tempdir, "fifo") os.mkfifo(fifo) - target = self.execute("read <{}; cat".format(fifo)) + target, dom0 = self.execute("read <{}; cat".format(fifo)) for i in range(0, data_size, msg_size): msg = data[i : i + msg_size] @@ -911,9 +894,10 @@ def test_buffer_stdin(self): (qrexec.MSG_DATA_EXIT_CODE, b"\0\0\0\0"), ], ) + self.check_dom0(dom0) def test_close_stdout_stderr_early(self): - target = self.execute( + target, dom0 = self.execute( """\ read echo closing stdout @@ -946,9 +930,10 @@ def test_close_stdout_stderr_early(self): target.recv_message(), (qrexec.MSG_DATA_EXIT_CODE, struct.pack("&0 @@ -973,11 +958,12 @@ def test_stdio_socket(self): (qrexec.MSG_DATA_EXIT_CODE, b"\0\0\0\0"), ], ) + self.check_dom0(dom0) def test_exit_before_closing_streams(self): fifo = os.path.join(self.tempdir, "fifo") os.mkfifo(fifo) - target = self.execute( + target, dom0 = self.execute( """\ # duplicate original stdin to fd 3, because bash will # close original stdin in child process @@ -1021,6 +1007,7 @@ def test_exit_before_closing_streams(self): (qrexec.MSG_DATA_EXIT_CODE, struct.pack(" Date: Sat, 13 Apr 2024 15:24:16 -0400 Subject: [PATCH 2/2] Fix flaky qrexec agent tests Various tests assumed that qrexec would deliver stdout in a single message. Qrexec does not make this guarantee: calls to write(2), send(2), sendmsg(2), etc are not guaranteed to correspond 1-to-1 to MSG_DATA_STDOUT messages on the vchan. This caused https://gitlab.com/QubesOS/qubes-core-qrexec/-/jobs/6616564043 to wrongly fail, even thouggh the code is correct. Fix this problem by concatenating the payloads of all stdout messages into a single bytes object before comparing with the expected stdout value. Also add a utility function for this, saving a lot of code in tests. --- qrexec/tests/socket/agent.py | 158 +++++++---------------------------- 1 file changed, 29 insertions(+), 129 deletions(-) diff --git a/qrexec/tests/socket/agent.py b/qrexec/tests/socket/agent.py index 94dfb133..9c56dfe6 100644 --- a/qrexec/tests/socket/agent.py +++ b/qrexec/tests/socket/agent.py @@ -56,6 +56,21 @@ def check_dom0(self, dom0): ), ) + def assertExpectedStdout(self, target, expected_stdout: bytes, *, exit_code=0): + messages = util.sort_messages(target.recv_all_messages()) + self.assertListEqual(messages[-3:], + [ + (qrexec.MSG_DATA_STDOUT, b""), + (qrexec.MSG_DATA_STDERR, b""), + (qrexec.MSG_DATA_EXIT_CODE, struct.pack("