Submit #754377: xlnt-community xlnt 5606f5b Heap-based Buffer Overflowinfo

Titlexlnt-community xlnt 5606f5b Heap-based Buffer Overflow
Description### Description We discovered a Heap-buffer-overflow vulnerability in xlnt. The crash occurs in the xlnt::detail::decode_base64 function when parsing an encrypted XLSX file (specifically involving Agile Encryption info). The ASAN report indicates a WRITE of size 1 occurring exactly 0 bytes after the allocated region. This suggests an Off-by-one error where the Base64 decoder writes one byte past the end of the destination vector. Vendor confirmed and fixed this vulnerability in commit [f2d7bf4](https://github.com/xiaohunqupo/xlnt/commit/f2d7bf494e5c52706843cf7eb9892821bffb0734). ### Environment - OS: Linux x86_64 - Complier: Clang - Build Configuration: Release mode with ASan enabled. ### Vulnerability Details - Target: xlnt - Vulnerability Type: CWE-193: Off-by-one Error / CWE-122: Heap-based Buffer Overflow - Function: xlnt::detail::decode_base64 - Location: source/detail/cryptography/base64.cpp:176 - Caller: read_agile_encryption_info at source/detail/cryptography/xlsx_crypto_consumer.cpp:208 - Root Cause Analysis: The function decode_base64 calculates the expected output size and allocates a std::vector (in this case, 70 bytes) at line 106. During the decoding loop, logic related to padding or index calculation appears to be slightly incorrect for certain malformed or edge-case Base64 input strings. At line 176, the code attempts to write the decoded byte into the vector, but the index exceeds the allocated size by exactly 1 byte. ### 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/xl1/repro): ``` ./harness repro ``` <details> <summary>ASAN report</summary> ``` ==44083==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x507000002056 at pc 0x7fb0d851e7a9 bp 0x7ffe97816c30 sp 0x7ffe97816c28 WRITE of size 1 at 0x507000002056 thread T0 #0 0x7fb0d851e7a8 in xlnt::detail::decode_base64(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/detail/cryptography/base64.cpp:176:32 #1 0x7fb0d85753ce in (anonymous namespace)::read_agile_encryption_info(std::istream&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:208:31 #2 0x7fb0d85753ce in (anonymous namespace)::read_encryption_info(std::istream&, std::__cxx11::basic_string<char16_t, std::char_traits<char16_t>, std::allocator<char16_t>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:278:22 #3 0x7fb0d856ff58 in (anonymous namespace)::decrypt_xlsx(std::vector<unsigned char, std::allocator<unsigned char>> const&, std::__cxx11::basic_string<char16_t, std::char_traits<char16_t>, std::allocator<char16_t>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:323:28 #4 0x7fb0d856ff58 in xlnt::detail::decrypt_xlsx(std::vector<unsigned char, std::allocator<unsigned char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:339:12 #5 0x7fb0d8572ed2 in xlnt::detail::xlsx_consumer::read(std::istream&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:345:28 #6 0x7fb0d83e76c9 in xlnt::workbook::load(std::istream&) /src/xlnt/source/workbook/workbook.cpp:906:22 #7 0x7fb0d83e5f77 in xlnt::workbook::load(xlnt::path const&) /src/xlnt/source/workbook/workbook.cpp:942:5 #8 0x7fb0d840d018 in xlnt::workbook::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/workbook/workbook.cpp:929:12 #9 0x5645520ee27e in main /src/xlnt/fuzz_xlnt.cpp:18:12 #10 0x7fb0d7c201c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e) #11 0x7fb0d7c2028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e) #12 0x56455200a594 in _start (/src/xlnt/fuzz_xlnt+0x2d594) (BuildId: 9402de847aab9fb45990c4e2333074ba128dc968) 0x507000002056 is located 0 bytes after 70-byte region [0x507000002010,0x507000002056) allocated by thread T0 here: #0 0x5645520eba41 in operator new(unsigned long) (/src/xlnt/fuzz_xlnt+0x10ea41) (BuildId: 9402de847aab9fb45990c4e2333074ba128dc968) #1 0x7fb0d851d498 in std::__new_allocator<unsigned char>::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/new_allocator.h:151:27 #2 0x7fb0d851d498 in std::allocator_traits<std::allocator<unsigned char>>::allocate(std::allocator<unsigned char>&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/alloc_traits.h:482:20 #3 0x7fb0d851d498 in std::_Vector_base<unsigned char, std::allocator<unsigned char>>::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:381:20 #4 0x7fb0d851d498 in std::_Vector_base<unsigned char, std::allocator<unsigned char>>::_M_create_storage(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:398:33 #5 0x7fb0d851d498 in std::_Vector_base<unsigned char, std::allocator<unsigned char>>::_Vector_base(unsigned long, std::allocator<unsigned char> const&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:335:9 #6 0x7fb0d851d498 in std::vector<unsigned char, std::allocator<unsigned char>>::vector(unsigned long, std::allocator<unsigned char> const&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:557:9 #7 0x7fb0d851d498 in xlnt::detail::decode_base64(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/detail/cryptography/base64.cpp:106:19 #8 0x7fb0d85753ce in (anonymous namespace)::read_agile_encryption_info(std::istream&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:208:31 #9 0x7fb0d85753ce in (anonymous namespace)::read_encryption_info(std::istream&, std::__cxx11::basic_string<char16_t, std::char_traits<char16_t>, std::allocator<char16_t>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:278:22 #10 0x7fb0d856ff58 in (anonymous namespace)::decrypt_xlsx(std::vector<unsigned char, std::allocator<unsigned char>> const&, std::__cxx11::basic_string<char16_t, std::char_traits<char16_t>, std::allocator<char16_t>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:323:28 #11 0x7fb0d856ff58 in xlnt::detail::decrypt_xlsx(std::vector<unsigned char, std::allocator<unsigned char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:339:12 #12 0x7fb0d8572ed2 in xlnt::detail::xlsx_consumer::read(std::istream&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/detail/cryptography/xlsx_crypto_consumer.cpp:345:28 #13 0x7fb0d83e76c9 in xlnt::workbook::load(std::istream&) /src/xlnt/source/workbook/workbook.cpp:906:22 #14 0x7fb0d83e5f77 in xlnt::workbook::load(xlnt::path const&) /src/xlnt/source/workbook/workbook.cpp:942:5 #15 0x7fb0d840d018 in xlnt::workbook::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) /src/xlnt/source/workbook/workbook.cpp:929:12 #16 0x5645520ee27e in main /src/xlnt/fuzz_xlnt.cpp:18:12 #17 0x7fb0d7c201c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e) #18 0x7fb0d7c2028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e) #19 0x56455200a594 in _start (/src/xlnt/fuzz_xlnt+0x2d594) (BuildId: 9402de847aab9fb45990c4e2333074ba128dc968) SUMMARY: AddressSanitizer: heap-buffer-overflow /src/xlnt/source/detail/cryptography/base64.cpp:176:32 in xlnt::detail::decode_base64(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) Shadow bytes around the buggy address: 0x507000001d80: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00 0x507000001e00: 00 00 00 00 04 fa fa fa fa fa 00 00 00 00 00 00 0x507000001e80: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00 00 0x507000001f00: 00 04 fa fa fa fa 00 00 00 00 00 00 00 00 00 fa 0x507000001f80: fa fa fa fa fd fd fd fd fd fd fd fd fd fa fa fa =>0x507000002000: fa fa 00 00 00 00 00 00 00 00[06]fa fa fa fa fa 0x507000002080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x507000002100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x507000002180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x507000002200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x507000002280: 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 redzon
Source⚠️ https://github.com/xlnt-community/xlnt/issues/137
User Oneafter (UID 92781)
Submission02/09/2026 02:10 AM (2 months ago)
Moderation02/18/2026 06:59 PM (10 days later)
StatusAccepted
VulDB entry346649 [xlnt-community xlnt up to 1.6.1 Encrypted XLSX File Parser base64.cpp decode_base64 off-by-one]
Points20

Are you interested in using VulDB?

Download the whitepaper to learn more about our service!