Project: little-cms (mm2/little-cms), language: c++
Vulnerability instance: lcms.cve-2018-16435
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/mm2/Little-CMS/issues/171
## Description:
Issue: mm2/Little-CMS#171
Title: Multiply integer overflow in AllocateDataSet
State: closed
Created by: quangnh89
Created at: 2018-08-14 01:36:36+00:00

Issue Body:
**VULNERABILITY DETAILS**

I have audited source code of lcms library and I have founded a vulnerability in `AllocateDataSet` function (cmscgats.c). The attached it8 could crash lcms when ASAN was enabled on Linux. This vulnerability is assigned [CVE-2018-16435](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16435).
 
```
void AllocateDataSet(cmsIT8* it8)
{
    TABLE* t = GetTable(it8);

    if (t -> Data) return;    // Already allocated

    t-> nSamples   = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
    t-> nPatches   = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));

    t-> Data = (char**)AllocChunk (it8, ((cmsUInt32Number) t->nSamples + 1) * ((cmsUInt32Number) t->nPatches + 1) *sizeof (char*)); // <<------ Vuln here 
    if (t->Data == NULL) {

        SynError(it8, "AllocateDataSet: Unable to allocate data array");
    }
}
```

if `nSamples` is 2 and `nPatches` is 0x55555555, `((cmsUInt32Number) t->nSamples + 1) * ((cmsUInt32Number) t->nPatches + 1) *sizeof (char*)` is larger than the maximum representable value (0xffffffff). The result of an overflow is that the least significant representable bits of the result are stored. `Data` will point to a small memory region and can not use to store large data.

**REPRODUCTION CASE**

Following code will trigger crash
```
#include <stdio.h>
#include <lcms2.h>
#include "lcms2_internal.h"

int main(int argc, char* argv[]){
    cmsIT8LoadFromFile(NULL, "AllocateDataSet.crash.IT8");
    return 0;
}
```

ASAN Log:
```
=================================================================
==12907==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x62a000005200 at pc 0x000000592bc5 bp 0x7ffd5f5c46c0 sp 0x7ffd5f5c46b8
WRITE of size 8 at 0x62a000005200 thread T0
    #0 0x592bc4 in SetData /root/lcms2/lcms2-asan/src/cmscgats.c:1549:45
    #1 0x5986e3 in DataSection /root/lcms2/lcms2-asan/src/cmscgats.c:1894:18
    #2 0x590259 in ParseIT8 /root/lcms2/lcms2-asan/src/cmscgats.c:2070:26
    #3 0x5915e6 in cmsIT8LoadFromFile /root/lcms2/lcms2-asan/src/cmscgats.c:2365:10
    #4 0x4eb86b in main /root/lcms2/test.c:7:5
    #5 0x7f9a0b6b682f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #6 0x41abb8 in _start (/root/lcms2/test+0x41ab68)

0x62a000005200 is located 0 bytes to the right of 20480-byte region [0x62a000000200,0x62a000005200)
allocated by thread T0 here:
    #0 0x4bb2e3 in __interceptor_malloc /tmp/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:88:3
    #1 0x4ebca8 in _cmsMallocZero /root/lcms2/lcms2-asan/src/cmserr.c:88:15
    #2 0x59474c in AllocBigBlock /root/lcms2/lcms2-asan/src/cmscgats.c:1053:17
    #3 0x58b62c in AllocChunk /root/lcms2/lcms2-asan/src/cmscgats.c:1095:52
    #4 0x58af66 in cmsIT8Alloc /root/lcms2/lcms2-asan/src/cmscgats.c:1310:35
    #5 0x5913e2 in cmsIT8LoadFromFile /root/lcms2/lcms2-asan/src/cmscgats.c:2349:13
    #6 0x4eb86b in main /root/lcms2/test.c:7:5
    #7 0x7f9a0b6b682f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /root/lcms2/lcms2-asan/src/cmscgats.c:1549:45 in SetData
Shadow bytes around the buggy address:
  0x0c547fff89f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c547fff8a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c547fff8a10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c547fff8a20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c547fff8a30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c547fff8a40:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c547fff8a50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c547fff8a60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c547fff8a70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c547fff8a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c547fff8a90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==12907==ABORTING
```

