diff --git a/daemon/libnetwork/internal/nftables/nft_cgo_linux.go b/daemon/libnetwork/internal/nftables/nft_cgo_linux.go index 02cecccbae..6674eb5855 100644 --- a/daemon/libnetwork/internal/nftables/nft_cgo_linux.go +++ b/daemon/libnetwork/internal/nftables/nft_cgo_linux.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "runtime" "unsafe" "github.com/containerd/log" @@ -24,7 +25,13 @@ func preflight() error { return nil } -type nftCtx C.struct_nft_ctx +// nftCtx owns a libnftables context. The context is freed by [nftCtx.Close], +// or by a runtime cleanup if the nftCtx becomes unreachable without being +// closed. +type nftCtx struct { + handle *C.struct_nft_ctx + cleanup runtime.Cleanup +} // Apply calls libnftables to execute the nftables commands in nftCmd. func (h *nftCtx) Apply(ctx context.Context, nftCmd []byte) error { @@ -34,9 +41,12 @@ func (h *nftCtx) Apply(ctx context.Context, nftCmd []byte) error { cCmd := C.CString(string(nftCmd)) defer C.free(unsafe.Pointer(cCmd)) - ret := C.nft_run_cmd_from_buffer((*C.struct_nft_ctx)(h), cCmd) - stdout := C.GoString(C.nft_ctx_get_output_buffer((*C.struct_nft_ctx)(h))) - stderr := C.GoString(C.nft_ctx_get_error_buffer((*C.struct_nft_ctx)(h))) + ret := C.nft_run_cmd_from_buffer(h.handle, cCmd) + stdout := C.GoString(C.nft_ctx_get_output_buffer(h.handle)) + stderr := C.GoString(C.nft_ctx_get_error_buffer(h.handle)) + // Keep h reachable until libnftables is done with its context, so that the + // cleanup can't free it out from under these calls. + runtime.KeepAlive(h) if ret != 0 { return fmt.Errorf("libnftables: failed to apply commands (code %d), stderr: %s", int(ret), stderr) } @@ -60,9 +70,22 @@ func newNftCtx() (_ *nftCtx, retErr error) { if ret := C.nft_ctx_buffer_error(handle); ret != 0 { return nil, fmt.Errorf("libnftables: failed to set error buffer (code %d)", int(ret)) } - return (*nftCtx)(handle), nil + h := &nftCtx{handle: handle} + h.cleanup = runtime.AddCleanup(h, func(handle *C.struct_nft_ctx) { + C.nft_ctx_free(handle) + }, handle) + return h, nil } +// Close frees the libnftables context. It is idempotent, but h must not be used +// for anything else after it's been closed. func (h *nftCtx) Close() { - C.nft_ctx_free((*C.struct_nft_ctx)(h)) + h.cleanup.Stop() + if h.handle != nil { + C.nft_ctx_free(h.handle) + h.handle = nil + } + // Stop only cancels the cleanup if h hasn't already become unreachable, so h + // must be kept alive across the call to avoid a double free. + runtime.KeepAlive(h) }