Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/azurefile/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
volumeMetadataReplaceMap[pvNameMetadata] = v
case serverNameField:
case folderNameField:
if v != "" {
if err := isValidFolderName(v); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid folderName in storage class: %v", err)
}
}
case clientIDField:
case tenantIDField:
case confidentialContainerLabelField:
Expand Down
17 changes: 17 additions & 0 deletions pkg/azurefile/controllerserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,23 @@ var _ = ginkgo.Describe("TestCreateVolume", func() {
gomega.Expect(err).To(gomega.Equal(expectedErr))
})
})
ginkgo.When("Invalid folderName", func() {
ginkgo.It("should fail", func(ctx context.Context) {
allParam := map[string]string{
folderNameField: "my|folder",
}

req := &csi.CreateVolumeRequest{
Name: "folderName-invalid",
CapacityRange: stdCapRange,
VolumeCapabilities: stdVolCap,
Parameters: allParam,
}
expectedErr := status.Errorf(codes.InvalidArgument, "invalid folderName in storage class: folderName(\"my|folder\") contains invalid character in segment \"my|folder\", characters \\:*?\"<>| are not allowed")
_, err := d.CreateVolume(ctx, req)
gomega.Expect(err).To(gomega.Equal(expectedErr))
})
})
ginkgo.When("Invalid PublicNetworkAccess", func() {
ginkgo.It("should fail", func(ctx context.Context) {
allParam := map[string]string{
Expand Down
5 changes: 5 additions & 0 deletions pkg/azurefile/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,11 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
diskName = v
case folderNameField:
folderName = v
if folderName != "" {
if err := isValidFolderName(folderName); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid folderName: %v", err)
}
}
case createFolderIfNotExistField:
createFolderIfNotExist = strings.EqualFold(v, trueValue)
case serverNameField:
Expand Down
13 changes: 13 additions & 0 deletions pkg/azurefile/nodeserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,19 @@ func TestNodeStageVolume(t *testing.T) {
DefaultError: status.Error(codes.InvalidArgument, "protocol(test_protocol) is not supported, supported protocol list: [smb nfs]"),
},
},
{
desc: "[Error] Invalid folderName",
req: &csi.NodeStageVolumeRequest{VolumeId: "vol_1", StagingTargetPath: sourceTest,
VolumeCapability: &stdVolCap,
VolumeContext: map[string]string{
folderNameField: "my*folder",
shareNameField: "test_sharename",
serverNameField: "test_servername",
}},
expectedErr: testutil.TestError{
DefaultError: status.Errorf(codes.InvalidArgument, "invalid folderName: folderName(\"my*folder\") contains invalid character in segment \"my*folder\", characters \\:*?\"<>| are not allowed"),
},
},
{
desc: "[Error] Invalid fsGroupChangePolicy",
req: &csi.NodeStageVolumeRequest{VolumeId: "vol_1", StagingTargetPath: sourceTest,
Expand Down
48 changes: 48 additions & 0 deletions pkg/azurefile/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,54 @@ func setCredentialCache(server, clientID, tenantID, tokenFile string) ([]byte, e
return cmd.CombinedOutput()
}

// invalidFolderNameChars contains characters not allowed in Azure file share folder names
var invalidFolderNameChars = regexp.MustCompile(`[\\:*?"<>|]`)

// isValidFolderName checks if a folder name is valid for Azure file share.
// Empty folderName is allowed. Folder name may contain "/" as path separator for nested folders.
// Each path segment must not contain \:*?"<>| or control characters,
// must not be ".." (directory traversal), and must not end with a period or space.
func isValidFolderName(folderName string) error {
if folderName == "" {
return nil
}

// reject null bytes early — they truncate C strings and are a path injection risk
if strings.ContainsRune(folderName, 0) {
return fmt.Errorf("folderName(%q) contains null byte which is not allowed", folderName)
}

segments := strings.Split(strings.Trim(folderName, "/"), "/")
for _, seg := range segments {
if seg == "" {
return fmt.Errorf("folderName contains empty path segment")
}

// ".." and "." are not allowed as path segments (directory traversal)
if seg == ".." || seg == "." {
return fmt.Errorf("folderName(%q) contains disallowed path segment %q", folderName, seg)
}

// check for invalid characters
if invalidFolderNameChars.MatchString(seg) {
return fmt.Errorf("folderName(%q) contains invalid character in segment %q, characters \\:*?\"<>| are not allowed", folderName, seg)
}

// check for control characters (0x00-0x1F)
for _, c := range seg {
if c >= 0x00 && c <= 0x1F {
return fmt.Errorf("folderName(%q) contains control character in segment %q", folderName, seg)
}
}

// must not end with period or space
if strings.HasSuffix(seg, ".") || strings.HasSuffix(seg, " ") {
return fmt.Errorf("folderName(%q) segment %q must not end with a period or space", folderName, seg)
}
}
return nil
}

// isValidTokenFileName checks if the token file name is valid
// fileName should only contain alphanumeric characters, hyphens
func isValidTokenFileName(fileName string) bool {
Expand Down
49 changes: 49 additions & 0 deletions pkg/azurefile/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1558,3 +1558,52 @@ func TestIsValidTokenFileName(t *testing.T) {
})
}
}

func TestIsValidFolderName(t *testing.T) {
tests := []struct {
name string
folder string
expectErr bool
}{
{name: "valid simple", folder: "myfolder", expectErr: false},
{name: "valid nested", folder: "a/b/c", expectErr: false},
{name: "valid with leading slash", folder: "/myfolder", expectErr: false},
{name: "valid with trailing slash", folder: "myfolder/", expectErr: false},
{name: "valid with pvc placeholder", folder: "${pvc.metadata.name}", expectErr: false},
{name: "empty string is allowed", folder: "", expectErr: false},
{name: "backslash", folder: "my\\folder", expectErr: true},
{name: "colon", folder: "my:folder", expectErr: true},
{name: "asterisk", folder: "my*folder", expectErr: true},
{name: "question mark", folder: "my?folder", expectErr: true},
{name: "double quote", folder: `my"folder`, expectErr: true},
{name: "less than", folder: "my<folder", expectErr: true},
{name: "greater than", folder: "my>folder", expectErr: true},
{name: "pipe", folder: "my|folder", expectErr: true},
{name: "control char", folder: "my\x01folder", expectErr: true},
{name: "null byte", folder: "my\x00folder", expectErr: true},
{name: "whitespace only", folder: " ", expectErr: true},
{name: "whitespace only segment in path", folder: "a/ /b", expectErr: true},
{name: "dot dot segment", folder: "..", expectErr: true},
{name: "dot dot in path", folder: "a/../b", expectErr: true},
{name: "ends with period", folder: "folder.", expectErr: true},
{name: "ends with space", folder: "folder ", expectErr: true},
{name: "empty segment", folder: "a//b", expectErr: true},
{name: "nested segment ends with period", folder: "a/b./c", expectErr: true},
{name: "valid hyphen and underscore", folder: "my-folder_name", expectErr: false},
{name: "valid dots in middle", folder: "my.folder.name", expectErr: false},
{name: "invalid single dot segment", folder: ".", expectErr: true},
{name: "reserved name CON is allowed", folder: "CON", expectErr: false},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := isValidFolderName(tc.folder)
if tc.expectErr && err == nil {
t.Fatalf("isValidFolderName(%q) expected error but got nil", tc.folder)
}
if !tc.expectErr && err != nil {
t.Fatalf("isValidFolderName(%q) unexpected error: %v", tc.folder, err)
}
})
}
}
Loading