Skip to content

Commit

Permalink
Fix UAF on listener arg on event during enable()
Browse files Browse the repository at this point in the history
The C++ code first installs a no-op listener in the entity, then completes construction of
the C++ object and only then sets the application listener.  The dds_set_listener
operation then invokes the listeners for any events that happened before the application
listener was installed.

This is different from not installing a no-op listener: if there is no listener, an event
is propagates up the entity hierarchy, checking whether any of those has a listener
installed.  If there is one, it is invoked and the event is considered handled, and the
status flag cleared (leaving no trace of the event).  If the application is trying to
install a listener for the event, then this results in invoking the wrong listener.

Cyclone offers a mode in which the status flag is not cleared.  This means one can define
a set of listener functions that do absolutely nothing and without clearing the status
flags.

The code re-used its normal callback functions.  Those inspect the listener argument, and
during construction of the entity there is a window where the argument for the no-op
listener is freed but the callback functions remain installed.  This fixes that by using
separate functions that do nothing so that we don't have to touch the listener
callbacks/argument data maintained in the C++ binding.

The normal functions used to check the "reset on invoke" flag that is used to
enable/disable clearing the status flags for deciding whether or not to actually do
anything.  The standard API doesn't allow this mode of not clearing the status flag on
listener invocation, so it could be abused in this manner.  With the introduction of
a separate set of no-op functions, the need for this abuse disappeared.  That, too, has
been fixed.

Details of the UAF:

1. When creating a data writer, it will first use EntityDelegate::listener_set to set the
   listener_callbacks to the special "no-op" mode of the normal listener C++ functions.

2. It then updates the listener_callbacks to those of the "real" listener (again using
   EntityDelegate::listener_set).  The entity is not yet "enabled" and the call to the
   C API dds_set_listener is skipped.

3. It then frees the previous listener_callbacks with its argument.

4. If an event causing a listener invocation happens now, the callback function is called
   with the freed argument pointer because the C API still references it.

Thanks to @ruoruoniao for the discovery and root-cause analysis.

Signed-off-by: Erik Boasson <[email protected]>
  • Loading branch information
