Talos Vulnerability Report

TALOS-2024-1929

libigl readOFF stack-based buffer overflow vulnerability

May 28, 2024
CVE Number

CVE-2024-24686,CVE-2024-24685,CVE-2024-24684

SUMMARY

Multiple stack-based buffer overflow vulnerabilities exist in the readOFF functionality of libigl v2.5.0. A specially crafted .off file can lead to stack-based buffer overflow. An attacker can provide a malicious file to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

libigl v2.5.0

PRODUCT URLS

libigl - https://libigl.github.io/

CVSSv3 SCORE

7.8 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-121 - Stack-based Buffer Overflow

DETAILS

libigl is a C++ geometry processing library that is designed to be simple to integrate into projects using a header-only construction for the code base. This library is widely utilized in industries ranging from Triple-A game development to 3D printing, and it can be found in many applications that require the geometry processing of various file formats.

In the readOFF function, multiple stack based buffer overflow exists due to the unsafe usage of the fscanf function to read strings into various buffers whose size is hardcoded to 1000 bytes.

CVE-2024-24684 - Header parsing

`

    IGL_INLINE bool igl::readOFF(
      FILE * off_file,
      std::vector<std::vector<Scalar > > & V,
      std::vector<std::vector<Index > > & F,
      std::vector<std::vector<Scalar > > & N,
      std::vector<std::vector<Scalar > > & C)
    {

        ...

      // First line is always OFF
[0]   char header[1000];
        ...
[1]   if(fscanf(off_file,"%s\n",header)!=1
         || !(
           string(header).compare(0, OFF.length(), OFF)==0 ||
           string(header).compare(0, COFF.length(), COFF)==0 ||
           string(header).compare(0,NOFF.length(),NOFF)==0))
      {
           ...
      }

      ...

      return true;

`

We can see above that at [0] a stack-based buffer called header is defined with an hardcoded size of 1000 bytes. The call to fscanf at [1] is unsafe and if the first line of the header of the .off files is longer than 1000 bytes it will overflow the header buffer.

CVE-2024-24685 - Comments in vertex parsing

`

template <typename Scalar, typename Index>
IGL_INLINE bool igl::readOFF(
  FILE * off_file,
  std::vector<std::vector<Scalar > > & V,
  std::vector<std::vector<Index > > & F,
  std::vector<std::vector<Scalar > > & N,
  std::vector<std::vector<Scalar > > & C)
{
    ...
  for(int i = 0;i<number_of_vertices;)
  {
[0] fgets(line, 1000, off_file);
    double x,y,z,nx,ny,nz;
[1] if(sscanf(line, "%lg %lg %lg %lg %lg %lg",&x,&y,&z,&nx,&ny,&nz)>= 3)
    {
      ...
    }else if(
[2]     fscanf(off_file,"%[#]",&tic_tac_toe)==1)
    {
[3]   char comment[1000];
[4]   fscanf(off_file,"%[^\n]",comment);
    }else
    {
        ...
    }
  }
  ...
  return true;
}

`

We can see above tha while iterating over vertices, a line is read from the .off file at [0]. If this line doesn’t match the pattern at [1] then more data is read from [2] to test if a comment is starting (trying to read a #). If so a full line is read at [4] into the comment buffer defined at [3]. The size of the comment buffer is hardcoded to be 1000 bytes, as thus, if the line read into it is longer than 1000 characters, a stack-buffer overflow will occur.

CVE-2024-24686 - Comments in faces parsing

`

template <typename Scalar, typename Index>
IGL_INLINE bool igl::readOFF(
  FILE * off_file,
  std::vector<std::vector<Scalar > > & V,
  std::vector<std::vector<Index > > & F,
  std::vector<std::vector<Scalar > > & N,
  std::vector<std::vector<Scalar > > & C)
{
    ...
  // Read faces
  for(int i = 0;i<number_of_faces;)
  {
    std::vector<Index > face;
    int valence;
[0] if(fscanf(off_file,"%d",&valence)==1)
    {
        ...
    }else if(
[1]          fscanf(off_file,"%[#]",&tic_tac_toe)==1)
    {
[2]   char comment[1000];
[3]   fscanf(off_file,"%[^\n]",comment);
    }else
    {
        ...
  }
  fclose(off_file);
  return true;
}

`

We can see above tha while iterating over faces, the function tries to read an integer from the .off file at [0]. If this fails, an attempt is done to read a # [1]. On success more data is read from the file at [3] into the comment buffer defined at [2]. The size of the comment buffer is hardcoded to be 1000 bytes, as thus, if the line read into it is longer than 1000 characters, a stack-buffer overflow will occur.

TIMELINE

2023-11-22 - Initial Vendor Contact
2023-11-28 - Initial Vendor Contact
2023-11-30 - Request for confirmation
2023-12-11 - Advisories sent
2024-02-07 - Four more advisories sent, after the initial two
2024-02-27 - Request for status update
2024-04-10 - Request for status update
2ß24-05-15 - Request for status update via Github issue, no reply
2024-05-28 - Public Release

Credit

Discovered by Philippe Laulheret of Cisco Talos.