[Bug]`_.truncate` hangs forever with a zero-length-capable regex `separator`
Description
_.truncate(string, { separator }) enters an infinite loop when separator is a regex that can match the empty string at a non-zero offset (e.g. /(?<=,)\s*/). The inner exec loop never advances separator.lastIndex, so a zero-length match makes exec return the same match forever. One CPU core pins at 100% and the call never returns.
This is not a security issue (already triaged as out of scope: the separator is application-chosen, and every documented separator is safe). It is a correctness bug in the isRegExp(separator) branch.
Steps to reproduce
const _ = require('lodash');
_.truncate('a,b' + 'X'.repeat(200), { length: 30, separator: /(?<=,)\s*/ });
// never returnsExpected
Returns a truncated string in bounded time, as it does for string and ordinary regex separators.
Actual
Infinite loop; the process hangs at 100% CPU.
Root cause
lodash.js, truncate, the isRegExp(separator) branch:
separator.lastIndex = 0;
while ((match = separator.exec(substring))) {
var newEnd = match.index; // lastIndex is never advanced
}For a zero-length match, match.index === separator.lastIndex and lastIndex stays fixed, so exec returns the identical match on every iteration.
Recommended fix
Advance lastIndex past zero-length matches inside the loop:
separator.lastIndex = 0;
while ((match = separator.exec(substring))) {
var newEnd = match.index;
if (match[0].length === 0) {
separator.lastIndex++;
}
}Environment
- lodash 4.18.1
- Node.js, any recent version
Source: lodash/lodash