Skip to content

Commit 610a04b

Browse files
committed
Fix CVE-2026-22591
This commit fixes CVE-2026-22591 by limiting the number of subexpressions in a filter expression. This is a squashed commit of a privately reviewed branch. Signed-off-by: Miguel Company <miguelcompany@eprosima.com> Reviewed-by: Ricardo González Moreno <ricardo@richiware.dev>
1 parent 076fb1f commit 610a04b

File tree

5 files changed

+433
-45
lines changed

5 files changed

+433
-45
lines changed

src/cpp/fastdds/domain/DomainParticipantImpl.cpp

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@
5151
#include <fastdds/rtps/writer/WriterDiscoveryStatus.hpp>
5252

5353
#include <fastdds/builtin/type_lookup_service/TypeLookupManager.hpp>
54-
#include <fastdds/core/policy/QosPolicyUtils.hpp>
5554
#include <fastdds/publisher/DataWriterImpl.hpp>
5655
#include <fastdds/publisher/PublisherImpl.hpp>
5756
#include <fastdds/subscriber/SubscriberImpl.hpp>
5857
#include <fastdds/topic/ContentFilteredTopicImpl.hpp>
58+
#include <fastdds/topic/DDSSQLFilter/DDSFilterFactory.hpp>
5959
#include <fastdds/topic/TopicImpl.hpp>
6060
#include <fastdds/topic/TopicProxy.hpp>
6161
#include <fastdds/topic/TopicProxyFactory.hpp>
@@ -90,6 +90,54 @@ using rtps::ReaderDiscoveryStatus;
9090
using rtps::ResourceEvent;
9191
using rtps::WriterDiscoveryStatus;
9292

