goffi is pre-1.0. Security fixes are released as a patch of the latest minor release line; older lines are not patched.
| Version | Supported |
|---|---|
| 0.6.x | ✅ |
| < 0.6.0 | ❌ |
Stable releases (v1.0+) will follow semantic versioning with long-term support.
We take security seriously. If you discover a security vulnerability in goffi, please report it responsibly.
DO NOT open a public GitHub issue for security vulnerabilities.
Report it privately through a GitHub Security Advisory: https://github.com/go-webgpu/goffi/security/advisories/new
Please include the following information in your report:
- Description of the vulnerability
- Steps to reproduce the issue (include minimal code example if possible)
- Affected versions (which versions are impacted)
- Platform (OS, architecture,
CGO_ENABLEDvalue, Go version) - Potential impact (memory corruption, race condition, crash, etc.)
- Suggested fix (if you have one)
We aim to:
- Acknowledge receipt within 72 hours
- Provide an initial assessment within 1 week
- Work with you on a coordinated disclosure timeline
- Credit you in the security advisory (unless you prefer to remain anonymous)
goffi provides a Foreign Function Interface (FFI) to C libraries without CGO. Calling C means leaving Go's memory safety: the C side can read or write anything it is given, and a wrong type description is not caught by the compiler.
Risk: FFI involves passing Go pointers to C code and vice versa, which can lead to memory corruption if handled incorrectly.
Attack Vectors:
- Invalid pointer addresses passed to C functions
- Buffer overflows when C code writes beyond Go-allocated memory
- C code keeping a Go pointer after the call returns (the GC may free that memory)
- Dangling pointers when C library frees memory unexpectedly
- Type confusion between Go and C representations
Mitigation in Library:
- ✅ Type descriptor validation before calls
- ✅ Exactly one argument value required per argument type
- ✅ Calls go through
runtime.cgocall(GC-safe stack switch) - ✅ ABI behaviour checked by C-backed end-to-end tests on native CI runners
- ✅ CI runs the test suite with
-race, which also enablescheckptrpointer checks - 🔄 Fuzzing (planned)
User Recommendations:
// ❌ BAD - pointer hidden in a uintptr: if the goroutine stack moves,
// the uintptr keeps the old address and C reads stale memory.
buf := make([]byte, 64)
addr := uintptr(unsafe.Pointer(&buf[0]))
_, _ = ffi.CallFunction(&cif, fn, nil, []unsafe.Pointer{unsafe.Pointer(&addr)})
// ✅ GOOD - keep pointers as unsafe.Pointer for the duration of the call.
buf := make([]byte, 64)
p := unsafe.Pointer(&buf[0])
_, _ = ffi.CallFunction(&cif, fn, nil, []unsafe.Pointer{unsafe.Pointer(&p)})
runtime.KeepAlive(buf)
// ✅ If C keeps the pointer after the call returns, or a callback reads it
// later, pin the memory (runtime.Pinner) or allocate it on the C side.C strings must be NUL-terminated ("hello\x00"); Go strings are not.
Risk: FFI calls involve low-level system calls and assembly code. Concurrent access can cause race conditions.
Example Attack:
Thread 1: Prepares CallInterface with argTypes=[int32]
Thread 2: Modifies argTypes=[pointer] before call
Result: Type confusion, memory corruption
Mitigation:
- ✅ CI runs the test suite with
-raceon every runner that has a race detector - ✅ A prepared
CallInterfaceis only read byCallFunction; aCallInterfacebuilt by hand is classified on a copy, never written ⚠️ Users must ensureCallInterfaceis not modified during use
User Best Practices:
// ❌ BAD - Preparing a CallInterface while another goroutine calls it
var globalCIF types.CallInterface
go func() {
ffi.PrepareCallInterface(&globalCIF, ...) // Race!
}()
go func() {
ffi.CallFunction(&globalCIF, ...) // Race!
}()
// ✅ GOOD - One CallInterface per function, prepared once
cif := &types.CallInterface{}
ffi.PrepareCallInterface(cif, ...)
// Safe to call concurrently after preparation (each goroutine with its own argument values)
go func() { ffi.CallFunction(cif, ...) }()
go func() { ffi.CallFunction(cif, ...) }()Risk: goffi uses hand-written assembly for the System V AMD64 and AAPCS64 ABIs (Windows AMD64 calls go through Go's syscall.SyscallN). Assembly bugs can cause crashes or security vulnerabilities.
Attack Vectors:
- Stack corruption due to incorrect frame setup
- Register corruption if calling convention violated
- Red zone violations on System V (128 bytes below RSP)
- Shadow space violations on Win64 (32 bytes for 4 parameters)
- Incorrect float/SSE register handling
Mitigation:
- ✅ ABI rules taken from the ABI specifications and libffi
- ✅ Bugs are first reproduced by a failing test, then fixed
- ✅ Tests on native CI runners: Linux, Windows and macOS on amd64 and arm64; FreeBSD amd64 in VMs
- ✅ C-backed end-to-end tests (struct sizes and classes, stack arguments, narrow integers, variadic, callbacks from C)
Current Status: see the platform table in README.md. FreeBSD arm64 is cross-compiled only; Android arm64 is a guarded preview.
Risk: FFI requires matching Go types to C types. Mismatches can cause crashes or undefined behavior.
Example Attack:
// C function: void process(int32_t value)
// Go code:
var value uint64 = 0xFFFFFFFF00000001
ffi.CallFunction(&cif, funcPtr, nil, []unsafe.Pointer{
unsafe.Pointer(&value), // ❌ Passing uint64 as int32!
})
// Result: C function reads garbage or crashesMitigation:
- ✅ Type descriptors are validated in
PrepareCallInterface - ✅ Typed errors with field context (
TypeValidationError,InvalidCallInterfaceError, …) ⚠️ goffi cannot check that a descriptor matches the C prototype — that is the caller's job
User Best Practices:
// ✅ ALWAYS match Go types to C types exactly
// C: void func(int32_t x, double y, const char* s)
var x int32 = 42
var y float64 = 3.14
var s = "hello\x00"
ffi.PrepareCallInterface(&cif, types.DefaultCall,
types.VoidTypeDescriptor,
[]*types.TypeDescriptor{
types.SInt32TypeDescriptor, // int32
types.DoubleTypeDescriptor, // double
types.PointerTypeDescriptor, // const char*
},
)Risk: Loading arbitrary dynamic libraries can execute malicious code.
Attack Vectors:
- DLL hijacking on Windows (loading a malicious DLL found earlier in the search order)
LD_PRELOAD/LD_LIBRARY_PATHattacks on Linux- Path traversal in library names
- Loading libraries with constructor functions (immediate code execution)
Mitigation:
⚠️ User responsible for validating library paths⚠️ goffi uses the OS loader (LoadLibraryW/dlopen) with its default search order — no custom path logic- ✅ Names containing NUL bytes are rejected
User Best Practices:
// ❌ BAD - Loading user-provided library paths
libPath := os.Args[1] // User input!
handle, _ := ffi.LoadLibrary(libPath) // Malicious DLL!
// ✅ GOOD - Validate and sanitize library names
allowedLibs := map[string]bool{
"wgpu_native.dll": true,
"libvulkan.so.1": true,
}
if !allowedLibs[libName] {
return errors.New("library not allowed")
}
handle, err := ffi.LoadLibrary(libName)Risk: Leaking library handles or excessive FFI calls can exhaust resources.
Mitigation:
- ✅
FreeLibrary()provided for cleanup - ✅ Documentation emphasizes
defer ffi.FreeLibrary(handle) - ✅ Context support (
CallFunctionContext) — checked before the call starts; a C function that is already running cannot be interrupted
User Best Practices:
// ✅ ALWAYS use defer to free libraries
handle, err := ffi.LoadLibrary("mylib.dll")
if err != nil {
return err
}
defer ffi.FreeLibrary(handle) // Prevent leaks
// ✅ Use context for timeouts
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
_, err = ffi.CallFunctionContext(ctx, &cif, funcPtr, unsafe.Pointer(&result), args)Status: Actively mitigated via type validation and runtime checks.
Risk Level: Medium to High
Description: FFI inherently involves unsafe.Pointer operations. Incorrect pointer handling can lead to crashes or memory corruption.
Mitigation:
- Type descriptors validated before use
- Assembly code reviewed for stack safety
- Tests with the race detector and
checkptr - User documentation emphasizes safe pointer handling
Status: Tested on native CI runners for every production target except FreeBSD arm64.
Risk Level: Medium
Description: Hand-written assembly and per-platform argument layout may contain ABI compliance bugs.
Mitigation:
- C-backed end-to-end tests on Linux, Windows, macOS (amd64 and arm64) and FreeBSD amd64
- Each ABI fix is proven first by a test that fails on the unfixed code
- 🔄 Formal ABI verification tools (planned)
goffi has no module dependencies — go.mod has no require lines:
- Pure Go implementation; with
CGO_ENABLED=0no C code is compiled - Standard library only (
unsafe,runtime,syscall,context, …) - Tests use only the standard
testingpackage; the C-backed end-to-end tests compileffi/testdata/structtest.cfrom source
- ✅ Unit tests with invalid arguments
- ✅ Race detector (with
checkptr) on every CI runner that supports it - ✅ golangci-lint (errcheck, govet, ineffassign, staticcheck, unused) on every target
- ✅ Native CI on Linux, Windows, macOS × amd64/arm64 and FreeBSD VMs
- ✅ Benchmark validation (FFI overhead 88-114ns)
- 🔄 Fuzzing
- 🔄 Static analysis with gosec and govulncheck in CI
- 🔄 Security audit by external experts
- 🔄 Formal ABI compliance verification
No security vulnerabilities have been reported or fixed yet.
When vulnerabilities are addressed, they will be listed here with:
- CVE ID (if assigned)
- Affected versions
- Fixed in version
- Severity (Critical/High/Medium/Low)
- Credit to reporter
- GitHub Security Advisory: https://github.com/go-webgpu/goffi/security/advisories/new
- Public Issues (for non-sensitive bugs): https://github.com/go-webgpu/goffi/issues
- Discussions: https://github.com/go-webgpu/goffi/discussions
goffi does not currently have a bug bounty program. We rely on responsible disclosure from the security community.
If you report a valid security vulnerability:
- ✅ Public credit in security advisory (if desired)
- ✅ Acknowledgment in CHANGELOG
- ✅ Priority review and quick fix
Thank you for helping keep goffi secure! 🔒
Security is a journey, not a destination. We continuously improve our security posture with each release.