Repository: NVIDIA/nvidia-container-toolkit

Issue:
Disable chmod hook by default
The chmod `createContainer` hook was added as a workaround to a specific `crun` issue for device nodes that are found in subdirectories of `/dev`. Since this has since been addressed in `crun`, we should disable this hook by default to reduce the number of supported hooks.

This means that by default the:
1. runtime will not add the `chmod` hook to the incomming OCI runtime spec.
2. CDI spec generation will not generate a `chmod` hook in the CDI spec.

We should:
1. Provide a `feature` flag that allows users to opt-in to hook injection.
2. Provide a mechanism of enabling this hook in the `nvcdi` API
3. Provide a command line option to the `nvidia-ctk cdi generate` command that enables the hook.

Discussion:

Solution patch:
diff --git a/cmd/nvidia-ctk/cdi/generate/generate.go b/cmd/nvidia-ctk/cdi/generate/generate.go
index 05423773..9fe9522e 100644
--- a/cmd/nvidia-ctk/cdi/generate/generate.go
+++ b/cmd/nvidia-ctk/cdi/generate/generate.go
@@ -62,6 +62,7 @@ type options struct {
 	configSearchPaths  []string
 	librarySearchPaths []string
 	disabledHooks      []string
+	enabledHooks       []string
 
 	csv struct {
 		files          []string
@@ -214,6 +215,13 @@ func (m command) build() *cli.Command {
 				Destination: &opts.disabledHooks,
 				Sources:     cli.EnvVars("NVIDIA_CTK_CDI_GENERATE_DISABLED_HOOKS"),
 			},
+			&cli.StringSliceFlag{
+				Name:        "enable-hook",
+				Aliases:     []string{"enable-hooks"},
+				Usage:       "Explicitly enable a hook in the generated CDI specification. This overrides disabled hooks. This can be specified multiple times.",
+				Destination: &opts.enabledHooks,
+				Sources:     cli.EnvVars("NVIDIA_CTK_CDI_GENERATE_ENABLED_HOOKS"),
+			},
 		},
 	}
 
@@ -258,6 +266,12 @@ func (m command) validateFlags(c *cli.Command, opts *options) error {
 	if err := cdi.ValidateClassName(opts.class); err != nil {
 		return fmt.Errorf("invalid CDI class name: %v", err)
 	}
+
+	for _, hook := range opts.enabledHooks {
+		if hook == "all" {
+			return fmt.Errorf("enabling all hooks is not supported")
+		}
+	}
 	return nil
 }
 
@@ -313,14 +327,12 @@ func (m command) generateSpec(opts *options) (spec.Interface, error) {
 		nvcdi.WithLibrarySearchPaths(opts.librarySearchPaths),
 		nvcdi.WithCSVFiles(opts.csv.files),
 		nvcdi.WithCSVIgnorePatterns(opts.csv.ignorePatterns),
+		nvcdi.WithDisabledHooks(opts.disabledHooks...),
+		nvcdi.WithEnabledHooks(opts.enabledHooks...),
 		// We set the following to allow for dependency injection:
 		nvcdi.WithNvmlLib(opts.nvmllib),
 	}
 
-	for _, hook := range opts.disabledHooks {
-		cdiOptions = append(cdiOptions, nvcdi.WithDisabledHook(hook))
-	}
-
 	cdilib, err := nvcdi.New(cdiOptions...)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create CDI library: %v", err)
diff --git a/internal/discover/hooks.go b/internal/discover/hooks.go
index 36e13c78..b61c41d5 100644
--- a/internal/discover/hooks.go
+++ b/internal/discover/hooks.go
@@ -50,6 +50,14 @@ const (
 	defaultNvidiaCDIHookPath = "/usr/bin/nvidia-cdi-hook"
 )
 
+// defaultDisabledHooks defines hooks that are disabled by default.
+// These hooks can be explicitly enabled using the WithEnabledHooks option.
+var defaultDisabledHooks = []HookName{
+	// ChmodHook is disabled by default as it was a workaround for older
+	// versions of crun that has since been fixed.
+	ChmodHook,
+}
+
 var _ Discover = (*Hook)(nil)
 
 // Devices returns an empty list of devices for a Hook discoverer.