93+
static size_t get_filter_max_subexpressions(
94+
const DomainParticipantQos& qos)
95+
{
96+
constexpr const char parameter_name[] = "dds.sql.expression.max_subexpressions";
97+
const std::string* property = fastdds::rtps::PropertyPolicyHelper::find_property(
98+
qos.properties(), parameter_name);
99+
if (nullptr != property)
100+
{
101+
try
102+
{
103+
return std::stoul(*property);
104+
}
105+
catch (...)
106+
{
107+
EPROSIMA_LOG_WARNING(DOMAIN_PARTICIPANT,
108+
"Invalid value for dds.sql.expression.max_subexpressions property: "
109+
<< *property << ". Will use default value of "
110+
<< DDSSQLFilter::DDSFilterFactory::DEFAULT_MAX_SUBEXPRESSIONS);
111+
}
112+
}
113+
114+
return DDSSQLFilter::DDSFilterFactory::DEFAULT_MAX_SUBEXPRESSIONS;
115+
}
116+
117+
static size_t get_filter_max_expression_length(
118+
const DomainParticipantQos& qos)
119+
{
120+
constexpr const char parameter_name[] = "dds.sql.expression.max_expression_length";
121+
const std::string* property = fastdds::rtps::PropertyPolicyHelper::find_property(
122+
qos.properties(), parameter_name);
123+
if (nullptr != property)
124+
{
125+
try
126+
{
127+
return std::stoul(*property);
128+
}
129+
catch (...)
130+
{
131+
EPROSIMA_LOG_WARNING(DOMAIN_PARTICIPANT,
132+
"Invalid value for dds.sql.expression.max_expression_length property: "
133+
<< *property << ". Will use default value of "
134+
<< DDSSQLFilter::DDSFilterFactory::DEFAULT_MAX_EXPRESSION_LENGTH);
135+
}
136+
}
137+
138+
return DDSSQLFilter::DDSFilterFactory::DEFAULT_MAX_EXPRESSION_LENGTH;
139+
}
140+
93141
DomainParticipantImpl::DomainParticipantImpl(
94142
DomainParticipant* dp,
95143
DomainId_t did,
@@ -103,6 +151,7 @@ DomainParticipantImpl::DomainParticipantImpl(
103151
, listener_(listen)
104152
, default_pub_qos_(PUBLISHER_QOS_DEFAULT)
105153
, default_sub_qos_(SUBSCRIBER_QOS_DEFAULT)
154+
, dds_sql_filter_factory_(get_filter_max_subexpressions(qos), get_filter_max_expression_length(qos))
106155
, default_topic_qos_(TOPIC_QOS_DEFAULT)
107156
, id_counter_(0)
108157
#pragma warning (disable : 4355 )

src/cpp/fastdds/topic/DDSSQLFilter/DDSFilterFactory.cpp

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -541,59 +541,69 @@ ReturnCode_t DDSFilterFactory::create_content_filter(
541541
}
542542
}
543543
}
544-
else if (std::strlen(filter_expression) == 0)
545-
{
546-
delete_content_filter(filter_class_name, filter_instance);
547-
filter_instance = &empty_expression_;
548-
ret = RETCODE_OK;
549-
}
550544
else
551545
{
552-
std::shared_ptr<xtypes::TypeObject> type_object = get_complete_type_object(type_name, data_type);
546+
size_t expression_len = std::strlen(filter_expression);
553547

554-
if (!type_object)
548+
if (0 == expression_len)
555549
{
556-
EPROSIMA_LOG_ERROR(DDSSQLFILTER, "No TypeObject found for type " << type_name);
550+
delete_content_filter(filter_class_name, filter_instance);
551+
filter_instance = &empty_expression_;
552+
ret = RETCODE_OK;
557553
}
558-
else
554+
else if (validate_filter_expression(filter_expression, expression_len))
559555
{
560-
auto node = parser::parse_filter_expression(filter_expression, type_object);
561-
if (node)
556+
std::shared_ptr<xtypes::TypeObject> type_object = get_complete_type_object(type_name, data_type);
557+
558+
if (!type_object)
562559
{
563-
DynamicType::_ref_type dyn_type = DynamicTypeBuilderFactory::get_instance()->create_type_w_type_object(
564-
*type_object)->build();
565-
if (dyn_type)
560+
EPROSIMA_LOG_ERROR(DDSSQLFILTER, "No TypeObject found for type " << type_name);
561+
ret = RETCODE_PRECONDITION_NOT_MET;
562+
}
563+
else
564+
{
565+
auto node = parser::parse_filter_expression(filter_expression, type_object);
566+
if (node)
566567
{
567-
DDSFilterExpression* expr = get_expression();
568-
expr->set_type(dyn_type);
569-
size_t n_params = filter_parameters.length();
570-
expr->parameters.reserve(n_params);
571-
while (expr->parameters.size() < n_params)
568+
DynamicTypeBuilderFactory::_ref_type factory = DynamicTypeBuilderFactory::get_instance();
569+
DynamicType::_ref_type dyn_type = factory->create_type_w_type_object(*type_object)->build();
570+
if (dyn_type)
572571
{
573-
expr->parameters.emplace_back();
574-
}
575-
ExpressionParsingState state{ std::make_shared<xtypes::TypeObject>(*type_object),
576-
filter_parameters, expr };
577-
ret = convert_tree<DDSFilterCondition>(state, expr->root, *(node->children[0]));
578-
if (RETCODE_OK == ret)
579-
{
580-
delete_content_filter(filter_class_name, filter_instance);
581-
filter_instance = expr;
572+
DDSFilterExpression* expr = get_expression();
573+
expr->set_type(dyn_type);
574+
size_t n_params = filter_parameters.length();
575+
expr->parameters.reserve(n_params);
576+
while (expr->parameters.size() < n_params)
577+
{
578+
expr->parameters.emplace_back();
579+
}
580+
ExpressionParsingState state{ std::make_shared<xtypes::TypeObject>(*type_object),
581+
filter_parameters, expr };
582+
ret = convert_tree<DDSFilterCondition>(state, expr->root, *(node->children[0]));
583+
if (RETCODE_OK == ret)
584+
{
585+
delete_content_filter(filter_class_name, filter_instance);
586+
filter_instance = expr;
587+
}
588+
else
589+
{
590+
delete_content_filter(filter_class_name, expr);
591+
}
582592
}
583593
else
584594
{
585-
delete_content_filter(filter_class_name, expr);
595+
ret = RETCODE_BAD_PARAMETER;
586596
}
587597
}
588598
else
589599
{
590600
ret = RETCODE_BAD_PARAMETER;
591601
}
592602
}
593-
else
594-
{
595-
ret = RETCODE_BAD_PARAMETER;
596-
}
603+
}
604+
else
605+
{
606+
ret = RETCODE_BAD_PARAMETER;
597607
}
598608
}
599609

