| Description | ### Description
We discovered a Segmentation Fault vulnerability in xlnt. The crash occurs in the xlnt::detail::xlsx_consumer::read_office_document function when parsing a malformed XLSX file.
The ASAN report indicates a SEGV on address 0x000000000008. The stack trace points to std::find_if and std::pair construction, triggered by a lambda function inside xlsx_consumer.cpp. This indicates a NULL pointer dereference (accessing a member at offset 0x8 from a NULL pointer) when processing document relationships.
### Environment
- OS: Linux x86_64
- Complier: Clang
- Build Configuration: Release mode with ASan enabled.
### Vulnerability Details
- Target: xlnt
- Vulnerability Type: CWE-476: NULL Pointer Dereference
- Function: xlnt::detail::xlsx_consumer::read_office_document
- Location: source/detail/serialization/xlsx_consumer.cpp:2308:22
- Caller: read_part -> populate_workbook -> workbook::load
- Root Cause Analysis: The crash occurs at line 2308, where std::find_if is used to search for a specific relationship. The stack trace shows: std::basic_string(...) <- std::pair(...) <- std::find_if(...) <- read_office_document. It appears that the lambda function passed to find_if attempts to access properties (likely Id or Target) of a relationship object that is either NULL or invalid (e.g., missing XML attributes). When std::string or std::pair tries to construct from this missing data, it dereferences the NULL pointer (specifically at offset 0x8), causing the crash.
### Reproduce
1. Build xlnt and harness with Release optimization and ASAN enabled.
<details>
<summary>harness.c</summary>
```
#include <xlnt/xlnt.hpp>
#include <iostream>
#include <string>
#include <vector>
int main(int argc, char **argv) {
if (argc < 2) {
return 0;
}
std::string filepath = argv[1];
try {
xlnt::workbook wb;
wb.load(filepath);
if (wb.sheet_count() > 0) {
auto ws = wb.active_sheet();
for (auto row : ws.rows(false)) {
for (auto cell : row) {
(void)cell.to_string();
}
}
}
} catch (const xlnt::exception& e) {
} catch (const std::exception& e) {
} catch (...) {
}
return 0;
}
```
</details>
2. Run with the crashing [file](https://github.com/oneafter/0128/blob/main/xl4/repro):
```
./harness repro
```
<details>
<summary>ASAN report</summary>
```
AddressSanitizer:DEADLYSIGNAL
=================================================================
==46162==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x7f115207c51b bp 0x7ffd734f8e30 sp 0x7ffd734f8d80 T0)
==46162==The signal is caused by a READ memory access.
==46162==Hint: address points to the zero page.
#0 0x7f115207c51b in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/basic_string.h:551
#1 0x7f115207c51b in std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, true>(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>> const&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_pair.h:586:4
#2 0x7f115201d525 in bool __gnu_cxx::__ops::_Iter_pred<xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0>::operator()<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>>(std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/predefined_ops.h:318:24
#3 0x7f115201d525 in std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true> std::__find_if<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, __gnu_cxx::__ops::_Iter_pred<xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0>>(std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, __gnu_cxx::__ops::_Iter_pred<xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0>, std::input_iterator_tag) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_algobase.h:2055:36
#4 0x7f115201d525 in std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true> std::__find_if<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, __gnu_cxx::__ops::_Iter_pred<xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0>>(std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, __gnu_cxx::__ops::_Iter_pred<xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0>) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_algobase.h:2117:14
#5 0x7f115201d525 in std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true> std::find_if<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0>(std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, false, true>, xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_algo.h:3923:14
#6 0x7f115201d525 in xlnt::detail::xlsx_consumer::read_office_document(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/repro/xlnt/source/detail/serialization/xlsx_consumer.cpp:2308:22
#7 0x7f1151f631b8 in xlnt::detail::xlsx_consumer::read_part(std::vector<xlnt::relationship, std::allocator<xlnt::relationship>> const&) /src/repro/xlnt/source/detail/serialization/xlsx_consumer.cpp:1752:9
#8 0x7f1151ecac86 in xlnt::detail::xlsx_consumer::populate_workbook(bool) /src/repro/xlnt/source/detail/serialization/xlsx_consumer.cpp:1902:5
#9 0x7f1151b12076 in xlnt::workbook::load(std::istream&) /src/repro/xlnt/source/workbook/workbook.cpp:965:18
#10 0x7f1151b10415 in xlnt::workbook::load(xlnt::path const&) /src/repro/xlnt/source/workbook/workbook.cpp:1020:5
#11 0x7f1151b492c2 in void xlnt::workbook::load_internal<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/repro/xlnt/source/workbook/workbook.cpp:996:12
#12 0x7f1151b492c2 in xlnt::workbook::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/repro/xlnt/source/workbook/workbook.cpp:1007:12
#13 0x55ca78a702be in main /src/repro/xlnt/fuzz_xlnt.cpp:18:12
#14 0x7f11512cb1c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a1 |
|---|