fix(storage): validate internode paths and erasure payloads

Storage REST request bodies and Grid RPC frames bypass the HTTP validity middleware, allowing wire-supplied paths and malformed FileInfo values to reach xlStorage unchecked.

Wrap the remotely exposed StorageAPI with a guard that covers every path-bearing method, including nested metadata fields. Reject traversal and destructive volume-root aliases before path cleaning can erase them, and validate erasure geometry and part sizes at the same wire boundary. Keep a raw-volume check in getVolDir for peer-S3 calls that bypass the wrapper.

Reflection, fuzz, traversal, peer-S3, compatibility, and malformed-erasure tests pin the complete method surface and prove that legal object names remain accepted.

Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Feng Ruohang
2026-08-04 22:40:43 +08:00
parent a36fd8fffb
commit ca7baa670d
6 changed files with 1370 additions and 1 deletions
+16 -1
View File
@@ -75,6 +75,10 @@ var (
storageListDirRPC = grid.NewStream[*grid.MSS, grid.NoPayload, *ListDirResult](grid.HandlerListDir, grid.NewMSS, nil, func() *ListDirResult { return &ListDirResult{} }).WithOutCapacity(1)
)
// getStorageViaEndpoint returns the drive UNGUARDED. It is for local callers
// only, whose arguments the server itself constructed. Anything serving a
// remote peer must go through storageRESTServer.getStorage(), which wraps this
// in guardedStorage to reject traversal in wire-supplied paths.
func getStorageViaEndpoint(endpoint Endpoint) StorageAPI {
globalLocalDrivesMu.RLock()
defer globalLocalDrivesMu.RUnlock()
@@ -85,7 +89,18 @@ func getStorageViaEndpoint(endpoint Endpoint) StorageAPI {
}
func (s *storageRESTServer) getStorage() StorageAPI {
return getStorageViaEndpoint(s.endpoint)
st := getStorageViaEndpoint(s.endpoint)
if st == nil {
// Must stay an untyped nil. IsAuthValid and checkID compare the result
// against nil before authenticating, and a nil interface wrapped in a
// value struct does not compare equal to nil - that would turn an
// unauthenticated request against a drive that has not come up yet
// into a nil dereference.
return nil
}
// Reject traversal in paths carried by request bodies and grid RPC frames,
// neither of which the global HTTP middleware can see. See guardedStorage.
return guardedStorage{st}
}
func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {