Skip to content

Commit 4eba3a9

Browse files
committed
Addressed PR feedback
Signed-off-by: Harsh Rawat <[email protected]>
1 parent 2b6af52 commit 4eba3a9

File tree

6 files changed

+159
-21
lines changed

6 files changed

+159
-21
lines changed

cmd/containerd-shim-runhcs-v1/pod.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func createPod(ctx context.Context, events publisher, req *task.CreateTaskReques
171171

172172
var parent *uvm.UtilityVM
173173
var lopts *uvm.OptionsLCOW
174-
if oci.IsIsolated(s) || oci.IsIsolatedJobContainer(s) {
174+
if oci.IsIsolated(s) {
175175
// Create the UVM parent
176176
opts, err := oci.SpecToUVMCreateOpts(ctx, s, fmt.Sprintf("%s@vm", req.ID), owner)
177177
if err != nil {

cmd/containerd-shim-runhcs-v1/task_hcs_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ func Test_handleProcessArgsForIsolatedJobContainer(t *testing.T) {
332332
specs *specs.Process
333333
expectedCmdLine string
334334
expectedArgs []string
335-
initialUsername string
336335
expectedUsername string
337336
}{
338337
{
@@ -454,10 +453,6 @@ func Test_handleProcessArgsForIsolatedJobContainer(t *testing.T) {
454453
for _, tt := range tests {
455454
t.Run(tt.name, func(t *testing.T) {
456455
taskSpec := &specs.Spec{Annotations: tt.taskAnnotations}
457-
// Ensure initial Username is set if provided
458-
if tt.initialUsername != "" {
459-
tt.specs.User.Username = tt.initialUsername
460-
}
461456

462457
handleProcessArgsForIsolatedJobContainer(taskSpec, tt.specs)
463458

internal/hcsoci/hcsdoc_wcow.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"strings"
1515

1616
"github.com/Microsoft/go-winio/pkg/fs"
17+
"github.com/Microsoft/hcsshim/internal/ospath"
1718
specs "github.com/opencontainers/runtime-spec/specs-go"
1819
"github.com/sirupsen/logrus"
1920

@@ -496,7 +497,12 @@ func createWindowsContainerDocument(ctx context.Context, coi *createOptionsInter
496497

497498
// If the customer specified a custom rootfs path then use that instead of default c:\hpc.
498499
if customRootFsPath, ok := coi.Spec.Annotations[annotations.HostProcessRootfsLocation]; ok {
499-
v2Container.Storage.PrivilegedContainerRootPath = customRootFsPath
500+
customRootFsPathSanitized, err := ospath.Sanitize(customRootFsPath, ospath.DisallowedUVMMountPrefixes)
501+
if err != nil {
502+
return nil, nil, err
503+
}
504+
505+
v2Container.Storage.PrivilegedContainerRootPath = customRootFsPathSanitized
500506
}
501507
}
502508

internal/ospath/join.go

Lines changed: 0 additions & 14 deletions
This file was deleted.

internal/ospath/path.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package ospath
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"path"
8+
"path/filepath"
9+
"strings"
10+
)
11+
12+
var (
13+
errUnsafePath = errors.New("unsafe path detected")
14+
15+
// DisallowedUVMMountPrefixes represents common locations within UVM
16+
// where we do not want mounts to happen from customer perspective.
17+
DisallowedUVMMountPrefixes = []string{
18+
`C:\Windows`,
19+
`C:\mounts`,
20+
`C:\EFI`,
21+
`C:\SandboxMounts`,
22+
}
23+
)
24+
25+
// Join joins paths using the target OS's path separator.
26+
func Join(os string, elem ...string) string {
27+
if os == "windows" {
28+
return filepath.Join(elem...)
29+
}
30+
return path.Join(elem...)
31+
}
32+
33+
// Sanitize validates and normalizes a Windows path.
34+
func Sanitize(path string, disallowedPrefixes []string) (string, error) {
35+
if path == "" {
36+
return "", errUnsafePath
37+
}
38+
39+
// Normalize the path.
40+
cleanPath := filepath.Clean(path)
41+
42+
// Reject UNC paths (\\server\share or //server/share)
43+
if strings.HasPrefix(cleanPath, `\\`) || strings.HasPrefix(cleanPath, `//`) {
44+
return "", errUnsafePath
45+
}
46+
47+
// Check if the path is not in the disallowed paths.
48+
for _, prefix := range disallowedPrefixes {
49+
if strings.HasPrefix(cleanPath, prefix) {
50+
return "", errUnsafePath
51+
}
52+
}
53+
54+
// Reject if the path already exists (file/dir/symlink/junction).
55+
// Use Lstat so we do not follow symlinks.
56+
var err error
57+
if _, err = os.Lstat(cleanPath); err == nil {
58+
// Path exists
59+
return "", fmt.Errorf("%s: path %q already exists", errUnsafePath, cleanPath)
60+
}
61+
if !os.IsNotExist(err) {
62+
// Unexpected error (e.g., permission issues)
63+
return "", fmt.Errorf("%s: error checking existence for %q: %w", errUnsafePath, cleanPath, err)
64+
}
65+
66+
return cleanPath, nil
67+
}

internal/ospath/path_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package ospath
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestSanitize(t *testing.T) {
11+
// Create a temp folder and file to simulate "already exists"
12+
existingDir := t.TempDir()
13+
existingFile := filepath.Join(existingDir, "exists.txt")
14+
if err := os.WriteFile(existingFile, []byte("data"), 0644); err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
tests := []struct {
19+
name string
20+
path string
21+
disallowedPrefixes []string
22+
expectedPath string
23+
expectedErrPrefix string
24+
}{
25+
{
26+
name: "valid path",
27+
path: filepath.Join(existingDir, "test"),
28+
expectedPath: filepath.Join(existingDir, "test"),
29+
},
30+
{
31+
name: "empty path",
32+
path: "",
33+
expectedErrPrefix: errUnsafePath.Error(),
34+
},
35+
{
36+
name: "path traversal",
37+
path: `C:\foo\..\Windows`,
38+
disallowedPrefixes: []string{`C:\Windows`},
39+
expectedErrPrefix: errUnsafePath.Error(),
40+
},
41+
{
42+
name: "UNC path",
43+
path: `\\server\share`,
44+
expectedErrPrefix: errUnsafePath.Error(),
45+
},
46+
{
47+
name: "disallowed prefix",
48+
path: `C:\Windows\System32`,
49+
disallowedPrefixes: []string{`C:\Windows`},
50+
expectedErrPrefix: errUnsafePath.Error(),
51+
},
52+
{
53+
name: "existing folder",
54+
path: existingDir,
55+
expectedErrPrefix: "already exists",
56+
},
57+
{
58+
name: "existing file",
59+
path: existingFile,
60+
expectedErrPrefix: "already exists",
61+
},
62+
}
63+
64+
for _, tt := range tests {
65+
t.Run(tt.name, func(t *testing.T) {
66+
got, err := Sanitize(tt.path, tt.disallowedPrefixes)
67+
68+
if tt.expectedErrPrefix != "" {
69+
if err == nil {
70+
t.Fatalf("expected error containing %q, got nil", tt.expectedErrPrefix)
71+
}
72+
if !strings.Contains(err.Error(), tt.expectedErrPrefix) {
73+
t.Fatalf("expected error to contain %q, got %v", tt.expectedErrPrefix, err)
74+
}
75+
} else if err != nil {
76+
t.Fatalf("expected no error, got %v", err)
77+
}
78+
79+
if !strings.EqualFold(got, tt.expectedPath) {
80+
t.Errorf("expected path %q, got %q", tt.expectedPath, got)
81+
}
82+
})
83+
}
84+
}

0 commit comments

Comments
 (0)