**PATCH**
```
--- a/src/cmscgats.c
+++ b/src/cmscgats.c
@@ -1504,6 +1504,16 @@
     t-> nSamples   = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
     t-> nPatches   = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
 
+#if (UINT_MAX == 4294967295U)
+    const cmsUInt32Number UInt32Max = UINT_MAX;
+#elif (ULONG_MAX == 4294967295U)
+    const cmsUInt32Number UInt32Max = ULONG_MAX;
+#endif
+    if (((cmsUInt32Number) t->nSamples + 1) >  (UInt32Max / sizeof (char*)) / ((cmsUInt32Number) t->nPatches + 1)) {
+        SynError(it8, "AllocateDataSet: Unable to allocate data array");
+        return;
+    }
+
     t-> Data = (char**)AllocChunk (it8, ((cmsUInt32Number) t->nSamples + 1) * ((cmsUInt32Number) t->nPatches + 1) *sizeof (char*));
     if (t->Data == NULL) {
```

I have checked the lastest version of LCMS. The vulnerability still exists. 

https://github.com/mm2/Little-CMS/blob/master/src/cmscgats.c#L1509

Testcase:
[AllocateDataSet.crash.IT8.txt](https://github.com/mm2/Little-CMS/files/2285206/AllocateDataSet.crash.IT8.txt)


Comments:
Comment by quangnh89 on 2018-08-16 09:22:55+00:00:
hi @mm2 . I have reviewed this commit (https://github.com/mm2/Little-CMS/commit/768f70ca405cd3159d990e962d54456773bb8cf8) and I understand that this issue is resolved. May you confirm it?

---

Comment by mm2 on 2018-08-16 13:19:50+00:00:
Hi, thanks for your detailed report and for the patch. Having examined the source, it seems to me it is safer and makes more sense to limit the amount of data that a CGATS file can hold. I have modified the code to deal with such situation.
Thanks again!

---

Comment by quangnh89 on 2018-09-04 03:15:33+00:00:
This vulnerability is assigned [CVE-2018-16435](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16435). 

Attachments:
https://github.com/mm2/Little-CMS/files/2285206/AllocateDataSet.crash.IT8.txt

Commit References:
768f70ca405cd3159d990e962d54456773bb8cf8

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
./configure
make -j$(nproc) all

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool
RUN git clone https://github.com/mm2/little-cms little-cms
RUN git -C little-cms checkout c2a1cd9c929952c5aeca8fee91649a83e8fc8021
WORKDIR $SRC/little-cms
COPY build.sh $SRC/

Additional files:
filename: cmsIT8_load_fuzzer.c
content: // Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#include <stdint.h>

#include "lcms2.h"

// The main sink
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  if (size == 0)
    return 0;

  cmsHANDLE handle = cmsIT8LoadFromMem(0, (void *)data, size);
  if (handle)
    cmsIT8Free(handle);

  return 0;
}

filename: cmsIT8_load_fuzzer.options
content: [libfuzzer]
dict = icc.dict

filename: cms_transform_fuzzer.c
content: // Copyright 2016 The PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>

#include "lcms2.h"

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  cmsHPROFILE srcProfile = cmsOpenProfileFromMem(data, size);
  if (!srcProfile) return 0;

  cmsHPROFILE dstProfile = cmsCreate_sRGBProfile();
  if (!dstProfile) {
    cmsCloseProfile(srcProfile);
    return 0;
  }

  cmsColorSpaceSignature srcCS = cmsGetColorSpace(srcProfile);
  cmsUInt32Number nSrcComponents = cmsChannelsOf(srcCS);
  cmsUInt32Number srcFormat;
  if (srcCS == cmsSigLabData) {
    srcFormat =
        COLORSPACE_SH(PT_Lab) | CHANNELS_SH(nSrcComponents) | BYTES_SH(0);
  } else {
    srcFormat =
        COLORSPACE_SH(PT_ANY) | CHANNELS_SH(nSrcComponents) | BYTES_SH(1);
  }

  cmsUInt32Number intent = 0;
  cmsUInt32Number flags = 0;
  cmsHTRANSFORM hTransform = cmsCreateTransform(
      srcProfile, srcFormat, dstProfile, TYPE_BGR_8, intent, flags);
  cmsCloseProfile(srcProfile);
  cmsCloseProfile(dstProfile);
  if (!hTransform) return 0;

  uint8_t output[4];
  if (T_BYTES(srcFormat) == 0) {  // 0 means double
    double input[nSrcComponents];
    for (uint32_t i = 0; i < nSrcComponents; i++) input[i] = 0.5f;
    cmsDoTransform(hTransform, input, output, 1);
  } else {
    uint8_t input[nSrcComponents];
    for (uint32_t i = 0; i < nSrcComponents; i++) input[i] = 128;
    cmsDoTransform(hTransform, input, output, 1);
  }
  cmsDeleteTransform(hTransform);

  return 0;
}

