Repository: eclipse-cyclonedds/cyclonedds (C)

Issue:
CycloneDDS crashes when  a subscription matched listener is invoked on a reader for DCPSSubscriptions
In an attempt to harden CycloneDDS by trying some "weird"  scenarios I ran into the following.

When a listener on subscription matched is attached to a reader for DCPSSubscriptions, CycloneDDS crashes. The following code (in C) reproduces this issue.

```
static void handle_subscription_matched_cb (dds_entity_t rd, const dds_subscription_matched_status_t status, void *arg)
{
  dds_instance_handle_t ih;
  dds_builtintopic_endpoint_t *ep;

  (void)arg; /* to silence compiler about unused parameters */
  ih = status.last_publication_handle;
  assert(ih != DDS_HANDLE_NIL);
  if ((ep = dds_get_matched_publication_data(rd, ih)) == NULL) {
    /* the endpoint does not exist, remove the client from the table of clients */
    printf("handle_subscription_matched_cb: endpoint NOT found\n");
  } else {
    printf("handle_subscription_matched_cb: endpoint found\n");
    dds_builtintopic_free_endpoint(ep);
  }
  return;
}

int main (int argc, char *argv[])
{
  dds_entity_t participant, reader;
  dds_listener_t *listener;
  dds_return_t rc;

  (void)argc; /* to silence compiler about unused parameters */
  (void)argv; /* to silence compiler about unused parameters */
  /* make sure to set the config URI before creating the participant */
  if ((participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL)) < 0) {
    fprintf(stderr, "dds_create_participant failed\n");
    goto err_create_participant;
  }
  if ((reader = dds_create_reader(participant, DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION, NULL, NULL)) < 0) {
    fprintf(stderr, "dds_create_reader failed\n");
    goto err_create_reader;
  }
  if ((listener = dds_create_listener(NULL)) == NULL) {
    fprintf(stderr, "dds_create_listener failed\n");
    goto err_create_listener;
  }
  dds_lset_subscription_matched(listener, handle_subscription_matched_cb);
  if ((rc = dds_set_listener(reader, listener)) < 0) {
    fprintf(stderr, "dds_set_listener failed\n");
    goto err_set_listener;
  }
  printf("Success!\n");
  dds_delete_listener(listener);
  dds_delete(participant);
  printf("Bye\n");
  return 0;

/* cleanup in case of error */
err_set_listener:
  dds_delete_listener(listener);
err_create_listener:
  dds_delete(reader);
err_create_reader:
  dds_delete(participant);
err_create_participant:
  return -1;
}
```
Executing this code will lead to SEGV with a stack trace that looks like this:

```
==9929==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000020 (pc 0x7ffb4281f803 bp 0x7ffe9cbc87f0 sp 0x7ffe9cbc87a0 T0)
==9929==The signal is caused by a READ memory access.
==9929==Hint: address points to the zero page.
    #0 0x7ffb4281f802 in dds_get_matched_publication_data /home/lex/Repositories/TheFixer/cyclonedds/src/core/ddsc/src/dds_matched.c:116
    #1 0x56515f71a55d in handle_subscription_matched_cb /home/lex/Repositories/TheFixer/transient_publisher/src/main.c:25
    #2 0x7ffb42809525 in status_cb_subscription_matched_invoke /home/lex/Repositories/TheFixer/cyclonedds/src/core/ddsc/src/dds_reader.c:377
    #3 0x7ffb4280a0c8 in dds_reader_invoke_cbs_for_pending_events /home/lex/Repositories/TheFixer/cyclonedds/src/core/ddsc/src/dds_reader.c:473
    #4 0x7ffb4281b9cd in dds_entity_deriver_invoke_cbs_for_pending_events /home/lex/Repositories/TheFixer/cyclonedds/src/core/ddsc/src/dds__types.h:264
    #5 0x7ffb4281db24 in dds_set_listener /home/lex/Repositories/TheFixer/cyclonedds/src/core/ddsc/src/dds_entity.c:1082
    #6 0x56515f71a6f3 in main /home/lex/Repositories/TheFixer/transient_publisher/src/main.c:57
    #7 0x7ffb42341c86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)
    #8 0x56515f71a409 in _start (/home/lex/Repositories/TheFixer/transient_publisher/build/bin/main+0x1409)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/lex/Repositories/TheFixer/cyclonedds/src/core/ddsc/src/dds_matched.c:116 in dds_get_matched_publication_data
```



Discussion: It's not so much the listener as a bug in `dds_get_matched_publication_data`: the built-in topic are published by strange "orphan writers" and those, true to their name, don't have a parent to dereference 🤦‍♀️.

