From 9a9ebdd0358d487d383191263c66ba62940d27be Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Mon, 21 Sep 2026 18:55:44 +0800 Subject: [PATCH] skip directories when resolving the nvml library path tryResolveLibrary returned the first candidate path that EvalSymlinks could resolve, and EvalSymlinks resolves directories just as happily as it resolves files. A directory named libnvidia-ml.so.1 sitting in an earlier search path therefore won over the real library further down the list, and the dlopen that follows failed with a not-found error. Only take a candidate if it actually resolves to a regular file. Same fix as go-nvlib#108, since this file carries its own copy of that code. Adds a test for the resolution helper covering the plain lookup, the shadowing case, a symlinked library (driver roots are full of those) and the fallback to the bare library name. The copyright header on the test file follows the form the rest of the repo uses. Signed-off-by: rootkiller6788 --- cmd/nvidia-device-plugin/root.go | 6 ++ cmd/nvidia-device-plugin/root_test.go | 96 +++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 cmd/nvidia-device-plugin/root_test.go diff --git a/cmd/nvidia-device-plugin/root.go b/cmd/nvidia-device-plugin/root.go index db9cec76e..6e86fbca6 100644 --- a/cmd/nvidia-device-plugin/root.go +++ b/cmd/nvidia-device-plugin/root.go @@ -67,6 +67,12 @@ func (r root) tryResolveLibrary(libraryName string) string { if err != nil { continue } + // EvalSymlinks succeeds for directories too, so a directory named after + // the library in an earlier search path would shadow the real one. + info, err := os.Stat(resolved) + if err != nil || !info.Mode().IsRegular() { + continue + } return resolved } diff --git a/cmd/nvidia-device-plugin/root_test.go b/cmd/nvidia-device-plugin/root_test.go new file mode 100644 index 000000000..cb1f63ee7 --- /dev/null +++ b/cmd/nvidia-device-plugin/root_test.go @@ -0,0 +1,96 @@ +/** +# 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 main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTryResolveLibrary(t *testing.T) { + const libraryName = "libnvidia-ml.so.1" + + testCases := []struct { + description string + // dirs, files and target are created relative to the test root before resolving. + dirs []string + files []string + link string + target string + // expected is the resolved path relative to the test root, or "" if + // the input library name is expected to be returned as is. + expected string + }{ + { + description: "library in first search path", + files: []string{"usr/lib64/" + libraryName}, + expected: "usr/lib64/" + libraryName, + }, + { + description: "directory in earlier search path does not shadow library", + dirs: []string{"usr/lib64/" + libraryName}, + files: []string{"usr/lib/x86_64-linux-gnu/" + libraryName}, + expected: "usr/lib/x86_64-linux-gnu/" + libraryName, + }, + { + description: "only directories found", + dirs: []string{"usr/lib64/" + libraryName}, + expected: "", + }, + { + description: "symlink to a library is resolved", + files: []string{"usr/lib/x86_64-linux-gnu/" + libraryName + ".580.126.09"}, + link: "usr/lib64/" + libraryName, + target: "usr/lib/x86_64-linux-gnu/" + libraryName + ".580.126.09", + expected: "usr/lib/x86_64-linux-gnu/" + libraryName + ".580.126.09", + }, + { + description: "library not found", + expected: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + rootDir := t.TempDir() + + for _, d := range tc.dirs { + require.NoError(t, os.MkdirAll(filepath.Join(rootDir, d), 0755)) + } + for _, f := range tc.files { + require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(rootDir, f)), 0755)) + require.NoError(t, os.WriteFile(filepath.Join(rootDir, f), []byte{}, 0644)) + } + if tc.link != "" { + require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(rootDir, tc.link)), 0755)) + require.NoError(t, os.Symlink(filepath.Join(rootDir, tc.target), filepath.Join(rootDir, tc.link))) + } + + r := root(rootDir) + resolved := r.tryResolveLibrary(libraryName) + + if tc.expected == "" { + require.Equal(t, libraryName, resolved) + return + } + require.Equal(t, filepath.Join(rootDir, tc.expected), resolved) + }) + } +}