valyala / gozstd

go wrapper for zstd
MIT License
420 stars 60 forks source link

Cgo wrappers cannot safely pass uintptr to C functions #33

Open evanj opened 3 years ago

evanj commented 3 years ago

This package passes uintptr to C functions, which is not safe. It can cause memory corruption in rare cases. I did not try to reproduce this bug with this package, because it is subtle. However, we have a package with similar code, and we ended up with a memory corruption bug: https://github.com/DataDog/zstd/issues/90 . I'm filing this issue because I noticed this package uses the same pattern, so it probably has the same problem.

The unlucky sequence is:

One example piece of code that does this is: https://github.com/valyala/gozstd/blob/master/gozstd.go#L17 , however there are many others. In many cases, I think using unsafe.Pointer instead will not impact the performance of gozstd. For example, the Writer API already copies data into its own heap-allocated buffer, before calling Cgo.

For details, see my reproduction of this problem: https://github.com/evanj/cgouintptrbug

valyala commented 3 years ago

@evanj , thanks for the detailed analysis. It looks like the bug can be triggered only if the byte slice b is allocated on a stack for the call C.f(uintptr(unsafe.Pointer(&b[0]))). This issue doesn't apply if b is allocated on a heap, since the &b[0] address doesn't change during stack growth.

evanj commented 3 years ago

That is my analysis as well. However, in that case, I think this means the optimization of using uintptr_t is useless: my understanding is this optimization allows the compiler to be able to allocate objects on the stack. If they aren't ever allocated on the stack, you might as well use unsafe.Pointer because it is both safe and equivalent in terms of memory allocations (I think?)