Skip to content

Commit b3c54ec

Browse files
reject object names with '\' on windows (#16856)
1 parent 6c11dbf commit b3c54ec

File tree

3 files changed

+72
-6
lines changed

3 files changed

+72
-6
lines changed

cmd/object-api-multipart_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"context"
2323
"fmt"
2424
"reflect"
25+
"runtime"
2526
"strings"
2627
"testing"
2728

@@ -32,6 +33,9 @@ import (
3233

3334
// Wrapper for calling NewMultipartUpload tests for both Erasure multiple disks and single node setup.
3435
func TestObjectNewMultipartUpload(t *testing.T) {
36+
if runtime.GOOS == globalWindowsOSName {
37+
t.Skip()
38+
}
3539
ExecObjectLayerTest(t, testObjectNewMultipartUpload)
3640
}
3741

@@ -110,9 +114,18 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test
110114
{"--", object, uploadID, BucketNotFound{}},
111115
{"foo", object, uploadID, BucketNotFound{}},
112116
{bucket, object, "foo-foo", InvalidUploadID{}},
113-
{bucket, "\\", uploadID, InvalidUploadID{}},
114117
{bucket, object, uploadID, nil},
115118
}
119+
120+
if runtime.GOOS != globalWindowsOSName {
121+
abortTestCases = append(abortTestCases, struct {
122+
bucketName string
123+
objName string
124+
uploadID string
125+
expectedErrType error
126+
}{bucket, "\\", uploadID, InvalidUploadID{}})
127+
}
128+
116129
// Iterating over creatPartCases to generate multipart chunks.
117130
for i, testCase := range abortTestCases {
118131
err = obj.AbortMultipartUpload(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, opts)

cmd/object-api-utils.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,7 @@ func IsValidObjectPrefix(object string) bool {
174174
// work with file systems, we will reject here
175175
// to return object name invalid rather than
176176
// a cryptic error from the file system.
177-
if strings.ContainsRune(object, 0) {
178-
return false
179-
}
180-
return true
177+
return !strings.ContainsRune(object, 0)
181178
}
182179

183180
// checkObjectNameForLengthAndSlash -check for the validity of object name length and prefis as slash
@@ -199,7 +196,7 @@ func checkObjectNameForLengthAndSlash(bucket, object string) error {
199196
if runtime.GOOS == globalWindowsOSName {
200197
// Explicitly disallowed characters on windows.
201198
// Avoids most problematic names.
202-
if strings.ContainsAny(object, `:*?"|<>`) {
199+
if strings.ContainsAny(object, `\:*?"|<>`) {
203200
return ObjectNameInvalid{
204201
Bucket: bucket,
205202
Object: object,

cmd/object-api-utils_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,75 @@ package cmd
1919

2020
import (
2121
"bytes"
22+
"context"
2223
"fmt"
2324
"io"
2425
"net/http"
26+
"net/http/httptest"
2527
"reflect"
28+
"runtime"
2629
"strconv"
2730
"testing"
2831

2932
"github.com/klauspost/compress/s2"
33+
"github.com/minio/minio/internal/auth"
3034
"github.com/minio/minio/internal/config/compress"
3135
"github.com/minio/minio/internal/crypto"
3236
"github.com/minio/pkg/trie"
3337
)
3438

39+
// Wrapper
40+
func TestPathTraversalExploit(t *testing.T) {
41+
if runtime.GOOS != globalWindowsOSName {
42+
t.Skip()
43+
}
44+
defer DetectTestLeak(t)()
45+
ExecExtendedObjectLayerAPITest(t, testPathTraversalExploit, []string{"PutObject"})
46+
}
47+
48+
// testPathTraversal exploit test, exploits path traversal on windows
49+
// with following object names "\\../.minio.sys/config/iam/${username}/identity.json"
50+
// #16852
51+
func testPathTraversalExploit(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
52+
credentials auth.Credentials, t *testing.T,
53+
) {
54+
if err := newTestConfig(globalMinioDefaultRegion, obj); err != nil {
55+
t.Fatalf("Initializing config.json failed")
56+
}
57+
58+
objectName := `\../.minio.sys/config/hello.txt`
59+
60+
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
61+
rec := httptest.NewRecorder()
62+
// construct HTTP request for Get Object end point.
63+
req, err := newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", bucketName, objectName),
64+
int64(5), bytes.NewReader([]byte("hello")), credentials.AccessKey, credentials.SecretKey, map[string]string{})
65+
if err != nil {
66+
t.Fatalf("failed to create HTTP request for Put Object: <ERROR> %v", err)
67+
}
68+
69+
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
70+
// Call the ServeHTTP to execute the handler.
71+
apiRouter.ServeHTTP(rec, req)
72+
73+
ctx, cancel := context.WithCancel(GlobalContext)
74+
defer cancel()
75+
76+
// Now check if we actually wrote to backend (regardless of the response
77+
// returned by the server).
78+
z := obj.(*erasureServerPools)
79+
xl := z.serverPools[0].sets[0]
80+
erasureDisks := xl.getDisks()
81+
parts, errs := readAllFileInfo(ctx, erasureDisks, bucketName, objectName, "", false)
82+
for i := range parts {
83+
if errs[i] == nil {
84+
if parts[i].Name == objectName {
85+
t.Errorf("path traversal allowed to allow writing to minioMetaBucket: %s", instanceType)
86+
}
87+
}
88+
}
89+
}
90+
3591
// Tests validate bucket name.
3692
func TestIsValidBucketName(t *testing.T) {
3793
testCases := []struct {

0 commit comments

Comments
 (0)