[하루한줄] CVE-2024-33078: Tencent libpag에서 발견된 두 가지 취약점

URL

https://github.com/Tencent/libpag/issues/2229

https://github.com/Tencent/libpag/issues/2230

Target

  • libpag

Explain

vector 및 raster 기반의 PAG(Portable Animated Graphics) 파일용 실시간 렌더링 라이브러인 libpag에서 발견된 두 가지 취약점의 세부 정보가 공개되었습니다. 두 취약점 모두 DecodeStream.cpp 구현에 존재합니다.

CVE-2024-33078 - Heap Buffer Oveflow

첫 번째 취약점은 DecodeStream::readUTF8String() 함수에 존재합니다.

std::string DecodeStream::readUTF8String() {
  if (_position < dataView.size()) {
    auto text = reinterpret_cast<const char*>(dataView.bytes() + _position);
    auto textLength = strlen(text);
    if (textLength > dataView.size() - _position) {
      textLength = dataView.size() - _position;
      positionChanged(static_cast<off_t>(textLength));
    } else {
      positionChanged(static_cast<off_t>(textLength + 1));
    }
    return {text, textLength};
  } else {
    PAGThrowError(context, "End of file was encountered.");
  }
  return "";
}

해당 함수는 dataView.bytes() + _poisition 위치의 문자열 text 길이인 textLengthstrlen() 함수를 사용해 구합니다. text가 가리키는 문자열이 null로 끝나지 않는 경우 strlen()은 정확한 길이를 측정하지 못하고 예상보다 큰 값을 반환합니다. 이후 textLength 길이만큼 메모리를 읽는 과정에서 파일 스트림이 할당된 메모리 이후의 값을 읽어 Heap Buffer Oveflow가 트리거됩니다.

취약점의 패치는 아래와 같이 strnlen() 함수를 사용함으로써 읽을 수 있는 최대 길이를 maxLength만큼 지정하는 것으로 이루어졌습니다. 해당 패치 커밋은 링크에서 확인 가능합니다.

std::string DecodeStream::readUTF8String() {
  if (_position < dataView.size()) {
    auto text = reinterpret_cast<const char*>(dataView.bytes() + _position);
    auto maxLength = dataView.size() - _position;
    auto textLength = strnlen(text, maxLength);
    if (textLength < maxLength) {
      positionChanged(static_cast<off_t>(textLength + 1));
      return {text, textLength};
    }
  }
  PAGThrowError(context, "End of file was encountered.");
  return "";
}

Unknown CVE - Integer Overflow

CVE Number가 할당되지 않은 두 번째 취약점은 DecodeStream.h 헤더의 타입 선언 및 DecodeStream::checkEndOfFile() 함수에 존재합니다.

// DecodeStream.h
//...
 private:
  tgfx::DataView dataView = {};
  uint32_t _position = 0;        //(1)
  uint64_t _bitPosition = 0;

  void bitPositionChanged(off_t offset);

  void positionChanged(off_t offset);

  bool checkEndOfFile(uint32_t bytesToRead);
};
//...

// DecodeStream.cpp
//...
bool DecodeStream::checkEndOfFile(uint32_t bytesToRead) {
  if (_position + bytesToRead > dataView.size()) {          //(2)
    PAGThrowError(context, "End of file was encountered.");
    return true;
  }
  return false;
}
//...

DecodeStream::checkEndOfFile() 함수는 EOF 여부를 확인하는 함수로, 현재 스트림 커서 위치 _positionbytesToRead를 더한 값이 파일 전체 크기보다 크면 현재 파일의 끝으로 간주합니다.

그러나 (1)에서 _positionuint32_t 로 선언되어 있어 (2)의 조건 연산 결과가 uint32_t 표현범위보다 큰 경우 integer overflow가 발생하고, 이로 인해 파일의 EOF를 넘어 Heap Buffer Overflow 등의 추가적인 영향을 줄 수 있습니다.

취약점 패치는 스트림 처리 관련 변수들의 타입을 size_t 로 변경하는 것으로 이루어졌으며, 패치 커밋은 링크에서 확인 가능합니다.

Reference

CVE-2024-33078 Crash PoC