Commit 6a9e444adb for strongswan.org

commit 6a9e444adb129de61b94f8f9fa57d2856d99fe29
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Wed Jul 1 18:16:10 2026 +0200

    asn1-parser: Prevent infinite loops when parsing SEQUENCE|SET OF structures

    As fixed by the previous commit, some rules can lead to infinite loops
    when parsing crafted data.  This prevents such kind of problems by
    keeping track of the progress made during a loop iteration, in terms of
    moving the blob pointer by parsing data, and aborting if not.

diff --git a/src/libstrongswan/asn1/asn1_parser.c b/src/libstrongswan/asn1/asn1_parser.c
index e48ac3c11d..01e63d2617 100644
--- a/src/libstrongswan/asn1/asn1_parser.c
+++ b/src/libstrongswan/asn1/asn1_parser.c
@@ -72,6 +72,11 @@ struct private_asn1_parser_t {
 	 */
 	int loopAddr[ASN1_MAX_LEVEL + 1];

+	/**
+	 * Blob pointer at start of loop iteration, to detect stalled loops
+	 */
+	u_char *loopPtr[ASN1_MAX_LEVEL + 1];
+
 	/**
 	 * Current parsing pointer for each level
 	 */
@@ -107,7 +112,20 @@ METHOD(asn1_parser_t, iterate, bool,
 	{
 		if (this->loopAddr[obj.level] && this->blobs[obj.level+1].len > 0)
 		{
-			this->line = this->loopAddr[obj.level]; /* another iteration */
+			/* prevent infinite loops by ensuring that there was progress during
+			 * the last iteration.  if the parsing rules are incorrect (e.g.
+			 * using only OPT instead of CHOICE), this might not be the case */
+			if (this->loopPtr[obj.level] == this->blobs[obj.level+1].ptr)
+			{
+				DBG1(DBG_ASN, "L%d - %s:  loop made no progress, aborting",
+					this->level0 + obj.level,
+					this->objects[this->loopAddr[obj.level]-1].name);
+				this->success = FALSE;
+				goto end;
+			}
+			/* another iteration */
+			this->loopPtr[obj.level] = this->blobs[obj.level+1].ptr;
+			this->line = this->loopAddr[obj.level];
 			obj = this->objects[this->line];
 		}
 		else
@@ -236,6 +254,7 @@ METHOD(asn1_parser_t, iterate, bool,
 		{
 			/* at least one item, start the loop */
 			this->loopAddr[obj.level] = this->line + 1;
+			this->loopPtr[obj.level] = blob1->ptr;
 		}
 		else
 		{
diff --git a/src/libstrongswan/tests/suites/test_asn1_parser.c b/src/libstrongswan/tests/suites/test_asn1_parser.c
index afca545713..8bdb6af778 100644
--- a/src/libstrongswan/tests/suites/test_asn1_parser.c
+++ b/src/libstrongswan/tests/suites/test_asn1_parser.c
@@ -112,6 +112,42 @@ START_TEST(test_asn1_parser_loop)
 }
 END_TEST

+/*******************************************************************************
+ * stalled loop
+ */
+
+/* IMPORTANT: these rules are faulty on purpose. if no CHOICE matches, this
+ * caused an infinite loop (now prevented). use proper ASN1_CHOICE|CH flags! */
+static const asn1Object_t stalledLoopObjects[] = {
+	{ 0, "stalledLoop",		ASN1_SEQUENCE,		ASN1_LOOP          }, /* 0 */
+	{ 1,   "choice1",		ASN1_OCTET_STRING,	ASN1_OPT|ASN1_BODY }, /* 1 */
+	{ 1,   "end opt",		ASN1_EOC,			ASN1_END           }, /* 2 */
+	{ 1,   "choice2",		ASN1_INTEGER,		ASN1_OPT|ASN1_BODY }, /* 3 */
+	{ 1,   "end opt",		ASN1_EOC,			ASN1_END           }, /* 4 */
+	{ 1,   "choice3",		ASN1_UTF8STRING,	ASN1_OPT|ASN1_BODY }, /* 5 */
+	{ 1,   "end opt",		ASN1_EOC,			ASN1_END           }, /* 6 */
+	{ 0, "end loop",		ASN1_EOC,			ASN1_END           }, /* 7 */
+	{ 0, "exit",			ASN1_EOC,			ASN1_EXIT          }
+};
+
+asn1_test_t stalled_loop_tests[] = {
+	/* empty SEQUENCE: loop skipped entirely */
+	{ TRUE,  0, chunk_from_chars(0x30, 0x00) },
+	/* single valid OCTET STRING: handled normally */
+	{ TRUE,  1, chunk_from_chars(0x30, 0x03, 0x04, 0x01, 0xaa) },
+	/* first valid OCTET STRING, then BIT STRING: stall on second iteration */
+	{ FALSE, 1, chunk_from_chars(0x30, 0x07, 0x04, 0x01, 0xaa,
+											 0x03, 0x02, 0x00, 0x01) },
+	/* single BIT STRING: no choice matches, stall on first iteration */
+	{ FALSE, 0, chunk_from_chars(0x30, 0x04, 0x03, 0x02, 0x00, 0x01) },
+};
+
+START_TEST(test_asn1_parser_stalled_loop)
+{
+	run_parser_test(stalledLoopObjects, 1, &stalled_loop_tests[_i]);
+}
+END_TEST
+
 /*******************************************************************************
  * default
  */
@@ -378,6 +414,7 @@ Suite *asn1_parser_suite_create()

 	tc = tcase_create("loop");
 	tcase_add_loop_test(tc, test_asn1_parser_loop, 0, countof(loop_tests));
+	tcase_add_loop_test(tc, test_asn1_parser_stalled_loop, 0, countof(stalled_loop_tests));
 	suite_add_tcase(s, tc);

 	tc = tcase_create("default");