@@ -620,6 +630,50 @@ ReturnCode_t DDSFilterFactory::delete_content_filter(
620630
return RETCODE_OK;
621631
}
622632

633+
bool DDSFilterFactory::validate_filter_expression(
634+
const char* expression,
635+
const size_t expression_len) const
636+
{
637+
if (expression_len > max_expression_length_)
638+
{
639+
// Expression too long
640+
return false;
641+
}
642+
643+
// Check parentheses balance and count
644+
size_t num_parentheses = 0;
645+
size_t pending_size = expression_len;
646+
for (const char* ptr = expression; (pending_size > 0) && (*ptr != '\0'); ++ptr, --pending_size)
647+
{
648+
if (*ptr == '(')
649+
{
650+
++num_parentheses;
651+
652+
if (num_parentheses > max_subexpressions_)
653+
{
654+
// Too many nested parentheses
655+
return false;
656+
}
657+
}
658+
else if (*ptr == ')')
659+
{
660+
if (num_parentheses == 0)
661+
{
662+
// Unmatched closing parenthesis
663+
return false;
664+
}
665+
--num_parentheses;
666+
}
667+
}
668+
if (num_parentheses != 0)
669+
{
670+
// Unmatched opening parenthesis
671+
return false;
672+
}
673+
674+
return true;
675+
}
676+
623677
} // namespace DDSSQLFilter
624678
} // namespace dds
625679
} // namespace fastdds

src/cpp/fastdds/topic/DDSSQLFilter/DDSFilterFactory.hpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ class DDSFilterFactory final : public IContentFilterFactory
4040

4141
public:
4242

43+
static constexpr size_t DEFAULT_MAX_SUBEXPRESSIONS = 256;
44+
static constexpr size_t DEFAULT_MAX_EXPRESSION_LENGTH = 16 * 1024;
45+
46+
explicit DDSFilterFactory(
47+
size_t max_subexpressions,
48+
size_t max_expression_length)
49+
: max_subexpressions_(max_subexpressions)
50+
, max_expression_length_(max_expression_length)
51+
{
52+
}
53+
4354
~DDSFilterFactory();
4455

4556
ReturnCode_t create_content_filter(
@@ -56,6 +67,20 @@ class DDSFilterFactory final : public IContentFilterFactory
5667

5768
private:
5869

70+
/**
71+
* @brief Validate a DDS-SQL filter expression.
72+
*
73+
* Performs basic validation checks on the expression before passing it to the PEGTL parser.
74+
*
75+
* @param[in] expression The filter expression to validate.
76+
* @param[in] expression_len The length of the filter expression.
77+
*
78+
* @return true if the expression is valid, false otherwise.
79+
*/
80+
bool validate_filter_expression(
81+
const char* expression,
82+
const size_t expression_len) const;
83+
5984
/**
6085
* Retrieve a DDSFilterExpression from the pool.
6186
*
@@ -86,6 +111,10 @@ class DDSFilterFactory final : public IContentFilterFactory
86111
DDSFilterEmptyExpression empty_expression_;
87112
/// Pool of DDSFilterExpression objects
88113
ObjectPool<DDSFilterExpression*> expression_pool_;
114+
/// Maximum number of sub-expressions allowed in a filter expression
115+
const size_t max_subexpressions_ = DEFAULT_MAX_SUBEXPRESSIONS;
116+
/// Maximum length of a filter expression
117+
const size_t max_expression_length_ = DEFAULT_MAX_EXPRESSION_LENGTH;
89118

90119
};
91120

0 commit comments

Comments
 (0)