Skip to content

Commit 7e0cd45

Browse files
committed
Check for valid paths in create-symlinks hook
This change updates the create-symlinks hook to always evaluate link paths in the container's root filesystem. In addition the executable is updated to return an error if a link could not be created. Signed-off-by: Christopher Desiniotis <cdesiniotis@nvidia.com>
1 parent a04e3ac commit 7e0cd45

File tree

12 files changed

+944
-8
lines changed

12 files changed

+944
-8
lines changed

cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"path/filepath"
2424
"strings"
2525

26+
"github.com/moby/sys/symlink"
2627
"github.com/urfave/cli/v2"
2728

2829
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
@@ -96,13 +97,12 @@ func (m command) run(c *cli.Context, cfg *config) error {
9697
}
9798
parts := strings.Split(l, "::")
9899
if len(parts) != 2 {
99-
m.logger.Warningf("Invalid link specification %v", l)
100-
continue
100+
return fmt.Errorf("invalid symlink specification %v", l)
101101
}
102102

103103
err := m.createLink(containerRoot, parts[0], parts[1])
104104
if err != nil {
105-
m.logger.Warningf("Failed to create link %v: %v", parts, err)
105+
return fmt.Errorf("failed to create link %v: %w", parts, err)
106106
}
107107
created[l] = true
108108
}
@@ -132,12 +132,17 @@ func (m command) createLink(containerRoot string, targetPath string, link string
132132
return nil
133133
}
134134

135-
m.logger.Infof("Symlinking %v to %v", linkPath, targetPath)
136-
err = os.MkdirAll(filepath.Dir(linkPath), 0755)
135+
resolvedLinkPath, err := symlink.FollowSymlinkInScope(linkPath, containerRoot)
136+
if err != nil {
137+
return fmt.Errorf("failed to follow path for link %v relative to %v: %w", link, containerRoot, err)
138+
}
139+
140+
m.logger.Infof("Symlinking %v to %v", resolvedLinkPath, targetPath)
141+
err = os.MkdirAll(filepath.Dir(resolvedLinkPath), 0755)
137142
if err != nil {
138143
return fmt.Errorf("failed to create directory: %v", err)
139144
}
140-
err = os.Symlink(targetPath, linkPath)
145+
err = os.Symlink(targetPath, resolvedLinkPath)
141146
if err != nil {
142147
return fmt.Errorf("failed to create symlink: %v", err)
143148
}

cmd/nvidia-cdi-hook/create-symlinks/create-symlinks_test.go

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@ package symlinks
33
import (
44
"os"
55
"path/filepath"
6+
"strings"
67
"testing"
78

89
testlog "github.com/sirupsen/logrus/hooks/test"
9-
1010
"github.com/stretchr/testify/require"
1111

1212
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
1313
)
1414

