Skip to content

Don't close the fd again after a failed cuImportExternalMemory - #479

Merged
elFarto merged 1 commit into
elFarto:masterfrom
zaixinpan3:fix/cuda-import-fd-double-close
Oct 11, 2026
Merged

elFarto merged 1 commit into
elFarto:masterfrom
zaixinpan3:fix/cuda-import-fd-double-close

Conversation

@zaixinpan3

Copy link
Copy Markdown
Contributor

Fixes #478.

libcuda (615.71.09) takes ownership of the CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD descriptor passed to cuImportExternalMemory() and closes it even when the import fails. The driver then closed the same descriptor a second time in every failure path:

  • importExternalBackingToCuda() / importExternalBufferToCuda(): close(importFd)
  • direct_allocateBackingImage_perPlane(): close(memFd)
  • direct_allocateBackingImage_single(): fail: closes memFd
  • import_to_cuda(): returns with image->nvFd still set, so bail: closes it again

In a multi-threaded client another thread can allocate the same descriptor number between the two closes, and the second close() destroys that thread's fd. This is how Chromium's GPU process ends up with FATAL:base/files/scoped_file.cc:45 Check failed: . : Bad file descriptor (9) (SIGTRAP, exit_code=133) some seconds after a batch of vaCreateSurfaces (import mode) failed, VA error: resource allocation failed from its VaapiImageProcessor. Details, logs and a browser-free reproduction are in #478.

The change never touches the descriptor after cuImportExternalMemory(), regardless of the result. Success paths are unchanged.

Tested on master d03f8cb, RTX 4070, driver 615.71.09, NVD_BACKEND=direct, with the reproduction from #478 under a close() tracer:

build second close() with EBADF per failed import import result
unpatched 1 VA_STATUS_ERROR_ALLOCATION_FAILED
patched 0 VA_STATUS_ERROR_ALLOCATION_FAILED (unchanged, as expected)

H.264 / VP9 / AV1 decoding and dma-buf export in Chromium 153 keep working with the patched driver.

🤖 Generated with Claude Code

libcuda takes ownership of the OPAQUE_FD handle passed to
cuImportExternalMemory and closes it even when the import fails
(observed with 615.71.09, verified with an LD_PRELOAD close() tracer).
The driver then closed the same descriptor a second time in every
failure path. In a single-threaded caller that only yields EBADF, but
in a multi-threaded process such as Chromium's GPU process another
thread can allocate the same number in between, and the second close()
silently destroys that thread's descriptor. Chromium later hits the
PCHECK in base/files/scoped_file.cc when it closes the dma-buf fd of a
video frame, and the GPU process dies with SIGTRAP (exit code 133).

The failing import is reached whenever Chromium's VaapiImageProcessor
re-imports frame-pool dma-bufs through a second VADisplay: the second
NVDriver instance has no backing-image cache hit, mmap() of the dma-buf
fails with EACCES and cuImportExternalMemory returns CUDA_ERROR_UNKNOWN.

Never touch the descriptor after cuImportExternalMemory, regardless of
the result: importExternalBackingToCuda, importExternalBufferToCuda,
direct_allocateBackingImage_perPlane/_single and import_to_cuda.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@elFarto

elFarto commented Oct 11, 2026

Copy link
Copy Markdown
Owner

This is a good catch, thanks for the patch.

@elFarto
elFarto merged commit 4430507 into elFarto:master Oct 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Double close() of the fd passed to cuImportExternalMemory on failure steals another thread's fd (Chromium GPU process SIGTRAP, exit_code=133)

2 participants