CVE-2020-28596
A stack-based buffer overflow vulnerability exists in the Objparser::objparse() functionality of Prusa Research PrusaSlicer 2.2.0 and Master (commit 4b040b856). A specially crafted obj file can lead to code execution. An attacker can provide a malicious file to trigger this vulnerability.
Prusa Research PrusaSlicer 2.2.0
Prusa Research PrusaSlicer Master (commit 4b040b856)
https://www.prusa3d.com/prusaslicer/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-121 - Stack-based Buffer Overflow
Prusa Slicer is an open-source 3-D printer slicing program forked off Slic3r that can convert various 3-D model file formats and can output corresponding 3-D printer-readable Gcode.
One of the input file formats PrusaSlicer can deal with is .obj
files, the code mainly handling this can be found in PrusaSlicer/src/libslic3r/Format/OBJ.cpp
and PrusaSlicer/src/libslic3r/Format/objparser.cpp
. We now proceed to trace the code-path from entry to vulnerability:
bool load_obj(const char *path, TriangleMesh *meshptr){
if(meshptr == nullptr) return false;
// Parse the OBJ file.
ObjParser::ObjData data;
if (! ObjParser::objparse(path, data)) { // [1]
// die "Failed to parse $file\n" if !-e $path;
return false;
}
// [...]
At [1], we provide a path to a .obj
file, which is then processed into the ObjParser::ObjData
object, which we will examine the structure of when needed. Continuing into ObjParser::objparse
:
bool objparse(const char *path, ObjData &data)
{
FILE *pFile = boost::nowide::fopen(path, "rt");
if (pFile == 0)
return false;
try {
char buf[65536 * 2]; // [1]
size_t len = 0;
size_t lenPrev = 0;
while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) { // [2]
len += lenPrev;
size_t lastLine = 0;
for (size_t i = 0; i < len; ++ i)
if (buf[i] == '\r' || buf[i] == '\n') {
buf[i] = 0;
char *c = buf + lastLine;
while (*c == ' ' || *c == '\t')
++ c;
obj_parseline(c, data);
lastLine = i + 1;
}
lenPrev = len - lastLine; //
memmove(buf, buf + lastLine, lenPrev);
}
}
catch (std::bad_alloc&) {
printf("Out of memory\r\n");
}
::fclose(pFile);
// printf("vertices: %d\r\n", data.vertices.size() / 4);
// printf("coords: %d\r\n", data.coordinates.size());
return true;
}
At [2] we can clearly see the call to fread
reading into a fixed-size stack buffer at [1] inside of a while loop that is completely at the input file’s behest, resulting in a stack-based buffer overflow that can lead to arbitrary code execution.
=================================================================
==593876==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff5b94fd80 at pc 0x0000004d3521 bp 0x7fff5b92fc70 sp 0x7fff5b92f438
WRITE of size 12928 at 0x7fff5b94fd80 thread T0
#0 0x4d3520 in fread (/root/boop/assorted_fuzzing/prusaslicer/obj_fuzzdir/fuzzobj.bin+0x4d3520)
#1 0x7f2f2ceb7a94 in ObjParser::objparse(char const*, ObjParser::ObjData&) boop/assorted_fuzzing/prusaslicer/PrusaSlicer/src/libslic3r/Format/objparser.cpp:332:17
Address 0x7fff5b94fd80 is located in stack of thread T0 at offset 131104 in frame
#0 0x7f2f2ceb78af in ObjParser::objparse(char const*, ObjParser::ObjData&) /boop/assorted_fuzzing/prusaslicer/PrusaSlicer/src/libslic3r/Format/objparser.cpp:323
This frame has 1 object(s):
[32, 131104) 'buf' (line 329) <== Memory access at offset 131104 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 (/boop/assorted_fuzzing/prusaslicer/obj_fuzzdir/fuzzobj.bin+0x4d3520) in fread
Shadow bytes around the buggy address:
0x10006b721f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10006b721f70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10006b721f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10006b721f90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10006b721fa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10006b721fb0:[f3]f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x10006b721fc0: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x10006b721fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10006b721fe0: 00 00 00 00 f1 f1 f1 f1 00 00 00 00 00 00 00 00
0x10006b721ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10006b722000: 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2 f2
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
Shadow gap: cc
==593876==ABORTING
2020-12-14 - Vendor disclosure
2021-01-14 - Vendor patched
2021-04-21 - Public release
Discovered by Lilith >_> of Cisco Talos.