mirror of
https://github.com/pgsty/minio.git
synced 2026-07-24 06:26:17 +03:00
fix: CVE-2026-42600 remove ReadMultiple storage-REST API
The internode storage-REST ReadMultiple endpoint (/rmpl) joined attacker-controlled Bucket/Prefix/Files into a filesystem path with no validation, letting a peer with internode credentials read files outside the drive root (GHSA-xh8f-g2qw-gcm7). ReadMultiple has had no production caller since upstream #20390 removed the last one (listParts) in Sep 2024; multipart now uses ReadParts (/rps). Following the upstream fix, remove the whole API instead of validating paths: route constant and registration, server handler, REST client wrapper, the StorageAPI/xlStorage/xlStorageDiskIDCheck methods, the storageMetricReadMultiple metric, and the ReadMultipleReq/Resp datatypes. Regenerated the msgp and stringer outputs; storageRESTVersion stays at v63. Co-authored-by: Codex <codex@openai.com> Co-authored-by: Claude Code <claude-code@anthropic.com>
This commit is contained in:
@@ -3187,81 +3187,6 @@ func (s *xlStorage) ReadParts(ctx context.Context, volume string, partMetaPaths
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// ReadMultiple will read multiple files and send each back as response.
|
||||
// Files are read and returned in the given order.
|
||||
// The resp channel is closed before the call returns.
|
||||
// Only a canceled context will return an error.
|
||||
func (s *xlStorage) ReadMultiple(ctx context.Context, req ReadMultipleReq, resp chan<- ReadMultipleResp) error {
|
||||
defer xioutil.SafeClose(resp)
|
||||
|
||||
volumeDir := pathJoin(s.drivePath, req.Bucket)
|
||||
found := 0
|
||||
for _, f := range req.Files {
|
||||
if contextCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
r := ReadMultipleResp{
|
||||
Bucket: req.Bucket,
|
||||
Prefix: req.Prefix,
|
||||
File: f,
|
||||
}
|
||||
|
||||
var data []byte
|
||||
var mt time.Time
|
||||
|
||||
fullPath := pathJoin(volumeDir, req.Prefix, f)
|
||||
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
||||
if err := w.Run(func() (err error) {
|
||||
if req.MetadataOnly {
|
||||
data, mt, err = s.readMetadataWithDMTime(ctx, fullPath)
|
||||
} else {
|
||||
data, mt, err = s.readAllDataWithDMTime(ctx, req.Bucket, volumeDir, fullPath)
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
if !IsErr(err, errFileNotFound, errVolumeNotFound) {
|
||||
r.Exists = true
|
||||
r.Error = err.Error()
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case resp <- r:
|
||||
}
|
||||
if req.AbortOn404 && !r.Exists {
|
||||
// We stop at first file not found.
|
||||
// We have already reported the error, return nil.
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
diskHealthCheckOK(ctx, nil)
|
||||
if req.MaxSize > 0 && int64(len(data)) > req.MaxSize {
|
||||
r.Exists = true
|
||||
r.Error = fmt.Sprintf("max size (%d) exceeded: %d", req.MaxSize, len(data))
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case resp <- r:
|
||||
continue
|
||||
}
|
||||
}
|
||||
found++
|
||||
r.Exists = true
|
||||
r.Data = data
|
||||
r.Modtime = mt
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case resp <- r:
|
||||
}
|
||||
if req.MaxResults > 0 && found >= req.MaxResults {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *xlStorage) StatInfoFile(ctx context.Context, volume, path string, glob bool) (stat []StatInfo, err error) {
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user