1515
func TestDoesLinkExist(t *testing.T) {
1616
tmpDir := t.TempDir()
17-
1817
require.NoError(
1918
t,
2019
makeFs(tmpDir,
@@ -42,6 +41,120 @@ func TestDoesLinkExist(t *testing.T) {
4241
require.False(t, exists)
4342
}
4443

44+
func TestCreateLink(t *testing.T) {
45+
type link struct {
46+
path string
47+
target string
48+
}
49+
type expectedLink struct {
50+
link
51+
err error
52+
}
53+
54+
testCases := []struct {
55+
description string
56+
containerContents []dirOrLink
57+
link link
58+
expectedCreateError error
59+
expectedLinks []expectedLink
60+
}{
61+
{
62+
description: "link to / resolves to container root",
63+
containerContents: []dirOrLink{
64+
{path: "/lib/foo", target: "/"},
65+
},
66+
link: link{
67+
path: "/lib/foo/libfoo.so",
68+
target: "libfoo.so.1",
69+
},
70+
expectedLinks: []expectedLink{
71+
{
72+
link: link{
73+
path: "{{ .containerRoot }}/libfoo.so",
74+
target: "libfoo.so.1",
75+
},
76+
},
77+
},
78+
},
79+
{
80+
description: "link to / resolves to container root; parent relative link",
81+
containerContents: []dirOrLink{
82+
{path: "/lib/foo", target: "/"},
83+
},
84+
link: link{
85+
path: "/lib/foo/libfoo.so",
86+
target: "../libfoo.so.1",
87+
},
88+
expectedLinks: []expectedLink{
89+
{
90+
link: link{
91+
path: "{{ .containerRoot }}/libfoo.so",
92+
target: "../libfoo.so.1",
93+
},
94+
},
95+
},
96+
},
97+
{
98+
description: "link to / resolves to container root; absolute link",
99+
containerContents: []dirOrLink{
100+
{path: "/lib/foo", target: "/"},
101+
},
102+
link: link{
103+
path: "/lib/foo/libfoo.so",
104+
target: "/a-path-in-container/foo/libfoo.so.1",
105+
},
106+
expectedLinks: []expectedLink{
107+
{
108+
link: link{
109+
path: "{{ .containerRoot }}/libfoo.so",
110+
target: "/a-path-in-container/foo/libfoo.so.1",
111+
},
112+
},
113+
{
114+
// We also check that the target is NOT created.
115+
link: link{
116+
path: "{{ .containerRoot }}/a-path-in-container/foo/libfoo.so.1",
117+
},
118+
err: os.ErrNotExist,
119+
},
120+
},
121+
},
122+
}
123+
124+
for _, tc := range testCases {
125+
t.Run(tc.description, func(t *testing.T) {
126+
tmpDir := t.TempDir()
127+
hostRoot := filepath.Join(tmpDir, "/host-root/")
128+
containerRoot := filepath.Join(tmpDir, "/container-root")
129+
130+
require.NoError(t, makeFs(hostRoot))
131+
require.NoError(t, makeFs(containerRoot, tc.containerContents...))
132+
133+
// nvidia-cdi-hook create-symlinks --link linkSpec
134+
err := getTestCommand().createLink(containerRoot, tc.link.target, tc.link.path)
135+
// TODO: We may be able to replace this with require.ErrorIs.
136+
if tc.expectedCreateError != nil {
137+
require.Error(t, err)
138+
} else {
139+
require.NoError(t, err)
140+
}
141+
142+
for _, expectedLink := range tc.expectedLinks {
143+
path := strings.Replace(expectedLink.path, "{{ .containerRoot }}", containerRoot, -1)
144+
path = strings.Replace(path, "{{ .hostRoot }}", hostRoot, -1)
145+
if expectedLink.target != "" {
146+
target, err := symlinks.Resolve(path)
147+
require.ErrorIs(t, err, expectedLink.err)
148+
require.Equal(t, expectedLink.target, target)
149+
} else {
150+
_, err := os.Stat(path)
151+
require.ErrorIs(t, err, expectedLink.err)
152+
}
153+
}
154+
})
155+
}
156+
}
157+
45158
func TestCreateLinkRelativePath(t *testing.T) {
46159
tmpDir := t.TempDir()
47160
hostRoot := filepath.Join(tmpDir, "/host-root/")
@@ -131,6 +244,8 @@ func TestCreateLinkOutOfBounds(t *testing.T) {
131244
// require.Error(t, err)
132245
_, err = os.Lstat(filepath.Join(hostRoot, "libfoo.so"))
133246
require.ErrorIs(t, err, os.ErrNotExist)
247+
_, err = os.Lstat(filepath.Join(containerRoot, hostRoot, "libfoo.so"))
248+
require.NoError(t, err)
134249
}
135250

136251
type dirOrLink struct {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/NVIDIA/go-nvlib v0.6.1
77
github.com/NVIDIA/go-nvml v0.12.4-0
88
github.com/fsnotify/fsnotify v1.7.0
9+
github.com/moby/sys/symlink v0.3.0
910
github.com/opencontainers/runtime-spec v1.2.0
1011
github.com/pelletier/go-toml v1.9.5
1112
github.com/sirupsen/logrus v1.9.3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
2828
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2929
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
3030
github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs=
31+
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
32+
github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=
3133
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
3234
github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
3335
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=

0 commit comments

Comments
 (0)