Same is true for `dds_get_matched_subscription_data`.

Stupid mistake 🙃, easy to fix 🙂



Fix (patch):
diff --git a/src/core/ddsc/src/dds_matched.c b/src/core/ddsc/src/dds_matched.c
index fe3adf453c..58679b71f3 100644
--- a/src/core/ddsc/src/dds_matched.c
+++ b/src/core/ddsc/src/dds_matched.c
@@ -76,6 +76,8 @@ static dds_builtintopic_endpoint_t *make_builtintopic_endpoint (const ddsi_guid_
   return ep;
 }
 
+static const struct ddsi_entity_common null_entity_common;
+
 dds_builtintopic_endpoint_t *dds_get_matched_subscription_data (dds_entity_t writer, dds_instance_handle_t ih)
 {
   dds_writer *wr;
@@ -83,15 +85,19 @@ dds_builtintopic_endpoint_t *dds_get_matched_subscription_data (dds_entity_t wri
     return NULL;
 
   dds_builtintopic_endpoint_t *ret = NULL;
-  struct ddsi_entity_common *rdc;
-  struct dds_qos *rdqos;
-  struct ddsi_entity_common *ppc;
+  const struct ddsi_entity_common *rdc;
+  const struct dds_qos *rdqos;
+  const struct ddsi_entity_common *ppc;
 
   // thread must be "awake" while pointers to DDSI entities are being used
   struct ddsi_domaingv * const gv = &wr->m_entity.m_domain->gv;
   ddsi_thread_state_awake (ddsi_lookup_thread_state (), gv);
   if (ddsi_writer_find_matched_reader (wr->m_wr, ih, &rdc, &rdqos, &ppc))
+  {
+    if (ppc == NULL)
+      ppc = &null_entity_common;
     ret = make_builtintopic_endpoint (&rdc->guid, &ppc->guid, ppc->iid, rdqos);
+  }
   ddsi_thread_state_asleep (ddsi_lookup_thread_state ());
 
   dds_writer_unlock (wr);
@@ -105,15 +111,19 @@ dds_builtintopic_endpoint_t *dds_get_matched_publication_data (dds_entity_t read
     return NULL;
 
   dds_builtintopic_endpoint_t *ret = NULL;
-  struct ddsi_entity_common *wrc;
-  struct dds_qos *wrqos;
-  struct ddsi_entity_common *ppc;
+  const struct ddsi_entity_common *wrc;
+  const struct dds_qos *wrqos;
+  const struct ddsi_entity_common *ppc;
 
   // thread must be "awake" while pointers to DDSI entities are being used
   struct ddsi_domaingv * const gv = &rd->m_entity.m_domain->gv;
   ddsi_thread_state_awake (ddsi_lookup_thread_state (), gv);
   if (ddsi_reader_find_matched_writer (rd->m_rd, ih, &wrc, &wrqos, &ppc))
+  {
+    if (ppc == NULL)
+      ppc = &null_entity_common;
     ret = make_builtintopic_endpoint (&wrc->guid, &ppc->guid, ppc->iid, wrqos);
+  }
   ddsi_thread_state_asleep (ddsi_lookup_thread_state ());
 
   dds_reader_unlock (rd);
diff --git a/src/core/ddsi/include/dds/ddsi/ddsi_endpoint_match.h b/src/core/ddsi/include/dds/ddsi/ddsi_endpoint_match.h
index 1e576ad6c0..a3d7e1e7c6 100644
--- a/src/core/ddsi/include/dds/ddsi/ddsi_endpoint_match.h
+++ b/src/core/ddsi/include/dds/ddsi/ddsi_endpoint_match.h
@@ -32,11 +32,33 @@ dds_return_t ddsi_writer_get_matched_subscriptions (struct ddsi_writer *wr, uint
 /** @component endpoint_matching */
 dds_return_t ddsi_reader_get_matched_publications (struct ddsi_reader *rd, uint64_t *wrs, size_t nwrs);
 
-/** @component endpoint_matching */
-bool ddsi_writer_find_matched_reader (struct ddsi_writer *wr, uint64_t ih, struct ddsi_entity_common **rdc, struct dds_qos **rdqos, struct ddsi_entity_common **ppc);
+/** @brief Lookup the instance handle of a matching reader and return it, it's qos and participant
+ * @component endpoint_matching
+ *
+ * @note Thread state must be "awake", returned pointers are aliases; lifetime ends on leaving the "awake" state; contents may not be modified.
+ *
+ * @param[in] wr writer for which to lookup ih in the setting of matching readers
+ * @param[in] ih instance handle of a reader
+ * @param[out] rdc pointer to the common entity data of the reader
+ * @param[out] rdqos pointer to the QoS of the reader
+ * @param[out] ppc participant, may be null for an orphan reader
+ * @return true iff ih corresponds to a matched reader, output parameters are undefined if not
+ */
+bool ddsi_writer_find_matched_reader (struct ddsi_writer *wr, uint64_t ih, const struct ddsi_entity_common **rdc, const struct dds_qos **rdqos, const struct ddsi_entity_common **ppc);
 
-/** @component endpoint_matching */
-bool ddsi_reader_find_matched_writer (struct ddsi_reader *rd, uint64_t ih, struct ddsi_entity_common **wrc, struct dds_qos **wrqos, struct ddsi_entity_common **ppc);
+/** @brief Lookup the instance handle of a matching writer and return it, it's qos and participant
+ * @component endpoint_matching
+ *
+ * @note Thread state must be "awake", returned pointers are aliases; lifetime ends on leaving the "awake" state; contents may not be modified.
+ *
+ * @param[in] rd reader for which to lookup ih in the setting of matching writers
+ * @param[in] ih instance handle of a writer
+ * @param[out] wrc pointer to the common entity data of the writer
+ * @param[out] wrqos pointer to the QoS of the writer
+ * @param[out] ppc participant, may be null for an orphan writer
+ * @return true iff ih corresponds to a matched writer, output parameters are undefined if not
+ */
+bool ddsi_reader_find_matched_writer (struct ddsi_reader *rd, uint64_t ih, const struct ddsi_entity_common **wrc, const struct dds_qos **wrqos, const struct ddsi_entity_common **ppc);
 
 #if defined (__cplusplus)
 }
diff --git a/src/core/ddsi/src/ddsi_endpoint_match.c b/src/core/ddsi/src/ddsi_endpoint_match.c
index 567fc0d5c0..4d20694997 100644
--- a/src/core/ddsi/src/ddsi_endpoint_match.c
+++ b/src/core/ddsi/src/ddsi_endpoint_match.c
@@ -1864,7 +1864,7 @@ dds_return_t ddsi_reader_get_matched_publications (struct ddsi_reader *rd, uint6
   return (dds_return_t) nwrs_act;
 }
 
-bool ddsi_writer_find_matched_reader (struct ddsi_writer *wr, uint64_t ih, struct ddsi_entity_common **rdc, struct dds_qos **rdqos, struct ddsi_entity_common **ppc)
+bool ddsi_writer_find_matched_reader (struct ddsi_writer *wr, uint64_t ih, const struct ddsi_entity_common **rdc, const struct dds_qos **rdqos, const struct ddsi_entity_common **ppc)
 {
   /* FIXME: this ought not be so inefficient */
   struct ddsi_domaingv *gv = wr->e.gv;
@@ -1895,14 +1895,15 @@ bool ddsi_writer_find_matched_reader (struct ddsi_writer *wr, uint64_t ih, struc
       found = true;
       *rdc = &rd->e;
       *rdqos = rd->xqos;
-      *ppc = &rd->c.pp->e;
+      // The day orphan readers are introduced in addition to orphan writers, rd->c.pp may be a null pointer
+      *ppc = rd->c.pp ? &rd->c.pp->e : NULL;
     }
   }
   ddsrt_mutex_unlock (&wr->e.lock);
   return found;
 }
 
-bool ddsi_reader_find_matched_writer (struct ddsi_reader *rd, uint64_t ih, struct ddsi_entity_common **wrc, struct dds_qos **wrqos, struct ddsi_entity_common **ppc)
+bool ddsi_reader_find_matched_writer (struct ddsi_reader *rd, uint64_t ih, const struct ddsi_entity_common **wrc, const struct dds_qos **wrqos, const struct ddsi_entity_common **ppc)
 {
   /* FIXME: this ought not be so inefficient */
   struct ddsi_domaingv *gv = rd->e.gv;
@@ -1933,7 +1934,8 @@ bool ddsi_reader_find_matched_writer (struct ddsi_reader *rd, uint64_t ih, struc
       found = true;
       *wrc = &wr->e;
       *wrqos = wr->xqos;
-      *ppc = &wr->c.pp->e;
+      // Orphan writers have no participant
+      *ppc = wr->c.pp ? &wr->c.pp->e : NULL;
     }
   }
   ddsrt_mutex_unlock (&rd->e.lock);