Vulnerability: Denial of service in three This affects the package three before 0.125.0. This can happen when handling rgb or hsl colors. PoC: var three = require('three') function build_blank (n) { var ret = "rgb(" for (var i = 0; i < n; i++) { ret += " " } return ret + ""; } var Color = three.Color var time = Date.now(); new Color(build_blank(50000)) var time_cost = Date.now() - time; console.log(time_cost+" ms")
Vulnerability ID: GHSA-fq6p-x6j3-cmmq
CWE: CWE-400
Severity score: 0
Project: https://github.com/mrdoob/three.js
Published: 2021-03-01
File: src/math/Color.js
Commit message: Fix ReDoS
Commit author: Yeting Li

Vulnerable version (before fix):
 class Color {  			let color; 			const name = m[ 1 ]; 			const components = m[ 2 ].replace(/^\s*/, '');  			switch ( name ) {  				case 'rgb': 				case 'rgba':  					if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {  						// rgb(255,0,0) rgba(255,0,0,0.5) 						this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255; class Color {  					}  					if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {  						// rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) 						this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100; class Color { 				case 'hsl': 				case 'hsla':  					if ( color = /^(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {  						// hsl(120,50%,50%) hsla(120,50%,50%,0.5) 						const h = parseFloat( color[ 1 ] ) / 360;

Fixed version (after fix):
 class Color {  			let color; 			const name = m[ 1 ]; 			const components = m[ 2 ];  			switch ( name ) {  				case 'rgb': 				case 'rgba':  					if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {  						// rgb(255,0,0) rgba(255,0,0,0.5) 						this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255; class Color {  					}  					if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {  						// rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) 						this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100; class Color { 				case 'hsl': 				case 'hsla':  					if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {  						// hsl(120,50%,50%) hsla(120,50%,50%,0.5) 						const h = parseFloat( color[ 1 ] ) / 360;

Patch:
patch: @@ -169,14 +169,14 @@ class Color {
 
 			let color;
 			const name = m[ 1 ];
-			const components = m[ 2 ].replace(/^\s*/, '');
+			const components = m[ 2 ];
 
 			switch ( name ) {
 
 				case 'rgb':
 				case 'rgba':
 
-					if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+					if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
 
 						// rgb(255,0,0) rgba(255,0,0,0.5)
 						this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
@@ -189,7 +189,7 @@ class Color {
 
 					}
 
-					if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+					if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
 
 						// rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)
 						this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
@@ -207,7 +207,7 @@ class Color {
 				case 'hsl':
 				case 'hsla':
 
-					if ( color = /^(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+					if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
 
 						// hsl(120,50%,50%) hsla(120,50%,50%,0.5)
 						const h = parseFloat( color[ 1 ] ) / 360;
path: src/math/Color.js