Skip to content

update-ldcache hook: exec ldconfig from a sealed memfd - #2059

Open
klueska wants to merge 1 commit into
mainfrom
memfd-execveat-ldcache-hook
Open

update-ldcache hook: exec ldconfig from a sealed memfd#2059
klueska wants to merge 1 commit into
mainfrom
memfd-execveat-ldcache-hook

Conversation

@klueska

@klueska klueska commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

UpdateLDCache used to mount a fresh /proc inside the container root, just so it could bind-mount the host ldconfig binary in through a TOCTOU-safe /proc/self/fd/<n> target and then exec it by that same path once pivoted.

This breaks under gVisor. Its createContainer hook doesn't have permission to mount a new /proc inside its sandbox, so mountProc fails with EPERM. This constitutes a real regression, because this hook worked fine before the changes described above were introduced.

To address this, UpdateLDCache now clones the host ldconfig binary into a sealed, anonymous memfd (via libcontainer/exeseal.CloneBinary) before touching any namespace or pivoting, then execs it by descriptor with execveat(fd, "", ..., AT_EMPTY_PATH). A memfd has no path and no mount-namespace membership, so it comes through pivot_root untouched. We don't need to bind-mount anything into the container, and we don't need to mount /proc anywhere.

This is also more isolated than before. Since ldconfig has no use for /proc or /sys, prepareRoot now masks both with an empty tmpfs before pivoting, if they're present in the container root, instead of leaving a real working procfs mounted there and trusting ldconfig never reads it.

Test plan

  • go build ./internal/ldconfig/... ./cmd/nvidia-cdi-hook/...
  • go vet ./internal/ldconfig/...
  • go test ./internal/ldconfig/...
  • Verified against real GPU hardware through gVisor's runsc runtime, as well as standard runc: docker run --runtime=runsc --device nvidia.com/gpu=0 against a non-Debian image resolves the driver libraries and runs nvidia-smi correctly, confirming the hook and the /proc//sys masking both work under gVisor's restricted-namespace gofer process.

@coveralls

coveralls commented Sep 9, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 34443089629

Coverage increased (+0.01%) to 44.03%

Details

  • Coverage increased (+0.01%) from the base build.
  • Patch coverage: 56 uncovered changes across 2 files (0 of 56 lines covered, 0.0%).
  • 6 coverage regressions across 3 files.

Uncovered Changes

File Changed Covered %
internal/ldconfig/ldconfig_linux.go 38 0 0.0%
internal/ldconfig/ldconfig.go 18 0 0.0%

Coverage Regressions

6 previously-covered lines in 3 files lost coverage.

File Lines Losing Coverage Coverage
internal/ldconfig/ldconfig.go 4 31.5%
cmd/nvidia-ctk/config/config.go 1 42.11%
internal/ldconfig/ldconfig_linux.go 1 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 13518
Covered Lines: 5952
Line Coverage: 44.03%
Coverage Strength: 0.44 hits per line

💛 - Coveralls

klueska added a commit to klueska/ray that referenced this pull request Sep 9, 2026
…or GPU sandboxes

update-ldcache stays disabled under gVisor (nvidia-ctk's hook needs to
bind-mount /proc, which doesn't work from gVisor's gofer_mount hook
execution), so CDI-mounted driver libraries never get their ldcache
updated. Harmless on Debian/Ubuntu images, where the mounted path
already sits on ld.so's default search path, but breaks dynamic
linking (e.g. nvidia-smi's dlopen("libnvidia-ml.so.1")) on RHEL/Alpine
images.

nvidia-ctk always sets NVIDIA_CTK_LIBCUDA_DIR to those same
directories in its CDI edits. image_manager._apply_gpu_cdi_edits now
prepends that value onto LD_LIBRARY_PATH by name/reference right after
merging the CDI edits, purely as a workaround for the disabled hook --
marked with a TODO to remove once
NVIDIA/nvidia-container-toolkit#2059 lands and
is backported to a 1.18.x release, at which point re-enabling the hook
makes this unnecessary. Unlike an ldconfig-based fix this needs no
writable /etc/ld.so.cache, so it works regardless of the sandbox's
readonly setting.

Verified against a real RHEL UBI9-minimal sandbox, including the case
where the sandbox's own image/env= already sets LD_LIBRARY_PATH (kept,
not clobbered). Adds test_sandbox_gpu_nvidia_smi_runs_on_non_debian_image
to cover it.

Signed-off-by: Kevin Klues <[email protected]>
klueska added a commit to klueska/ray that referenced this pull request Sep 9, 2026
…or GPU sandboxes

