acc_tests.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /* Tests in the "accounting" test case for the Expat test suite
  2. __ __ _
  3. ___\ \/ /_ __ __ _| |_
  4. / _ \\ /| '_ \ / _` | __|
  5. | __// \| |_) | (_| | |_
  6. \___/_/\_\ .__/ \__,_|\__|
  7. |_| XML parser
  8. Copyright (c) 2001-2006 Fred L. Drake, Jr. <[email protected]>
  9. Copyright (c) 2003 Greg Stein <[email protected]>
  10. Copyright (c) 2005-2007 Steven Solie <[email protected]>
  11. Copyright (c) 2005-2012 Karl Waclawek <[email protected]>
  12. Copyright (c) 2016-2024 Sebastian Pipping <[email protected]>
  13. Copyright (c) 2017-2022 Rhodri James <[email protected]>
  14. Copyright (c) 2017 Joe Orton <[email protected]>
  15. Copyright (c) 2017 José Gutiérrez de la Concha <[email protected]>
  16. Copyright (c) 2018 Marco Maggi <[email protected]>
  17. Copyright (c) 2019 David Loffredo <[email protected]>
  18. Copyright (c) 2020 Tim Gates <[email protected]>
  19. Copyright (c) 2021 Donghee Na <[email protected]>
  20. Copyright (c) 2023 Sony Corporation / Snild Dolkow <[email protected]>
  21. Licensed under the MIT license:
  22. Permission is hereby granted, free of charge, to any person obtaining
  23. a copy of this software and associated documentation files (the
  24. "Software"), to deal in the Software without restriction, including
  25. without limitation the rights to use, copy, modify, merge, publish,
  26. distribute, sublicense, and/or sell copies of the Software, and to permit
  27. persons to whom the Software is furnished to do so, subject to the
  28. following conditions:
  29. The above copyright notice and this permission notice shall be included
  30. in all copies or substantial portions of the Software.
  31. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  32. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  33. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  34. NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  35. DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  36. OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  37. USE OR OTHER DEALINGS IN THE SOFTWARE.
  38. */
  39. #include <math.h> /* NAN, INFINITY */
  40. #include <stdio.h>
  41. #include <string.h>
  42. #include "expat_config.h"
  43. #include "expat.h"
  44. #include "internal.h"
  45. #include "common.h"
  46. #include "minicheck.h"
  47. #include "chardata.h"
  48. #include "handlers.h"
  49. #include "acc_tests.h"
  50. #if XML_GE == 1
  51. START_TEST(test_accounting_precision) {
  52. struct AccountingTestCase cases[] = {
  53. {"<e/>", NULL, NULL, 0},
  54. {"<e></e>", NULL, NULL, 0},
  55. /* Attributes */
  56. {"<e k1=\"v2\" k2=\"v2\"/>", NULL, NULL, 0},
  57. {"<e k1=\"v2\" k2=\"v2\"></e>", NULL, NULL, 0},
  58. {"<p:e xmlns:p=\"https://domain.invalid/\" />", NULL, NULL, 0},
  59. {"<e k=\"&amp;&apos;&gt;&lt;&quot;\" />", NULL, NULL,
  60. sizeof(XML_Char) * 5 /* number of predefined entities */},
  61. {"<e1 xmlns='https://example.org/'>\n"
  62. " <e2 xmlns=''/>\n"
  63. "</e1>",
  64. NULL, NULL, 0},
  65. /* Text */
  66. {"<e>text</e>", NULL, NULL, 0},
  67. {"<e1><e2>text1<e3/>text2</e2></e1>", NULL, NULL, 0},
  68. {"<e>&amp;&apos;&gt;&lt;&quot;</e>", NULL, NULL,
  69. sizeof(XML_Char) * 5 /* number of predefined entities */},
  70. {"<e>&#65;&#41;</e>", NULL, NULL, 0},
  71. /* Prolog */
  72. {"<?xml version=\"1.0\"?><root/>", NULL, NULL, 0},
  73. /* Whitespace */
  74. {" <e1> <e2> </e2> </e1> ", NULL, NULL, 0},
  75. {"<e1 ><e2 /></e1 >", NULL, NULL, 0},
  76. {"<e1><e2 k = \"v\"/><e3 k = 'v'/></e1>", NULL, NULL, 0},
  77. /* Comments */
  78. {"<!-- Comment --><e><!-- Comment --></e>", NULL, NULL, 0},
  79. /* Processing instructions */
  80. {"<?xml-stylesheet type=\"text/xsl\" href=\"https://domain.invalid/\" media=\"all\"?><e/>",
  81. NULL, NULL, 0},
  82. {"<?pi0?><?pi1 ?><?pi2 ?><r/><?pi4?>", NULL, NULL, 0},
  83. # ifdef XML_DTD
  84. {"<?pi0?><?pi1 ?><?pi2 ?><!DOCTYPE r SYSTEM 'first.ent'><r/>",
  85. "<?pi3?><!ENTITY % e1 SYSTEM 'second.ent'><?pi4?>%e1;<?pi5?>", "<?pi6?>",
  86. 0},
  87. # endif /* XML_DTD */
  88. /* CDATA */
  89. {"<e><![CDATA[one two three]]></e>", NULL, NULL, 0},
  90. /* The following is the essence of this OSS-Fuzz finding:
  91. https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=34302
  92. https://oss-fuzz.com/testcase-detail/4860575394955264
  93. */
  94. {"<!DOCTYPE r [\n"
  95. "<!ENTITY e \"111<![CDATA[2 <= 2]]>333\">\n"
  96. "]>\n"
  97. "<r>&e;</r>\n",
  98. NULL, NULL, sizeof(XML_Char) * strlen("111<![CDATA[2 <= 2]]>333")},
  99. # ifdef XML_DTD
  100. /* Conditional sections */
  101. {"<!DOCTYPE r [\n"
  102. "<!ENTITY % draft 'INCLUDE'>\n"
  103. "<!ENTITY % final 'IGNORE'>\n"
  104. "<!ENTITY % import SYSTEM \"first.ent\">\n"
  105. "%import;\n"
  106. "]>\n"
  107. "<r/>\n",
  108. "<![%draft;[<!--1-->]]>\n"
  109. "<![%final;[<!--22-->]]>",
  110. NULL, sizeof(XML_Char) * (strlen("INCLUDE") + strlen("IGNORE"))},
  111. # endif /* XML_DTD */
  112. /* General entities */
  113. {"<!DOCTYPE root [\n"
  114. "<!ENTITY nine \"123456789\">\n"
  115. "]>\n"
  116. "<root>&nine;</root>",
  117. NULL, NULL, sizeof(XML_Char) * strlen("123456789")},
  118. {"<!DOCTYPE root [\n"
  119. "<!ENTITY nine \"123456789\">\n"
  120. "]>\n"
  121. "<root k1=\"&nine;\"/>",
  122. NULL, NULL, sizeof(XML_Char) * strlen("123456789")},
  123. {"<!DOCTYPE root [\n"
  124. "<!ENTITY nine \"123456789\">\n"
  125. "<!ENTITY nine2 \"&nine;&nine;\">\n"
  126. "]>\n"
  127. "<root>&nine2;&nine2;&nine2;</root>",
  128. NULL, NULL,
  129. sizeof(XML_Char) * 3 /* calls to &nine2; */ * 2 /* calls to &nine; */
  130. * (strlen("&nine;") + strlen("123456789"))},
  131. {"<!DOCTYPE r [\n"
  132. " <!ENTITY five SYSTEM 'first.ent'>\n"
  133. "]>\n"
  134. "<r>&five;</r>",
  135. "12345", NULL, 0},
  136. {"<!DOCTYPE r [\n"
  137. " <!ENTITY five SYSTEM 'first.ent'>\n"
  138. "]>\n"
  139. "<r>&five;</r>",
  140. "\xEF\xBB\xBF" /* UTF-8 BOM */, NULL, 0},
  141. # ifdef XML_DTD
  142. /* Parameter entities */
  143. {"<!DOCTYPE r [\n"
  144. "<!ENTITY % comment \"<!---->\">\n"
  145. "%comment;\n"
  146. "]>\n"
  147. "<r/>",
  148. NULL, NULL, sizeof(XML_Char) * strlen("<!---->")},
  149. {"<!DOCTYPE r [\n"
  150. "<!ENTITY % ninedef \"&#60;!ENTITY nine &#34;123456789&#34;&#62;\">\n"
  151. "%ninedef;\n"
  152. "]>\n"
  153. "<r>&nine;</r>",
  154. NULL, NULL,
  155. sizeof(XML_Char)
  156. * (strlen("<!ENTITY nine \"123456789\">") + strlen("123456789"))},
  157. {"<!DOCTYPE r [\n"
  158. "<!ENTITY % comment \"<!--1-->\">\n"
  159. "<!ENTITY % comment2 \"&#37;comment;<!--22-->&#37;comment;\">\n"
  160. "%comment2;\n"
  161. "]>\n"
  162. "<r/>\n",
  163. NULL, NULL,
  164. sizeof(XML_Char)
  165. * (strlen("%comment;<!--22-->%comment;") + 2 * strlen("<!--1-->"))},
  166. {"<!DOCTYPE r [\n"
  167. " <!ENTITY % five \"12345\">\n"
  168. " <!ENTITY % five2def \"&#60;!ENTITY five2 &#34;[&#37;five;][&#37;five;]]]]&#34;&#62;\">\n"
  169. " %five2def;\n"
  170. "]>\n"
  171. "<r>&five2;</r>",
  172. NULL, NULL, /* from "%five2def;": */
  173. sizeof(XML_Char)
  174. * (strlen("<!ENTITY five2 \"[%five;][%five;]]]]\">")
  175. + 2 /* calls to "%five;" */ * strlen("12345")
  176. + /* from "&five2;": */ strlen("[12345][12345]]]]"))},
  177. {"<!DOCTYPE r SYSTEM \"first.ent\">\n"
  178. "<r/>",
  179. "<!ENTITY % comment '<!--1-->'>\n"
  180. "<!ENTITY % comment2 '<!--22-->%comment;<!--22-->%comment;<!--22-->'>\n"
  181. "%comment2;",
  182. NULL,
  183. sizeof(XML_Char)
  184. * (strlen("<!--22-->%comment;<!--22-->%comment;<!--22-->")
  185. + 2 /* calls to "%comment;" */ * strlen("<!---->"))},
  186. {"<!DOCTYPE r SYSTEM 'first.ent'>\n"
  187. "<r/>",
  188. "<!ENTITY % e1 PUBLIC 'foo' 'second.ent'>\n"
  189. "<!ENTITY % e2 '<!--22-->%e1;<!--22-->'>\n"
  190. "%e2;\n",
  191. "<!--1-->", sizeof(XML_Char) * strlen("<!--22--><!--1--><!--22-->")},
  192. {
  193. "<!DOCTYPE r SYSTEM 'first.ent'>\n"
  194. "<r/>",
  195. "<!ENTITY % e1 SYSTEM 'second.ent'>\n"
  196. "<!ENTITY % e2 '%e1;'>",
  197. "<?xml version='1.0' encoding='utf-8'?>\n"
  198. "hello\n"
  199. "xml" /* without trailing newline! */,
  200. 0,
  201. },
  202. {
  203. "<!DOCTYPE r SYSTEM 'first.ent'>\n"
  204. "<r/>",
  205. "<!ENTITY % e1 SYSTEM 'second.ent'>\n"
  206. "<!ENTITY % e2 '%e1;'>",
  207. "<?xml version='1.0' encoding='utf-8'?>\n"
  208. "hello\n"
  209. "xml\n" /* with trailing newline! */,
  210. 0,
  211. },
  212. {"<!DOCTYPE doc SYSTEM 'first.ent'>\n"
  213. "<doc></doc>\n",
  214. "<!ELEMENT doc EMPTY>\n"
  215. "<!ENTITY % e1 SYSTEM 'second.ent'>\n"
  216. "<!ENTITY % e2 '%e1;'>\n"
  217. "%e1;\n",
  218. "\xEF\xBB\xBF<!ATTLIST doc a1 CDATA 'value'>" /* UTF-8 BOM */,
  219. strlen("\xEF\xBB\xBF<!ATTLIST doc a1 CDATA 'value'>")},
  220. # endif /* XML_DTD */
  221. };
  222. const size_t countCases = sizeof(cases) / sizeof(cases[0]);
  223. size_t u = 0;
  224. for (; u < countCases; u++) {
  225. const unsigned long long expectedCountBytesDirect
  226. = strlen(cases[u].primaryText);
  227. const unsigned long long expectedCountBytesIndirect
  228. = (cases[u].firstExternalText ? strlen(cases[u].firstExternalText) : 0)
  229. + (cases[u].secondExternalText ? strlen(cases[u].secondExternalText)
  230. : 0)
  231. + cases[u].expectedCountBytesIndirectExtra;
  232. XML_Parser parser = XML_ParserCreate(NULL);
  233. XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
  234. if (cases[u].firstExternalText) {
  235. XML_SetExternalEntityRefHandler(parser,
  236. accounting_external_entity_ref_handler);
  237. XML_SetUserData(parser, (void *)&cases[u]);
  238. }
  239. enum XML_Status status
  240. = _XML_Parse_SINGLE_BYTES(parser, cases[u].primaryText,
  241. (int)strlen(cases[u].primaryText), XML_TRUE);
  242. if (status != XML_STATUS_OK) {
  243. _xml_failure(parser, __FILE__, __LINE__);
  244. }
  245. const unsigned long long actualCountBytesDirect
  246. = testingAccountingGetCountBytesDirect(parser);
  247. const unsigned long long actualCountBytesIndirect
  248. = testingAccountingGetCountBytesIndirect(parser);
  249. XML_ParserFree(parser);
  250. if (actualCountBytesDirect != expectedCountBytesDirect) {
  251. fprintf(
  252. stderr,
  253. "Document " EXPAT_FMT_SIZE_T("") " of " EXPAT_FMT_SIZE_T("") ": Expected " EXPAT_FMT_ULL(
  254. "") " count direct bytes, got " EXPAT_FMT_ULL("") " instead.\n",
  255. u + 1, countCases, expectedCountBytesDirect, actualCountBytesDirect);
  256. fail("Count of direct bytes is off");
  257. }
  258. if (actualCountBytesIndirect != expectedCountBytesIndirect) {
  259. fprintf(
  260. stderr,
  261. "Document " EXPAT_FMT_SIZE_T("") " of " EXPAT_FMT_SIZE_T("") ": Expected " EXPAT_FMT_ULL(
  262. "") " count indirect bytes, got " EXPAT_FMT_ULL("") " instead.\n",
  263. u + 1, countCases, expectedCountBytesIndirect,
  264. actualCountBytesIndirect);
  265. fail("Count of indirect bytes is off");
  266. }
  267. }
  268. }
  269. END_TEST
  270. START_TEST(test_billion_laughs_attack_protection_api) {
  271. XML_Parser parserWithoutParent = XML_ParserCreate(NULL);
  272. XML_Parser parserWithParent = XML_ExternalEntityParserCreate(
  273. parserWithoutParent, XCS("entity123"), NULL);
  274. if (parserWithoutParent == NULL)
  275. fail("parserWithoutParent is NULL");
  276. if (parserWithParent == NULL)
  277. fail("parserWithParent is NULL");
  278. // XML_SetBillionLaughsAttackProtectionMaximumAmplification, error cases
  279. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(NULL, 123.0f)
  280. == XML_TRUE)
  281. fail("Call with NULL parser is NOT supposed to succeed");
  282. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(parserWithParent,
  283. 123.0f)
  284. == XML_TRUE)
  285. fail("Call with non-root parser is NOT supposed to succeed");
  286. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(
  287. parserWithoutParent, NAN)
  288. == XML_TRUE)
  289. fail("Call with NaN limit is NOT supposed to succeed");
  290. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(
  291. parserWithoutParent, -1.0f)
  292. == XML_TRUE)
  293. fail("Call with negative limit is NOT supposed to succeed");
  294. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(
  295. parserWithoutParent, 0.9f)
  296. == XML_TRUE)
  297. fail("Call with positive limit <1.0 is NOT supposed to succeed");
  298. // XML_SetBillionLaughsAttackProtectionMaximumAmplification, success cases
  299. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(
  300. parserWithoutParent, 1.0f)
  301. == XML_FALSE)
  302. fail("Call with positive limit >=1.0 is supposed to succeed");
  303. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(
  304. parserWithoutParent, 123456.789f)
  305. == XML_FALSE)
  306. fail("Call with positive limit >=1.0 is supposed to succeed");
  307. if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(
  308. parserWithoutParent, INFINITY)
  309. == XML_FALSE)
  310. fail("Call with positive limit >=1.0 is supposed to succeed");
  311. // XML_SetBillionLaughsAttackProtectionActivationThreshold, error cases
  312. if (XML_SetBillionLaughsAttackProtectionActivationThreshold(NULL, 123)
  313. == XML_TRUE)
  314. fail("Call with NULL parser is NOT supposed to succeed");
  315. if (XML_SetBillionLaughsAttackProtectionActivationThreshold(parserWithParent,
  316. 123)
  317. == XML_TRUE)
  318. fail("Call with non-root parser is NOT supposed to succeed");
  319. // XML_SetBillionLaughsAttackProtectionActivationThreshold, success cases
  320. if (XML_SetBillionLaughsAttackProtectionActivationThreshold(
  321. parserWithoutParent, 123)
  322. == XML_FALSE)
  323. fail("Call with non-NULL parentless parser is supposed to succeed");
  324. XML_ParserFree(parserWithParent);
  325. XML_ParserFree(parserWithoutParent);
  326. }
  327. END_TEST
  328. START_TEST(test_helper_unsigned_char_to_printable) {
  329. // Smoke test
  330. unsigned char uc = 0;
  331. for (;; uc++) {
  332. set_subtest("char %u", (unsigned)uc);
  333. const char *const printable = unsignedCharToPrintable(uc);
  334. if (printable == NULL)
  335. fail("unsignedCharToPrintable returned NULL");
  336. else if (strlen(printable) < (size_t)1)
  337. fail("unsignedCharToPrintable returned empty string");
  338. if (uc == (unsigned char)-1) {
  339. break;
  340. }
  341. }
  342. // Two concrete samples
  343. set_subtest("char 'A'");
  344. if (strcmp(unsignedCharToPrintable('A'), "A") != 0)
  345. fail("unsignedCharToPrintable result mistaken");
  346. set_subtest("char '\\'");
  347. if (strcmp(unsignedCharToPrintable('\\'), "\\\\") != 0)
  348. fail("unsignedCharToPrintable result mistaken");
  349. }
  350. END_TEST
  351. START_TEST(test_amplification_isolated_external_parser) {
  352. // NOTE: Length 44 is precisely twice the length of "<!ENTITY a SYSTEM 'b'>"
  353. // (22) that is used in function accountingGetCurrentAmplification in
  354. // xmlparse.c.
  355. // 1.........1.........1.........1.........1..4 => 44
  356. const char doc[] = "<!ENTITY % p1 '123456789_123456789_1234567'>";
  357. const int docLen = (int)sizeof(doc) - 1;
  358. const float maximumToleratedAmplification = 2.0f;
  359. struct TestCase {
  360. int offsetOfThreshold;
  361. enum XML_Status expectedStatus;
  362. };
  363. struct TestCase cases[] = {
  364. {-2, XML_STATUS_ERROR}, {-1, XML_STATUS_ERROR}, {0, XML_STATUS_ERROR},
  365. {+1, XML_STATUS_OK}, {+2, XML_STATUS_OK},
  366. };
  367. for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
  368. const int offsetOfThreshold = cases[i].offsetOfThreshold;
  369. const enum XML_Status expectedStatus = cases[i].expectedStatus;
  370. const unsigned long long activationThresholdBytes
  371. = docLen + offsetOfThreshold;
  372. set_subtest("offsetOfThreshold=%d, expectedStatus=%d", offsetOfThreshold,
  373. expectedStatus);
  374. XML_Parser parser = XML_ParserCreate(NULL);
  375. assert_true(parser != NULL);
  376. assert_true(XML_SetBillionLaughsAttackProtectionMaximumAmplification(
  377. parser, maximumToleratedAmplification)
  378. == XML_TRUE);
  379. assert_true(XML_SetBillionLaughsAttackProtectionActivationThreshold(
  380. parser, activationThresholdBytes)
  381. == XML_TRUE);
  382. XML_Parser ext_parser = XML_ExternalEntityParserCreate(parser, NULL, NULL);
  383. assert_true(ext_parser != NULL);
  384. const enum XML_Status actualStatus
  385. = _XML_Parse_SINGLE_BYTES(ext_parser, doc, docLen, XML_TRUE);
  386. assert_true(actualStatus == expectedStatus);
  387. if (actualStatus != XML_STATUS_OK) {
  388. assert_true(XML_GetErrorCode(ext_parser)
  389. == XML_ERROR_AMPLIFICATION_LIMIT_BREACH);
  390. }
  391. XML_ParserFree(ext_parser);
  392. XML_ParserFree(parser);
  393. }
  394. }
  395. END_TEST
  396. #endif // XML_GE == 1
  397. void
  398. make_accounting_test_case(Suite *s) {
  399. #if XML_GE == 1
  400. TCase *tc_accounting = tcase_create("accounting tests");
  401. suite_add_tcase(s, tc_accounting);
  402. tcase_add_test(tc_accounting, test_accounting_precision);
  403. tcase_add_test(tc_accounting, test_billion_laughs_attack_protection_api);
  404. tcase_add_test(tc_accounting, test_helper_unsigned_char_to_printable);
  405. tcase_add_test__ifdef_xml_dtd(tc_accounting,
  406. test_amplification_isolated_external_parser);
  407. #else
  408. UNUSED_P(s);
  409. #endif /* XML_GE == 1 */
  410. }