Skip to content

Commit 79268d8

Browse files
author
Dave Allison
committed
Fix VectorHeader/pointer invalidation after buffer relocation
PayloadBuffer's VectorPush, VectorReserve, VectorResize, Realloc, PrimeBitmapAllocator, and BitMapRun::Allocate all dereferenced pointers into the buffer (hdr, p) after Allocate/Realloc/AllocateBitMapRun calls that may have relocated the underlying buffer, leaving those pointers dangling. Save the offset before any allocation and re-derive the pointer afterward across all six call sites. Also drop a leftover write through the dangling hdr in VectorPush that slipped through a previous partial fix. Add VectorPushWithResize, VectorReserveWithResize, and VectorResizeWithResize regression tests that drive the resizer with a small (256-byte) resizable buffer and verify data integrity afterward. The existing tests all used fixed-size buffers and could not trigger the bug. Original fix by Damian Stulich <damjan.stulic@getcruise.com>.
1 parent 5a659f4 commit 79268d8

3 files changed

Lines changed: 129 additions & 7 deletions

File tree

toolbelt/payload_buffer.cc

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -584,10 +584,13 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n,
584584
}
585585
// Need to free the old block and allocate a new one as the small block
586586
// index is different.
587+
BufferOffset p_offset = (*buffer)->ToOffset(p);
587588
void *newp = Allocate(buffer, n, false, enable_small_block);
588589
if (newp == NULL) {
589590
return NULL;
590591
}
592+
// Re-derive p since Allocate may have triggered a buffer resize.
593+
p = (*buffer)->ToAddress(p_offset);
591594
memcpy(newp, p, decoded_length);
592595
if (clear && n > static_cast<uint32_t>(decoded_length)) {
593596
memset(reinterpret_cast<char *>(newp) + decoded_length, 0,
@@ -662,10 +665,13 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n,
662665
// one, copy the memory and free the old block. We are guaranteed that
663666
// the new block is larger than the original one since if it was smaller
664667
// we can always reuse the block.
668+
BufferOffset p_offset = (*buffer)->ToOffset(p);
665669
void *newp = Allocate(buffer, n, false, enable_small_block);
666670
if (newp == NULL) {
667671
return NULL;
668672
}
673+
// Re-derive p since Allocate may have triggered a buffer resize.
674+
p = (*buffer)->ToAddress(p_offset);
669675
memcpy(newp, p, orig_length);
670676
if (clear) {
671677
memset(reinterpret_cast<char *>(newp) + orig_length, 0, n - orig_length);
@@ -686,15 +692,14 @@ bool PayloadBuffer::PrimeBitmapAllocator(PayloadBuffer **self, size_t size) {
686692
return false;
687693
}
688694
(*self)->bitmaps[index] = offset;
689-
VectorHeader *hdr = (*self)->ToAddress<VectorHeader>((*self)->bitmaps[index]);
690695

691696
BitMapRun *run = PayloadBuffer::AllocateBitMapRun(
692697
self, bitmp_run_infos[index].size, bitmp_run_infos[index].num);
693698
if (run == nullptr) {
694699
return false;
695700
}
696-
// Add to the vector, this might move the vector contents but the header
697-
// will stay where it is.
701+
// Re-derive hdr since AllocateBitMapRun may have triggered a buffer resize.
702+
VectorHeader *hdr = (*self)->ToAddress<VectorHeader>((*self)->bitmaps[index]);
698703
(*self)->VectorPush<BufferOffset>(self, hdr, (*self)->ToOffset(run), false);
699704
return true;
700705
}
@@ -742,8 +747,10 @@ void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t, int size,
742747
}
743748
(*pb)->bitmaps[index] = offset;
744749
}
745-
VectorHeader *hdr = (*pb)->ToAddress<VectorHeader>((*pb)->bitmaps[index]);
746750
for (;;) {
751+
// Re-derive hdr each iteration since allocations below may trigger a
752+
// buffer resize (realloc), invalidating any previous pointer.
753+
VectorHeader *hdr = (*pb)->ToAddress<VectorHeader>((*pb)->bitmaps[index]);
747754
// Go backwards through the elements as that is most likely to find a free
748755
// bit.
749756
for (int i = hdr->num_elements - 1; i >= 0; i--) {
@@ -781,8 +788,9 @@ void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t, int size,
781788
if (run == nullptr) {
782789
return nullptr;
783790
}
784-
// Add to the vector, this might move the vector contents but the header
785-
// will stay where it is.
791+
// Re-derive hdr since AllocateBitMapRun may have triggered a buffer
792+
// resize, invalidating the previous pointer.
793+
hdr = (*pb)->ToAddress<VectorHeader>((*pb)->bitmaps[index]);
786794
(*pb)->VectorPush<BufferOffset>(pb, hdr, (*pb)->ToOffset(run), false);
787795
}
788796
}