update-ldcache stays disabled under gVisor (nvidia-ctk's hook needs to
bind-mount /proc, which doesn't work from gVisor's gofer_mount hook
execution), so CDI-mounted driver libraries never get their ldcache
updated. Harmless on Debian/Ubuntu images, where the mounted path
already sits on ld.so's default search path, but breaks dynamic
linking (e.g. nvidia-smi's dlopen("libnvidia-ml.so.1")) on RHEL/Alpine
images.

nvidia-ctk always sets NVIDIA_CTK_LIBCUDA_DIR to those same
directories in its CDI edits. image_manager._apply_gpu_cdi_edits now
prepends that value onto LD_LIBRARY_PATH by name/reference right after
merging the CDI edits, purely as a workaround for the disabled hook --
marked with a TODO to remove once
NVIDIA/nvidia-container-toolkit#2059 lands and
is backported to a 1.18.x release, at which point re-enabling the hook
makes this unnecessary. Unlike an ldconfig-based fix this needs no
writable /etc/ld.so.cache, so it works regardless of the sandbox's
readonly setting.

Verified against a real RHEL UBI9-minimal sandbox, including the case
where the sandbox's own image/env= already sets LD_LIBRARY_PATH (kept,
not clobbered). Adds test_sandbox_gpu_nvidia_smi_runs_on_non_debian_image
to cover it.

Signed-off-by: Kevin Klues <[email protected]>
UpdateLDCache used to mount a fresh /proc inside the container root,
just so it could bind-mount the host ldconfig binary in through a
TOCTOU-safe /proc/self/fd/<n> target and then exec it by that same
path once pivoted.

This breaks under gVisor. Its createContainer hook doesn't have
permission to mount a new /proc inside the container, so mountProc
fails with EPERM. This constitutes a real regression, because this
hook worked fine before the changes described above were introduced.

To address this, UpdateLDCache now clones the host ldconfig binary
into a sealed, anonymous memfd (via libcontainer/exeseal.CloneBinary)
before touching any namespace or pivoting, then execs it by
descriptor with execveat(fd, "", ..., AT_EMPTY_PATH). A memfd has no
path and no mount-namespace membership, so it comes through
pivot_root untouched. We don't need to bind-mount anything into the
container, and we don't need to mount /proc anywhere.

This is also more isolated than before. Since ldconfig has no use
for /proc or /sys, prepareRoot now masks both with an empty tmpfs
before pivoting, if they're present in the container root, instead of
leaving a real working procfs mounted there and trusting ldconfig
never reads it.

We verified all of this against real GPU hardware through gVisor's
runsc runtime, as well as standard runc. `docker run --runtime=runsc
--device nvidia.com/gpu=0` against a non-Debian image resolves the
driver libraries and runs nvidia-smi correctly, confirming the hook
and the /proc/sys masking both work under gVisor's restricted-
namespace gofer process.

Signed-off-by: Kevin Klues <[email protected]>
@klueska
klueska force-pushed the memfd-execveat-ldcache-hook branch from 39412ef to 9353dc4 Compare September 10, 2026 05:56
klueska added a commit to klueska/ray that referenced this pull request Sep 10, 2026
…or GPU sandboxes

update-ldcache stays disabled under gVisor (nvidia-ctk's hook needs to
bind-mount /proc, which doesn't work from gVisor's gofer_mount hook
execution), so CDI-mounted driver libraries never get their ldcache
updated. Harmless on Debian/Ubuntu images, where the mounted path
already sits on ld.so's default search path, but breaks dynamic
linking (e.g. nvidia-smi's dlopen("libnvidia-ml.so.1")) on RHEL/Alpine
images.

nvidia-ctk always sets NVIDIA_CTK_LIBCUDA_DIR to those same
directories in its CDI edits. image_manager._apply_gpu_cdi_edits now
prepends that value onto LD_LIBRARY_PATH by name/reference right after
merging the CDI edits, purely as a workaround for the disabled hook --
marked with a TODO to remove once
NVIDIA/nvidia-container-toolkit#2059 lands and
is backported to a 1.18.x release, at which point re-enabling the hook
makes this unnecessary. Unlike an ldconfig-based fix this needs no
writable /etc/ld.so.cache, so it works regardless of the sandbox's
readonly setting.

Verified against a real RHEL UBI9-minimal sandbox, including the case
where the sandbox's own image/env= already sets LD_LIBRARY_PATH (kept,
not clobbered). Adds test_sandbox_gpu_nvidia_smi_runs_on_non_debian_image
to cover it.

Signed-off-by: Kevin Klues <[email protected]>
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.

2 participants