io.Copy を上限付きで実行する
「もうlintに怒られない!Goでより安全にzipを扱うために」を読んで「なるほど!」と思ったが,(おそらくコードをかなり端折ってるんだと思うが)サンプル・コードがツッコミどころ満載なので,うちのブログでも覚え書として記しておく。 自作ツールでも圧縮データの解凍処理を使ってるしね。
gosec について
ところで,上述のリンク先で使われている gosec は統合 linter である golangci-lint から呼び出すことができるが,既定では無効になっている。
有効にするには --enable
オプションで gosec を指定すればよい。
$ golangci-lint run --enable gosec ./...
gosec が既定で有効になっていないのは,特に unsafe
標準パッケージを使っている場合の誤検知が多すぎるかららしい。
私の環境で試してみたが処理速度的には問題ないようなので,一度は試してみてもいいかもしれない。
最初のサンプル・コード
まずは起点となるサンプル・コードを挙げておこう。 こんな感じでどうだろう。
package main
import (
"bytes"
"compress/zlib"
"fmt"
"io"
"os"
"strings"
)
func compress(dst io.Writer, src io.Reader) error {
zw := zlib.NewWriter(dst)
defer zw.Close()
if _, err := io.Copy(zw, src); err != nil {
return err
}
return nil
}
func extract(dst io.Writer, src io.Reader) error {
r, err := zlib.NewReader(src)
if err != nil {
return err
}
if _, err := io.Copy(dst, r); err != nil {
return err
}
return nil
}
func main() {
content := "Hello world\n" //raw data
zbuf := &bytes.Buffer{}
//compress raw data
if err := compress(zbuf, strings.NewReader(content)); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
//display compressed data
b := zbuf.Bytes()
fmt.Printf("%d bytes: %v\n", len(b), b)
//extract from compressed data
buf := &bytes.Buffer{}
err := extract(buf, bytes.NewReader(b))
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
//display extracted data
if _, err := io.Copy(os.Stdout, buf); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
}
最初に紹介した記事で使っている archive/zip
パッケージはファイルシステムを含んでいるので,今回はもっとシンプルに compress/zlib
パッケージ(RFC 1950 準拠)を使ってみた。
これならデータの圧縮と解凍のみ注視できるだろう。
このコードを実行すると
$ go run sample.go
24 bytes: [120 156 242 72 205 201 201 87 40 207 47 202 73 225 2 4 0 0 255 255 28 242 4 71]
Hello world
と一応は問題なく動く。 これで準備OK。
Decompression Bomb Vulnerability
では,このコードに対して lint をかけてみる。
$ golangci-lint run --enable gosec ./...
sample1.go:27:15: G110: Potential DoS vulnerability via decompression bomb (gosec)
if _, err := io.Copy(dst, r); err != nil {
^
おー,出た出た。
“Decompression bomb” というのは
ということで,主にウイルス対策ツールをターゲットにした攻撃手段らしい。
まぁ 42.zip
みたいなファイルを解凍したら普通にシステムが死ぬと思うけど(笑)
解凍処理の改修
“Decompression bomb” を緩和する方法はいくつかあるようだが,いちばん安直なのは io
.Copy()
関数に上限を設けることだろう。
そのための関数として io
.CopyN()
が用意されている。
func io.CopyN(dst io.Writer, src io.Reader, n int64) (written int64, err error)
機能としては
ということで
と見なすことができそうだ。
そこで最初のサンプルコードの extract()
関数を以下のように書き直す。
const maxSize = 1024 * 1024 * 1024 //1GB
var ErrTooLarge = errors.New("too laege decompressed data")
func extract(dst io.Writer, src io.Reader) error {
r, err := zlib.NewReader(src)
if err != nil {
return err
}
if _, err := io.CopyN(dst, r, maxSize); err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
return ErrTooLarge
}
とりあえずコピーの上限は1GBとした(深い意味はない)。 これで少なくとも gosec に怒られることはなくなる。
これ仕込むのかぁ。 頑張ろう。
ブックマーク
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。