Talos Vulnerability Report

TALOS-2022-1594

ADMesh stl_fix_normal_directions improper array index validation vulnerability

April 3, 2023
CVE Number

CVE-2022-38072

SUMMARY

An improper array index validation vulnerability exists in the stl_fix_normal_directions functionality of ADMesh Master Commit 767a105 and v0.98.4. A specially-crafted stl file can lead to a heap 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.

ADMesh Master Commit 767a105
Slic3r libslic3r Master Commit b1a5500
ADMesh v0.98.4

PRODUCT URLS

ADMesh - https://github.com/admesh/admesh libslic3r - http://slic3r.org

CVSSv3 SCORE

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

CWE

CWE-118 - Incorrect Access of Indexable Resource (‘Range Error’)

DETAILS

ADMesh is a C library used to process 3D triangular meshes.

The ADMesh library can be used to repair an STL in different aspects, one of which is the normal direction facet. Following the ADMesh’s function stl_fix_normal_directions can be used to repair the STL to have the facets in normal directions:

void stl_fix_normal_directions(stl_file *stl) {
  char *norm_sw;
  [...]
    
  /* Initialize list that keeps track of already fixed facets. */
  norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char));                                   [1]
  if(norm_sw == NULL) perror("stl_fix_normal_directions");


  [...]

  /* Say that we've fixed this facet: */
  norm_sw[facet_num] = 1;
  checked++;

  for(;;) {
    [...]
      /* If this edge of the facet is connected: */
      if(stl->neighbors_start[facet_num].neighbor[j] != -1) {
        /* If we haven't fixed this facet yet, add it to the list: */
        if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) {                                 [2]
          /* Add node to beginning of list. */
          newn = (struct stl_normal*)malloc(sizeof(struct stl_normal));
          if(newn == NULL) perror("stl_fix_normal_directions");
          newn->facet_num = stl->neighbors_start[facet_num].neighbor[j];
          newn->next = head->next;
          head->next = newn;
        }
      }
    }
    [...]
}

At [1] the array of fixed facets, named norm_sw, is allocated using stl->stats.number_of_facets. At [2] norm_sw is accessed using the neighbor value of the facets that the loop is currently fixing. Based on the provided STL, this value can exceed the actual capacity of the norm_sw array.

Indeed, we can analyze, at [2], the relevant values to realize that norm_sw is accessed out of bounds:

(gdb) heap chunk norm_sw
Chunk(addr=0x560a136a6890, size=0xe10, flags=PREV_INUSE)
Chunk size: 3600 (0xe10)
Usable size: 3592 (0xe08)
Previous chunk size: 139680311529408 (0x7f09db59cfc0)
PREV_INUSE flag: On
IS_MMAPPED flag: Off
NON_MAIN_ARENA flag: Off

Because norm_sw is an array of characters, we have exactly 0xe08 usable positions. However, at a certain point we will have the following:

(gdb) p stl->neighbors_start[facet_num].neighbor[j]
$1 = 0xe7d

So norm_sw[stl->neighbors_start[facet_num].neighbor[j]] is going to access out of the bounds of norm_sw:

(gdb) p &norm_sw[stl->neighbors_start[facet_num].neighbor[j]]
$23 = 0x55555555d70d "U"

(gdb) p *(struct stl_normal *) 0x0055555555d700
$21 = {
  facet_num = 0x824d,
  next = 0x55555555d6e0
}

Because no check about the value used as index is performed, with the STL tested, the instruction at [2] will access the memory region of one of the nodes created inside the code block executed if [2] is true. Later on, a 1 is going to be written at norm_sw[stl->neighbors_start[facet_num].neighbor[j]] position.

Crash Information

=================================================================
==8==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x620000000efd at pc 0x7ffff78fcdee bp 0x7fffffffe240 sp 0x7fffffffe238
READ of size 1 at 0x620000000efd thread T0
    #0 0x7ffff78fcded in stl_fix_normal_directions /app/admesh/src/normals.c:147
    #1 0x7ffff79132a5 in stl_repair /app/admesh/src/util.c:603
    #2 0x55555555a955 in main /app/admesh/src/admesh.c:383
    #3 0x7ffff75f381c in __libc_start_main ../csu/libc-start.c:332
    #4 0x555555558349 in _start (/app/admesh/build/admesh+0x4349)

0x620000000efd is located 119 bytes to the right of 3590-byte region [0x620000000080,0x620000000e86)
allocated by thread T0 here:
    #0 0x7ffff79d53b7 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:77
    #1 0x7ffff78fcace in stl_fix_normal_directions /app/admesh/src/normals.c:117
    #2 0x7ffff79132a5 in stl_repair /app/admesh/src/util.c:603
    #3 0x55555555a955 in main /app/admesh/src/admesh.c:383
    #4 0x7ffff75f381c in __libc_start_main ../csu/libc-start.c:332

SUMMARY: AddressSanitizer: heap-buffer-overflow /app/admesh/src/normals.c:147 in stl_fix_normal_directions
Shadow bytes around the buggy address:
  0x0c407fff8180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c407fff8190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c407fff81a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c407fff81b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c407fff81c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c407fff81d0: 06 fa fa fa fa fa fa fa fa fa fa fa fa fa fa[fa]
  0x0c407fff81e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c407fff81f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c407fff8200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c407fff8210: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c407fff8220: 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
==8==ABORTING
TIMELINE

2022-09-01 - Initial Vendor Contact
2022-09-13 - Vendor Disclosure
2022-11-21 - Vendor Patch Release
2023-04-03 - Public Release

Credit

Discovered by Francesco Benvenuto of Cisco Talos.