@@ -77,7 +85,14 @@ func (h *Hook) Hooks() ([]Hook, error) {
 	return []Hook{*h}, nil
 }
 
-type Option func(*cdiHookCreator)
+type hookCreatorOptions struct {
+	nvidiaCDIHookPath string
+	disabledHooks     []HookName
+	enabledHooks      []HookName
+	debugLogging      bool
+}
+
+type Option func(*hookCreatorOptions)
 
 type cdiHookCreator struct {
 	nvidiaCDIHookPath string
@@ -100,39 +115,66 @@ type HookCreator interface {
 	Create(HookName, ...string) *Hook
 }
 
-// WithDisabledHooks sets the set of hooks that are disabled for the CDI hook creator.
+func WithDebugLogging(debugLogging bool) Option {
+	return func(hco *hookCreatorOptions) {
+		hco.debugLogging = debugLogging
+	}
+}
+
+// WithDisabledHooks explicitly disables the specified hooks.
 // This can be specified multiple times.
 func WithDisabledHooks(hooks ...HookName) Option {
-	return func(c *cdiHookCreator) {
-		for _, hook := range hooks {
-			c.disabledHooks[hook] = true
-		}
+	return func(c *hookCreatorOptions) {
+		c.disabledHooks = append(c.disabledHooks, hooks...)
+	}
+}
+
+// WithEnabledHooks explicitly enables the specified hooks.
+// This is useful for enabling hooks that are disabled by default.
+func WithEnabledHooks(hooks ...HookName) Option {
+	return func(c *hookCreatorOptions) {
+		c.enabledHooks = append(c.enabledHooks, hooks...)
 	}
 }
 
 // WithNVIDIACDIHookPath sets the path to the nvidia-cdi-hook binary.
 func WithNVIDIACDIHookPath(nvidiaCDIHookPath string) Option {
-	return func(c *cdiHookCreator) {
+	return func(c *hookCreatorOptions) {
 		c.nvidiaCDIHookPath = nvidiaCDIHookPath
 	}
 }
 
 func NewHookCreator(opts ...Option) HookCreator {
-	cdiHookCreator := &cdiHookCreator{
+	o := &hookCreatorOptions{
 		nvidiaCDIHookPath: defaultNvidiaCDIHookPath,
-		disabledHooks:     make(map[HookName]bool),
 	}
 	for _, opt := range opts {
-		opt(cdiHookCreator)
+		opt(o)
 	}
 
-	if cdiHookCreator.disabledHooks[AllHooks] {
+	o.disabledHooks = append(o.disabledHooks, defaultDisabledHooks...)
+
+	disabledHooks := make(map[HookName]bool)
+	for _, h := range o.disabledHooks {
+		disabledHooks[h] = true
+	}
+
+	if disabledHooks[AllHooks] && len(o.enabledHooks) == 0 {
 		return &allDisabledHookCreator{}
 	}
 
-	cdiHookCreator.fixedArgs = getFixedArgsForCDIHookCLI(cdiHookCreator.nvidiaCDIHookPath)
+	for _, h := range o.enabledHooks {
+		disabledHooks[h] = false
+	}
+
+	c := &cdiHookCreator{
+		nvidiaCDIHookPath: o.nvidiaCDIHookPath,
+		disabledHooks:     disabledHooks,
+		fixedArgs:         getFixedArgsForCDIHookCLI(o.nvidiaCDIHookPath),
+		debugLogging:      o.debugLogging,
+	}
 
-	return cdiHookCreator
+	return c
 }
 
 // Create creates a new hook with the given name and arguments.
@@ -150,21 +192,19 @@ func (c cdiHookCreator) Create(name HookName, args ...string) *Hook {
 	}
 }
 
-// isDisabled checks if the specified hook name is disabled.
 func (c cdiHookCreator) isDisabled(name HookName, args ...string) bool {
-	if c.disabledHooks[name] {
+	disabled, ok := c.disabledHooks[name]
+	if ok {
+		return disabled
+	}
+	if c.disabledHooks[AllHooks] {
 		return true
 	}
 
+	// still reject hooks that require args if none were provided
 	switch name {
-	case CreateSymlinksHook:
-		if len(args) == 0 {
-			return true
-		}
-	case ChmodHook:
-		if len(args) == 0 {
-			return true
-		}
+	case CreateSymlinksHook, ChmodHook:
+		return len(args) == 0
 	}
 	return false
 }
diff --git a/internal/runtime/runtime_factory.go b/internal/runtime/runtime_factory.go
index c5ea892f..f1d5ddca 100644
--- a/internal/runtime/runtime_factory.go
+++ b/internal/runtime/runtime_factory.go
@@ -76,6 +76,7 @@ func newSpecModifier(logger logger.Interface, cfg *config.Config, ociSpec oci.Sp
 	}
 
 	hookCreator := discover.NewHookCreator(discover.WithNVIDIACDIHookPath(cfg.NVIDIACTKConfig.Path))
+
 	var modifiers modifier.List
 	for _, modifierType := range supportedModifierTypes(mode) {
 		switch modifierType {
diff --git a/pkg/nvcdi/lib.go b/pkg/nvcdi/lib.go
index 781b1550..4369a721 100644
--- a/pkg/nvcdi/lib.go
+++ b/pkg/nvcdi/lib.go
@@ -59,6 +59,7 @@ type nvcdilib struct {
 	featureFlags map[FeatureFlag]bool
 
 	disabledHooks []discover.HookName
+	enabledHooks  []discover.HookName
 	hookCreator   discover.HookCreator
 }
 
@@ -147,6 +148,7 @@ func New(opts ...Option) (Interface, error) {
 	l.hookCreator = discover.NewHookCreator(
 		discover.WithNVIDIACDIHookPath(l.nvidiaCDIHookPath),
 		discover.WithDisabledHooks(l.disabledHooks...),
+		discover.WithEnabledHooks(l.enabledHooks...),
 	)
 
 	w := wrapper{
diff --git a/pkg/nvcdi/options.go b/pkg/nvcdi/options.go
index 25494ebf..8af7d475 100644
--- a/pkg/nvcdi/options.go
+++ b/pkg/nvcdi/options.go
@@ -157,14 +157,23 @@ func WithLibrarySearchPaths(paths []string) Option {
 	}
 }
 
-// WithDisabledHook allows specific hooks to the disabled.
-// This option can be specified multiple times for each hook.
-func WithDisabledHook[T string | HookName](hook T) Option {
+// WithDisabledHooks allows specific hooks to be disabled.
+func WithDisabledHooks[T string | HookName](hooks ...T) Option {
 	return func(o *nvcdilib) {
-		o.disabledHooks = append(o.disabledHooks, discover.HookName(hook))
+		for _, hook := range hooks {
+			o.disabledHooks = append(o.disabledHooks, discover.HookName(hook))
+		}
 	}
 }
 
+// WithDisabledHook allows specific hooks to be disabled.
+// This option can be specified multiple times for each hook.
+//
+// Deprecated: Use WithDisabledHooks instead
+func WithDisabledHook[T string | HookName](hook T) Option {
+	return WithDisabledHooks(hook)
+}
+
 // WithFeatureFlag allows specified features to be toggled on.
 // This option can be specified multiple times for each feature flag.
 func WithFeatureFlag(featureFlag FeatureFlag) Option {
@@ -175,3 +184,13 @@ func WithFeatureFlag(featureFlag FeatureFlag) Option {
 		o.featureFlags[featureFlag] = true
 	}
 }
+
+// WithEnabledHooks explicitly enables a specific set of hooks.
+// If a hook is explicitly enabled, this takes precedence over it being disabled.
+func WithEnabledHooks[T string | HookName](hooks ...T) Option {
+	return func(o *nvcdilib) {
+		for _, hook := range hooks {
+			o.enabledHooks = append(o.enabledHooks, discover.HookName(hook))
+		}
+	}
+}



Test patch:
diff --git a/cmd/nvidia-ctk-installer/toolkit/toolkit_test.go b/cmd/nvidia-ctk-installer/toolkit/toolkit_test.go
index f17d646e..2e2141b6 100644
--- a/cmd/nvidia-ctk-installer/toolkit/toolkit_test.go
+++ b/cmd/nvidia-ctk-installer/toolkit/toolkit_test.go
@@ -84,6 +84,8 @@ devices:
               hostPath: /host/driver/root/dev/nvidia-caps-imex-channels/channel1
             - path: /dev/nvidia-caps-imex-channels/channel2047
               hostPath: /host/driver/root/dev/nvidia-caps-imex-channels/channel2047
+            - path: /dev/nvidia-caps/nvidia-cap1
+              hostPath: /host/driver/root/dev/nvidia-caps/nvidia-cap1
 containerEdits:
     env:
         - NVIDIA_CTK_LIBCUDA_DIR=/lib/x86_64-linux-gnu
diff --git a/cmd/nvidia-ctk/cdi/generate/generate_test.go b/cmd/nvidia-ctk/cdi/generate/generate_test.go
index 3551a463..a4181a3e 100644
--- a/cmd/nvidia-ctk/cdi/generate/generate_test.go
+++ b/cmd/nvidia-ctk/cdi/generate/generate_test.go
@@ -359,6 +359,91 @@ containerEdits:
             - nodev
             - rbind
             - rprivate
+`,
+		},
+		{
+			description: "enableChmodHook",
+			options: options{
+				format:        "yaml",
+				mode:          "management",
+				vendor:        "example.com",
+				class:         "device",
+				driverRoot:    driverRoot,
+				enabledHooks:  []string{"chmod"},
+				disabledHooks: []string{"enable-cuda-compat", "update-ldcache", "disable-device-node-modification"},
+			},
+			expectedOptions: options{
+				format:            "yaml",
+				mode:              "management",
+				vendor:            "example.com",
+				class:             "device",
+				nvidiaCDIHookPath: "/usr/bin/nvidia-cdi-hook",
+				driverRoot:        driverRoot,
+				enabledHooks:      []string{"chmod"},
+				disabledHooks:     []string{"enable-cuda-compat", "update-ldcache", "disable-device-node-modification"},
+			},
+			expectedSpec: `---
+cdiVersion: 0.5.0
+kind: example.com/device
+devices:
+    - name: all
+      containerEdits:
+        deviceNodes:
+            - path: /dev/nvidia0
+              hostPath: {{ .driverRoot }}/dev/nvidia0
+            - path: /dev/nvidiactl
+              hostPath: {{ .driverRoot }}/dev/nvidiactl
+            - path: /dev/nvidia-caps-imex-channels/channel0
+              hostPath: {{ .driverRoot }}/dev/nvidia-caps-imex-channels/channel0
+            - path: /dev/nvidia-caps-imex-channels/channel1
+              hostPath: {{ .driverRoot }}/dev/nvidia-caps-imex-channels/channel1
+            - path: /dev/nvidia-caps-imex-channels/channel2047
+              hostPath: {{ .driverRoot }}/dev/nvidia-caps-imex-channels/channel2047
+            - path: /dev/nvidia-caps/nvidia-cap1
+              hostPath: {{ .driverRoot }}/dev/nvidia-caps/nvidia-cap1
+        hooks:
+            - hookName: createContainer
+              path: /usr/bin/nvidia-cdi-hook
+              args:
+                - nvidia-cdi-hook
+                - chmod
+                - --mode
+                - "755"
+                - --path
+                - /dev/nvidia-caps
+              env:
+                - NVIDIA_CTK_DEBUG=false
+containerEdits:
+    env:
+        - NVIDIA_CTK_LIBCUDA_DIR=/lib/x86_64-linux-gnu
+        - NVIDIA_VISIBLE_DEVICES=void
+    hooks:
+        - hookName: createContainer
+          path: /usr/bin/nvidia-cdi-hook
+          args:
+            - nvidia-cdi-hook
+            - create-symlinks
+            - --link
+            - libcuda.so.1::/lib/x86_64-linux-gnu/libcuda.so
+          env:
+            - NVIDIA_CTK_DEBUG=false
+    mounts:
+        - hostPath: {{ .driverRoot }}/lib/x86_64-linux-gnu/libcuda.so.999.88.77
+          containerPath: /lib/x86_64-linux-gnu/libcuda.so.999.88.77
+          options:
+            - ro
+            - nosuid
+            - nodev
+            - rbind
+            - rprivate
+        - hostPath: {{ .driverRoot }}/lib/x86_64-linux-gnu/vdpau/libvdpau_nvidia.so.999.88.77
+          containerPath: /lib/x86_64-linux-gnu/vdpau/libvdpau_nvidia.so.999.88.77
+          options:
+            - ro
+            - nosuid
+            - nodev
+            - rbind
+            - rprivate
 `,
 		},
 	}
diff --git a/internal/discover/hooks_test.go b/internal/discover/hooks_test.go
new file mode 100644
index 00000000..e33d2821
--- /dev/null
+++ b/internal/discover/hooks_test.go
@@ -0,0 +1,469 @@
+/**
+# Copyright (c) NVIDIA CORPORATION.  All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+**/
+
+package discover
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewHookCreator(t *testing.T) {
+	testCases := []struct {
+		name     string
+		opts     []Option
+		expected HookCreator
+	}{
+		{
+			name: "default creates cdiHookCreator with default path",
+			opts: nil,
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: defaultNvidiaCDIHookPath,
+				fixedArgs:         []string{"nvidia-cdi-hook"},
+				disabledHooks: map[HookName]bool{
+					ChmodHook: true, // ChmodHook is disabled by default
+				},
+			},
+		},
+		{
+			name: "custom nvidia-cdi-hook path",
+			opts: []Option{
+				WithNVIDIACDIHookPath("/custom/path/nvidia-cdi-hook"),
+			},
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: "/custom/path/nvidia-cdi-hook",
+				fixedArgs:         []string{"nvidia-cdi-hook"},
+				disabledHooks: map[HookName]bool{
+					ChmodHook: true,
+				},
+			},
+		},
+		{
+			name: "AllHooks disabled returns allDisabledHookCreator",
+			opts: []Option{
+				WithDisabledHooks(AllHooks),
+			},
+			expected: &allDisabledHookCreator{},
+		},
+		{
+			name: "AllHooks disabled but UpdateLDCacheHook enabled",
+			opts: []Option{
+				WithDisabledHooks(AllHooks),
+				WithEnabledHooks(UpdateLDCacheHook),
+			},
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: defaultNvidiaCDIHookPath,
+				fixedArgs:         []string{"nvidia-cdi-hook"},
+				disabledHooks: map[HookName]bool{
+					AllHooks:          true,
+					UpdateLDCacheHook: false,
+					ChmodHook:         true,
+				},
+			},
+		},
+		{
+			name: "multiple hooks disabled and enabled",
+			opts: []Option{
+				WithDisabledHooks(UpdateLDCacheHook, CreateSymlinksHook, EnableCudaCompatHook, DisableDeviceNodeModificationHook),
+				WithEnabledHooks(ChmodHook, UpdateLDCacheHook),
+			},
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: defaultNvidiaCDIHookPath,
+				fixedArgs:         []string{"nvidia-cdi-hook"},
+				disabledHooks: map[HookName]bool{
+					UpdateLDCacheHook:                 false,
+					CreateSymlinksHook:                true,
+					EnableCudaCompatHook:              true,
+					ChmodHook:                         false,
+					DisableDeviceNodeModificationHook: true,
+				},
+			},
+		},
+		{
+			name: "WithDisabledHooks can be called multiple times",
+			opts: []Option{
+				WithDisabledHooks(UpdateLDCacheHook),
+				WithDisabledHooks(CreateSymlinksHook),
+				WithDisabledHooks(EnableCudaCompatHook),
+			},
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: defaultNvidiaCDIHookPath,
+				fixedArgs:         []string{"nvidia-cdi-hook"},
+				disabledHooks: map[HookName]bool{
+					UpdateLDCacheHook:    true,
+					CreateSymlinksHook:   true,
+					EnableCudaCompatHook: true,
+					ChmodHook:            true, // Default disabled
+				},
+			},
+		},
+		{
+			name: "WithEnabledHooks overrides defaultDisabledHooks",
+			opts: []Option{
+				WithEnabledHooks(ChmodHook),
+			},
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: defaultNvidiaCDIHookPath,
+				fixedArgs:         []string{"nvidia-cdi-hook"},
+				disabledHooks: map[HookName]bool{
+					ChmodHook: false, // ChmodHook is enabled
+				},
+			},
+		},
+		{
+			name: "nvidia-ctk binary path affects fixed args",
+			opts: []Option{
+				WithNVIDIACDIHookPath("/usr/bin/nvidia-ctk"),
+			},
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: "/usr/bin/nvidia-ctk",
+				fixedArgs:         []string{"nvidia-ctk", "hook"},
+				disabledHooks: map[HookName]bool{
+					ChmodHook: true,
+				},
+			},
+		},
+		{
+			name: "nvidia-ctk in custom NVIDIA toolkit path",
+			opts: []Option{
+				WithNVIDIACDIHookPath("/usr/local/nvidia/toolkit/nvidia-ctk"),
+			},
+			expected: &cdiHookCreator{
+				nvidiaCDIHookPath: "/usr/local/nvidia/toolkit/nvidia-ctk",
+				fixedArgs:         []string{"nvidia-ctk", "hook"},
+				disabledHooks: map[HookName]bool{
+					ChmodHook: true,
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			hookCreator := NewHookCreator(tc.opts...)
+
+			require.EqualValues(t, tc.expected, hookCreator)
+		})
+	}
+}
+func TestCDIHookCreator_Create(t *testing.T) {
+	testCases := []struct {
+		name         string
+		hookCreator  HookCreator
+		hookName     HookName
+		args         []string
+		expectedHook *Hook
+	}{
+		{
+			name:        "CreateSymlinksHook with args",
+			hookCreator: NewHookCreator(WithNVIDIACDIHookPath(defaultNvidiaCDIHookPath)),
+			hookName:    CreateSymlinksHook,
+			args:        []string{"/source::/target", "/source2::/target2"},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      "/usr/bin/nvidia-cdi-hook",
+				Args:      []string{"nvidia-cdi-hook", "create-symlinks", "--link", "/source::/target", "--link", "/source2::/target2"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name:         "CreateSymlinksHook without args returns nil",
+			hookCreator:  NewHookCreator(WithNVIDIACDIHookPath(defaultNvidiaCDIHookPath)),
+			hookName:     CreateSymlinksHook,
+			args:         []string{},
+			expectedHook: nil,
+		},
+		{
+			name: "ChmodHook with args (when enabled)",
+			hookCreator: NewHookCreator(
+				WithNVIDIACDIHookPath(defaultNvidiaCDIHookPath),
+				WithEnabledHooks(ChmodHook),
+			),
+			hookName: ChmodHook,
+			args:     []string{"/path/to/file1", "/path/to/file2"},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      defaultNvidiaCDIHookPath,
+				Args:      []string{"nvidia-cdi-hook", "chmod", "--mode", "755", "--path", "/path/to/file1", "--path", "/path/to/file2"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name:         "ChmodHook disabled by default returns nil",
+			hookCreator:  NewHookCreator(WithNVIDIACDIHookPath(defaultNvidiaCDIHookPath)),
+			hookName:     ChmodHook,
+			args:         []string{"/path/to/file"},
+			expectedHook: nil, // ChmodHook is disabled by default
+		},
+		{
+			name:        "UpdateLDCacheHook with no args",
+			hookCreator: NewHookCreator(WithNVIDIACDIHookPath(defaultNvidiaCDIHookPath)),
+			hookName:    UpdateLDCacheHook,
+			args:        []string{},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      defaultNvidiaCDIHookPath,
+				Args:      []string{"nvidia-cdi-hook", "update-ldcache"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name:        "UpdateLDCacheHook with args",
+			hookCreator: NewHookCreator(WithNVIDIACDIHookPath(defaultNvidiaCDIHookPath)),
+			hookName:    UpdateLDCacheHook,
+			args:        []string{"--folder", "/usr/lib64"},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      defaultNvidiaCDIHookPath,
+				Args:      []string{"nvidia-cdi-hook", "update-ldcache", "--folder", "/usr/lib64"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name:        "EnableCudaCompatHook",
+			hookCreator: NewHookCreator(),
+			hookName:    EnableCudaCompatHook,
+			args:        []string{"--root", "/some/root"},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      defaultNvidiaCDIHookPath,
+				Args:      []string{"nvidia-cdi-hook", "enable-cuda-compat", "--root", "/some/root"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name:        "DisableDeviceNodeModificationHook",
+			hookCreator: NewHookCreator(WithNVIDIACDIHookPath(defaultNvidiaCDIHookPath)),
+			hookName:    DisableDeviceNodeModificationHook,
+			args:        []string{},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      defaultNvidiaCDIHookPath,
+				Args:      []string{"nvidia-cdi-hook", "disable-device-node-modification"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name:        "nvidia-ctk binary uses different args format",
+			hookCreator: NewHookCreator(WithNVIDIACDIHookPath("/usr/bin/nvidia-ctk")),
+			hookName:    UpdateLDCacheHook,
+			args:        []string{},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      "/usr/bin/nvidia-ctk",
+				Args:      []string{"nvidia-ctk", "hook", "update-ldcache"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name: "nvidia-ctk in custom NVIDIA toolkit path",
+			hookCreator: NewHookCreator(
+				WithNVIDIACDIHookPath("/usr/local/nvidia/toolkit/nvidia-ctk"),
+			),
+			hookName: EnableCudaCompatHook,
+			args:     []string{"--root", "/some/root"},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      "/usr/local/nvidia/toolkit/nvidia-ctk",
+				Args:      []string{"nvidia-ctk", "hook", "enable-cuda-compat", "--root", "/some/root"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=false"},
+			},
+		},
+		{
+			name:         "hook disabled when in disabledHooks list",
+			hookCreator:  NewHookCreator(WithDisabledHooks(UpdateLDCacheHook)),
+			hookName:     UpdateLDCacheHook,
+			args:         []string{},
+			expectedHook: nil,
+		},
+		{
+			name:         "allDisabled with unknown hook",
+			hookCreator:  &allDisabledHookCreator{},
+			hookName:     HookName("unknown-hook"),
+			args:         []string{},
+			expectedHook: nil,
+		},
+		{
+			name:         "allDisabled with defined hook",
+			hookCreator:  &allDisabledHookCreator{},
+			hookName:     UpdateLDCacheHook,
+			args:         []string{},
+			expectedHook: nil,
+		},
+		{
+			name:        "debug logging enabled",
+			hookCreator: NewHookCreator(WithDebugLogging(true)),
+			hookName:    UpdateLDCacheHook,
+			args:        []string{},
+			expectedHook: &Hook{
+				Lifecycle: "createContainer",
+				Path:      defaultNvidiaCDIHookPath,
+				Args:      []string{"nvidia-cdi-hook", "update-ldcache"},
+				Env:       []string{"NVIDIA_CTK_DEBUG=true"},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			hookCreator := tc.hookCreator
+
+			hook := hookCreator.Create(tc.hookName, tc.args...)
+			require.Equal(t, tc.expectedHook, hook)
+		})
+	}
+}
+
+func TestCDIHookCreator_isDisabled(t *testing.T) {
+	tests := []struct {
+		name           string
+		disabledHooks  []HookName
+		enabledHooks   []HookName
+		hookName       HookName
+		args           []string
+		expectedResult bool
+	}{
+		{
+			name:           "hook explicitly disabled",
+			disabledHooks:  []HookName{ChmodHook},
+			hookName:       ChmodHook,
+			args:           []string{"/path/to/file"},
+			expectedResult: true, // ChmodHook is disabled by default and explicitly disabled
+		},
+		{
+			name:           "hook explicitly enabled overrides disabled",
+			disabledHooks:  []HookName{ChmodHook},
+			enabledHooks:   []HookName{ChmodHook},
+			hookName:       ChmodHook,
+			args:           []string{"/path/to/file"},
+			expectedResult: false,
+		},
+		{
+			name:           "hook not in disabled map and not AllHooks disabled",
+			disabledHooks:  []HookName{ChmodHook},
+			hookName:       UpdateLDCacheHook,
+			args:           []string{},
+			expectedResult: false,
+		},
+		{
+			name:           "CreateSymlinksHook requires args - no args provided",
+			disabledHooks:  []HookName{},
+			hookName:       CreateSymlinksHook,
+			args:           []string{},
+			expectedResult: true,
+		},
+		{
+			name:           "CreateSymlinksHook requires args - args provided",
+			disabledHooks:  []HookName{},
+			hookName:       CreateSymlinksHook,
+			args:           []string{"/path/to/symlink"},
+			expectedResult: false,
+		},
+		{
+			name:           "ChmodHook requires args - no args provided",
+			disabledHooks:  []HookName{},
+			hookName:       ChmodHook,
+			args:           []string{},
+			expectedResult: true,
+		},
+		{
+			name:           "ChmodHook requires args - args provided",
+			disabledHooks:  []HookName{},
+			enabledHooks:   []HookName{ChmodHook}, // Enable ChmodHook since it's disabled by default
+			hookName:       ChmodHook,
+			args:           []string{"/path/to/file"},
+			expectedResult: false,
+		},
+		{
+			name:           "UpdateLDCacheHook doesn't require args - no args provided",
+			disabledHooks:  []HookName{},
+			hookName:       UpdateLDCacheHook,
+			args:           []string{},
+			expectedResult: false,
+		},
+		{
+			name:           "UpdateLDCacheHook doesn't require args - args provided",
+			disabledHooks:  []HookName{},
+			hookName:       UpdateLDCacheHook,
+			args:           []string{"arg1", "arg2"},
+			expectedResult: false,
+		},
+		{
+			name:           "EnableCudaCompatHook doesn't require args - no args provided",
+			disabledHooks:  []HookName{},
+			hookName:       EnableCudaCompatHook,
+			args:           []string{},
+			expectedResult: false,
+		},
+		{
+			name:           "DisableDeviceNodeModificationHook doesn't require args - no args provided",
+			disabledHooks:  []HookName{},
+			hookName:       DisableDeviceNodeModificationHook,
+			args:           []string{},
+			expectedResult: false,
+		},
+		// Edge cases
+		{
+			name:           "nil disabled hooks map",
+			disabledHooks:  nil,
+			hookName:       UpdateLDCacheHook,
+			args:           []string{},
+			expectedResult: false,
+		},
+		{
+			name:           "unknown hook name",
+			disabledHooks:  []HookName{ChmodHook},
+			hookName:       HookName("unknown-hook"),
+			args:           []string{},
+			expectedResult: false,
+		},
+		// Multiple args tests
+		{
+			name:           "CreateSymlinksHook with multiple args",
+			disabledHooks:  []HookName{},
+			hookName:       CreateSymlinksHook,
+			args:           []string{"/path1", "/path2", "/path3"},
+			expectedResult: false,
+		},
+		{
+			name:           "ChmodHook with multiple args",
+			disabledHooks:  []HookName{},
+			enabledHooks:   []HookName{ChmodHook}, // Enable ChmodHook since it's disabled by default
+			hookName:       ChmodHook,
+			args:           []string{"/path1", "/path2"},
+			expectedResult: false,
+		},
+		{
+			name:           "UpdateLDCacheHook with multiple args",
+			disabledHooks:  []HookName{},
+			hookName:       UpdateLDCacheHook,
+			args:           []string{"arg1", "arg2", "arg3"},
+			expectedResult: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			testCreator := NewHookCreator(
+				WithDisabledHooks(tt.disabledHooks...),
+				WithEnabledHooks(tt.enabledHooks...),
+			)
+
+			require.Equal(t, testCreator.(*cdiHookCreator).isDisabled(tt.hookName, tt.args...), tt.expectedResult)
+		})
+	}
+}
diff --git a/testdata/lookup/rootfs-1/dev/nvidia-caps/nvidia-cap1 b/testdata/lookup/rootfs-1/dev/nvidia-caps/nvidia-cap1
new file mode 100644
index 00000000..e69de29b