Open kitaharazy opened 2 years ago
I am not sure if I understand the bug. In my option, the root cause is because one chunk's start address is another chunk's end address. It means the two chunk have a "0-byte overlap". The bug is crazy, it may means we can not use tcmalloc, because the memory allocated by tcmalloc is completely contiguous but ptmalloc not.
I have another method, we can reserve 8-byte address at the end of chunk at any time. Then chunks in memory will not be contiguous.
We can even use the reserved area as 'heap canary`.
How this overlap happened: link
reproduction case:
compile it by v++
#include<vector>
#include<iostream>
char data[0x9] = "aaaaaaaa";
void trigger(){
std::vector<std::string> v;
v.push_back(std::string(data));
printf("\nv._M_impl._M_start: %#lx\nv._M_impl._M_end: %#lx\nv._M_impl._M_end_of_storage: %#lx\n\n",*(unsigned long *)((char *)&v+0),*(unsigned long *)((char *)&v+8),*(unsigned long *)((char *)&v+0x10));
std::vector<std::string> v2;
v2.push_back(std::string(data));
printf("\nv2._M_impl._M_start: %#lx\nv2._M_impl._M_end: %#lx\nv2._M_impl._M_end_of_storage: %#lx\n\n",*(unsigned long *)((char *)&v2+0),*(unsigned long *)((char *)&v2+8),*(unsigned long *)((char *)&v2+0x10));
}
int main(){
std::vector<std::string> args;
args.push_back(std::string(data));
printf("\nargs._M_impl._M_start: %#lx\nargs._M_impl._M_end: %#lx\nargs._M_impl._M_end_of_storage: %#lx\n\n",*(unsigned long *)((char *)&args+0),*(unsigned long *)((char *)&args+8),*(unsigned long *)((char *)&args+0x10));
trigger();
}
In ptmalloc, this kind of vector overlap won't happen, in violet it will happen.
I think the reason why the bug won't happen in ptmalloc is that ptmalloc will add extra bytes for each chunk's control struct, so two chunk allocate by malloc() won't be 0-byte overlap. Therefore, maybe we can try reserving some bytes, also can be used as heap canary
, at the end of each chunk as what Zheng said.
violet:
args._M_impl._M_start: 0x147d7fa183e0
args._M_impl._M_end: 0x147d7fa18400
args._M_impl._M_end_of_storage: 0x147d7fa18400
v._M_impl._M_start: 0x147d7fa18000
v._M_impl._M_end: 0x147d7fa18020 // *
v._M_impl._M_end_of_storage: 0x147d7fa18020 // *
v2._M_impl._M_start: 0x147d7fa18020 // *
v2._M_impl._M_end: 0x147d7fa18040
v2._M_impl._M_end_of_storage: 0x147d7fa18040
native clang++
args._M_impl._M_start: 0x417eb0
args._M_impl._M_end: 0x417ed0
args._M_impl._M_end_of_storage: 0x417ed0
v._M_impl._M_start: 0x4182f0
v._M_impl._M_end: 0x418310 // *
v._M_impl._M_end_of_storage: 0x418310 // *
v2._M_impl._M_start: 0x418320 // *
v2._M_impl._M_end: 0x418340
v2._M_impl._M_end_of_storage: 0x418340
The escape is meant to be simple and light, we shouldn’t make it too complex.
On Sun, Oct 9, 2022 at 13:40 dataisland @.***> wrote:
We can even use the reserved area as 'heap canary`.
— Reply to this email directly, view it on GitHub https://github.com/Markakd/safe_tcmalloc/issues/2#issuecomment-1272603979, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEOMNZO2XIHBZWPNHNNSBRTWCMGSDANCNFSM6AAAAAARAYKGA4 . You are receiving this because you are subscribed to this thread.Message ID: @.***>
Basically, in cnedfunction.cc,
void cNEDFunction::parseSignature(const char *signature)
In the end of
splitTypeAndName
,~vector -> ~_Vector_base
will finally be called to_M_deallocate
,At this time, the
std::vector<std::string> v
create insplitTypeAndName
is going to be deallocated,however, when the local
std::vector<std::string> v
being deallocated:Notice that
v._M_start = 0x147d7fa20400
which equals toargs._M_impl._M_finish = 0x147d7fa20400
Because of that when v._M_start = 0x147d7fa20400 is poisioned, it will affect args._M_impl like:
which means that
args._M_impl._M_finish = args._M_impl._M_end_of_storage = v._M_impl._M_start = 0cdeadbeefdeadbeef
After we reach the end of
parseSignature
,~vector
will also be called to deallocatestd::vector<std::string> args
But because the poison, the args is like:Which is obviously invalid.
The escape bug in this case can be triggered and expounded through the gdb script: