diff --git a/Freeswitch.2017.sln b/Freeswitch.2017.sln
index fe36ea8714..54c119cc12 100644
--- a/Freeswitch.2017.sln
+++ b/Freeswitch.2017.sln
@@ -532,6 +532,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_tts_format", "tests\un
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mod_test", "src\mod\applications\mod_test\mod_test.2017.vcxproj", "{E9FF8127-D5F0-2398-59EB-702CE55F7800}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_switch_xml", "tests\unit\test_switch_xml.2017.vcxproj", "{FED39CFF-E6CD-1C09-844D-F1174B8A5C18}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
All|Win32 = All|Win32
@@ -2390,6 +2392,18 @@ Global
{E9FF8127-D5F0-2398-59EB-702CE55F7800}.Release|Win32.Build.0 = Release|Win32
{E9FF8127-D5F0-2398-59EB-702CE55F7800}.Release|x64.ActiveCfg = Release|x64
{E9FF8127-D5F0-2398-59EB-702CE55F7800}.Release|x64.Build.0 = Release|x64
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.All|Win32.ActiveCfg = Debug|Win32
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.All|Win32.Build.0 = Debug|Win32
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.All|x64.ActiveCfg = Debug|x64
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.All|x64.Build.0 = Debug|x64
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Debug|Win32.ActiveCfg = Debug|Win32
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Debug|Win32.Build.0 = Debug|Win32
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Debug|x64.ActiveCfg = Debug|x64
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Debug|x64.Build.0 = Debug|x64
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Release|Win32.ActiveCfg = Release|Win32
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Release|Win32.Build.0 = Release|Win32
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Release|x64.ActiveCfg = Release|x64
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2578,6 +2592,7 @@ Global
{589A07E7-5DE5-49FD-A62C-27795B806AFB} = {9388C266-C3FC-468A-92EF-0CBC35941412}
{3745B86B-6BE8-3E67-FCB9-BE62A6131D67} = {9388C266-C3FC-468A-92EF-0CBC35941412}
{E9FF8127-D5F0-2398-59EB-702CE55F7800} = {E72B5BCB-6462-4D23-B419-3AF1A4AC3D78}
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18} = {9388C266-C3FC-468A-92EF-0CBC35941412}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {09840DE7-9208-45AA-9667-1A71EE93BD1E}
diff --git a/src/switch_xml.c b/src/switch_xml.c
index 0194645640..027b8f093b 100644
--- a/src/switch_xml.c
+++ b/src/switch_xml.c
@@ -103,6 +103,10 @@ void globfree(glob_t *);
#define SWITCH_XML_WS "\t\r\n " /* whitespace */
#define SWITCH_XML_ERRL 128 /* maximum error string length */
+/* Limits for entity expansion to prevent excessive resource consumption */
+#define SWITCH_XML_MAX_ENTITY_EXPANSION_DEPTH 20 /* Maximum recursion depth for entity expansion */
+#define SWITCH_XML_MAX_ENTITY_EXPANSION_COUNT 10000 /* Maximum number of entity expansions */
+
static void preprocess_exec_set(char *keyval)
{
char *key = keyval;
@@ -760,23 +764,54 @@ static switch_xml_t switch_xml_close_tag(switch_xml_root_t root, char *name, cha
return NULL;
}
+/* Depth-limited version with resource limits for entity validation */
+static int switch_xml_ent_ok_with_depth(char *name, char *s, char **ent, int depth, unsigned long *check_count)
+{
+ int i;
+
+ /* Prevent excessive recursion during entity validation */
+ if (depth > SWITCH_XML_MAX_ENTITY_EXPANSION_DEPTH) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING,
+ "Entity validation depth limit exceeded (%d > %d) for entity: %s\n",
+ depth, SWITCH_XML_MAX_ENTITY_EXPANSION_DEPTH, name);
+
+ return 0; /* Treat as invalid - too deep */
+ }
+
+ for (;; s++) {
+ while (*s && *s != '&') {
+ s++; /* find next entity reference */
+ }
+
+ if (!*s)
+ return 1;
+
+ /* Increment check counter for each entity reference found */
+ if (++(*check_count) > SWITCH_XML_MAX_ENTITY_EXPANSION_COUNT) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING,
+ "Entity validation count limit exceeded (%lu > %d) for entity: %s\n",
+ *check_count, SWITCH_XML_MAX_ENTITY_EXPANSION_COUNT, name);
+
+ return 0; /* Treat as invalid - too many entity references */
+ }
+
+ if (!strncmp(s + 1, name, strlen(name)))
+ return 0; /* circular ref. */
+
+ for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2);
+
+ if (ent[i] && !switch_xml_ent_ok_with_depth(name, ent[i + 1], ent, depth + 1, check_count))
+ return 0;
+ }
+}
+
/* checks for circular entity references, returns non-zero if no circular
references are found, zero otherwise */
static int switch_xml_ent_ok(char *name, char *s, char **ent)
{
- int i;
+ unsigned long check_count = 0;
- for (;; s++) {
- while (*s && *s != '&')
- s++; /* find next entity reference */
- if (!*s)
- return 1;
- if (!strncmp(s + 1, name, strlen(name)))
- return 0; /* circular ref. */
- for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2);
- if (ent[i] && !switch_xml_ent_ok(name, ent[i + 1], ent))
- return 0;
- }
+ return switch_xml_ent_ok_with_depth(name, s, ent, 0, &check_count);
}
/* called when the parser finds a processing instruction */
diff --git a/tests/unit/switch_xml.c b/tests/unit/switch_xml.c
index a48aa4f032..0e5fc861d5 100644
--- a/tests/unit/switch_xml.c
+++ b/tests/unit/switch_xml.c
@@ -185,6 +185,71 @@ FST_MINCORE_BEGIN("./conf")
}
FST_TEST_END()
+ FST_TEST_BEGIN(test_exponential_entity_expansion)
+ {
+ /* Test handling of exponentially nested entity definitions
+ * Each entity references the previous one 10 times, creating
+ * 10^10 total references which would consume excessive memory
+ * if fully expanded. Parser should enforce expansion limits.
+ */
+ const char *nested_entities =
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "]>\n"
+ "&lol10;";
+
+ switch_xml_t xml = switch_xml_parse_str_dynamic((char *)nested_entities, SWITCH_TRUE);
+
+ if (xml) {
+ const char *error = switch_xml_error(xml);
+ if (error && *error) {
+ /* Parser enforced expansion limits */
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,
+ "Parser correctly enforced entity expansion limits: %s\n", error);
+ switch_xml_free(xml);
+ } else {
+ /* Parser did not enforce limits */
+ switch_xml_free(xml);
+ fst_fail("Parser did not enforce entity expansion limits");
+ }
+ } else {
+ /* Parser returned NULL */
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,
+ "Parser rejected excessive entity expansion\n");
+ }
+ }
+ FST_TEST_END()
+
+ FST_TEST_BEGIN(test_entity_expansion_limit)
+ {
+ /* Test that reasonable entity usage still works */
+ const char *safe_entities =
+ "\n"
+ "\n"
+ "\n"
+ "]>\n"
+ "&product;";
+
+ switch_xml_t xml = switch_xml_parse_str_dynamic((char *)safe_entities, SWITCH_TRUE);
+
+ fst_requires(xml);
+ fst_check_string_equals(xml->txt, "FreeSWITCH Media Server");
+ switch_xml_free(xml);
+ }
+ FST_TEST_END()
}
FST_SUITE_END()
}
diff --git a/tests/unit/test_switch_xml.2017.vcxproj b/tests/unit/test_switch_xml.2017.vcxproj
new file mode 100644
index 0000000000..60240f138b
--- /dev/null
+++ b/tests/unit/test_switch_xml.2017.vcxproj
@@ -0,0 +1,208 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ Win32
+
+
+ Release
+ x64
+
+
+
+ test_switch_xml
+ test_switch_xml
+ Win32Proj
+ 10.0
+ {FED39CFF-E6CD-1C09-844D-F1174B8A5C18}
+
+
+
+ Application
+ MultiByte
+ $(DefaultPlatformToolset)
+
+
+ Application
+ MultiByte
+ $(DefaultPlatformToolset)
+
+
+ Application
+ MultiByte
+ $(DefaultPlatformToolset)
+
+
+ Application
+ MultiByte
+ $(DefaultPlatformToolset)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ProjectFileVersion>10.0.30319.1
+ $(SolutionDir)$(PlatformName)\$(Configuration)\
+ $(PlatformName)\$(Configuration)\$(ProjectName)\
+ false
+ $(SolutionDir)$(PlatformName)\$(Configuration)\
+ $(Platform)\$(Configuration)\$(ProjectName)\
+ false
+ $(SolutionDir)$(PlatformName)\$(Configuration)\
+ $(PlatformName)\$(Configuration)\$(ProjectName)\
+ false
+ $(SolutionDir)$(Platform)\$(Configuration)\
+ $(Platform)\$(Configuration)\$(ProjectName)\
+ false
+
+
+
+ $(SolutionDir)src\include;%(AdditionalIncludeDirectories)
+ SWITCH_TEST_BASE_DIR_FOR_CONF="..\\..\\tests\\unit\\";%(PreprocessorDefinitions)
+
+
+ Bcrypt.lib;%(AdditionalDependencies)
+
+
+
+
+
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ EnableFastChecks
+ MultiThreadedDebugDLL
+
+
+ Level4
+ ProgramDatabase
+ true
+ 6031;6340;6246;6011;6387;%(DisableSpecificWarnings)
+
+
+ $(OutDir);%(AdditionalLibraryDirectories)
+ true
+ Console
+ true
+
+
+ MachineX86
+
+
+
+
+
+ X64
+
+
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ EnableFastChecks
+ MultiThreadedDebugDLL
+
+
+ Level4
+ ProgramDatabase
+ true
+ 6031;6340;6246;6011;6387;%(DisableSpecificWarnings)
+
+
+ $(OutDir);%(AdditionalLibraryDirectories)
+ true
+ Console
+ true
+
+
+ MachineX64
+
+
+
+
+
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+
+
+ Level4
+ ProgramDatabase
+ 6031;6340;6246;6011;6387;%(DisableSpecificWarnings)
+
+
+ $(OutDir);%(AdditionalLibraryDirectories)
+ false
+ Console
+ true
+ true
+ true
+
+
+ MachineX86
+
+
+
+
+
+ X64
+
+
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+
+
+ Level4
+ ProgramDatabase
+ 6031;6340;6246;6011;6387;%(DisableSpecificWarnings)
+
+
+ $(OutDir);%(AdditionalLibraryDirectories)
+ false
+ Console
+ true
+ true
+ true
+
+
+ MachineX64
+
+
+
+
+
+
+
+ {202d7a4e-760d-4d0e-afa1-d7459ced30ff}
+ false
+
+
+
+
+
+
\ No newline at end of file