説明
展開Readerの出力をサイズ制限なしでコピーすると、小さな入力が非常に大きな出力に広がり、CPU、メモリ、ディスクを枯渇させるおそれがあります。
想定される影響
- サーバーのリソース枯渇やプロセスの障害が発生する可能性があります。
- アップロードやアーカイブ処理がサービス拒否攻撃の入口になるおそれがあります。
対処方法
- 実際の展開出力に上限を設け、超過したデータを拒否してください。
io.LimitReaderは上限に達してもEOFを返すため、サイズ超過を別途判定する必要があります。 - 宣言された非圧縮サイズだけを信頼せず、実際に読み取ったバイト数を確認してください。アーカイブ全体の出力サイズ、エントリ数、処理時間も制限してください。
- Readerの作成・コピーのエラーを処理し、失敗した処理の部分出力を破棄してください。
例
エラーを返す関数内部の抜粋です。src は io.Reader、dst は一時出力で、compress/gzip、io、fmt を使います。呼び出し元はエラー時に部分出力を破棄する必要があります。上限の10 MiBは例で、超過を確認するために最大1バイトを追加で読みます。
変更前
go
r, _ := gzip.NewReader(src)
_, _ = io.Copy(dst, r)
変更後
go
const maxExpanded int64 = 10 << 20
r, err := gzip.NewReader(src)
if err != nil {
return err
}
defer r.Close()
n, err := io.Copy(dst, io.LimitReader(r, maxExpanded+1))
if err != nil {
return err
}
if n > maxExpanded {
return fmt.Errorf("expanded data exceeds limit")
}
return nil
解説:
- 変更前: 展開Readerから上限なしの
io.Copyやio.CopyBufferで最後までコピーすると、小さな入力が膨張し、CPU、メモリ、ディスクを枯渇させるおそれがあります。 - 変更後: エラーを処理し、実際の出力サイズを確認します。上限を超えるとエラーを返すため、呼び出し元で一時出力を破棄する必要があります。