Merge pull request #29945 from Rishiii57:fix/filestorage-recursion-depth-limit-29939

core(persistence): add recursion depth limit to XML/YAML/JSON parsers
This commit is contained in:
Alexander Smorkalov
2026-09-15 12:02:50 +03:00
committed by GitHub
5 changed files with 54 additions and 11 deletions
+1
View File
@@ -27,6 +27,7 @@ typedef void* gzFile;
//=====================================================================================
static const size_t PARSER_BASE64_BUFFER_SIZE = 1024U * 1024U / 8U;
static const int CV_PERSISTENCE_MAX_DEPTH = 1024;
namespace base64 {
+12 -6
View File
@@ -673,11 +673,14 @@ public:
return ptr;
}
char* parseSeq( char* ptr, FileNode& node )
char* parseSeq( char* ptr, FileNode& node, int depth = 0 )
{
if (!ptr)
CV_PARSE_ERROR_CPP( "ptr is NULL" );
if( depth > CV_PERSISTENCE_MAX_DEPTH )
CV_PARSE_ERROR_CPP("Too many nested collections");
if ( *ptr != '[' )
CV_PARSE_ERROR_CPP( "'[' - left-brace of seq is missing" );
else
@@ -696,9 +699,9 @@ public:
FileNode child = fs->addNode(node, std::string(), FileNode::NONE );
if ( *ptr == '[' )
ptr = parseSeq( ptr, child );
ptr = parseSeq( ptr, child, depth + 1 );
else if ( *ptr == '{' )
ptr = parseMap( ptr, child );
ptr = parseMap( ptr, child, depth + 1 );
else
ptr = parseValue( ptr, child );
}
@@ -727,11 +730,14 @@ public:
return ptr;
}
char* parseMap( char* ptr, FileNode& node )
char* parseMap( char* ptr, FileNode& node, int depth = 0 )
{
if (!ptr)
CV_PARSE_ERROR_CPP("ptr is NULL");
if( depth > CV_PERSISTENCE_MAX_DEPTH )
CV_PARSE_ERROR_CPP("Too many nested collections");
if ( *ptr != '{' )
CV_PARSE_ERROR_CPP( "'{' - left-brace of map is missing" );
else
@@ -756,9 +762,9 @@ public:
break;
if ( *ptr == '[' )
ptr = parseSeq( ptr, child );
ptr = parseSeq( ptr, child, depth + 1 );
else if ( *ptr == '{' )
ptr = parseMap( ptr, child );
ptr = parseMap( ptr, child, depth + 1 );
else
ptr = parseValue( ptr, child );
}
+5 -2
View File
@@ -458,11 +458,14 @@ public:
return true;
}
char* parseValue( char* ptr, FileNode& node )
char* parseValue( char* ptr, FileNode& node, int depth = 0 )
{
if (!ptr)
CV_PARSE_ERROR_CPP("Invalid input");
if( depth > CV_PERSISTENCE_MAX_DEPTH )
CV_PARSE_ERROR_CPP("Too many nested collections");
FileNode new_elem;
bool have_space = true;
int value_type = node.type();
@@ -521,7 +524,7 @@ public:
new_elem = fs->addNode(node, key, elem_type, 0);
if (!binary_string)
ptr = parseValue(ptr, new_elem);
ptr = parseValue(ptr, new_elem, depth + 1);
else
{
ptr = fs->parseBase64( ptr, 0, new_elem);
+6 -3
View File
@@ -446,11 +446,14 @@ public:
return ptr;
}
char* parseValue( char* ptr, FileNode& node, int min_indent, bool is_parent_flow )
char* parseValue( char* ptr, FileNode& node, int min_indent, bool is_parent_flow, int depth = 0 )
{
if (!ptr)
CV_PARSE_ERROR_CPP("Invalid input");
if( depth > CV_PERSISTENCE_MAX_DEPTH )
CV_PARSE_ERROR_CPP("Too many nested collections");
char* endptr = 0;
char c = ptr[0], d = ptr[1];
int value_type = FileNode::NONE;
@@ -695,7 +698,7 @@ public:
break;
elem = fs->addNode(node, std::string(), FileNode::NONE);
}
ptr = parseValue( ptr, elem, new_min_indent, true );
ptr = parseValue( ptr, elem, new_min_indent, true, depth + 1 );
}
fs->finalizeCollection(node);
}
@@ -781,7 +784,7 @@ public:
elem = fs->addNode(node, std::string(), FileNode::NONE);
}
ptr = skipSpaces( ptr, indent + 1, INT_MAX );
ptr = parseValue( ptr, elem, indent + 1, false );
ptr = parseValue( ptr, elem, indent + 1, false, depth + 1 );
ptr = skipSpaces( ptr, 0, INT_MAX );
if( ptr - fs->bufferStart() != indent )
{
+30
View File
@@ -2143,6 +2143,36 @@ TEST(Core_InputOutput, FileStorage_invalid_path_regression_21448_JSON)
fs.release();
}
TEST(Core_InputOutput, FileStorage_recursion_depth_limit_29939_XML)
{
const int N = 2000; // well past CV_PERSISTENCE_MAX_DEPTH, small enough to run fast
std::string open_tags, close_tags;
for (int i = 0; i < N; i++) { open_tags += "<a>"; close_tags += "</a>"; }
std::string content = "<?xml version=\"1.0\"?>\n<opencv_storage>\n"
+ open_tags + "1" + close_tags + "\n</opencv_storage>\n";
EXPECT_THROW(FileStorage(content, FileStorage::READ | FileStorage::MEMORY), cv::Exception);
}
TEST(Core_InputOutput, FileStorage_recursion_depth_limit_29939_YAML)
{
const int N = 2000;
std::string content = "%YAML:1.0\n---\n" + std::string(N, '[') + std::string(N, ']') + "\n";
EXPECT_THROW(FileStorage(content, FileStorage::READ | FileStorage::MEMORY), cv::Exception);
}
TEST(Core_InputOutput, FileStorage_recursion_depth_limit_29939_JSON)
{
const int N = 2000;
std::string content;
for (int i = 0; i < N; i++) content += "{\"a\":";
content += "1";
for (int i = 0; i < N; i++) content += "}";
content += "\n";
EXPECT_THROW(FileStorage(content, FileStorage::READ | FileStorage::MEMORY), cv::Exception);
}
// see https://github.com/opencv/opencv/issues/25073
typedef testing::TestWithParam< std::string > Core_InputOutput_regression_25073;