Talos Vulnerability Report

TALOS-2023-1784

libigl readOFF stack-based buffer overflow vulnerabilities

May 28, 2024
CVE Number

CVE-2023-35950,CVE-2023-35953,CVE-2023-35952,CVE-2023-35951,CVE-2023-35949

SUMMARY

Multiple stack-based buffer overflow vulnerabilities exist in the readOFF.cpp functionality of libigl v2.4.0. A specially-crafted .off file can lead to a buffer overflow. An attacker can arbitrary code execution to trigger these vulnerabilities.

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.4.0
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, 3D printing, and can be found in many applications that require the geometry processing of various file formats.

Multiple vulnerabilities exist in the parsing of OFF files within libigl. These vulnerabilities are centered around the unsafe usage of fscanf that results in multiple buffer overflows within the readOFF function.

CVE-2023-35949 - Read faces tic_tac_toe buffer overflow

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)
{
  ...
  int number_of_vertices;
  int number_of_faces;
  int number_of_edges;
  char tic_tac_toe;                                      [1]
  char line[1000];
  bool still_comments = true;
  ...
  // Read faces
  for(int i = 0;i<number_of_faces;)
  {
    std::vector<Index > face;
    int valence;
    if(fscanf(off_file,"%d",&valence)==1)
    {
      face.resize(valence);
      for(int j = 0;j<valence;j++)
      {
        int index;
        if(j<valence-1)
        {
          fscanf(off_file,"%d",&index);
        }else{
          fscanf(off_file,"%d%*[^\n]",&index);
        }

        face[j] = index;
      }
      F[i] = face;
      i++;
    }else if(
             fscanf(off_file,"%[#]",&tic_tac_toe)==1)     [2]
    {
     ...    

At [1] a single byte buffer on the stack is used for tic_tac_toe. At [2] an unbounded format string is passed to fscanf. Since the format string is not bounded in length, a file containing multiple # symbols will cause a buffer overflow. Additionally, a charset fscanf will terminate the buffer with a NULL byte, resulting in a buffer overflow even if a single # is present.

Crash Information

Summary: ASAN detected stack-buffer-overflow in __isoc99_fscanf after a WRITE leading to SIGABRT (si_signo=6) / SI_TKILL (si_code=-6)
Command line: ./example @@
Testcase: ../build/output/default/crashes/id:000011,sig:11,src:000020,time:10797,execs:23314,op:havoc,rep:4
Crash bucket: 1c89039da5712d3e7df1442330bf3ea0

Crashing thread backtrace:
#0  0x00007ffff7aaaa7c in __pthread_kill_implementation (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:44

#1  0x00007ffff7aaaa7c in __pthread_kill_internal (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:78

#2  0x00007ffff7aaaa7c in __GI___pthread_kill (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:89

#3  0x00007ffff7a56476 in __GI_raise (/lib/x86_64-linux-gnu/libc.so.6)
                       at ../sysdeps/posix/raise.c:26

#4  0x00007ffff7a3c7f3 in __GI_abort (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./stdlib/abort.c:79

#5  0x0000555555655207 in __sanitizer::Abort() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#6  0x0000555555653031 in __sanitizer::Die() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#7  0x0000555555635e57 in __asan::ScopedInErrorReport::~ScopedInErrorReport() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#8  0x0000555555638c6c in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#9  0x00005555555cd793 in scanf_common(void*, int, bool, char const*, __va_list_tag*) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#10 0x00005555555ce2fd in __isoc99_fscanf (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#11 0x0000555555678fc9 in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                        30: bool igl::readOFF<double, int>(off_file = (FILE *)<optimized out>, V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: int i = <optimized out>; */
                       |||: /* Local reference: FILE * off_file = <optimized out>; */
                       148:       i++;
                       149:     }else if(
                       150:              fscanf(off_file,"%[#]",&tic_tac_toe)==1)
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:150

#12 0x0000555555672b5d in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       ??: bool igl::readOFF<double, int>(V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 8, capacity 8 = {std::vector of length 3, capacity 3 = {8, 0.10000000000000001, 0.75}, std::vector of length 3, capacity 3 = {0, 0.80000000000000004, 0.79000000000000004}, std::vector of length 3, capacity 3 = {-1, 0.40000000000000002, 0.75}, std::vector of length 3, capacity 3 = {-1, 0.40000000000000002, 0.75}, std::vector of length 3, capacity 3 = {4, 0.69999999999999996, 4}, std::vector of length 3, capacity 3 = {0.69999999999999996, 0, 0.75}, std::vector of length 3, capacity 3 = {-1, 1, 0.75}, std::vector of length 3, capacity 3 = {4, 0, 2}}, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 8, capacity 8 = {std::vector of length 3, capacity 3 = {0, 0, 0}, std::vector of length 3, capacity 3 = {0, 0, 0}, std::vector of length 3, capacity 3 = {0, 0, 0}, std::vector of length 3, capacity 3 = {0, 0, 0}, std::vector of length 3, capacity 3 = {0.0027450980392156859, 0.0029411764705882353, 0}, std::vector of length 3, capacity 3 = {0.0027450980392156859, 0.0029411764705882353, 0}, std::vector of length 3, capacity 3 = {0.0027450980392156859, 0.0029411764705882353, 0}, std::vector of length 3, capacity 3 = {0.011764705882352941, 0.0039215686274509803, 0}}, off_file_name = (const std::string)) {
                       ||:
                       ||: /* Local reference: FILE * off_file = 0x0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & V = std::vector of length 8, capacity 8 = {std::vector of length 3, ca... */
                       ||: /* Local reference: class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > & F = <optimized out>; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & N = std::vector of length 0, capacity 0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & C = std::vector of length 8, capacity 8 = {std::vector of length 3, ca... */
                       24:     return false;
                       25:   }
                       26:   return readOFF(off_file,V,F,N,C);
                       ||:
                       --: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:26

#13 0x0000555555672b5d in igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> > (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       168: bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(str = (const std::string), V = (class Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > &)<optimized out>, F = (class Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: bool success = <optimized out>; */
                       |||: /* Local reference: const std::string str = ; */
                       175:   std::vector<std::vector<int> > vF;
                       176:   std::vector<std::vector<double> > vC;
                       177:   bool success = igl::readOFF(str,vV,vF,vN,vC);
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:177

#14 0x00005555556723b1 in main (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       7: int main(argc = (int)<optimized out>, argv = (char **)<optimized out>) {
                       |: /* Local reference: int argc = <optimized out>; */
                       |: /* Local reference: char ** argv = <optimized out>; */
                       8: {
                       9:       igl::readOFF(argv[1], V, F);
                       |:
                       -: }
                       at /home/fuzz/libigl-example-project/main.cpp:9

ASAN Report:
=================================================================
==8645==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff6000761 at pc 0x5555555cd771 bp 0x7fffffffdc40 sp 0x7fffffffd3c8
WRITE of size 14 at 0x7ffff6000761 thread T0
/usr/bin/addr2line: DWARF error: invalid or unhandled FORM value: 0x23
    #0 0x5555555cd770 in scanf_common(void*, int, bool, char const*, __va_list_tag*) crtstuff.c:?
    #1 0x5555555ce2fc in __interceptor___isoc99_fscanf ??:?
    #2 0x555555678fc8 in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?
    #3 0x555555672b5c in bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&) ld-temp.o:?
    #4 0x5555556723b0 in main ??:?
    #5 0x7ffff7a3dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x7ffff7a3de3f in __libc_start_main_impl csu/../csu/libc-start.c:392
    #7 0x5555555abdb4 in _start ??:?

Address 0x7ffff6000761 is located in stack of thread T0 at offset 1889 in frame
    #0 0x55555567522f in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?

  This frame has 32 object(s):
    [32, 40) '__dnew.i.i567'
    [64, 72) '__dnew.i.i551'
    [96, 104) '__dnew.i.i523'
    [128, 136) '__dnew.i.i510'
    [160, 168) '__dnew.i.i498'
    [192, 1192) 'header' (line 44)
    [1328, 1360) 'OFF' (line 45)
    [1392, 1424) 'NOFF' (line 46)
    [1456, 1488) 'COFF' (line 47)
    [1520, 1552) 'ref.tmp9' (line 48)
    [1584, 1616) 'ref.tmp22' (line 48)
    [1648, 1680) 'ref.tmp37' (line 48)
    [1712, 1744) 'ref.tmp136' (line 58)
    [1776, 1808) 'ref.tmp150' (line 59)
    [1840, 1844) 'number_of_vertices' (line 61)
    [1856, 1860) 'number_of_faces' (line 62)
    [1872, 1876) 'number_of_edges' (line 63)
    [1888, 1889) 'tic_tac_toe' (line 64) <== Memory access at offset 1889 overflows this variable
    [1904, 2904) 'line' (line 65)
    [3040, 3048) 'x' (line 84)
    [3072, 3080) 'y' (line 84)
    [3104, 3112) 'z' (line 84)
    [3136, 3144) 'nx' (line 84)
    [3168, 3176) 'ny' (line 84)
    [3200, 3208) 'nz' (line 84)
    [3232, 3256) 'vertex' (line 87)
    [3296, 3320) 'normal' (line 96)
    [3360, 4360) 'comment' (line 115)
    [4496, 4520) 'face' (line 130)
    [4560, 4564) 'valence' (line 131)
    [4576, 4580) 'index' (line 137)
    [4592, 5592) 'comment320' (line 152)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow crtstuff.c:? in scanf_common(void*, int, bool, char const*, __va_list_tag*)
Shadow bytes around the buggy address:
  0x10007ebf8090: 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2
  0x10007ebf80a0: f2 f2 f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 00 00
  0x10007ebf80b0: 00 00 f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 f8 f8
  0x10007ebf80c0: f8 f8 f2 f2 f2 f2 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8
  0x10007ebf80d0: f8 f8 f2 f2 f2 f2 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8
=>0x10007ebf80e0: f8 f8 f2 f2 f2 f2 04 f2 04 f2 04 f2[01]f2 00 00
  0x10007ebf80f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==8645==ABORTING

Crash context:
Execution stopped here ==> 0x00007ffff7aaaa7c: mov    r13d,eax

Register info:
   rax - 0x0000000000000000 (0)
   rbx - 0x00007ffff7957800 (140737347155968)
   rcx - 0x00007ffff7aaaa7c (140737348545148)
   rdx - 0x0000000000000006 (6)
   rsi - 0x00000000000021c5 (8645)
   rdi - 0x00000000000021c5 (8645)
   rbp - 0x00000000000021c5 (0x21c5)
   rsp - 0x00007fffffffc420 (0x7fffffffc420)
    r8 - 0x00007fffffffc4f0 (140737488340208)
    r9 - 0xbfffff00000fffff (-4611687117937967105)
   r10 - 0x0000000000000008 (8)
   r11 - 0x0000000000000246 (582)
   r12 - 0x0000000000000006 (6)
   r13 - 0x0000000000000016 (22)
   r14 - 0x1000000000000000 (1152921504606846976)
   r15 - 0x2000000000000000 (2305843009213693952)
   rip - 0x00007ffff7aaaa7c (0x7ffff7aaaa7c <__GI___pthread_kill+300>)
eflags - 0x00000246 ([ PF ZF IF ])
    cs - 0x00000033 (51)
    ss - 0x0000002b (43)
    ds - 0x00000000 (0)
    es - 0x00000000 (0)
    fs - 0x00000000 (0)
    gs - 0x00000000 (0)    

Mitigation

This fscanf format string should be limited to a single byte read to prevent a buffer overflow of tic_tac_toe. It is important to note that charset matches add a null terminator automatically to any data written to the buffer, which means tic_tac_toe should be at least a 2 byte array. Alternatively, since the information captured in this fscanf is useless, * can be used in the format string to ignore the characters instead.

CVE-2023-35950 - header buffer overflow

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)
{
  using namespace std;
  V.clear();
  F.clear();
  N.clear();
  C.clear();

  // First line is always OFF
  char header[1000];                         [1]
  const std::string OFF("OFF");
  const std::string NOFF("NOFF");
  const std::string COFF("COFF");
  if(fscanf(off_file,"%s\n",header)!=1 {     [2]
      ...

At [1] buffer of 1000 bytes is created on the stack, at [2] an unbounded format string is passed to fscanf. Since no length is specified in the format string, a buffer overflow occurs at [2] when writing data to the header buffer.

Crash Information

Summary: ASAN detected stack-buffer-overflow in __isoc99_fscanf after a WRITE leading to SIGABRT (si_signo=6) / SI_TKILL (si_code=-6)
Command line: ./example @@
Testcase: ../build/output/default/crashes/id:000034,sig:11,src:000030+000120,time:750847,execs:1791265,op:splice,rep:8
Crash bucket: 76f3b993613d5b3a59289692bcc8105d

Crashing thread backtrace:
#0  0x00007ffff7aaaa7c in __pthread_kill_implementation (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:44

#1  0x00007ffff7aaaa7c in __pthread_kill_internal (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:78

#2  0x00007ffff7aaaa7c in __GI___pthread_kill (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:89

#3  0x00007ffff7a56476 in __GI_raise (/lib/x86_64-linux-gnu/libc.so.6)
                       at ../sysdeps/posix/raise.c:26

#4  0x00007ffff7a3c7f3 in __GI_abort (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./stdlib/abort.c:79

#5  0x0000555555655207 in __sanitizer::Abort() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#6  0x0000555555653031 in __sanitizer::Die() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#7  0x0000555555635e57 in __asan::ScopedInErrorReport::~ScopedInErrorReport() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#8  0x0000555555638c6c in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#9  0x00005555555cd793 in scanf_common(void*, int, bool, char const*, __va_list_tag*) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#10 0x00005555555ce2fd in __isoc99_fscanf (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#11 0x0000555555675d17 in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       30: bool igl::readOFF<double, int>(off_file = (FILE *)<optimized out>, V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>) {
                       ||:
                       ||: /* Local reference: FILE * off_file = <optimized out>; */
                       46:   const std::string NOFF("NOFF");
                       47:   const std::string COFF("COFF");
                       48:   if(fscanf(off_file,"%s\n",header)!=1
                       ||:
                       --: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:48

#12 0x0000555555672b5d in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       ??: bool igl::readOFF<double, int>(V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, off_file_name = (const std::string)) {
                       ||:
                       ||: /* Local reference: FILE * off_file = 0x0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & V = std::vector of length 0, capacity 0; */
                       ||: /* Local reference: class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > & F = <optimized out>; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & N = std::vector of length 0, capacity 0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & C = std::vector of length 0, capacity 0; */
                       24:     return false;
                       25:   }
                       26:   return readOFF(off_file,V,F,N,C);
                       ||:
                       --: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:26

#13 0x0000555555672b5d in igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> > (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       168: bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(str = (const std::string), V = (class Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > &)<optimized out>, F = (class Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: bool success = <optimized out>; */
                       |||: /* Local reference: const std::string str = ; */
                       175:   std::vector<std::vector<int> > vF;
                       176:   std::vector<std::vector<double> > vC;
                       177:   bool success = igl::readOFF(str,vV,vF,vN,vC);
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:177

#14 0x00005555556723b1 in main (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       7: int main(argc = (int)<optimized out>, argv = (char **)<optimized out>) {
                       |: /* Local reference: int argc = <optimized out>; */
                       |: /* Local reference: char ** argv = <optimized out>; */
                       8: {
                       9:       igl::readOFF(argv[1], V, F);
                       |:
                       -: }
                       at /home/fuzz/libigl-example-project/main.cpp:9

ASAN Report:
=================================================================
==8713==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff60004a8 at pc 0x5555555cd771 bp 0x7fffffffdc40 sp 0x7fffffffd3c8
WRITE of size 1270 at 0x7ffff60004a8 thread T0
/usr/bin/addr2line: DWARF error: invalid or unhandled FORM value: 0x23
    #0 0x5555555cd770 in scanf_common(void*, int, bool, char const*, __va_list_tag*) crtstuff.c:?
    #1 0x5555555ce2fc in __interceptor___isoc99_fscanf ??:?
    #2 0x555555675d16 in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?
    #3 0x555555672b5c in bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&) ld-temp.o:?
    #4 0x5555556723b0 in main ??:?
    #5 0x7ffff7a3dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x7ffff7a3de3f in __libc_start_main_impl csu/../csu/libc-start.c:392
    #7 0x5555555abdb4 in _start ??:?

Address 0x7ffff60004a8 is located in stack of thread T0 at offset 1192 in frame
    #0 0x55555567522f in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?

  This frame has 32 object(s):
    [32, 40) '__dnew.i.i567'
    [64, 72) '__dnew.i.i551'
    [96, 104) '__dnew.i.i523'
    [128, 136) '__dnew.i.i510'
    [160, 168) '__dnew.i.i498'
    [192, 1192) 'header' (line 44)
    [1328, 1360) 'OFF' (line 45) <== Memory access at offset 1192 partially underflows this variable
    [1392, 1424) 'NOFF' (line 46) <== Memory access at offset 1192 partially underflows this variable
    [1456, 1488) 'COFF' (line 47) <== Memory access at offset 1192 partially underflows this variable
    [1520, 1552) 'ref.tmp9' (line 48) <== Memory access at offset 1192 partially underflows this variable
    [1584, 1616) 'ref.tmp22' (line 48) <== Memory access at offset 1192 partially underflows this variable
    [1648, 1680) 'ref.tmp37' (line 48) <== Memory access at offset 1192 partially underflows this variable
    [1712, 1744) 'ref.tmp136' (line 58) <== Memory access at offset 1192 partially underflows this variable
    [1776, 1808) 'ref.tmp150' (line 59) <== Memory access at offset 1192 partially underflows this variable
    [1840, 1844) 'number_of_vertices' (line 61) <== Memory access at offset 1192 partially underflows this variable
    [1856, 1860) 'number_of_faces' (line 62) <== Memory access at offset 1192 partially underflows this variable
    [1872, 1876) 'number_of_edges' (line 63) <== Memory access at offset 1192 partially underflows this variable
    [1888, 1889) 'tic_tac_toe' (line 64) <== Memory access at offset 1192 partially underflows this variable
    [1904, 2904) 'line' (line 65) <== Memory access at offset 1192 partially underflows this variable
    [3040, 3048) 'x' (line 84)
    [3072, 3080) 'y' (line 84)
    [3104, 3112) 'z' (line 84)
    [3136, 3144) 'nx' (line 84)
    [3168, 3176) 'ny' (line 84)
    [3200, 3208) 'nz' (line 84)
    [3232, 3256) 'vertex' (line 87)
    [3296, 3320) 'normal' (line 96)
    [3360, 4360) 'comment' (line 115)
    [4496, 4520) 'face' (line 130)
    [4560, 4564) 'valence' (line 131)
    [4576, 4580) 'index' (line 137)
    [4592, 5592) 'comment320' (line 152)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow crtstuff.c:? in scanf_common(void*, int, bool, char const*, __va_list_tag*)
Shadow bytes around the buggy address:
  0x10007ebf8040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10007ebf8090: 00 00 00 00 00[f2]f2 f2 f2 f2 f2 f2 f2 f2 f2 f2
  0x10007ebf80a0: f2 f2 f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 00 00
  0x10007ebf80b0: 00 00 f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 f8 f8
  0x10007ebf80c0: f8 f8 f2 f2 f2 f2 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8
  0x10007ebf80d0: f8 f8 f2 f2 f2 f2 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8
  0x10007ebf80e0: f8 f8 f2 f2 f2 f2 f8 f2 f8 f2 f8 f2 f8 f2 f8 f8
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
==8713==ABORTING

Crash context:
Execution stopped here ==> 0x00007ffff7aaaa7c: mov    r13d,eax

Register info:
   rax - 0x0000000000000000 (0)
   rbx - 0x00007ffff7957800 (140737347155968)
   rcx - 0x00007ffff7aaaa7c (140737348545148)
   rdx - 0x0000000000000006 (6)
   rsi - 0x0000000000002209 (8713)
   rdi - 0x0000000000002209 (8713)
   rbp - 0x0000000000002209 (0x2209)
   rsp - 0x00007fffffffc420 (0x7fffffffc420)
    r8 - 0x00007fffffffc4f0 (140737488340208)
    r9 - 0xbfffff00000fffff (-4611687117937967105)
   r10 - 0x0000000000000008 (8)
   r11 - 0x0000000000000246 (582)
   r12 - 0x0000000000000006 (6)
   r13 - 0x0000000000000016 (22)
   r14 - 0x1000000000000000 (1152921504606846976)
   r15 - 0x2000000000000000 (2305843009213693952)
   rip - 0x00007ffff7aaaa7c (0x7ffff7aaaa7c <__GI___pthread_kill+300>)
eflags - 0x00000246 ([ PF ZF IF ])
    cs - 0x00000033 (51)
    ss - 0x0000002b (43)
    ds - 0x00000000 (0)
    es - 0x00000000 (0)
    fs - 0x00000000 (0)
    gs - 0x00000000 (0)    

Mitigation

This fscanf format string should be limited to 1000 bytes to prevent a buffer overflow of header.

CVE-2023-35951 - Read vertices tic_tac_toe buffer overflow

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)
{
  ...
  int number_of_vertices;
  int number_of_faces;
  int number_of_edges;
  char tic_tac_toe;                                                           [1]
  char line[1000];
  bool still_comments = true;
  ...
  // Read vertices
  for(int i = 0;i<number_of_vertices;)
  {
    fgets(line, 1000, off_file);
    double x,y,z,nx,ny,nz;
    if(sscanf(line, "%lg %lg %lg %lg %lg %lg",&x,&y,&z,&nx,&ny,&nz)>= 3)
    {
      std::vector<Scalar > vertex;
      vertex.resize(3);
      vertex[0] = x;
      vertex[1] = y;
      vertex[2] = z;
      V[i] = vertex;

      if (has_normals)
      {
        std::vector<Scalar > normal;
        normal.resize(3);
        normal[0] = nx;
        normal[1] = ny;
        normal[2] = nz;
        N[i] = normal;
      }

      if (has_vertexColors)
      {
        C[i].resize(3);
        C[i][0] = nx / 255.0;
        C[i][1] = ny / 255.0;
        C[i][2] = nz / 255.0;
      }
      i++;
    }else if(    
        fscanf(off_file,"%[#]",&tic_tac_toe)==1)                               [2]
    ...    

At [1] a single byte buffer on the stack is used for tic_tac_toe. At [2] an unbounded format string is passed to fscanf. Since the format string is not bounded in length, a file containing multiple # symbols will cause a buffer overflow. Additionally, a charset fscanf will terminate the buffer with a NULL byte, resulting in a buffer overflow even if a single # is present.

Crash Information

Summary: ASAN detected stack-buffer-overflow in __isoc99_fscanf after a WRITE leading to SIGABRT (si_signo=6) / SI_TKILL (si_code=-6)
Command line: ./example @@
Testcase: ../build/output/default/crashes/id:000000,sig:11,src:000003,time:59,execs:328,op:havoc,rep:8
Crash bucket: fb71c85464507eafec3d7744056f9d3c

Crashing thread backtrace:
#0  0x00007ffff7aaaa7c in __pthread_kill_implementation (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:44

#1  0x00007ffff7aaaa7c in __pthread_kill_internal (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:78

#2  0x00007ffff7aaaa7c in __GI___pthread_kill (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:89

#3  0x00007ffff7a56476 in __GI_raise (/lib/x86_64-linux-gnu/libc.so.6)
                       at ../sysdeps/posix/raise.c:26

#4  0x00007ffff7a3c7f3 in __GI_abort (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./stdlib/abort.c:79

#5  0x0000555555655207 in __sanitizer::Abort() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#6  0x0000555555653031 in __sanitizer::Die() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#7  0x0000555555635e57 in __asan::ScopedInErrorReport::~ScopedInErrorReport() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#8  0x0000555555638c6c in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#9  0x00005555555cd793 in scanf_common(void*, int, bool, char const*, __va_list_tag*) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#10 0x00005555555ce2fd in __isoc99_fscanf (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#11 0x0000555555678388 in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                        30: bool igl::readOFF<double, int>(off_file = (FILE *)<optimized out>, V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 5, capacity 5 = {std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0}, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: int i = 0; */
                       |||: /* Local reference: FILE * off_file = <optimized out>; */
                       111:       i++;
                       112:     }else if(
                       113:         fscanf(off_file,"%[#]",&tic_tac_toe)==1)
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:113

#12 0x0000555555672b5d in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       ??: bool igl::readOFF<double, int>(V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 5, capacity 5 = {std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0}, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, off_file_name = (const std::string)) {
                       ||:
                       ||: /* Local reference: FILE * off_file = 0x0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & V = std::vector of length 5, capacity 5 = {std::vector of length 0, ca... */
                       ||: /* Local reference: class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > & F = <optimized out>; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & N = std::vector of length 0, capacity 0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & C = std::vector of length 0, capacity 0; */
                       24:     return false;
                       25:   }
                       26:   return readOFF(off_file,V,F,N,C);
                       ||:
                       --: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:26

#13 0x0000555555672b5d in igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> > (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       168: bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(str = (const std::string), V = (class Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > &)<optimized out>, F = (class Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: bool success = <optimized out>; */
                       |||: /* Local reference: const std::string str = ; */
                       175:   std::vector<std::vector<int> > vF;
                       176:   std::vector<std::vector<double> > vC;
                       177:   bool success = igl::readOFF(str,vV,vF,vN,vC);
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:177

#14 0x00005555556723b1 in main (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       7: int main(argc = (int)<optimized out>, argv = (char **)<optimized out>) {
                       |: /* Local reference: int argc = <optimized out>; */
                       |: /* Local reference: char ** argv = <optimized out>; */
                       8: {
                       9:       igl::readOFF(argv[1], V, F);
                       |:
                       -: }
                       at /home/fuzz/libigl-example-project/main.cpp:9

ASAN Report:
=================================================================
==8579==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff6000761 at pc 0x5555555cd771 bp 0x7fffffffdc60 sp 0x7fffffffd3e8
WRITE of size 2 at 0x7ffff6000761 thread T0
/usr/bin/addr2line: DWARF error: invalid or unhandled FORM value: 0x23
    #0 0x5555555cd770 in scanf_common(void*, int, bool, char const*, __va_list_tag*) crtstuff.c:?
    #1 0x5555555ce2fc in __interceptor___isoc99_fscanf ??:?
    #2 0x555555678387 in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?
    #3 0x555555672b5c in bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&) ld-temp.o:?
    #4 0x5555556723b0 in main ??:?
    #5 0x7ffff7a3dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x7ffff7a3de3f in __libc_start_main_impl csu/../csu/libc-start.c:392
    #7 0x5555555abdb4 in _start ??:?

Address 0x7ffff6000761 is located in stack of thread T0 at offset 1889 in frame
    #0 0x55555567522f in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?

  This frame has 32 object(s):
    [32, 40) '__dnew.i.i567'
    [64, 72) '__dnew.i.i551'
    [96, 104) '__dnew.i.i523'
    [128, 136) '__dnew.i.i510'
    [160, 168) '__dnew.i.i498'
    [192, 1192) 'header' (line 44)
    [1328, 1360) 'OFF' (line 45)
    [1392, 1424) 'NOFF' (line 46)
    [1456, 1488) 'COFF' (line 47)
    [1520, 1552) 'ref.tmp9' (line 48)
    [1584, 1616) 'ref.tmp22' (line 48)
    [1648, 1680) 'ref.tmp37' (line 48)
    [1712, 1744) 'ref.tmp136' (line 58)
    [1776, 1808) 'ref.tmp150' (line 59)
    [1840, 1844) 'number_of_vertices' (line 61)
    [1856, 1860) 'number_of_faces' (line 62)
    [1872, 1876) 'number_of_edges' (line 63)
    [1888, 1889) 'tic_tac_toe' (line 64) <== Memory access at offset 1889 overflows this variable
    [1904, 2904) 'line' (line 65)
    [3040, 3048) 'x' (line 84)
    [3072, 3080) 'y' (line 84)
    [3104, 3112) 'z' (line 84)
    [3136, 3144) 'nx' (line 84)
    [3168, 3176) 'ny' (line 84)
    [3200, 3208) 'nz' (line 84)
    [3232, 3256) 'vertex' (line 87)
    [3296, 3320) 'normal' (line 96)
    [3360, 4360) 'comment' (line 115)
    [4496, 4520) 'face' (line 130)
    [4560, 4564) 'valence' (line 131)
    [4576, 4580) 'index' (line 137)
    [4592, 5592) 'comment320' (line 152)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow crtstuff.c:? in scanf_common(void*, int, bool, char const*, __va_list_tag*)
Shadow bytes around the buggy address:
  0x10007ebf8090: 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2
  0x10007ebf80a0: f2 f2 f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 00 00
  0x10007ebf80b0: 00 00 f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 f8 f8
  0x10007ebf80c0: f8 f8 f2 f2 f2 f2 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8
  0x10007ebf80d0: f8 f8 f2 f2 f2 f2 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8
=>0x10007ebf80e0: f8 f8 f2 f2 f2 f2 04 f2 04 f2 04 f2[01]f2 00 00
  0x10007ebf80f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==8579==ABORTING

Crash context:
Execution stopped here ==> 0x00007ffff7aaaa7c: mov    r13d,eax

Register info:
   rax - 0x0000000000000000 (0)
   rbx - 0x00007ffff7957800 (140737347155968)
   rcx - 0x00007ffff7aaaa7c (140737348545148)
   rdx - 0x0000000000000006 (6)
   rsi - 0x0000000000002183 (8579)
   rdi - 0x0000000000002183 (8579)
   rbp - 0x0000000000002183 (0x2183)
   rsp - 0x00007fffffffc440 (0x7fffffffc440)
    r8 - 0x00007fffffffc510 (140737488340240)
    r9 - 0xbfffff00000fffff (-4611687117937967105)
   r10 - 0x0000000000000008 (8)
   r11 - 0x0000000000000246 (582)
   r12 - 0x0000000000000006 (6)
   r13 - 0x0000000000000016 (22)
   r14 - 0x1000000000000000 (1152921504606846976)
   r15 - 0x2000000000000000 (2305843009213693952)
   rip - 0x00007ffff7aaaa7c (0x7ffff7aaaa7c <__GI___pthread_kill+300>)
eflags - 0x00000246 ([ PF ZF IF ])
    cs - 0x00000033 (51)
    ss - 0x0000002b (43)
    ds - 0x00000000 (0)
    es - 0x00000000 (0)
    fs - 0x00000000 (0)
    gs - 0x00000000 (0)    

Mitigation

This fscanf format string should be limited to a single byte read to prevent a buffer overflow of tic_tac_toe. It is important to note that charset matches add a null terminator automatically to any data written to the buffer, which means tic_tac_toe should be at least a 2 byte array. Alternatively, since the information captured in this fscanf is useless, * can be used in the format string to ignore the characters instead.

CVE-2023-35952 - faces comment buffer overflow

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)
{
  ...
  int number_of_vertices;
  int number_of_faces;
  int number_of_edges;
  char tic_tac_toe;
  char line[1000];
  ...
  // Read faces
  for(int i = 0;i<number_of_faces;)
  {
    std::vector<Index > face;
    int valence;
    if(fscanf(off_file,"%d",&valence)==1)
    {
      face.resize(valence);
      for(int j = 0;j<valence;j++)
      {
        int index;
        if(j<valence-1)
        {
          fscanf(off_file,"%d",&index);
        }else{
          fscanf(off_file,"%d%*[^\n]",&index);
        }

        face[j] = index;
      }
      F[i] = face;
      i++;
    }else if(
             fscanf(off_file,"%[#]",&tic_tac_toe)==1)
    {
      char comment[1000];                                   [1]
      fscanf(off_file,"%[^\n]",comment);                    [2]
    }else
    {
      printf("Error: bad line\n");
      fclose(off_file);
      return false;
    }
  }
  fclose(off_file);
  return true;
}     

At [1] a 1000 byte buffer is created on the stack to hold the parsed comment. At [2] an unbounded format string is passsed to fscanf. Since this format string is an unbounded charset, a long comment that does not contain a newline will cause an overflow of the buffer comment.

Crash Information

Summary: ASAN detected stack-buffer-overflow in __isoc99_fscanf after a WRITE leading to SIGABRT (si_signo=6) / SI_TKILL (si_code=-6)
Command line: ./example @@
Testcase: ../build/output/default/crashes/hand_built_comment
Crash bucket: 77ef2cbae80aaba2f417f9359792c75c

Crashing thread backtrace:
#0  0x00007ffff7aaaa7c in __pthread_kill_implementation (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:44

#1  0x00007ffff7aaaa7c in __pthread_kill_internal (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:78

#2  0x00007ffff7aaaa7c in __GI___pthread_kill (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:89

#3  0x00007ffff7a56476 in __GI_raise (/lib/x86_64-linux-gnu/libc.so.6)
                       at ../sysdeps/posix/raise.c:26

#4  0x00007ffff7a3c7f3 in __GI_abort (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./stdlib/abort.c:79

#5  0x0000555555655207 in __sanitizer::Abort() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#6  0x0000555555653031 in __sanitizer::Die() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#7  0x0000555555635e57 in __asan::ScopedInErrorReport::~ScopedInErrorReport() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#8  0x0000555555638c6c in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#9  0x00005555555cd793 in scanf_common(void*, int, bool, char const*, __va_list_tag*) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#10 0x00005555555ce2fd in __isoc99_fscanf (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#11 0x0000555555679002 in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                        30: bool igl::readOFF<double, int>(off_file = (FILE *)<optimized out>, V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: FILE * off_file = <optimized out>; */
                       151:     {
                       152:       char comment[1000];
                       153:       fscanf(off_file,"%[^\n]",comment);
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:153

#12 0x0000555555672b5d in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       ??: bool igl::readOFF<double, int>(V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 8, capacity 8 = {std::vector of length 3, capacity 3 = {8, 0.10000000000000001, 0.75}, std::vector of length 3, capacity 3 = {0, 0.80000000000000004, 0.79000000000000004}, std::vector of length 3, capacity 3 = {-1, 0.40000000000000002, 0.75}, std::vector of length 3, capacity 3 = {-1, 0.40000000000000002, 0.75}, std::vector of length 3, capacity 3 = {4, 0.69999999999999996, 4}, std::vector of length 3, capacity 3 = {0.69999999999999996, 0, 0.75}, std::vector of length 3, capacity 3 = {-1, 1, 0.75}, std::vector of length 3, capacity 3 = {4, 0, 2}}, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, off_file_name = (const std::string)) {
                       ||:
                       ||: /* Local reference: FILE * off_file = 0x0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & V = std::vector of length 8, capacity 8 = {std::vector of length 3, ca... */
                       ||: /* Local reference: class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > & F = <optimized out>; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & N = std::vector of length 0, capacity 0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & C = std::vector of length 0, capacity 0; */
                       24:     return false;
                       25:   }
                       26:   return readOFF(off_file,V,F,N,C);
                       ||:
                       --: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:26

#13 0x0000555555672b5d in igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> > (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       168: bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(str = (const std::string), V = (class Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > &)<optimized out>, F = (class Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: bool success = <optimized out>; */
                       |||: /* Local reference: const std::string str = ; */
                       175:   std::vector<std::vector<int> > vF;
                       176:   std::vector<std::vector<double> > vC;
                       177:   bool success = igl::readOFF(str,vV,vF,vN,vC);
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:177

#14 0x00005555556723b1 in main (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       7: int main(argc = (int)<optimized out>, argv = (char **)<optimized out>) {
                       |: /* Local reference: int argc = <optimized out>; */
                       |: /* Local reference: char ** argv = <optimized out>; */
                       8: {
                       9:       igl::readOFF(argv[1], V, F);
                       |:
                       -: }
                       at /home/fuzz/libigl-example-project/main.cpp:9

ASAN Report:
=================================================================
==12222==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff60015d8 at pc 0x5555555cd771 bp 0x7fffffffdc60 sp 0x7fffffffd3e8
WRITE of size 1793 at 0x7ffff60015d8 thread T0
/usr/bin/addr2line: DWARF error: invalid or unhandled FORM value: 0x23
    #0 0x5555555cd770 in scanf_common(void*, int, bool, char const*, __va_list_tag*) crtstuff.c:?
    #1 0x5555555ce2fc in __interceptor___isoc99_fscanf ??:?
    #2 0x555555679001 in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?
    #3 0x555555672b5c in bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&) ld-temp.o:?
    #4 0x5555556723b0 in main ??:?
    #5 0x7ffff7a3dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x7ffff7a3de3f in __libc_start_main_impl csu/../csu/libc-start.c:392
    #7 0x5555555abdb4 in _start ??:?

Address 0x7ffff60015d8 is located in stack of thread T0 at offset 5592 in frame
    #0 0x55555567522f in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?

  This frame has 32 object(s):
    [32, 40) '__dnew.i.i569'
    [64, 72) '__dnew.i.i553'
    [96, 104) '__dnew.i.i525'
    [128, 136) '__dnew.i.i512'
    [160, 168) '__dnew.i.i500'
    [192, 1192) 'header' (line 44)
    [1328, 1360) 'OFF' (line 45)
    [1392, 1424) 'NOFF' (line 46)
    [1456, 1488) 'COFF' (line 47)
    [1520, 1552) 'ref.tmp9' (line 48)
    [1584, 1616) 'ref.tmp22' (line 48)
    [1648, 1680) 'ref.tmp37' (line 48)
    [1712, 1744) 'ref.tmp136' (line 58)
    [1776, 1808) 'ref.tmp150' (line 59)
    [1840, 1844) 'number_of_vertices' (line 61)
    [1856, 1860) 'number_of_faces' (line 62)
    [1872, 1876) 'number_of_edges' (line 63)
    [1888, 1890) 'tic_tac_toe' (line 64)
    [1904, 2904) 'line' (line 65)
    [3040, 3048) 'x' (line 84)
    [3072, 3080) 'y' (line 84)
    [3104, 3112) 'z' (line 84)
    [3136, 3144) 'nx' (line 84)
    [3168, 3176) 'ny' (line 84)
    [3200, 3208) 'nz' (line 84)
    [3232, 3256) 'vertex' (line 87)
    [3296, 3320) 'normal' (line 96)
    [3360, 4360) 'comment' (line 115)
    [4496, 4520) 'face' (line 130)
    [4560, 4564) 'valence' (line 131)
    [4576, 4580) 'index' (line 137)
    [4592, 5592) 'comment322' (line 152) <== Memory access at offset 5592 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow crtstuff.c:? in scanf_common(void*, int, bool, char const*, __va_list_tag*)
Shadow bytes around the buggy address:
  0x10007ebf8260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf82a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10007ebf82b0: 00 00 00 00 00 00 00 00 00 00 00[f3]f3 f3 f3 f3
  0x10007ebf82c0: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00
  0x10007ebf82d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf82e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf82f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==12222==ABORTING

Crash context:
Execution stopped here ==> 0x00007ffff7aaaa7c: mov    r13d,eax

Register info:
   rax - 0x0000000000000000 (0)
   rbx - 0x00007ffff7957800 (140737347155968)
   rcx - 0x00007ffff7aaaa7c (140737348545148)
   rdx - 0x0000000000000006 (6)
   rsi - 0x0000000000002fbe (12222)
   rdi - 0x0000000000002fbe (12222)
   rbp - 0x0000000000002fbe (0x2fbe)
   rsp - 0x00007fffffffc440 (0x7fffffffc440)
    r8 - 0x00007fffffffc510 (140737488340240)
    r9 - 0xbfffff00000fffff (-4611687117937967105)
   r10 - 0x0000000000000008 (8)
   r11 - 0x0000000000000246 (582)
   r12 - 0x0000000000000006 (6)
   r13 - 0x0000000000000016 (22)
   r14 - 0x1000000000000000 (1152921504606846976)
   r15 - 0x2000000000000000 (2305843009213693952)
   rip - 0x00007ffff7aaaa7c (0x7ffff7aaaa7c <__GI___pthread_kill+300>)
eflags - 0x00000246 ([ PF ZF IF ])
    cs - 0x00000033 (51)
    ss - 0x0000002b (43)
    ds - 0x00000000 (0)
    es - 0x00000000 (0)
    fs - 0x00000000 (0)
    gs - 0x00000000 (0)    

Mitigation

This fscanf format string should be limited to 1000 bytes to prevent a buffer overflow of comment.

CVE-2023-35953 - vertices comment buffer overflow

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)
{
  ...
  int number_of_vertices;
  int number_of_faces;
  int number_of_edges;
  char tic_tac_toe;
  char line[1000];
  bool still_comments = true;
  ...
  // Read vertices
  for(int i = 0;i<number_of_vertices;)
  {
    fgets(line, 1000, off_file);
    double x,y,z,nx,ny,nz;
    if(sscanf(line, "%lg %lg %lg %lg %lg %lg",&x,&y,&z,&nx,&ny,&nz)>= 3)
    {
      std::vector<Scalar > vertex;
      vertex.resize(3);
      vertex[0] = x;
      vertex[1] = y;
      vertex[2] = z;
      V[i] = vertex;

      if (has_normals)
      {
        std::vector<Scalar > normal;
        normal.resize(3);
        normal[0] = nx;
        normal[1] = ny;
        normal[2] = nz;
        N[i] = normal;
      }

      if (has_vertexColors)
      {
        C[i].resize(3);
        C[i][0] = nx / 255.0;
        C[i][1] = ny / 255.0;
        C[i][2] = nz / 255.0;
      }
      i++;
    }else if(
        fscanf(off_file,"%[#]",&tic_tac_toe)==1)
    {
      char comment[1000];                                                       [1]
      fscanf(off_file,"%[^\n]",comment);                                        [2]
    }else
    {
      printf("Error: bad line (%d)\n",i);
      if(feof(off_file))
      {
        fclose(off_file);
        return false;
      }
    }
  }
...    

At [1] a 1000 byte buffer is created on the stack to hold the parsed comment. At [2] an unbounded format string is passsed to fscanf. Since this format string is an unbounded charset, a long comment that does not contain a newline will cause an overflow of the buffer comment.

Crash Information

Summary: ASAN detected stack-buffer-overflow in __isoc99_fscanf after a WRITE leading to SIGABRT (si_signo=6) / SI_TKILL (si_code=-6)
Command line: ./example @@
Testcase: ../build/output/default/crashes/hand_built_vertices_comment
Crash bucket: eef839253a187b03b64c4f9117c21887

Crashing thread backtrace:
#0  0x00007ffff7aaaa7c in __pthread_kill_implementation (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:44

#1  0x00007ffff7aaaa7c in __pthread_kill_internal (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:78

#2  0x00007ffff7aaaa7c in __GI___pthread_kill (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./nptl/pthread_kill.c:89

#3  0x00007ffff7a56476 in __GI_raise (/lib/x86_64-linux-gnu/libc.so.6)
                       at ../sysdeps/posix/raise.c:26

#4  0x00007ffff7a3c7f3 in __GI_abort (/lib/x86_64-linux-gnu/libc.so.6)
                       at ./stdlib/abort.c:79

#5  0x0000555555655207 in __sanitizer::Abort() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#6  0x0000555555653031 in __sanitizer::Die() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#7  0x0000555555635e57 in __asan::ScopedInErrorReport::~ScopedInErrorReport() (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#8  0x0000555555638c6c in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#9  0x00005555555cd793 in scanf_common(void*, int, bool, char const*, __va_list_tag*) (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#10 0x00005555555ce2fd in __isoc99_fscanf (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
#11 0x00005555556783b6 in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                        30: bool igl::readOFF<double, int>(off_file = (FILE *)<optimized out>, V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 8, capacity 8 = {std::vector of length 3, capacity 3 = {8, 0.10000000000000001, 0.75}, std::vector of length 3, capacity 3 = {0, 0.80000000000000004, 0.79000000000000004}, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0}, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: FILE * off_file = <optimized out>; */
                       114:     {
                       115:       char comment[1000];
                       116:       fscanf(off_file,"%[^\n]",comment);
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:116

#12 0x0000555555672b5d in igl::readOFF<double, int> (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       ??: bool igl::readOFF<double, int>(V = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 8, capacity 8 = {std::vector of length 3, capacity 3 = {8, 0.10000000000000001, 0.75}, std::vector of length 3, capacity 3 = {0, 0.80000000000000004, 0.79000000000000004}, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0, std::vector of length 0, capacity 0}, F = (class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > &)<optimized out>, N = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, C = (class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > &)std::vector of length 0, capacity 0, off_file_name = (const std::string)) {
                       ||:
                       ||: /* Local reference: FILE * off_file = 0x0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & V = std::vector of length 8, capacity 8 = {std::vector of length 3, ca... */
                       ||: /* Local reference: class std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > & F = <optimized out>; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & N = std::vector of length 0, capacity 0; */
                       ||: /* Local reference: class std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > & C = std::vector of length 0, capacity 0; */
                       24:     return false;
                       25:   }
                       26:   return readOFF(off_file,V,F,N,C);
                       ||:
                       --: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:26

#13 0x0000555555672b5d in igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> > (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       168: bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(str = (const std::string), V = (class Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > &)<optimized out>, F = (class Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > &)<optimized out>) {
                       |||:
                       |||: /* Local reference: bool success = <optimized out>; */
                       |||: /* Local reference: const std::string str = ; */
                       175:   std::vector<std::vector<int> > vF;
                       176:   std::vector<std::vector<double> > vC;
                       177:   bool success = igl::readOFF(str,vV,vF,vN,vC);
                       |||:
                       ---: }
                       at _deps/libigl-src/include/igl/readOFF.cpp:177