toolbelt/payload_buffer.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,6 @@ inline void PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr,
487487
if (hdr->data == 0) {
488488
// The vector is empty, allocate it with a default size of 2.
489489
void *vecp = Allocate(self, 2 * sizeof(T), true, enable_small_block);
490-
hdr->data = (*self)->ToOffset(vecp);
491490
VectorHeader *new_hdr = (*self)->ToAddress<VectorHeader>(hdr_offset);
492491
new_hdr->data = (*self)->ToOffset(vecp);
493492
hdr = new_hdr;

toolbelt/payload_buffer_test.cc

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,121 @@ TEST(BufferTest, VectorExpandMore) {
503503
free(buffer);
504504
}
505505

506+
TEST(BufferTest, VectorPushWithResize) {
507+
char *buffer = (char *)calloc(256, 1);
508+
bool resized = false;
509+
PayloadBuffer *pb = new (buffer) PayloadBuffer(
510+
256, [&resized, &buffer](PayloadBuffer **p, size_t, size_t new_size) {
511+
#if defined(__clang__)
512+
#pragma clang diagnostic push
513+
#pragma clang diagnostic ignored "-Wclass-memaccess"
514+
#elif defined(__GNUC__)
515+
#pragma GCC diagnostic push
516+
#pragma GCC diagnostic ignored "-Wclass-memaccess"
517+
#endif
518+
*p = reinterpret_cast<PayloadBuffer *>(realloc(*p, new_size));
519+
#if defined(__clang__)
520+
#pragma clang diagnostic pop
521+
#elif defined(__GNUC__)
522+
#pragma GCC diagnostic pop
523+
#endif
524+
buffer = reinterpret_cast<char *>(*p);
525+
resized = true;
526+
});
527+
528+
PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader));
529+
BufferOffset msg_offset = pb->message;
530+
531+
constexpr int kCount = 200;
532+
for (int i = 0; i < kCount; i++) {
533+
VectorHeader *hdr = pb->ToAddress<VectorHeader>(msg_offset);
534+
PayloadBuffer::VectorPush<uint32_t>(&pb, hdr, i + 1);
535+
}
536+
ASSERT_TRUE(resized);
537+
538+
VectorHeader *hdr = pb->ToAddress<VectorHeader>(msg_offset);
539+
ASSERT_EQ(kCount, hdr->num_elements);
540+
for (int i = 0; i < kCount; i++) {
541+
uint32_t v = pb->VectorGet<uint32_t>(hdr, i);
542+
ASSERT_EQ(i + 1, v);
543+
}
544+
545+
pb->~PayloadBuffer();
546+
free(buffer);
547+
}
548+
549+
TEST(BufferTest, VectorReserveWithResize) {
550+
char *buffer = (char *)calloc(256, 1);
551+
bool resized = false;
552+
PayloadBuffer *pb = new (buffer) PayloadBuffer(
553+
256, [&resized, &buffer](PayloadBuffer **p, size_t, size_t new_size) {
554+
#if defined(__clang__)
555+
#pragma clang diagnostic push
556+
#pragma clang diagnostic ignored "-Wclass-memaccess"
557+
#elif defined(__GNUC__)
558+
#pragma GCC diagnostic push
559+
#pragma GCC diagnostic ignored "-Wclass-memaccess"
560+
#endif
561+
*p = reinterpret_cast<PayloadBuffer *>(realloc(*p, new_size));
562+
#if defined(__clang__)
563+
#pragma clang diagnostic pop
564+
#elif defined(__GNUC__)
565+
#pragma GCC diagnostic pop
566+
#endif
567+
buffer = reinterpret_cast<char *>(*p);
568+
resized = true;
569+
});
570+
571+
PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader));
572+
BufferOffset msg_offset = pb->message;
573+
574+
VectorHeader *hdr = pb->ToAddress<VectorHeader>(msg_offset);
575+
PayloadBuffer::VectorReserve<uint32_t>(&pb, hdr, 500);
576+
ASSERT_TRUE(resized);
577+
578+
hdr = pb->ToAddress<VectorHeader>(msg_offset);
579+
ASSERT_NE(0u, hdr->data);
580+
581+
pb->~PayloadBuffer();
582+
free(buffer);
583+
}
584+
585+
TEST(BufferTest, VectorResizeWithResize) {
586+
char *buffer = (char *)calloc(256, 1);
587+
bool resized = false;
588+
PayloadBuffer *pb = new (buffer) PayloadBuffer(
589+
256, [&resized, &buffer](PayloadBuffer **p, size_t, size_t new_size) {
590+
#if defined(__clang__)
591+
#pragma clang diagnostic push
592+
#pragma clang diagnostic ignored "-Wclass-memaccess"
593+
#elif defined(__GNUC__)
594+
#pragma GCC diagnostic push
595+
#pragma GCC diagnostic ignored "-Wclass-memaccess"
596+
#endif
597+
*p = reinterpret_cast<PayloadBuffer *>(realloc(*p, new_size));
598+
#if defined(__clang__)
599+
#pragma clang diagnostic pop
600+
#elif defined(__GNUC__)
601+
#pragma GCC diagnostic pop
602+
#endif
603+
buffer = reinterpret_cast<char *>(*p);
604+
resized = true;
605+
});
606+
607+
PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader));
608+
BufferOffset msg_offset = pb->message;
609+
610+
VectorHeader *hdr = pb->ToAddress<VectorHeader>(msg_offset);
611+
PayloadBuffer::VectorResize<uint32_t>(&pb, hdr, 500);
612+
ASSERT_TRUE(resized);
613+
614+
hdr = pb->ToAddress<VectorHeader>(msg_offset);
615+
ASSERT_EQ(500u, hdr->num_elements);
616+
617+
pb->~PayloadBuffer();
618+
free(buffer);
619+
}
620+
506621
TEST(BufferTest, Resizeable) {
507622
char *buffer = (char *)calloc(1, 512);
508623
bool resized = false;

0 commit comments

Comments
 (0)