Skip to content

Commit 8c9d3d8

Browse files
authored
Merge commit from fork
Check for valid paths in create-symlinks hook
2 parents efb18a7 + 7e0cd45 commit 8c9d3d8

File tree

12 files changed

+1144
-34
lines changed

12 files changed

+1144
-34
lines changed

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

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@
1717
package symlinks
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"os"
2223
"path/filepath"
2324
"strings"
2425

26+
"github.com/moby/sys/symlink"
2527
"github.com/urfave/cli/v2"
2628

2729
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
30+
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
2831
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
2932
)
3033

@@ -33,7 +36,6 @@ type command struct {
3336
}
3437

3538
type config struct {
36-
hostRoot string
3739
links cli.StringSlice
3840
containerSpec string
3941
}
@@ -65,12 +67,6 @@ func (m command) build() *cli.Command {
6567
Destination: &cfg.links,
6668
},
6769
// The following flags are testing-only flags.
68-
&cli.StringFlag{
69-
Name: "host-root",
70-
Usage: "The root on the host filesystem to use to resolve symlinks. This is only intended for testing.",
71-
Destination: &cfg.hostRoot,
72-
Hidden: true,
73-
},
7470
&cli.StringFlag{
7571
Name: "container-spec",
7672
Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN. This is only intended for testing.",
@@ -95,61 +91,77 @@ func (m command) run(c *cli.Context, cfg *config) error {
9591

9692
created := make(map[string]bool)
9793
for _, l := range cfg.links.Value() {
94+
if created[l] {
95+
m.logger.Debugf("Link %v already processed", l)
96+
continue
97+
}
9898
parts := strings.Split(l, "::")
9999
if len(parts) != 2 {
100-
m.logger.Warningf("Invalid link specification %v", l)
101-
continue
100+
return fmt.Errorf("invalid symlink specification %v", l)
102101
}
103102

104-
err := m.createLink(created, cfg.hostRoot, containerRoot, parts[0], parts[1])
103+
err := m.createLink(containerRoot, parts[0], parts[1])
105104
if err != nil {
106-
m.logger.Warningf("Failed to create link %v: %v", parts, err)
105+
return fmt.Errorf("failed to create link %v: %w", parts, err)
107106
}
107+
created[l] = true
108108
}
109109
return nil
110110
}
111111

112-
func (m command) createLink(created map[string]bool, hostRoot string, containerRoot string, target string, link string) error {
113-
linkPath, err := changeRoot(hostRoot, containerRoot, link)
112+
// createLink creates a symbolic link in the specified container root.
113+
// This is equivalent to:
114+
//
115+
// chroot {{ .containerRoot }} ln -s {{ .target }} {{ .link }}
116+
//
117+
// If the specified link already exists and points to the same target, this
118+
// operation is a no-op. If the link points to a different target, an error is
119+
// returned.
120+
//
121+
// Note that if the link path resolves to an absolute path oudside of the
122+
// specified root, this is treated as an absolute path in this root.
123+
func (m command) createLink(containerRoot string, targetPath string, link string) error {
124+
linkPath := filepath.Join(containerRoot, link)
125+
126+
exists, err := doesLinkExist(targetPath, linkPath)
114127
if err != nil {
115-
m.logger.Warningf("Failed to resolve path for link %v relative to %v: %v", link, containerRoot, err)
128+
return fmt.Errorf("failed to check if link exists: %w", err)
116129
}
117-
if created[linkPath] {
118-
m.logger.Debugf("Link %v already created", linkPath)
130+
if exists {
131+
m.logger.Debugf("Link %s already exists", linkPath)
119132
return nil
120133
}
121134

122-
targetPath, err := changeRoot(hostRoot, "/", target)
135+
resolvedLinkPath, err := symlink.FollowSymlinkInScope(linkPath, containerRoot)
123136
if err != nil {
124-
m.logger.Warningf("Failed to resolve path for target %v relative to %v: %v", target, "/", err)
137+
return fmt.Errorf("failed to follow path for link %v relative to %v: %w", link, containerRoot, err)
125138
}
126139

127-
m.logger.Infof("Symlinking %v to %v", linkPath, targetPath)
128-
err = os.MkdirAll(filepath.Dir(linkPath), 0755)
140+
m.logger.Infof("Symlinking %v to %v", resolvedLinkPath, targetPath)
141+
err = os.MkdirAll(filepath.Dir(resolvedLinkPath), 0755)
129142
if err != nil {
130143
return fmt.Errorf("failed to create directory: %v", err)
131144
}
132-
err = os.Symlink(target, linkPath)
145+
err = os.Symlink(targetPath, resolvedLinkPath)
133146
if err != nil {
134147
return fmt.Errorf("failed to create symlink: %v", err)
135148
}
136149

137150
return nil
138151
}
139152

140-
func changeRoot(current string, new string, path string) (string, error) {
141-
if !filepath.IsAbs(path) {
142-
return path, nil
153+
// doesLinkExist returns true if link exists and points to target.
154+
// An error is returned if link exists but points to a different target.
155+
func doesLinkExist(target string, link string) (bool, error) {
156+
currentTarget, err := symlinks.Resolve(link)
157+
if errors.Is(err, os.ErrNotExist) {
158+
return false, nil
143159
}
144-
145-
relative := path
146-
if current != "" {
147-
r, err := filepath.Rel(current, path)
148-
if err != nil {
149-
return "", err
150-
}
151-
relative = r
160+
if err != nil {
161+
return false, fmt.Errorf("failed to resolve existing symlink %s: %w", link, err)
152162
}
153-
154-
return filepath.Join(new, relative), nil
163+
if currentTarget == target {
164+
return true, nil
165+
}
166+
return true, fmt.Errorf("unexpected link target: %s", currentTarget)
155167
}

0 commit comments

Comments
 (0)