개요
TIFF (Tagged Image File Format) : 비트맵 이미지, 이미지 정보를 저장하는 데 사용하는 이미지 포맷
- 무손실 압축 파일 포맷이므로 원본 이미지의 디테일 등이 그대로 유지되므로 고품질 사진에 적합
- 파일 크기가 큼
- EXIF 포맷은 TIFF 포맷을 기반으로 제작되었음
CVE-2016-9297 : libtiff 4.0.6, TIFFFetchNormalTag 함수에서 발생하는 out-of-bounds read 취약점
TIFF 포맷 구조는 다음과 같다.

EXIF 포맷과 비슷하게 생겼으며, IFD의 구조는 동일하다.

크래시 분석
AFL에서 크래시 발견 후 ASAN 계측만 남겨두도록 컴파일 하였다.
CFLAGS="-fsanitize=address -g -O0" ./configure --prefix=/root/install/ --disable-sharedmakemake install
콜스택을 따라 분석을 진행하였다.
_TIFFPrintField에서 크래시가 발생한 것으로 보인다.
tif = TIFFOpen(argv[optind], chopstrips ? "rC" : "rc");if (tif != NULL) { if (dirnum != -1) { if (TIFFSetDirectory(tif, (tdir_t) dirnum)) tiffinfo(tif, order, flags, 1); } else if (diroff != 0) { if (TIFFSetSubDirectory(tif, diroff)) tiffinfo(tif, order, flags, 1); } else { do { toff_t offset=0; tiffinfo(tif, order, flags, 1); if (TIFFGetField(tif, TIFFTAG_EXIFIFD, &offset)) { if (TIFFReadEXIFDirectory(tif, offset)) { tiffinfo(tif, order, flags, 0); } } } while (TIFFReadDirectory(tif)); } TIFFClose(tif);}main
프로그램 실행 후 인자로 IFD의 오프셋, 번호를 입력하였을 때 디렉토리를 지정하는 작업을 수행한다.
크래시가 터진 함수는 tiffinfo이므로 옵션이 주어지더라도 상관 없다.
static voidtiffinfo(TIFF* tif, uint16 order, long flags, int is_image){ TIFFPrintDirectory(tif, stdout, flags);...tiffinfo
tiffinfo 함수는 TIFFPrintDirectory 함수를 호출한다.
해당 함수는 출력을 위한 함수로 디렉토리내의 엔트리를 가져온 후 출력한다.
voidTIFFPrintDirectory(TIFF* tif, FILE* fd, long flags){ TIFFDirectory *td = &tif->tif_dir; char *sep; uint16 i; long l, n;. . if (!_TIFFPrettyPrintField(tif, fip, fd, tag, value_count, raw_data)) _TIFFPrintField(fd, fip, value_count, raw_data); . .TIFFPrintDirectory
TIFFPrintDirectory 함수는 _TIFFPrettyPrintField에서 0이 반환되었을 때 즉, 커스텀 태그일 경우 자료형에 맞게 데이터를 가져와 출력해주는 _TIFFPrintDirectory 함수를 호출한다.
_TIFFPrintDirectory함수는 다음과 같은 작업을 수행한다.
static void_TIFFPrintField(FILE* fd, const TIFFField *fip,uint32 value_count, void *raw_data){ uint32 j; fprintf(fd, " %s: ", fip->field_name); for(j = 0; j < value_count; j++) { if(fip->field_type == TIFF_BYTE) fprintf(fd, "%u", ((uint8 *) raw_data)[j]); else if(fip->field_type == TIFF_UNDEFINED) fprintf(fd, "0x%x", (unsigned int) ((unsigned char *) raw_data)[j]); else if(fip->field_type == TIFF_SBYTE) fprintf(fd, "%d", ((int8 *) raw_data)[j]); else if(fip->field_type == TIFF_SHORT) fprintf(fd, "%u", ((uint16 *) raw_data)[j]); else if(fip->field_type == TIFF_SSHORT) fprintf(fd, "%d", ((int16 *) raw_data)[j]);.._TIFFPrintField
크래시 파일을 보면 해당 필드의 데이터 타입이 TIFF_ASCII 형이다.
해당 자료형을 출력해주는 소스코드는 다음과 같다.
else if(fip->field_type == TIFF_ASCII) { fprintf(fd, "%s", (char *) raw_data); break;}_TIFFPrintField
printf 함수는 NULL 바이트까지 나올 때가지 출력한다.
하지만 다음과 같이 NULL 바이트를 제외시킨 파일을 전달할 경우 허가되지 않은 영역의 데이터를 출력시킬 수 있다.


따라서 해당 취약점은 ASCII 자료형이고, NULL 바이트가 포함되어있지 않을 때 발생하는 out-of-bounds read취약점이라는 것을 알 수 있다.
다음과 같이 NULL 바이트를 삽입시켜주면 크래시가 발생하지 않는 것을 확인할 수 있다.



취약점 패치
해당 취약점은 TIFF 파일을 읽는 과정에서 ASCII 자료형을 가진 entry의 마지막 바이트가 NULL이 아닐 경우 강제로 NULL 바이트를 삽입하도록 패치되었다.
case TIFF_SETGET_C16_ASCII:{ uint8* data; assert(fip->field_readcount==TIFF_VARIABLE); assert(fip->field_passcount==1); if (dp->tdir_count>0xFFFF) err=TIFFReadDirEntryErrCount; else { err=TIFFReadDirEntryByteArray(tif,dp,&data); if (err==TIFFReadDirEntryErrOk) { int m; if( data[dp->tdir_count-1] != '\\0' ) { TIFFWarningExt(tif->tif_clientdata,module,"ASCII value for tag \\"%s\\" does not end in null byte. Forcing it to be null",fip->field_name); data[dp->tdir_count-1] = '\\0’;}..TIFFFetchNormalTag