G110:通过减压炸弹 (gosec) 的潜在 DoS 漏洞

G110: Potential DoS vulnerability via decompression bomb (gosec)

我收到以下 golintci 消息:

testdrive/utils.go:92:16: G110: Potential DoS vulnerability via decompression bomb (gosec)
    if _, err := io.Copy(targetFile, fileReader); err != nil {
                 ^

阅读相应的 CWE,我不清楚如何更正。

请指点

func unzip(archive, target string) error {
    reader, err := zip.OpenReader(archive)
    if err != nil {
        return err
    }

    for _, file := range reader.File {
        path := filepath.Join(target, file.Name) // nolint: gosec
        if file.FileInfo().IsDir() {
            if err := os.MkdirAll(path, file.Mode()); err != nil {
                return err
            }
            continue
        }

        fileReader, err := file.Open()
        if err != nil {
            return err
        }
        defer fileReader.Close() // nolint: errcheck

        targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
        if err != nil {
            return err
        }
        defer targetFile.Close() // nolint: errcheck

        if _, err := io.Copy(targetFile, fileReader); err != nil {
            return err
        }
    }

    return nil
}

假设您正在处理压缩数据,则需要使用 io.CopyN
您可以尝试使用 --nocompress 标志的解决方法。但这将导致数据被包含在未压缩的状态。

查看以下 PR 和相关问题:https://github.com/go-bindata/go-bindata/pull/50

您收到的警告来自 gosec 中提供的规则。

该规则专门检测 io.Copy 在文件解压缩时的使用情况。

这是一个潜在的问题,因为 io.Copy:

copies from src to dst until either EOF is reached on src or an error occurs.

因此,恶意负载可能会导致您的程序解压缩意外的大量数据并耗尽内存,从而导致警告消息中提到的拒绝服务。

特别是,gosec 将检查 (source) 您程序的 AST 并警告您使用 io.Copyio.CopyBuffer 以及以下任何一项:

  • "compress/gzip".NewReader
  • "compress/zlib".NewReaderNewReaderDict
  • "compress/bzip2".NewReader
  • "compress/flate".NewReaderNewReaderDict
  • "compress/lzw".NewReader
  • "archive/tar".NewReader
  • "archive/zip".NewReader
  • "*archive/zip".File.Open

使用 io.CopyN removes the warning because (quote) it "copies n bytes (or until an error) from src to dst", thus giving you (the program writer) control of how many bytes to copy. So you could pass an arbitrarily large n that you set based on the available resources of your application, or copy in chunks.

根据提供的各种指示,替换

        if _, err := io.Copy(targetFile, fileReader); err != nil {
            return err
        }

        for {
            _, err := io.CopyN(targetFile, fileReader, 1024)
            if err != nil {
                if err == io.EOF {
                    break
                }
                return err
            }
        }

PS 虽然这有助于内存占用,但这无助于复制很长 and/or 无限流的 DDOS 攻击 ...