From 2c699f6ba9c162878c69d0728298c1ab7308db72 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Mon, 5 Jun 2023 18:51:22 +0100
Subject: [PATCH] monitor test: Reproduce dbus/dbus#457

The exact failure mode reported in dbus/dbus#457 is quite difficult
to achieve in a reliable way in a unit test, because we'd have to send
enough messages to a client to fill up its queue, then stop that client
from draining its queue, while still triggering a message that gets a
reply from the bus driver. However, we can trigger the same crash in a
slightly different way by not allowing the client to receive a
particular message. I chose NameAcquired.

Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 986611ad0f7f67a3693e5672cd66bc608c00b228)
---
 .../valid-config-files/forbidding.conf.in     |  3 +
 test/monitor.c                                | 77 ++++++++++++++++---
 2 files changed, 71 insertions(+), 9 deletions(-)

diff --git a/test/data/valid-config-files/forbidding.conf.in b/test/data/valid-config-files/forbidding.conf.in
index d145613c..58b3cc6a 100644
--- a/test/data/valid-config-files/forbidding.conf.in
+++ b/test/data/valid-config-files/forbidding.conf.in
@@ -24,5 +24,8 @@
     <allow send_interface="com.example.CannotUnicast2" send_broadcast="true"/>
 
     <deny receive_interface="com.example.CannotReceive"/>
+
+    <!-- Used to reproduce dbus#457 -->
+    <deny receive_interface="org.freedesktop.DBus" receive_member="NameAcquired"/>
   </policy>
 </busconfig>
diff --git a/test/monitor.c b/test/monitor.c
index 182110f8..42e0734d 100644
--- a/test/monitor.c
+++ b/test/monitor.c
@@ -155,6 +155,21 @@ static Config side_effects_config = {
     TRUE
 };
 
+static dbus_bool_t
+config_forbids_name_acquired_signal (const Config *config)
+{
+  if (config == NULL)
+    return FALSE;
+
+  if (config->config_file == NULL)
+    return FALSE;
+
+  if (strcmp (config->config_file, forbidding_config.config_file) == 0)
+    return TRUE;
+
+  return FALSE;
+}
+
 static inline const char *
 not_null2 (const char *x,
     const char *fallback)
@@ -253,9 +268,6 @@ do { \
 
 #define assert_name_acquired(m) \
 do { \
-  DBusError _e = DBUS_ERROR_INIT; \
-  const char *_s; \
-    \
   g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
       ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_SIGNAL)); \
   g_assert_cmpstr (dbus_message_get_sender (m), ==, DBUS_SERVICE_DBUS); \
@@ -265,7 +277,14 @@ do { \
   g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); \
   g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
   g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \
+} while (0)
+
+#define assert_unique_name_acquired(m) \
+do { \
+  DBusError _e = DBUS_ERROR_INIT; \
+  const char *_s; \
     \
+  assert_name_acquired (m); \
   dbus_message_get_args (m, &_e, \
         DBUS_TYPE_STRING, &_s, \
         DBUS_TYPE_INVALID); \
@@ -333,6 +352,21 @@ do { \
   g_assert_cmpint (dbus_message_get_reply_serial (m), !=, 0); \
 } while (0)
 
+/* forbidding.conf does not allow receiving NameAcquired, so if we are in
+ * that configuration, then dbus-daemon synthesizes an error reply to itself
+ * and sends that to monitors */
+#define expect_name_acquired_error(queue, in_reply_to) \
+do { \
+  DBusMessage *message; \
+  \
+  message = g_queue_pop_head (queue); \
+  assert_error_reply (message, DBUS_SERVICE_DBUS, DBUS_SERVICE_DBUS, \
+                      DBUS_ERROR_ACCESS_DENIED); \
+  g_assert_cmpint (dbus_message_get_reply_serial (message), ==, \
+                   dbus_message_get_serial (in_reply_to)); \
+  dbus_message_unref (message); \
+} while (0)
+
 /* This is called after processing pending replies to our own method
  * calls, but before anything else.
  */
@@ -797,6 +831,11 @@ test_become_monitor (Fixture *f,
   test_assert_no_error (&f->e);
   g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
 
+  /* If the policy forbids receiving NameAcquired, then we'll never
+   * receive it, so behave as though we had */
+  if (config_forbids_name_acquired_signal (f->config))
+    got_unique = got_a = got_b = got_c = TRUE;
+
   while (!got_unique || !got_a || !got_b || !got_c)
     {
       if (g_queue_is_empty (&f->monitored))
@@ -1448,6 +1487,7 @@ test_dbus_daemon (Fixture *f,
 {
   DBusMessage *m;
   int res;
+  size_t n_expected;
 
   if (f->address == NULL)
     return;
@@ -1463,7 +1503,12 @@ test_dbus_daemon (Fixture *f,
   test_assert_no_error (&f->e);
   g_assert_cmpint (res, ==, DBUS_RELEASE_NAME_REPLY_RELEASED);
 
-  while (g_queue_get_length (&f->monitored) < 8)
+  n_expected = 8;
+
+  if (config_forbids_name_acquired_signal (context))
+    n_expected += 1;
+
+  while (g_queue_get_length (&f->monitored) < n_expected)
     test_main_context_iterate (f->ctx, TRUE);
 
   m = g_queue_pop_head (&f->monitored);
@@ -1476,10 +1521,12 @@ test_dbus_daemon (Fixture *f,
       "NameOwnerChanged", "sss", NULL);
   dbus_message_unref (m);
 
-  /* FIXME: should we get this? */
   m = g_queue_pop_head (&f->monitored);
-  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
-      "NameAcquired", "s", f->sender_name);
+  assert_name_acquired (m);
+
+  if (config_forbids_name_acquired_signal (f->config))
+    expect_name_acquired_error (&f->monitored, m);
+
   dbus_message_unref (m);
 
   m = g_queue_pop_head (&f->monitored);
@@ -1701,8 +1748,14 @@ static void
 expect_new_connection (Fixture *f)
 {
   DBusMessage *m;
+  size_t n_expected;
 
-  while (g_queue_get_length (&f->monitored) < 4)
+  n_expected = 4;
+
+  if (config_forbids_name_acquired_signal (f->config))
+    n_expected += 1;
+
+  while (g_queue_get_length (&f->monitored) < n_expected)
     test_main_context_iterate (f->ctx, TRUE);
 
   m = g_queue_pop_head (&f->monitored);
@@ -1719,7 +1772,11 @@ expect_new_connection (Fixture *f)
   dbus_message_unref (m);
 
   m = g_queue_pop_head (&f->monitored);
-  assert_name_acquired (m);
+  assert_unique_name_acquired (m);
+
+  if (config_forbids_name_acquired_signal (f->config))
+    expect_name_acquired_error (&f->monitored, m);
+
   dbus_message_unref (m);
 }
 
@@ -2044,6 +2101,8 @@ main (int argc,
       setup, test_method_call, teardown);
   g_test_add ("/monitor/forbidden-method", Fixture, &forbidding_config,
       setup, test_forbidden_method_call, teardown);
+  g_test_add ("/monitor/forbidden-reply", Fixture, &forbidding_config,
+      setup, test_dbus_daemon, teardown);
   g_test_add ("/monitor/dbus-daemon", Fixture, NULL,
       setup, test_dbus_daemon, teardown);
   g_test_add ("/monitor/selective", Fixture, &selective_config,
-- 
2.27.0

