Skip to content

fix(parser): add destructor directives to prevent memory leaks#3500

Merged
airween merged 4 commits intoowasp-modsecurity:v3/masterfrom
fzipi:fix/parser-sanitizer
Feb 17, 2026
Merged

fix(parser): add destructor directives to prevent memory leaks#3500
airween merged 4 commits intoowasp-modsecurity:v3/masterfrom
fzipi:fix/parser-sanitizer

Conversation

@fzipi
Copy link
Collaborator

@fzipi fzipi commented Feb 16, 2026

what

The Bison parser was missing %destructor directives for all semantic types using unique_ptr. When parsing errors occurred (via YYERROR), the parser would discard semantic values from the stack without properly destroying them, causing memory leaks detected by ASAN.

why

Added %destructor directives for:

  • std::unique_ptractions::Action
  • std::unique_ptr
  • std::unique_ptr<std::vector<std::unique_ptractions::Action>>
  • std::unique_ptr
  • std::unique_ptr
  • std::unique_ptr<std::vector<std::unique_ptr>>

The empty braces {} in the destructor directives are intentional. With C++ variant semantic values (api.value.type variant), Bison automatically calls the destructors of objects in the variant when discarding semantic values during error recovery.

This fixes memory leaks that occurred in scenarios such as:

  1. When ACTION_INIT macro fails and calls YYERROR (lines 885, 892)
  2. When rule creation fails and calls YYERROR (line 1130)
  3. Any other parsing error that causes the parser to discard semantic values during error recovery

references

Fixes owasp-modsecurity#3448

The Bison parser was missing %destructor directives for all semantic
types using unique_ptr. When parsing errors occurred (via YYERROR),
the parser would discard semantic values from the stack without
properly destroying them, causing memory leaks detected by ASAN.

Added %destructor directives for:
- std::unique_ptr<actions::Action>
- std::unique_ptr<RunTimeString>
- std::unique_ptr<std::vector<std::unique_ptr<actions::Action>>>
- std::unique_ptr<Operator>
- std::unique_ptr<Variable>
- std::unique_ptr<std::vector<std::unique_ptr<Variable>>>

The empty braces {} in the destructor directives are intentional.
With C++ variant semantic values (api.value.type variant), Bison
automatically calls the destructors of objects in the variant when
discarding semantic values during error recovery.

This fixes memory leaks that occurred in scenarios such as:
1. When ACTION_INIT macro fails and calls YYERROR (lines 885, 892)
2. When rule creation fails and calls YYERROR (line 1130)
3. Any other parsing error that causes the parser to discard
   semantic values during error recovery
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Bison %destructor directives for C++ variant semantic values that hold std::unique_ptr, aiming to ensure semantic values are properly cleaned up when the parser discards symbols during error recovery (e.g., via YYERROR), addressing ASAN-reported leaks (Fixes #3448).

Changes:

  • Introduces %destructor directives for several std::unique_ptr semantic types in the Seclang Bison grammar.
  • Adds inline comments documenting the intent and rationale for empty destructor bodies under api.value.type variant.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

The previous commit added %destructor directives but used inconsistent
spacing in nested template types. The %type declarations used spaces
between closing angle brackets (> > > >) but %destructor used (>>>>),
causing Bison to not associate the destructors with any symbols.

Modern Bison 3.8.2 detected this with warnings:
  "type <...> is used, but is not associated to any symbol"

This meant vector type destructors were silently ignored, leaving those
memory leaks from issue owasp-modsecurity#3448 unfixed. Fixed by matching the spacing
exactly between %type and %destructor declarations.

Also enhanced comments to explain why destructor bodies are empty when
using api.value.type variant.
@fzipi fzipi force-pushed the fix/parser-sanitizer branch from 91bb152 to d405133 Compare February 16, 2026 16:05
@fzipi
Copy link
Collaborator Author

fzipi commented Feb 16, 2026

The #line directives referencing "seclang-parser.tab.cc" are cosmetic and fine. Here's why:

What #line directives do:

  • They tell the compiler what source file and line number to report in error messages
  • They're automatically generated by Bison during parser generation
  • .tab.cc is Bison's default intermediate naming convention

Why it says .tab.cc even though the file is .cc:

  • Bison internally uses .tab.cc as the basename during generation
  • Even when using -o seclang-parser.cc, the #line directives keep the .tab name
  • This is standard Bison behavior and affects many projects

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 16, 2026

Quality Gate Passed Quality Gate passed

Issues
0 New issues
20 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

src/parser/seclang-scanner.cc:8694

  • num_to_read was changed to yy_size_t, but it's computed from an int expression (yy_buf_size - number_to_move - 1). If that expression is negative, it will wrap to a huge unsigned value and the while (num_to_read <= 0) growth loop will never run, risking an out-of-bounds write in YY_INPUT. Keep this value in a signed type (e.g., int/ptrdiff_t) or restructure the calculation so the “no space left” condition is checked before converting to an unsigned type.
			yy_size_t num_to_read =
			YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;

		while ( num_to_read <= 0 )
			{ /* Not enough room in the buffer - grow it. */

src/parser/seclang-scanner.cc:8709

  • The overflow/resize guard here no longer works as intended after switching new_size to yy_size_t: if (new_size <= 0) is effectively dead code for a size type, so the fallback growth path is unreachable. Consider keeping new_size signed (or computing in yy_size_t and checking for overflow explicitly) so buffer growth remains correct for large yy_buf_size values.
				yy_size_t new_size = b->yy_buf_size * 2;

				if ( new_size <= 0 )
					b->yy_buf_size += b->yy_buf_size / 8;
				else
					b->yy_buf_size *= 2;

src/parser/seclang-scanner.cc:9384

  • This yyless redefinition was updated to use yy_size_t, but the earlier #define yyless(n) (near line ~215) still uses int yyless_macro_arg = (n);. With yyleng now being yy_size_t, calls like yyless(yyleng) can still truncate via the first macro definition. Update the first yyless macro to use yy_size_t as well to keep behavior consistent.
#undef yyless
#define yyless(n) \
	do \
		{ \
		/* Undo effects of setting up yytext. */ \
        yy_size_t yyless_macro_arg = (n); \
        YY_LESS_LINENO(yyless_macro_arg);\
		yytext[yyleng] = (yy_hold_char); \
		(yy_c_buf_p) = yytext + yyless_macro_arg; \
		(yy_hold_char) = *(yy_c_buf_p); \
		*(yy_c_buf_p) = '\0'; \
		yyleng = yyless_macro_arg; \

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@airween airween mentioned this pull request Feb 17, 2026
Copy link
Member

@airween airween left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@airween airween merged commit 5f4880b into owasp-modsecurity:v3/master Feb 17, 2026
56 checks passed
@airween
Copy link
Member

airween commented Feb 17, 2026

Thank you @fzipi!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sanitizer reports (ASAN, UBSAN)

2 participants

Comments