Skip to content

Fix quadratic backtracking in rule DS440011 (long cipher-name runs hang scans) - #789

Open
Syed Muhammad Nafay Hassan Rizvi (Darkslayer3324j) wants to merge 1 commit into
microsoft:mainfrom
Darkslayer3324j:fix/ds440011-quadratic-cipher-regex
Open

Syed Muhammad Nafay Hassan Rizvi (Darkslayer3324j) wants to merge 1 commit into
microsoft:mainfrom
Darkslayer3324j:fix/ds440011-quadratic-cipher-regex

Conversation

@Darkslayer3324j

@Darkslayer3324j Syed Muhammad Nafay Hassan Rizvi (Darkslayer3324j) commented Sep 20, 2026

Copy link
Copy Markdown

Problem

Rule DS440011 (OpenSSL cipher-suite names) takes quadratic time on a long run of cipher-name characters, so a scan can hang on a single long line. With the default rules on const char *s = "AES-AES-AES-...";:

line length before after
100 KB ~13 s ~1.1-1.4 s
200 KB 51 s ~1.1-1.4 s
400 KB > 200 s (killed) ~1.2 s (flat: engine baseline)

Only DS440011 is affected: running each rule alone with --rule-ids on the 100 KB line gave DS440011 13.5 s, and DS440010, DS440000 and an empty no-op rule about 1.5 s (that is DevSkim's own baseline for a line this size).

The pattern is (AES|DH|...|PSK)[A-Z0-9\-]+-?(SHA|MD|GOST)[A-Z0-9\-]*. In a run with many cipher-name prefixes but no SHA/MD/GOST, every prefix occurrence starts a match whose [A-Z0-9\-]+ runs to the end of the run and then backtracks all the way back. The rule runs its regex before its openssl condition, so ordinary minified or generated files, or a hostile file in a scanned repo, can stall a whole scan.

Fix

  • Bound the run to [A-Z0-9\-]{1,64}. The longest real cipher-suite names are well under 64 characters.
  • Drop the -?, which was redundant because - is already in the character class.

I compared the old and new pattern on all 158 cipher names from openssl ciphers -V 'ALL:COMPLEMENTOFALL' plus 10 other names (TLS 1.3, PSK, GOST, EXP, IDEA), each in 6 contexts (bare, quoted, in a SSL_CTX_set_cipher_list call, in a ciphers = ...:HIGH:!aNULL string, embedded, and in a command line): 1008 cases, identical match spans. devskim analyze output on a small file with a real cipher string is unchanged.

Tests

  • DS440011 had no self-tests. Added a must-match (SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES256-GCM-SHA384"); // openssl) and a must-not-match ("AES-128-CBC"); devskim verify and ValidateDefaultRules pass.
  • Added HardcodedOpenSslCipherRuleDoesNotBacktrackOnLongRuns: analyzes a 200 KB AES- line with the default rules and asserts under 15 s. It fails on main (50.99 s) and passes with the change. dotnet test -f net8.0: 301 passed (300 without the new test); the suite went from 73 s to 24 s.
  • Changelog.md updated; devskim --version on this branch reports 1.0.99, matching the heading.

I noticed while investigating that the same regex text does not exist in DS440010 (its cipher-constant pattern uses _ separators) and that DS440010 stays fast on the same input.

Found by timing every regex rule against adversarial input; my first pass used a different regex engine and over-reported, so I re-measured with the real .NET engine before filing. Drafted with AI assistance (Claude Code).

The middle [A-Z0-9\-]+-? group backtracked from every cipher-name prefix in a
long run of cipher-name characters, so a 100 KB line took ~13 s and a 400 KB
line minutes. Bound the run to 64 characters and drop the redundant -?. Add
must-match/must-not-match self-tests and a regression test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Darkslayer3324j

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

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.

1 participant