eboasson committed Jun 28, 2024
1 parent 4940996 commit 15949d4
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/ddscxx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ set(sources
src/org/eclipse/cyclonedds/core/EntityDelegate.cpp
src/org/eclipse/cyclonedds/core/ReportUtils.cpp
src/org/eclipse/cyclonedds/core/ListenerDispatcher.cpp
src/org/eclipse/cyclonedds/core/NoopListener.cpp
src/org/eclipse/cyclonedds/core/InstanceHandleDelegate.cpp
src/org/eclipse/cyclonedds/core/EntitySet.cpp
src/org/eclipse/cyclonedds/core/MiscUtils.cpp
Expand Down
4 changes: 2 additions & 2 deletions src/ddscxx/include/dds/pub/detail/DataWriterImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <dds/pub/PublisherListener.hpp>
#include <dds/domain/DomainParticipantListener.hpp>
#include <org/eclipse/cyclonedds/core/ListenerDispatcher.hpp>
#include <org/eclipse/cyclonedds/core/NoopListener.hpp>

namespace dds
{
Expand Down Expand Up @@ -385,8 +386,7 @@ dds::pub::detail::DataWriter<T>::DataWriter(

std::string name = topic.name() + "_datawriter";

this->listener_set(nullptr, dds::core::status::StatusMask::all(), false);
dds_entity_t ddsc_writer = dds_create_writer (ddsc_pub, ddsc_topic, ddsc_qos, this->listener_callbacks);
dds_entity_t ddsc_writer = dds_create_writer (ddsc_pub, ddsc_topic, ddsc_qos, org::eclipse::cyclonedds::core::make_noop_listener().get());
dds_delete_qos(ddsc_qos);
ISOCPP_DDSC_RESULT_CHECK_AND_THROW(ddsc_writer, "Could not create DataWriter.");
topic_.delegate()->incrNrDependents();
Expand Down
4 changes: 2 additions & 2 deletions src/ddscxx/include/dds/sub/detail/TDataReaderImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <dds/sub/detail/SamplesHolder.hpp>
#include <dds/domain/DomainParticipantListener.hpp>
#include "dds/core/macros.hpp"
#include <org/eclipse/cyclonedds/core/NoopListener.hpp>



Expand Down Expand Up @@ -498,8 +499,7 @@ dds::sub::detail::DataReader<T>::common_constructor()
c_value *params = this->AnyDataReaderDelegate::td_.delegate()->reader_parameters();
#endif

this->listener_set(nullptr, dds::core::status::StatusMask::all(), false);
dds_entity_t ddsc_reader = dds_create_reader(ddsc_sub, ddsc_top, ddsc_qos, this->listener_callbacks);
dds_entity_t ddsc_reader = dds_create_reader(ddsc_sub, ddsc_top, ddsc_qos, org::eclipse::cyclonedds::core::make_noop_listener().get());
dds_delete_qos(ddsc_qos);
ISOCPP_DDSC_RESULT_CHECK_AND_THROW(ddsc_reader, "Could not create DataReader.");

Expand Down
26 changes: 26 additions & 0 deletions src/ddscxx/include/org/eclipse/cyclonedds/core/NoopListener.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright(c) 2024 ZettaScale Technology and others
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
// v. 1.0 which is available at
// http://www.eclipse.org/org/documents/edl-v10.php.
//
// SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause

#ifndef CYCLONEDDS_CORE_NOOPLISTENER_H_
#define CYCLONEDDS_CORE_NOOPLISTENER_H_

#include <memory>
#include <functional>
#include "dds/dds.h"

#include "dds/core/macros.hpp"

namespace org { namespace eclipse { namespace cyclonedds { namespace core {

extern OMG_DDS_API std::unique_ptr<dds_listener_t, std::function<void(dds_listener_t *)>> make_noop_listener();

} } } }

#endif /* CYCLONEDDS_CORE_NOOPLISTENER_H_ */
26 changes: 13 additions & 13 deletions src/ddscxx/src/org/eclipse/cyclonedds/core/ListenerDispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::InconsistentTopicStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -52,7 +52,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::OfferedDeadlineMissedStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -67,7 +67,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::OfferedIncompatibleQosStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -82,7 +82,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::LivelinessLostStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -97,7 +97,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::PublicationMatchedStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -113,7 +113,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::RequestedDeadlineMissedStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -128,7 +128,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::RequestedIncompatibleQosStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -143,7 +143,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::SampleRejectedStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -158,7 +158,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::LivelinessChangedStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -172,7 +172,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
la->cpp_ref->on_data_available(reader);
la->cpp_ref->release_callback_lock();
Expand All @@ -185,7 +185,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::SubscriptionMatchedStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -200,7 +200,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
org::eclipse::cyclonedds::core::SampleLostStatusDelegate sd;
sd.ddsc_status(&status);
Expand All @@ -215,7 +215,7 @@ extern "C"
org::eclipse::cyclonedds::core::ListenerArg *la =
reinterpret_cast<org::eclipse::cyclonedds::core::ListenerArg *>(arg);

if (la->reset_on_invoke && la->cpp_ref->obtain_callback_lock())
if (la->cpp_ref->obtain_callback_lock())
{
la->cpp_ref->on_data_readers(subscriber);
la->cpp_ref->release_callback_lock();
Expand Down
54 changes: 54 additions & 0 deletions src/ddscxx/src/org/eclipse/cyclonedds/core/NoopListener.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright(c) 2024 ZettaScale Technology and others
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
// v. 1.0 which is available at
// http://www.eclipse.org/org/documents/edl-v10.php.
//
// SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause

#include "org/eclipse/cyclonedds/core/NoopListener.hpp"

#ifdef _WIN32_DLL_
#define DDS_FN_EXPORT __declspec (dllexport)
#else
#define DDS_FN_EXPORT
#endif

namespace org { namespace eclipse { namespace cyclonedds { namespace core {

static void callback_on_inconsistent_topic_noop (dds_entity_t, dds_inconsistent_topic_status_t, void *) { }
static void callback_on_offered_deadline_missed_noop (dds_entity_t, dds_offered_deadline_missed_status_t, void *) { }
static void callback_on_offered_incompatible_qos_noop (dds_entity_t, dds_offered_incompatible_qos_status_t, void *) { }
static void callback_on_liveliness_lost_noop (dds_entity_t, dds_liveliness_lost_status_t, void *) { }
static void callback_on_publication_matched_noop (dds_entity_t, dds_publication_matched_status_t, void *) { }
static void callback_on_requested_deadline_missed_noop (dds_entity_t, dds_requested_deadline_missed_status_t, void *) { }
static void callback_on_requested_incompatible_qos_noop (dds_entity_t, dds_requested_incompatible_qos_status_t, void *) { }
static void callback_on_sample_rejected_noop (dds_entity_t, dds_sample_rejected_status_t, void *) { }
static void callback_on_liveliness_changed_noop (dds_entity_t, dds_liveliness_changed_status_t, void *) { }
static void callback_on_data_available_noop (dds_entity_t, void *) { }
static void callback_on_subscription_matched_noop (dds_entity_t, dds_subscription_matched_status_t, void *) { }
static void callback_on_sample_lost_noop (dds_entity_t, dds_sample_lost_status_t, void *) { }
static void callback_on_data_readers_noop (dds_entity_t, void *) { }

DDS_FN_EXPORT std::unique_ptr<dds_listener_t, std::function<void(dds_listener_t *)>> make_noop_listener()
{
dds_listener_t *callbacks = dds_create_listener(nullptr);
dds_lset_inconsistent_topic_arg(callbacks, callback_on_inconsistent_topic_noop, nullptr, false);
dds_lset_offered_deadline_missed_arg(callbacks, callback_on_offered_deadline_missed_noop, nullptr, false);
dds_lset_offered_incompatible_qos_arg(callbacks, callback_on_offered_incompatible_qos_noop, nullptr, false);
dds_lset_liveliness_lost_arg(callbacks, callback_on_liveliness_lost_noop, nullptr, false);
dds_lset_publication_matched_arg(callbacks, callback_on_publication_matched_noop, nullptr, false);
dds_lset_requested_deadline_missed_arg(callbacks, callback_on_requested_deadline_missed_noop, nullptr, false);
dds_lset_requested_incompatible_qos_arg(callbacks, callback_on_requested_incompatible_qos_noop, nullptr, false);
dds_lset_sample_rejected_arg(callbacks, callback_on_sample_rejected_noop, nullptr, false);
dds_lset_liveliness_changed_arg(callbacks, callback_on_liveliness_changed_noop, nullptr, false);
dds_lset_data_available_arg(callbacks, callback_on_data_available_noop, nullptr, false);
dds_lset_subscription_matched_arg(callbacks, callback_on_subscription_matched_noop, nullptr, false);
dds_lset_sample_lost_arg(callbacks, callback_on_sample_lost_noop, nullptr, false);
dds_lset_data_on_readers_arg(callbacks, callback_on_data_readers_noop, nullptr, false);
return std::unique_ptr<dds_listener_t, std::function<void(dds_listener_t *)>>(callbacks, &dds_delete_listener);
}

} } } }
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <org/eclipse/cyclonedds/sub/SubscriberDelegate.hpp>
#include <org/eclipse/cyclonedds/topic/AnyTopicDelegate.hpp>
#include <org/eclipse/cyclonedds/sub/BuiltinSubscriberDelegate.hpp>
#include <org/eclipse/cyclonedds/core/NoopListener.hpp>

org::eclipse::cyclonedds::core::Mutex org::eclipse::cyclonedds::domain::DomainParticipantDelegate::global_participants_lock_;
dds::domain::qos::DomainParticipantQos org::eclipse::cyclonedds::domain::DomainParticipantDelegate::default_participant_qos_;
Expand Down Expand Up @@ -79,7 +80,7 @@ org::eclipse::cyclonedds::domain::DomainParticipantDelegate::DomainParticipantDe

ddsc_qos = qos.delegate().ddsc_qos();
this->listener(listener, event_mask);
ddsc_par = dds_create_participant(static_cast<dds_domainid_t>(domain_id_), ddsc_qos, this->listener_callbacks);
ddsc_par = dds_create_participant(static_cast<dds_domainid_t>(domain_id_), ddsc_qos, org::eclipse::cyclonedds::core::make_noop_listener().get());

dds_delete_qos (ddsc_qos);
ISOCPP_DDSC_RESULT_CHECK_AND_THROW(ddsc_par, "Could not create DomainParticipant.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <org/eclipse/cyclonedds/pub/PublisherDelegate.hpp>
#include <org/eclipse/cyclonedds/pub/AnyDataWriterDelegate.hpp>
#include <org/eclipse/cyclonedds/core/ScopedLock.hpp>
#include <org/eclipse/cyclonedds/core/NoopListener.hpp>


namespace org
Expand Down Expand Up @@ -56,7 +57,7 @@ PublisherDelegate::PublisherDelegate(const dds::domain::DomainParticipant& dp,
}

this->listener(listener, event_mask);
ddsc_pub = dds_create_publisher(ddsc_par, ddsc_qos, this->listener_callbacks);
ddsc_pub = dds_create_publisher(ddsc_par, ddsc_qos, org::eclipse::cyclonedds::core::make_noop_listener().get());
dds_delete_qos(ddsc_qos);
ISOCPP_DDSC_RESULT_CHECK_AND_THROW(ddsc_pub, "Could not create publisher.");
this->set_ddsc_entity(ddsc_pub);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <org/eclipse/cyclonedds/sub/SubscriberDelegate.hpp>
#include <org/eclipse/cyclonedds/sub/AnyDataReaderDelegate.hpp>
#include <org/eclipse/cyclonedds/core/ScopedLock.hpp>
#include <org/eclipse/cyclonedds/core/NoopListener.hpp>

namespace org
{
Expand Down Expand Up @@ -55,7 +56,7 @@ SubscriberDelegate::SubscriberDelegate(
}

this->listener(listener, event_mask);
ddsc_sub = dds_create_subscriber(ddsc_par, ddsc_qos, this->listener_callbacks);
ddsc_sub = dds_create_subscriber(ddsc_par, ddsc_qos, org::eclipse::cyclonedds::core::make_noop_listener().get());

dds_delete_qos(ddsc_qos);
ISOCPP_DDSC_RESULT_CHECK_AND_THROW(ddsc_sub, "Could not create subscriber.");
Expand Down

0 comments on commit 15949d4

Please sign in to comment.