From 819bb3f45e117ca555057d3731a500185df82df4 Mon Sep 17 00:00:00 2001 From: Arash Javanmardi Date: Fri, 18 Sep 2026 10:48:55 +0200 Subject: [PATCH] Reject config paths that descend into non-struct fields Reject dotted --set keys that continue past a boolean, string, slice, or pointer leaf before enumerating fields with reflection. Return the existing invalid-option error instead of panicking. Cover malformed paths through the parser and command handler, preserve configuration files on failure, and retain valid nested and optional boolean settings. Signed-off-by: Arash Javanmardi --- cmd/nvidia-ctk/config/config.go | 3 + cmd/nvidia-ctk/config/config_test.go | 145 +++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/cmd/nvidia-ctk/config/config.go b/cmd/nvidia-ctk/config/config.go index a56aec3c9..b3728bc95 100644 --- a/cmd/nvidia-ctk/config/config.go +++ b/cmd/nvidia-ctk/config/config.go @@ -234,6 +234,9 @@ func getStruct(current reflect.Type, paths ...string) (reflect.StructField, erro return reflect.StructField{}, fmt.Errorf("%w: no fields selected", errUndefinedField) } tomlField := paths[0] + if current.Kind() != reflect.Struct { + return reflect.StructField{}, fmt.Errorf("%w: %q", errUndefinedField, tomlField) + } for f := range current.Fields() { v, ok := f.Tag.Lookup("toml") if !ok { diff --git a/cmd/nvidia-ctk/config/config_test.go b/cmd/nvidia-ctk/config/config_test.go index c92623db1..855a6e215 100644 --- a/cmd/nvidia-ctk/config/config_test.go +++ b/cmd/nvidia-ctk/config/config_test.go @@ -17,8 +17,11 @@ package config import ( + "os" + "path/filepath" "testing" + "github.com/pelletier/go-toml" "github.com/stretchr/testify/require" ) @@ -43,6 +46,65 @@ func TestSetFlagToKeyValue(t *testing.T) { expectedKey: "nvidia-container-cli.undefined", expectedError: errInvalidConfigOption, }, + { + description: "child of boolean option returns error", + setFlag: "disable-require.extra=true", + expectedKey: "disable-require.extra", + expectedError: errInvalidConfigOption, + }, + { + description: "child of string option returns error", + setFlag: "nvidia-container-cli.path.extra=/tmp/cli", + expectedKey: "nvidia-container-cli.path.extra", + expectedError: errInvalidConfigOption, + }, + { + description: "child of slice option returns error", + setFlag: "nvidia-container-cli.environment.extra=VALUE", + expectedKey: "nvidia-container-cli.environment.extra", + expectedError: errInvalidConfigOption, + }, + { + description: "child of pointer option returns error", + setFlag: "features.allow-ldconfig-from-container.extra=true", + expectedKey: "features.allow-ldconfig-from-container.extra", + expectedError: errInvalidConfigOption, + }, + { + description: "nested string option returns value", + setFlag: "nvidia-container-cli.path=/tmp/cli", + expectedKey: "nvidia-container-cli.path", + expectedValue: "/tmp/cli", + }, + { + description: "deeply nested string option returns value", + setFlag: "nvidia-container-runtime.modes.cdi.default-kind=example.com/gpu", + expectedKey: "nvidia-container-runtime.modes.cdi.default-kind", + expectedValue: "example.com/gpu", + }, + { + description: "pointer boolean option assumes true", + setFlag: "features.allow-ldconfig-from-container", + expectedKey: "features.allow-ldconfig-from-container", + expectedValue: true, + }, + { + description: "pointer boolean option returns true", + setFlag: "features.allow-ldconfig-from-container=true", + expectedKey: "features.allow-ldconfig-from-container", + expectedValue: true, + }, + { + description: "pointer boolean option returns false", + setFlag: "features.allow-ldconfig-from-container=false", + expectedKey: "features.allow-ldconfig-from-container", + expectedValue: false, + }, + { + description: "pointer boolean option returns nil", + setFlag: "features.allow-ldconfig-from-container=nil", + expectedKey: "features.allow-ldconfig-from-container", + }, { description: "boolean option assumes true", setFlag: "disable-require", @@ -140,3 +202,86 @@ func TestSetFlagToKeyValue(t *testing.T) { }) } } + +func TestConfigCommandSet(t *testing.T) { + testCases := map[string]struct { + setFlag string + expectedKey string + expectedValue any + expectedError error + }{ + "child of boolean": { + setFlag: "disable-require.extra=true", + expectedError: errInvalidConfigOption, + }, + "child of string": { + setFlag: "nvidia-container-cli.path.extra=/tmp/cli", + expectedError: errInvalidConfigOption, + }, + "child of slice": { + setFlag: "nvidia-container-cli.environment.extra=VALUE", + expectedError: errInvalidConfigOption, + }, + "child of pointer": { + setFlag: "features.allow-ldconfig-from-container.extra=true", + expectedError: errInvalidConfigOption, + }, + "nested string": { + setFlag: "nvidia-container-cli.path=/tmp/cli", + expectedKey: "nvidia-container-cli.path", + expectedValue: "/tmp/cli", + }, + "deeply nested string": { + setFlag: "nvidia-container-runtime.modes.cdi.default-kind=example.com/gpu", + expectedKey: "nvidia-container-runtime.modes.cdi.default-kind", + expectedValue: "example.com/gpu", + }, + "slice": { + setFlag: "nvidia-container-cli.environment=FIRST=1:SECOND=2", + expectedKey: "nvidia-container-cli.environment", + expectedValue: []any{"FIRST=1", "SECOND=2"}, + }, + "pointer boolean assumes true": { + setFlag: "features.allow-ldconfig-from-container", + expectedKey: "features.allow-ldconfig-from-container", + expectedValue: true, + }, + "pointer boolean false": { + setFlag: "features.allow-ldconfig-from-container=false", + expectedKey: "features.allow-ldconfig-from-container", + expectedValue: false, + }, + "pointer boolean nil": { + setFlag: "features.allow-ldconfig-from-container=nil", + expectedKey: "features.allow-ldconfig-from-container", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.toml") + original := []byte("disable-require = false\n[features]\nallow-ldconfig-from-container = true\n") + require.NoError(t, os.WriteFile(configPath, original, 0600)) + + var err error + require.NotPanics(t, func() { + cmd := NewCommand(nil) + err = cmd.Run(t.Context(), []string{"config", "--config-file", configPath, "--in-place", "--set", tc.setFlag}) + }) + require.ErrorIs(t, err, tc.expectedError) + + contents, readErr := os.ReadFile(configPath) + require.NoError(t, readErr) + if tc.expectedError != nil { + require.ErrorIs(t, err, errUndefinedField) + require.Contains(t, err.Error(), "invalid --set option "+tc.setFlag) + require.Equal(t, original, contents) + return + } + + cfg, err := toml.LoadBytes(contents) + require.NoError(t, err) + require.Equal(t, tc.expectedValue, cfg.Get(tc.expectedKey)) + }) + } +}