@@ -11,6 +11,14 @@ var ArraySet = require('./array-set').ArraySet;
1111var base64VLQ = require ( './base64-vlq' ) ;
1212var quickSort = require ( './quick-sort' ) . quickSort ;
1313
14+ // Upper bound for a section's `offset.line` in an indexed source map. A flat
15+ // map spends one ';' per generated line, so a tiny indexed map with a huge
16+ // offset.line turns into a huge amount of work and memory once its mappings
17+ // are copied into a SourceMapGenerator (CVE-2026-93749). Ten million lines is
18+ // far beyond any real generated file. Columns are VLQ-encoded as deltas and
19+ // don't amplify, so they only need to be valid integers.
20+ var MAX_SECTION_OFFSET_LINE = 1e7 ;
21+
1422function SourceMapConsumer ( aSourceMap , aSourceMapURL ) {
1523 var sourceMap = aSourceMap ;
1624 if ( typeof aSourceMap === 'string' ) {
@@ -935,6 +943,7 @@ function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) {
935943 line : - 1 ,
936944 column : 0
937945 } ;
946+ var maxOffsetLine = 0 ;
938947 this . _sections = sections . map ( function ( s ) {
939948 if ( s . url ) {
940949 // The url field will require support for asynchronicity.
@@ -945,22 +954,57 @@ function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) {
945954 var offsetLine = util . getArg ( offset , 'line' ) ;
946955 var offsetColumn = util . getArg ( offset , 'column' ) ;
947956
957+ if ( ! isValidOffset ( offsetLine ) || ! isValidOffset ( offsetColumn ) ) {
958+ throw new Error ( 'Section offset line and column must be non-negative integers.' ) ;
959+ }
960+ if ( offsetLine > MAX_SECTION_OFFSET_LINE ) {
961+ throw new Error ( 'Section offset line must not exceed ' + MAX_SECTION_OFFSET_LINE + '.' ) ;
962+ }
963+
948964 if ( offsetLine < lastOffset . line ||
949965 ( offsetLine === lastOffset . line && offsetColumn < lastOffset . column ) ) {
950966 throw new Error ( 'Section offsets must be ordered and non-overlapping.' ) ;
951967 }
952968 lastOffset = offset ;
953969
970+ var consumer = new SourceMapConsumer ( util . getArg ( s , 'map' ) , aSourceMapURL ) ;
971+
972+ // Offsets of a nested indexed map add up with this one, so apply the
973+ // bound to the total.
974+ var totalOffsetLine = offsetLine + ( consumer . _maxOffsetLine || 0 ) ;
975+ if ( totalOffsetLine > MAX_SECTION_OFFSET_LINE ) {
976+ throw new Error ( 'Section offset line must not exceed ' + MAX_SECTION_OFFSET_LINE +
977+ ', including offsets of nested sections.' ) ;
978+ }
979+ if ( totalOffsetLine > maxOffsetLine ) {
980+ maxOffsetLine = totalOffsetLine ;
981+ }
982+
954983 return {
955984 generatedOffset : {
956985 // The offset fields are 0-based, but we use 1-based indices when
957986 // encoding/decoding from VLQ.
958987 generatedLine : offsetLine + 1 ,
959988 generatedColumn : offsetColumn + 1
960989 } ,
961- consumer : new SourceMapConsumer ( util . getArg ( s , 'map' ) , aSourceMapURL )
990+ consumer : consumer
962991 }
963992 } ) ;
993+
994+ // The largest offset.line of any section, including nested sections.
995+ this . _maxOffsetLine = maxOffsetLine ;
996+ }
997+
998+ /**
999+ * Section offsets come from untrusted input and are used as line/column
1000+ * numbers, so only accept non-negative safe integers (rejects NaN, Infinity,
1001+ * strings, fractions, etc).
1002+ */
1003+ function isValidOffset ( aValue ) {
1004+ return typeof aValue === 'number' &&
1005+ aValue >= 0 &&
1006+ aValue <= 9007199254740991 && // Number.MAX_SAFE_INTEGER
1007+ Math . floor ( aValue ) === aValue ;
9641008}
9651009
9661010IndexedSourceMapConsumer . prototype = Object . create ( SourceMapConsumer . prototype ) ;
@@ -978,8 +1022,12 @@ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
9781022 get : function ( ) {
9791023 var sources = [ ] ;
9801024 for ( var i = 0 ; i < this . _sections . length ; i ++ ) {
981- for ( var j = 0 ; j < this . _sections [ i ] . consumer . sources . length ; j ++ ) {
982- sources . push ( this . _sections [ i ] . consumer . sources [ j ] ) ;
1025+ // Read the getter once per section: for a nested indexed map it is
1026+ // itself this getter, and re-reading it per item is exponential in the
1027+ // nesting depth (CVE-2026-93749).
1028+ var sectionSources = this . _sections [ i ] . consumer . sources ;
1029+ for ( var j = 0 ; j < sectionSources . length ; j ++ ) {
1030+ sources . push ( sectionSources [ j ] ) ;
9831031 }
9841032 }
9851033 return sources ;
0 commit comments