CVE-2021-23371 (TypeScript, published 2021-04-12T13:15Z)
Description: value: This affects the package chrono-node before 2.2.4. It hangs on a date-like string with lots of embedded spaces.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/wanasit/chrono
Files changed:
src/locales/de/constants.ts
src/locales/en/constants.ts
src/locales/fr/constants.ts
src/locales/nl/constants.ts
src/utils/pattern.ts
test/en/en_performance.test.ts
test/test_util.ts
Commit message: Fix: unblounded regex backtracking in timeunit parsing


Vulnerable code:
import { matchAnyPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s*`;
const SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE = SINGLE_TIME_UNIT_PATTERN.replace(/\((?!\?)/g, "(?:");

export const TIME_UNITS_PATTERN =
    `(?:(?:about|around)\\s*)?` +
    `${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE}\\s*(?:,?\\s*${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE})*`;
import { matchAnyPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s*`;
const SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE = SINGLE_TIME_UNIT_PATTERN.replace(/\((?!\?)/g, "(?:");

export const TIME_UNITS_PATTERN =
    `(?:(?:about|around)\\s*)?` +
    `${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE}\\s*(?:,?\\s*${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE})*`;
import { matchAnyPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s*`;
const SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE = SINGLE_TIME_UNIT_PATTERN.replace(/\((?!\?)/g, "(?:");

export const TIME_UNITS_PATTERN = `(?:${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE})+`;
import { matchAnyPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s*`;
const SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE = SINGLE_TIME_UNIT_PATTERN.replace(/\((?!\?)/g, "(?:");

export const TIME_UNITS_PATTERN =
    `(?:(?:binnen|in)\\s*)?` +
    `${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE}\\s*(?:,?\\s*${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE})*`;
export function patternWithWordBreak(regExp: RegExp): RegExp {
    return RegExp("" + regExp.source);
import { ParsedResult, ParsingOption } from "../src";

Fixed code:
import { matchAnyPattern, repeatedTimeunitPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s{0,5}(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s{0,5}`;
export const TIME_UNITS_PATTERN = repeatedTimeunitPattern("", SINGLE_TIME_UNIT_PATTERN);
import { matchAnyPattern, repeatedTimeunitPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s{0,5}(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s{0,5}`;
export const TIME_UNITS_PATTERN = repeatedTimeunitPattern(`(?:(?:about|around)\\s*)?`, SINGLE_TIME_UNIT_PATTERN);
import { matchAnyPattern, repeatedTimeunitPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s{0,5}(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s{0,5}`;
export const TIME_UNITS_PATTERN = repeatedTimeunitPattern("", SINGLE_TIME_UNIT_PATTERN);
import { matchAnyPattern, repeatedTimeunitPattern } from "../../utils/pattern";
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s{0,5}(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s{0,5}`;
export const TIME_UNITS_PATTERN = repeatedTimeunitPattern(`(?:(?:binnen|in)\\s*)?`, SINGLE_TIME_UNIT_PATTERN);
export function repeatedTimeunitPattern(prefix: string, singleTimeunitPattern: string): string {
    const singleTimeunitPatternNoCapture = singleTimeunitPattern.replace(/\((?!\?)/g, "(?:");
    return `${prefix}${singleTimeunitPatternNoCapture}\\s*(?:,?\\s{0,5}${singleTimeunitPatternNoCapture}){0,10}`;
import * as chrono from "../../src";
import { measureMilliSec } from "../test_util";

test("Test - Benchmarking against whitespace backtracking", () => {
    const time = measureMilliSec(() => {
        const str =
            "BGR3                                                                                         " +
            "                                                                                        186          " +
            "                                      days                                                           " +
            "                                                                                                     " +
            "                                                                                                     " +
            "           18                                                hours                                   " +
            "                                                                                                     " +
            "                                                                                                     " +
            "                                   37                                                minutes         " +
            "                                                                                                     " +
            "                                                                                                     " +
            "                                                             01                                      " +
            "          seconds";

        const results = chrono.parse(str);
        expect(results.length).toBe(0);
    });

    console.log(time);
    expect(time).toBeLessThan(1000);
});
import { en, ParsedResult, ParsingOption } from "../src";
export function measureMilliSec(block: () => void): number {
    const startTime = new Date().getMilliseconds();
    block();
    const endTime = new Date().getMilliseconds();
    return endTime - startTime;
}