Issue Summary
MathJax v4 encounters an infinite recursion loop ("Maximum call stack size exceeded") that crashes the browser tab when parsing a forced relation containing a binary operator (\mathrel{-}) placed immediately adjacent to a native relational operator (=), provided it is not at the beginning of the math block.
This acts as a potential client-side DoS vulnerability, as a single malicious or malformed formula in a forum post will permanently crash the browser tab for anyone viewing the page.
Steps to Reproduce:
- Load MathJax v4 on a web page / Go to the official MathJax Live Demo page or load MathJax v4 on a web page.
- Attempt to render the following LaTeX string:
$$h[mask] \mathrel{-}=$$
- Observe that the browser tab instantly freezes.
- Check the developer console to see the Maximum call stack size exceeded error.
Important Note on Reproduction: The preceding characters (like h[mask]) are strictly required to trigger the bug. If you parse \mathrel{-}= completely on its own, it renders safely. It appears that if the block is at the beginning of the line, the minus sign auto-downgrades from a BIN to an ORD, sidestepping the bug. If preceding characters exist, the - maintains its BIN status, triggering the infinite loop when negotiating spacing with the native = REL operator.
Expected Behavior:
Even if the LaTeX is considered poor practice (e.g., forcing a relation on a binary operator right next to another relation), the parser should either render it as written or throw a graceful parsing error. It should never enter an infinite loop that crashes the host browser.
Technical details:
- MathJax Version: 4
- Client OS: Windows 11
- Browser: Firefox 151.0.4 or Chrome 149.0.7827.103
I am using the following MathJax configuration on my site (though again, this crashes the standard Live Demo as well):
window.MathJax = {
startup: {
pageReady: () => {
for (const script of document.querySelectorAll('script[type^="math/tex"]')) {
const math = document.createElement('span');
math.innerText = script.text;
script.parentNode.replaceChild(math, script);
}
return MathJax.startup.defaultPageReady().then(() => {
document.querySelectorAll('a mjx-container').forEach(container => {
container.addEventListener('mousedown', (event) => {
const link = container.closest('a');
if (link && event.button === 0 && !event.ctrlKey && !event.metaKey) {
window.location.href = link.href;
}
});
});
});
}
},
tex: {
inlineMath: [ ['$','$'] ],
displayMath: [ ['$$','$$'] ],
processEscapes: true,
processEnvironments: false
},
options: {
ignoreHtmlClass: 'text-block'
}
};
and loading MathJax via
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-svg.js?config=newcm"></script>
Supporting information:
Live Example: Can be reproduced directly on the MathJax Live Demo site by pasting $$h[mask] \mathrel{-}=$$.
Console Error: The console explicitly reports Uncaught RangeError: Maximum call stack size exceeded.
Suspected Mechanism: The crash appears to happen during the AST spacing traversal (likely inside setTeXclass or similar). The generated by \mathrel claims to be a REL class, but the inner node - is a BIN class. When the native = sign (a REL) asks its neighbor for its spacing class, it seems to trigger an infinite ping-pong evaluation between the parent wrapper's forced class and the child node's native class.
Issue Summary
MathJax v4 encounters an infinite recursion loop ("Maximum call stack size exceeded") that crashes the browser tab when parsing a forced relation containing a binary operator (\mathrel{-}) placed immediately adjacent to a native relational operator (=), provided it is not at the beginning of the math block.
This acts as a potential client-side DoS vulnerability, as a single malicious or malformed formula in a forum post will permanently crash the browser tab for anyone viewing the page.
Steps to Reproduce:
$$h[mask] \mathrel{-}=$$Important Note on Reproduction: The preceding characters (like h[mask]) are strictly required to trigger the bug. If you parse \mathrel{-}= completely on its own, it renders safely. It appears that if the block is at the beginning of the line, the minus sign auto-downgrades from a BIN to an ORD, sidestepping the bug. If preceding characters exist, the - maintains its BIN status, triggering the infinite loop when negotiating spacing with the native = REL operator.
Expected Behavior:
Even if the LaTeX is considered poor practice (e.g., forcing a relation on a binary operator right next to another relation), the parser should either render it as written or throw a graceful parsing error. It should never enter an infinite loop that crashes the host browser.
Technical details:
I am using the following MathJax configuration on my site (though again, this crashes the standard Live Demo as well):
and loading MathJax via
Supporting information:
Live Example: Can be reproduced directly on the MathJax Live Demo site by pasting
$$h[mask] \mathrel{-}=$$.Console Error: The console explicitly reports Uncaught RangeError: Maximum call stack size exceeded.
Suspected Mechanism: The crash appears to happen during the AST spacing traversal (likely inside setTeXclass or similar). The generated by \mathrel claims to be a REL class, but the inner node - is a BIN class. When the native = sign (a REL) asks its neighbor for its spacing class, it seems to trigger an infinite ping-pong evaluation between the parent wrapper's forced class and the child node's native class.