Files
upstream-seaweedfs/weed/server/volume_grpc_copy_traversal_test.go
upstream-mirror 89f5be005d
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 03:23:42 +05:00

213 lines
6.7 KiB
Go

package weed_server
import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
"github.com/seaweedfs/seaweedfs/weed/stats"
"github.com/seaweedfs/seaweedfs/weed/storage"
"github.com/seaweedfs/seaweedfs/weed/storage/types"
"github.com/seaweedfs/seaweedfs/weed/util"
)
// newTraversalTestStore builds a single-location store rooted at dir.
func newTraversalTestStore(dir string) *storage.Store {
return storage.NewStore(
grpc.WithTransportCredentials(insecure.NewCredentials()),
"127.0.0.1", 0, 0, "", "test-store",
[]string{dir}, []int32{10}, []util.MinFreeSpace{{}},
dir, storage.NeedleMapInMemory,
[]types.DiskType{types.HardDriveType}, [][]string{nil},
0, stats.DiskIOProbeConfig{},
)
}
// fakeReceiveFileStream scripts a ReceiveFile request sequence and records the
// final response returned via SendAndClose.
type fakeReceiveFileStream struct {
grpc.ServerStream
reqs []*volume_server_pb.ReceiveFileRequest
index int
resp *volume_server_pb.ReceiveFileResponse
}
func (s *fakeReceiveFileStream) Recv() (*volume_server_pb.ReceiveFileRequest, error) {
if s.index >= len(s.reqs) {
return nil, io.EOF
}
r := s.reqs[s.index]
s.index++
return r, nil
}
func (s *fakeReceiveFileStream) SendAndClose(resp *volume_server_pb.ReceiveFileResponse) error {
s.resp = resp
return nil
}
func infoReq(info *volume_server_pb.ReceiveFileInfo) *volume_server_pb.ReceiveFileRequest {
return &volume_server_pb.ReceiveFileRequest{Data: &volume_server_pb.ReceiveFileRequest_Info{Info: info}}
}
func contentReq(b []byte) *volume_server_pb.ReceiveFileRequest {
return &volume_server_pb.ReceiveFileRequest{Data: &volume_server_pb.ReceiveFileRequest_FileContent{FileContent: b}}
}
// TestReceiveFile_RejectsTraversalExt ensures a client-supplied Ext with parent
// references cannot steer the EC-shard write outside the volume directory. The
// EC branch joins the Ext with util.Join, which path-cleans the ".." away
// before os.Create, so a bare concatenation check would miss it.
func TestReceiveFile_RejectsTraversalExt(t *testing.T) {
root := t.TempDir()
storeDir := filepath.Join(root, "a", "b", "store")
if err := os.MkdirAll(storeDir, 0o755); err != nil {
t.Fatal(err)
}
vs := &VolumeServer{store: newTraversalTestStore(storeDir)}
stream := &fakeReceiveFileStream{reqs: []*volume_server_pb.ReceiveFileRequest{
infoReq(&volume_server_pb.ReceiveFileInfo{
VolumeId: 4,
Ext: "/../../pwned.ec00",
IsEcVolume: true,
FileSize: 5,
}),
contentReq([]byte("pwned")),
}}
if err := vs.ReceiveFile(stream); err != nil {
t.Fatalf("ReceiveFile returned transport error: %v", err)
}
if stream.resp == nil || stream.resp.Error == "" {
t.Errorf("traversal ext was accepted; response = %+v", stream.resp)
}
assertNoEscapedFile(t, root, storeDir, "pwned")
}
// TestReceiveFile_RejectsTraversalCollection covers the sibling vector: the
// Collection is folded into the path as "<collection>_<vid>", so a value with
// a separator escapes the volume directory the same way a traversal ext does.
func TestReceiveFile_RejectsTraversalCollection(t *testing.T) {
root := t.TempDir()
storeDir := filepath.Join(root, "a", "b", "store")
if err := os.MkdirAll(storeDir, 0o755); err != nil {
t.Fatal(err)
}
vs := &VolumeServer{store: newTraversalTestStore(storeDir)}
stream := &fakeReceiveFileStream{reqs: []*volume_server_pb.ReceiveFileRequest{
infoReq(&volume_server_pb.ReceiveFileInfo{
VolumeId: 4,
Ext: ".ec00",
Collection: "../../pwned",
IsEcVolume: true,
FileSize: 5,
}),
contentReq([]byte("pwned")),
}}
if err := vs.ReceiveFile(stream); err != nil {
t.Fatalf("ReceiveFile returned transport error: %v", err)
}
if stream.resp == nil || stream.resp.Error == "" {
t.Errorf("traversal collection was accepted; response = %+v", stream.resp)
}
assertNoEscapedFile(t, root, storeDir, "pwned")
}
// TestReceiveFile_AcceptsNormalExt is the positive control: a legitimate EC
// shard extension still lands inside the volume directory.
func TestReceiveFile_AcceptsNormalExt(t *testing.T) {
storeDir := t.TempDir()
vs := &VolumeServer{store: newTraversalTestStore(storeDir)}
// A realistic collection name holding '.' and '-' must still be accepted.
stream := &fakeReceiveFileStream{reqs: []*volume_server_pb.ReceiveFileRequest{
infoReq(&volume_server_pb.ReceiveFileInfo{
VolumeId: 4,
Ext: ".ec00",
Collection: "my.bucket-1",
IsEcVolume: true,
FileSize: 5,
}),
contentReq([]byte("shard")),
}}
if err := vs.ReceiveFile(stream); err != nil {
t.Fatalf("ReceiveFile: %v", err)
}
if stream.resp == nil || stream.resp.Error != "" {
t.Fatalf("normal ext rejected: %+v", stream.resp)
}
if got, err := os.ReadFile(filepath.Join(storeDir, "my.bucket-1_4.ec00")); err != nil || string(got) != "shard" {
t.Fatalf("expected shard written inside volume dir, got %q err %v", got, err)
}
}
// fakeCopyFileServer records streamed content for CopyFile.
type fakeCopyFileServer struct {
grpc.ServerStream
sent []byte
}
func (s *fakeCopyFileServer) Send(resp *volume_server_pb.CopyFileResponse) error {
s.sent = append(s.sent, resp.FileContent...)
return nil
}
// TestCopyFile_RejectsTraversalExt ensures the read side cannot be steered to an
// arbitrary file with a traversal Ext.
func TestCopyFile_RejectsTraversalExt(t *testing.T) {
root := t.TempDir()
storeDir := filepath.Join(root, "a", "b", "store")
if err := os.MkdirAll(storeDir, 0o755); err != nil {
t.Fatal(err)
}
secret := filepath.Join(root, "a", "b", "secret.txt")
if err := os.WriteFile(secret, []byte("top secret"), 0o644); err != nil {
t.Fatal(err)
}
vs := &VolumeServer{store: newTraversalTestStore(storeDir)}
// util.Join(storeDir, "4"+"/../../secret.txt") path-cleans to the secret.
req := &volume_server_pb.CopyFileRequest{
VolumeId: 4,
Ext: "/../../secret.txt",
IsEcVolume: true,
StopOffset: 1 << 20,
}
server := &fakeCopyFileServer{}
err := vs.CopyFile(req, server)
if err == nil {
t.Errorf("CopyFile accepted a traversal ext")
}
if len(server.sent) != 0 {
t.Errorf("CopyFile leaked %d bytes for a traversal ext: %q", len(server.sent), server.sent)
}
}
// assertNoEscapedFile fails if any file whose name contains needle exists under
// root but outside storeDir.
func assertNoEscapedFile(t *testing.T, root, storeDir, needle string) {
t.Helper()
_ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
rel, _ := filepath.Rel(storeDir, path)
outside := rel == "" || rel[0] == '.'
if outside && strings.Contains(d.Name(), needle) {
t.Errorf("path traversal: file written outside volume dir at %s", path)
}
return nil
})
}