CVE-2022-25851 (published 2022-06-10T20:15Z)

The package jpeg-js before 0.4.4 are vulnerable to Denial of Service (DoS) where a particular piece of input will cause to enter an infinite loop and never return.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-835
cwe_name: Loop with Unreachable Exit Condition ('Infinite Loop')
description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
extended_description: If the loop can be influenced by an attacker, this weakness could allow attackers to consume excessive resources such as CPU or memory.
         

Fix information:
repo_url: https://github.com/jpeg-js/jpeg-js
commit_details: repo_url: https://github.com/jpeg-js/jpeg-js
author: Sohom Datta
author_date: 2022-06-03 20:10:52+05:30
msg: fix: validate sampling factors (#106)
repository: repo_url: https://github.com/jpeg-js/jpeg-js
repo_name: jpeg-js/jpeg-js
description: A pure javascript JPEG encoder and decoder for node.js
repo_language: JavaScript
owner: jpeg-js
file_changes: filename: decoder.js
change_type: ModificationType.MODIFY
diff: @@ -576,7 +576,9 @@ var JpegImage = (function jpegImage() {
         return array;
       }
       function prepareComponents(frame) {
-        var maxH = 0, maxV = 0;
+        // According to the JPEG standard, the sampling factor must be between 1 and 4
+        // See https://github.com/libjpeg-turbo/libjpeg-turbo/blob/9abeff46d87bd201a952e276f3e4339556a403a3/libjpeg.txt#L1138-L1146
+        var maxH = 1, maxV = 1;
         var component, componentId;
         for (componentId in frame.components) {
           if (frame.components.hasOwnProperty(componentId)) {
@@ -745,6 +747,11 @@ var JpegImage = (function jpegImage() {
               var h = data[offset + 1] >> 4;
               var v = data[offset + 1] & 15;
               var qId = data[offset + 2];
+
+              if ( h <= 0 || v <= 0 ) {
+                throw new Error('Invalid sampling factor, expected values above 0');
+              }
+
               frame.componentsOrder.push(componentId);
               frame.components[componentId] = {
                 h: h,

programming_language: JavaScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -7,10 +7,12 @@ function fixture(name) {
   return fs.readFileSync(path.join(__dirname, 'fixtures', name));
 }
 
-const SUPER_LARGE_JPEG_BASE64 =
-  '/9j/wJ39sP//DlKWvX+7xPlXkJa9f7v8DoDVAAD//zb6QAEAI2cBv3P/r4ADpX8Jf14AAAAAgCPE+VeQlr1/uwCAAAAVALNOjAGP2lIS';
+const SUPER_LARGE_JPEG_BASE64 = '/9j/wfFRBf//BdgC/9p/2P/E4d4=';
+
+const SUPER_LARGE_RESOLUTION_JPEG_BASE64 = '/9j/wfFR2PDh3g==';
 
 const SUPER_LARGE_JPEG_BUFFER = Buffer.from(SUPER_LARGE_JPEG_BASE64, 'base64');
+const SUPER_LARGE_RESOLUTION_JPEG_BUFFER = Buffer.from(SUPER_LARGE_RESOLUTION_JPEG_BASE64, 'base64');
 
 it('should be able read image with a bad e1 marker not preceeded by ff', function () {
     var jpegData = fixture('table-with-bad-e1.jpg');
@@ -274,8 +276,8 @@ it('should be able to decode large images within memory limits', () => {
 
 // See https://github.com/eugeneware/jpeg-js/issues/53
 it('should limit resolution exposure', function () {
-  expect(() => jpeg.decode(SUPER_LARGE_JPEG_BUFFER)).toThrow(
-    'maxResolutionInMP limit exceeded by 141MP',
+  expect(() => jpeg.decode(SUPER_LARGE_RESOLUTION_JPEG_BUFFER)).toThrow(
+    'maxResolutionInMP limit exceeded by 3405MP',
   );
 });
 
@@ -288,3 +290,9 @@ it('should limit memory exposure', function () {
   var jpegData = fixture('grumpycat.jpg');
   expect(() => jpeg.decode(jpegData)).not.toThrow();
 }, 30000);
+
+// See https://github.com/jpeg-js/jpeg-js/issues/105
+it('invalid sampling factor should error out', function () {
+  expect(() => jpeg.decode(Buffer.from('/9j/wfFR2AD/UdgA/9r/3g==', 'base64')).toThrow(
+    'Invalid sampling factor, expected values above 0'))
+});

programming_language: JavaScript