filename: cms_transform_fuzzer.options
content: [libfuzzer]
dict = icc.dict

filename: icc.dict
content: # Fuzzing dictionary for icc
# Extracted from lcms2.h of Little-CMS project 2.8.

magic="acsp"
sig="lcms"

# Base ICC type definitions
"chrm"
"clro"
"clrt"
"crdi"
"curv"
"data"
"dict"
"dtim"
"devs"
"mft2"
"mft1"
"mAB "
"mBA "
"meas"
"mluc"
"mpet"
"ncol"
"ncl2"
"para"
"pseq"
"psid"
"rcs2"
"sf32"
"scrn"
"sig "
"text"
"desc"
"uf32"
"bfd "
"ui16"
"ui32"
"ui64"
"ui08"
"vcgt"
"view"
"XYZ "

# Base ICC tag definitions
"A2B0"
"A2B1"
"A2B2"
"bXYZ"
"bXYZ"
"bTRC"
"B2A0"
"B2A1"
"B2A2"
"calt"
"targ"
"chad"
"chrm"
"clro"
"clrt"
"clot"
"ciis"
"cprt"
"crdi"
"data"
"dtim"
"dmnd"
"dmdd"
"devs"
"D2B0"
"D2B1"
"D2B2"
"D2B3"
"B2D0"
"B2D1"
"B2D2"
"B2D3"
"gamt"
"kTRC"
"gXYZ"
"gXYZ"
"gTRC"
"lumi"
"meas"
"bkpt"
"wtpt"
"ncol"
"ncl2"
"resp"
"rig0"
"pre0"
"pre1"
"pre2"
"desc"
"dscm"
"pseq"
"psid"
"psd0"
"psd1"
"psd2"
"psd3"
"ps2s"
"ps2i"
"rXYZ"
"rXYZ"
"rTRC"
"rig2"
"scrd"
"scrn"
"tech"
"bfd "
"vued"
"view"
"vcgt"
"meta"
"arts"

# ICC Technology tag
"dcam"
"fscn"
"rscn"
"ijet"
"twax"
"epho"
"esta"
"dsub"
"rpho"
"fprn"
"vidm"
"vidc"
"pjtv"
"CRT "
"PMD "
"AMD "
"KPCD"
"imgs"
"grav"
"offs"
"silk"
"flex"
"mpfs"
"mpfr"
"dmpc"
"dcpj"

# ICC Color spaces
"XYZ "
"Lab "
"Luv "
"YCbr"
"Yxy "
"RGB "
"GRAY"
"HSV "
"HLS "
"CMYK"
"CMY "
"MCH1"
"MCH2"
"MCH3"
"MCH4"
"MCH5"
"MCH6"
"MCH7"
"MCH8"
"MCH9"
"MCHA"
"MCHB"
"MCHC"
"MCHD"
"MCHE"
"MCHF"
"nmcl"
"1CLR"
"2CLR"
"3CLR"
"4CLR"
"5CLR"
"6CLR"
"7CLR"
"8CLR"
"9CLR"
"ACLR"
"BCLR"
"CCLR"
"DCLR"
"ECLR"
"FCLR"
"LuvK"

# ICC Profile Class
"scnr"
"mntr"
"prtr"
"link"
"abst"
"spac"
"nmcl"

# ICC Platforms
"APPL"
"MSFT"
"SUNW"
"SGI "
"TGNT"
"*nix"

# Reference gamut
"prmg"

# For cmsSigColorimetricIntentImageStateTag
"scoe"
"sape"
"fpce"
"rhoc"
"rpoc"

# Multi process elements types
"cvst"
"matf"
"clut"
"bACS"
"eACS"
"l2x "
"x2l "
"ncl "
"2 4 "
"4 2 "
"idn "
"d2l "
"l2d "
"d2x "
"x2d "
"clp "

# Types of CurveElements
"parf"
"samf"
"curf"

# Used in ResponseCurveType
"StaA"
"StaE"
"StaI"
"StaT"
"StaM"
"DN  "
"DN P"
"DNN "
"DNNP"