halide / Halide

a language for fast, portable data-parallel computation
https://halide-lang.org
Other
5.86k stars 1.07k forks source link

Address Sanitizer on Windows reports a buffer overflow in `Halide::serialize_pipeline` #8426

Open shoaibkamil opened 4 days ago

shoaibkamil commented 4 days ago

An internal Adobe user reports that running a generator executable results in a container-overflow in the serialization code. Specifically, the offending code appears to be in Halide::Internal::Serializer::serialize(class Halide::Pipeline const &, class std::vector<unsigned char, class std::allocator<unsigned char>> &).

steven-johnson commented 4 days ago

A repro case would help :-)

abadams commented 4 days ago

Some more detail on the complaint:

1>==16204==ERROR: AddressSanitizer: container-overflow on address 0x021081271820 at pc 0x7ff9c3819a86 bp 0x00f43c98a4c0 sp 0x00f43c989c50
1>WRITE of size 117776 at 0x021081271820 thread T0
1>    #0 0x7ff9c3819a85 in __asan_wrap_memmove D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors.inc:813
1>    #1 0x7ff991b286ad in std::vector<unsigned char, class std::allocator<unsigned char>>::_Insert_range<unsigned char *>(class std::_Vector_const_iterator<class std::_Vector_val<struct std::_Simple_types<unsigned char>>>, unsigned char *, unsigned char *, struct std::forward_iterator_tag) 
abadams commented 4 days ago

It looks like it has to be the insert call here:


    uint8_t *buf = builder.GetBufferPointer();
    int size = builder.GetSize();

    if (buf != nullptr && size > 0) {
        result.clear();
        result.reserve(size);
        result.insert(result.begin(), buf, buf + size);
    } else {
        user_error << "failed to serialize pipeline!\n";
    }

But I don't see what could be wrong with it. It's a bit weird to use begin() instead of end(), but the spec says those are equal for an empty container.

shoaibkamil commented 4 days ago

A repro case would help :-)

Agreed, we'll try to create one. I concur with @abadams that it's not immediately obvious what's wrong. @slomp is also looking into this.

abadams commented 2 days ago

It's likely this is a false positive. I believe in this case Halide.dll was compiled without asan, but the generator was compiled with asan, and the std::vector in question is one that was created in the generator and passed into Halide.dll to be resized and filled. When Halide resizes it, it probably doesn't set up the right asan tracking state to catch overflows.

slomp commented 1 day ago

This diff adds a new interface to workaround the issue. Now, the question is: is it worth? Halide_patch.diff.txt

abadams commented 1 day ago

I think this is just a complex case of: don't allocate memory in a dll and free it in an exe (or vice versa), because those may be separate heaps with separate settings. I know there are cases where we deliberately avoided doing this. Maybe there's some way to catch all such cases instead of playing whack-a-mole. Passing stl types by reference definitely seems problematic on windows - we could forbid that, but without testing it's going to creep back in.

The cleanest workaround I think is to change it to just return a pimpl intrusive pointer type like Halide::Buffer, so that no reallocations happen in the exe, and the constructor and destructor both happen in the dll

slomp commented 1 day ago

I like the idea of returning a Halide::Buffer. We could perhaps offer a type alias for that buffer for the (de)serialization interface(s).

There may be other places where this problem is prone to happen (std::string modifications come to mind).