CVE-2024-28243 (published 2024-03-25T20:15Z)

KaTeX is a JavaScript library for TeX math rendering on the web. KaTeX users who render untrusted mathematical expressions could encounter malicious input using `\\edef` that causes a near-infinite loop, despite setting `maxExpand` to avoid such loops. This can be used as an availability attack, where e.g. a client rendering another user's KaTeX input will be unable to use the site due to memory overflow, tying up the main thread, or stack overflow. Upgrade to KaTeX v0.16.10 to remove this vulnerability.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/KaTeX/KaTeX
commit_details: repo_url: https://github.com/KaTeX/KaTeX
author: Erik Demaine
author_date: 2024-03-24 17:57:37-04:00
msg: fix: \edef bypassing maxExpand via exponential blowup

Modify `\edef`, and `expandTokens` more generally, to count the number
of tokens in the entire expansion instead of just 1.  This prevents
exponential blowup in the expanded tokens via a linear number of `\edef`
and apparent expansions.
repository: repo_url: https://github.com/KaTeX/KaTeX
repo_name: KaTeX/KaTeX
description: Fast math typesetting for the web.
repo_language: JavaScript
owner: KaTeX
file_changes: filename: options.md
change_type: ModificationType.MODIFY
diff: @@ -26,7 +26,7 @@ You can provide an object of options as the last argument to [`katex.render` and
 - `minRuleThickness`: `number`. Specifies a minimum thickness, in ems, for fraction lines, `\sqrt` top lines, `{array}` vertical lines, `\hline`, `\hdashline`, `\underline`, `\overline`, and the borders of `\fbox`, `\boxed`, and `\fcolorbox`. The usual value for these items is `0.04`, so for `minRuleThickness` to be effective it should probably take a value slightly above `0.04`, say `0.05` or `0.06`. Negative values will be ignored.
 - `colorIsTextColor`: `boolean`. In early versions of both KaTeX (<0.8.0) and MathJax, the `\color` function expected the content to be a function argument, as in `\color{blue}{hello}`. In current KaTeX, `\color` is a switch, as in `\color{blue} hello`. This matches LaTeX behavior. If you want the old `\color` behavior, set option `colorIsTextColor` to true.
 - `maxSize`: `number`. All user-specified sizes, e.g. in `\rule{500em}{500em}`, will be capped to `maxSize` ems. If set to `Infinity` (the default), users can make elements and spaces arbitrarily large.
-- `maxExpand`: `number`. Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to `Infinity`, the macro expander will try to fully expand as in LaTeX. (default: 1000)
+- `maxExpand`: `number`. Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. `\edef` expansion counts all expanded tokens. If set to `Infinity`, the macro expander will try to fully expand as in LaTeX. (default: 1000)
 - `strict`: `boolean` or `string` or `function` (default: `"warn"`). If `false` or `"ignore`", allow features that make writing LaTeX convenient but are not actually supported by (Xe)LaTeX (similar to MathJax). If `true` or `"error"` (LaTeX faithfulness mode), throw an error for any such transgressions. If `"warn"` (the default), warn about such behavior via `console.warn`. Provide a custom function `handler(errorCode, errorMsg, token)` to customize behavior depending on the type of transgression (summarized by the string code `errorCode` and detailed in `errorMsg`); this function can also return `"ignore"`, `"error"`, or `"warn"` to use a built-in behavior.  A list of such features and their `errorCode`s:
 
   - `"unknownSymbol"`: Use of unknown Unicode symbol, which will likely also

programming_language: Markdown
filename: MacroExpander.js
change_type: ModificationType.MODIFY
diff: @@ -245,6 +245,18 @@ export default class MacroExpander implements MacroContextInterface {
         return args;
     }
 
+    /**
+     * Increment `expansionCount` by the specified amount.
+     * Throw an error if it exceeds `maxExpand`.
+     */
+    countExpansion(amount: number): void {
+        this.expansionCount += amount;
+        if (this.expansionCount > this.settings.maxExpand) {
+            throw new ParseError("Too many expansions: infinite loop or " +
+                "need to increase maxExpand setting");
+        }
+    }
+
     /**
      * Expand the next token only once if possible.
      *
@@ -276,11 +288,7 @@ export default class MacroExpander implements MacroContextInterface {
             this.pushToken(topToken);
             return false;
         }
-        this.expansionCount++;
-        if (this.expansionCount > this.settings.maxExpand) {
-            throw new ParseError("Too many expansions: infinite loop or " +
-                "need to increase maxExpand setting");
-        }
+        this.countExpansion(1);
         let tokens = expansion.tokens;
         const args = this.consumeArgs(expansion.numArgs, expansion.delimiters);
         if (expansion.numArgs) {
@@ -375,6 +383,9 @@ export default class MacroExpander implements MacroContextInterface {
                 output.push(token);
             }
         }
+        // Count all of these tokens as additional expansions, to prevent
+        // exponential blowup from linearly many \edef's.
+        this.countExpansion(output.length);
         return output;
     }
 

programming_language: TypeScript
filename: katex-spec.js
change_type: ModificationType.MODIFY
diff: @@ -3997,6 +3997,11 @@ describe("The maxExpand setting", () => {
         expect`\gdef\foo{\foo}\foo`.not.toParse(
             new Settings({maxExpand: 10}));
     });
+
+    it("should prevent exponential blowup via \\edef", () => {
+        expect`\edef0{x}\edef0{00}\edef0{00}\edef0{00}\edef0{00}`.not.toParse(
+            new Settings({maxExpand: 10}));
+    });
 });
 
 describe("The \\mathchoice function", function() {

programming_language: TypeScript