#14 0x00005555556723b1 in main (r-xp   /home/fuzz/libigl-example-project/asan_build/example)
                       7: int main(argc = (int)<optimized out>, argv = (char **)<optimized out>) {
                       |: /* Local reference: int argc = <optimized out>; */
                       |: /* Local reference: char ** argv = <optimized out>; */
                       8: {
                       9:       igl::readOFF(argv[1], V, F);
                       |:
                       -: }
                       at /home/fuzz/libigl-example-project/main.cpp:9

ASAN Report:
=================================================================
==14686==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff6001108 at pc 0x5555555cd771 bp 0x7fffffffdc60 sp 0x7fffffffd3e8
WRITE of size 1793 at 0x7ffff6001108 thread T0
/usr/bin/addr2line: DWARF error: invalid or unhandled FORM value: 0x23
    #0 0x5555555cd770 in scanf_common(void*, int, bool, char const*, __va_list_tag*) crtstuff.c:?
    #1 0x5555555ce2fc in __interceptor___isoc99_fscanf ??:?
    #2 0x5555556783b5 in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?
    #3 0x555555672b5c in bool igl::readOFF<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&) ld-temp.o:?
    #4 0x5555556723b0 in main ??:?
    #5 0x7ffff7a3dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x7ffff7a3de3f in __libc_start_main_impl csu/../csu/libc-start.c:392
    #7 0x5555555abdb4 in _start ??:?

Address 0x7ffff6001108 is located in stack of thread T0 at offset 4360 in frame
    #0 0x55555567522f in bool igl::readOFF<double, int>(_IO_FILE*, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&) ld-temp.o:?

  This frame has 32 object(s):
    [32, 40) '__dnew.i.i569'
    [64, 72) '__dnew.i.i553'
    [96, 104) '__dnew.i.i525'
    [128, 136) '__dnew.i.i512'
    [160, 168) '__dnew.i.i500'
    [192, 1192) 'header' (line 44)
    [1328, 1360) 'OFF' (line 45)
    [1392, 1424) 'NOFF' (line 46)
    [1456, 1488) 'COFF' (line 47)
    [1520, 1552) 'ref.tmp9' (line 48)
    [1584, 1616) 'ref.tmp22' (line 48)
    [1648, 1680) 'ref.tmp37' (line 48)
    [1712, 1744) 'ref.tmp136' (line 58)
    [1776, 1808) 'ref.tmp150' (line 59)
    [1840, 1844) 'number_of_vertices' (line 61)
    [1856, 1860) 'number_of_faces' (line 62)
    [1872, 1876) 'number_of_edges' (line 63)
    [1888, 1890) 'tic_tac_toe' (line 64)
    [1904, 2904) 'line' (line 65)
    [3040, 3048) 'x' (line 84)
    [3072, 3080) 'y' (line 84)
    [3104, 3112) 'z' (line 84)
    [3136, 3144) 'nx' (line 84)
    [3168, 3176) 'ny' (line 84)
    [3200, 3208) 'nz' (line 84)
    [3232, 3256) 'vertex' (line 87)
    [3296, 3320) 'normal' (line 96)
    [3360, 4360) 'comment' (line 115)
    [4496, 4520) 'face' (line 130) <== Memory access at offset 4360 partially underflows this variable
    [4560, 4564) 'valence' (line 131) <== Memory access at offset 4360 partially underflows this variable
    [4576, 4580) 'index' (line 137) <== Memory access at offset 4360 partially underflows this variable
    [4592, 5592) 'comment322' (line 152) <== Memory access at offset 4360 partially underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow crtstuff.c:? in scanf_common(void*, int, bool, char const*, __va_list_tag*)
Shadow bytes around the buggy address:
  0x10007ebf81d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf81e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf81f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007ebf8210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10007ebf8220: 00[f2]f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2
  0x10007ebf8230: f2 f2 f8 f8 f8 f2 f2 f2 f2 f2 f8 f2 f8 f2 f8 f8
  0x10007ebf8240: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
  0x10007ebf8250: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
  0x10007ebf8260: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
  0x10007ebf8270: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
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
==14686==ABORTING

Crash context:
Execution stopped here ==> 0x00007ffff7aaaa7c: mov    r13d,eax

Register info:
   rax - 0x0000000000000000 (0)
   rbx - 0x00007ffff7957800 (140737347155968)
   rcx - 0x00007ffff7aaaa7c (140737348545148)
   rdx - 0x0000000000000006 (6)
   rsi - 0x000000000000395e (14686)
   rdi - 0x000000000000395e (14686)
   rbp - 0x000000000000395e (0x395e)
   rsp - 0x00007fffffffc440 (0x7fffffffc440)
    r8 - 0x00007fffffffc510 (140737488340240)
    r9 - 0xbfffff00000fffff (-4611687117937967105)
   r10 - 0x0000000000000008 (8)
   r11 - 0x0000000000000246 (582)
   r12 - 0x0000000000000006 (6)
   r13 - 0x0000000000000016 (22)
   r14 - 0x1000000000000000 (1152921504606846976)
   r15 - 0x2000000000000000 (2305843009213693952)
   rip - 0x00007ffff7aaaa7c (0x7ffff7aaaa7c <__GI___pthread_kill+300>)
eflags - 0x00000246 ([ PF ZF IF ])
    cs - 0x00000033 (51)
    ss - 0x0000002b (43)
    ds - 0x00000000 (0)
    es - 0x00000000 (0)
    fs - 0x00000000 (0)
    gs - 0x00000000 (0)    

Mitigation

This fscanf format string should be limited to 1000 bytes to prevent a buffer overflow of comment.

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 Carl Hurd of Cisco Talos.