[インデックス 14246] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/zip
パッケージにおける、破損したZIPファイルの「extra data records」の取り扱いに関するバグ修正です。具体的には、ZIPファイルのヘッダに含まれる拡張フィールド(extra data)が不正な形式である場合に、ErrFormat
エラーを適切に返すように修正し、それまで発生していたパニックや予期せぬ動作を防ぎます。
コミット
commit 640d818f2ada4997c6cea1ab435de625f5ef944a
Author: Dave Cheney <dave@cheney.net>
Date: Wed Oct 31 03:51:59 2012 +1100
archive/zip: handle corrupt extra data records
Fixes #4302.
R=golang-dev, bradfitz, adg
CC=golang-dev
https://golang.org/cl/6811048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/640d818f2ada4997c6cea1ab435de625f5ef944a
元コミット内容
archive/zip: handle corrupt extra data records
Fixes #4302.
このコミットは、archive/zip
パッケージが破損したextra dataレコードを処理できるようにするものです。Go issue #4302 を修正します。
変更の背景
この変更は、Go言語のIssue #4302 に対応するものです。元の問題は、archive/zip
パッケージが、ZIPファイルのセントラルディレクトリヘッダやローカルファイルヘッダに含まれる「extra field」が破損している場合に、パニック(panic)を引き起こす可能性があったことです。
ZIPファイルフォーマットでは、ファイルヘッダやディレクトリヘッダに可変長の「extra field」と呼ばれる領域を設けています。この領域は、ZIP64拡張情報、AES暗号化情報、Unixタイムスタンプなど、標準フォーマットでは表現できない追加のメタデータを格納するために使用されます。extra fieldは、2バイトのヘッダIDと2バイトのデータサイズ、そしてその後に続くデータで構成されるレコードの集合体です。
Issue #4302では、このextra fieldのデータサイズが、実際に利用可能なバッファサイズを超えている場合に、archive/zip
パッケージがバッファオーバーリードを試み、結果としてGoランタイムのパニックを引き起こすという脆弱性が報告されました。これは、悪意のある、または単に破損したZIPファイルを処理する際に、アプリケーションがクラッシュする原因となり得ました。このコミットは、このような不正なextra fieldのサイズを検出し、パニックを回避して ErrFormat
を返すことで、堅牢性を向上させることを目的としています。
前提知識の解説
ZIPファイルフォーマット
ZIPファイルは、複数のファイルやディレクトリを単一のアーカイブにまとめるための一般的なファイルフォーマットです。その構造は、主に以下の要素で構成されます。
- ローカルファイルヘッダ (Local File Header): 各ファイルデータの前に配置され、ファイル名、圧縮方法、圧縮・非圧縮サイズなどの情報を含みます。
- ファイルデータ (File Data): 実際のファイルの内容が圧縮されて格納されます。
- データ記述子 (Data Descriptor): ローカルファイルヘッダで圧縮サイズやCRC-32が不明な場合(ストリーミング圧縮など)に使用されます。
- セントラルディレクトリヘッダ (Central Directory File Header): ZIPアーカイブの最後にまとめて配置され、アーカイブ内の各ファイルのメタデータ(ファイル名、圧縮方法、サイズ、オフセットなど)を格納します。これにより、アーカイブ全体をスキャンせずに特定のファイルにアクセスできます。
- セントラルディレクトリ終了レコード (End of Central Directory Record): セントラルディレクトリの終わりを示し、セントラルディレクトリの開始オフセットやエントリ数などの情報を含みます。
Extra Field (拡張フィールド)
ZIPファイルフォーマットの重要な特徴の一つに「Extra Field」があります。これは、ローカルファイルヘッダとセントラルディレクトリヘッダの両方に存在し、標準のZIPフォーマットではサポートされていない追加情報を格納するための可変長データ領域です。
Extra Fieldは、複数の「Extra Field Data Block」の集合体として構成されます。各ブロックは以下の構造を持ちます。
- Header ID (2バイト): このExtra Field Data Blockの種類を示すID。例えば、
0x0001
はZIP64拡張情報、0x5455
はUnixタイムスタンプなど。 - Data Size (2バイト): このExtra Field Data Blockのデータ部分のサイズ(バイト単位)。
- Data (Data Sizeバイト): 実際の拡張データ。
このコミットで問題となったのは、この「Data Size」フィールドの値が、実際に読み取り可能なバッファの残りのサイズよりも大きい場合に、archive/zip
パッケージがその不正なサイズを信じて読み込みを試み、結果としてパニックを引き起こすという点でした。
ZIP64拡張
ZIP64は、従来のZIPフォーマットの制限(ファイルサイズやアーカイブサイズが4GBまで、エントリ数が65535個までなど)を克服するために導入された拡張です。ZIP64の情報は、Extra Field Data Blockの一つとして格納されます。Header ID 0x0001
を持ち、非圧縮サイズ、圧縮サイズ、ローカルヘッダオフセット、ディスク番号などの64ビット値を格納します。
このコミットの変更箇所では、zip64ExtraId
(つまり 0x0001
) を持つExtra Field Data Blockを特別に処理していることがわかります。
技術的詳細
このコミットの核心は、archive/zip
パッケージがZIPファイルのextra data recordsをパースする際の堅牢性向上にあります。具体的には、src/pkg/archive/zip/reader.go
内の readDirectoryHeader
関数において、extra fieldの各レコードを読み込むループ内で、不正な size
値を検出するチェックが追加されました。
以前の実装では、extra fieldの各レコードの tag
(ヘッダID) と size
(データサイズ) を読み込んだ後、size
の値が残りのバッファサイズに対して有効であるかどうかの検証が行われていませんでした。そのため、もし size
が残りのバッファサイズよりも大きな値であった場合、b
(読み取りバッファ) から size
バイトを読み取ろうとすると、インデックスが範囲外となり、Goランタイムがパニック(runtime error: slice bounds out of range
など)を引き起こす可能性がありました。
追加されたコード if int(size) > len(b) { return ErrFormat }
は、この脆弱性に対処します。
b.uint16()
でsize
(Extra Field Data Blockのデータ部分のサイズ) を読み取ります。int(size) > len(b)
という条件で、読み取ったsize
が現在のバッファb
の残りの長さ (len(b)
) よりも大きいかどうかをチェックします。- もし
size
がlen(b)
よりも大きければ、それは不正なextra fieldの形式であることを意味します。この場合、関数は直ちにErrFormat
エラーを返して処理を中断します。これにより、不正なデータによるパニックを回避し、呼び出し元に適切なエラーを通知することができます。
この修正により、archive/zip
パッケージは、破損したZIPファイルや悪意を持って細工されたZIPファイルに対しても、より安全かつ予測可能な動作をするようになります。
テストファイル src/pkg/archive/zip/zip_test.go
に追加された TestInvalidExtraHedaer
関数は、この修正を検証するためのものです。このテストは、意図的に不正なextra fieldを持つZIPファイルを作成し、NewReader
が ErrFormat
を返すことを確認します。具体的には、FileHeader
の Extra
フィールドに、ヘッダIDとデータサイズが欠落した(つまり、tag
と len
がない)バイト列を直接設定することで、不正なextra fieldをシミュレートしています。これにより、readDirectoryHeader
関数が ErrFormat
を返すことを保証しています。
コアとなるコードの変更箇所
src/pkg/archive/zip/reader.go
--- a/src/pkg/archive/zip/reader.go
+++ b/src/pkg/archive/zip/reader.go
@@ -241,6 +241,9 @@ func readDirectoryHeader(f *File, r io.Reader) error {
for len(b) > 0 {
tag := b.uint16()
size := b.uint16()
+ if int(size) > len(b) {
+ return ErrFormat
+ }
if tag == zip64ExtraId {
// update directory values from the zip64 extra block
eb := readBuf(b)
src/pkg/archive/zip/zip_test.go
--- a/src/pkg/archive/zip/zip_test.go
+++ b/src/pkg/archive/zip/zip_test.go
@@ -173,3 +173,37 @@ func TestZip64(t *testing.T) {
t.Errorf("UncompressedSize64 %d, want %d", got, want)
}\n}\n+\n+// Issue 4302.\n+func TestInvalidExtraHedaer(t *testing.T) {\n+\tconst timeFormat = "20060102T150405.000.txt"\n+\n+\tvar buf bytes.Buffer\n+\tz := NewWriter(&buf)\n+\n+\tts := time.Now()\n+\tfilename := ts.Format(timeFormat)\n+\n+\th := FileHeader{\n+\t\tName: filename,\n+\t\tMethod: Deflate,\n+\t\tExtra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len
+\t}\n+\th.SetModTime(ts)\n+\n+\tfh, err := z.CreateHeader(&h)\n+\tif err != nil {\n+\t\tt.Fatalf("error creating header: %v", err)\n+\t}\n+\tif _, err := fh.Write([]byte("hi")); err != nil {\n+\t\tt.Fatalf("error writing content: %v", err)\n+\t}\n+\tif err := z.Close(); err != nil {\n+\t\tt.Fatal("error closing zip writer: %v", err)\n+\t}\n+\n+\tb := buf.Bytes()\n+\tif _, err = NewReader(bytes.NewReader(b), int64(len(b))); err == nil {\n+\t\tt.Fatal("expected ErrFormat")\n+\t}\n+}\n```
## コアとなるコードの解説
### `src/pkg/archive/zip/reader.go` の変更点
`readDirectoryHeader` 関数は、ZIPファイルのセントラルディレクトリヘッダを読み込む役割を担っています。このヘッダには、各ファイルのメタデータと、オプションでExtra Fieldが含まれます。
変更が加えられたのは、Extra Fieldをパースするループ内です。
```go
for len(b) > 0 {
tag := b.uint16()
size := b.uint16()
if int(size) > len(b) { // 追加された行
return ErrFormat // 追加された行
}
if tag == zip64ExtraId {
// update directory values from the zip64 extra block
eb := readBuf(b)
ここで b
は、Extra Fieldの残りのバイト列を表す readBuf
型の変数です。
tag := b.uint16()
: Extra Field Data BlockのヘッダID (2バイト) を読み込みます。size := b.uint16()
: Extra Field Data Blockのデータ部分のサイズ (2バイト) を読み込みます。if int(size) > len(b)
: ここが今回の修正の肝です。読み込んだsize
が、現在b
に残っているバイト数 (len(b)
) よりも大きい場合、それは不正なデータであることを意味します。なぜなら、size
バイトのデータを読み込もうとしても、バッファの終端を超えてしまうからです。return ErrFormat
: 上記の条件が真の場合、ErrFormat
エラーを返して処理を中断します。これにより、不正なデータによるパニックを防ぎ、エラーハンドリングを適切に行うことができます。
この修正により、archive/zip
パッケージは、不正なExtra Fieldを持つZIPファイルに対しても、より堅牢な動作をするようになりました。
src/pkg/archive/zip/zip_test.go
の変更点
TestInvalidExtraHedaer
という新しいテスト関数が追加されました。このテストの目的は、不正なExtra Fieldを持つZIPファイルを作成し、archive/zip
パッケージがそれを適切に ErrFormat
として検出することを確認することです。
func TestInvalidExtraHedaer(t *testing.T) {
const timeFormat = "20060102T150405.000.txt"
var buf bytes.Buffer
z := NewWriter(&buf)
ts := time.Now()
filename := ts.Format(timeFormat)
h := FileHeader{
Name: filename,
Method: Deflate,
Extra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len
}
h.SetModTime(ts)
fh, err := z.CreateHeader(&h)
if err != nil {
t.Fatalf("error creating header: %v", err)
}
if _, err := fh.Write([]byte("hi")); err != nil {
t.Fatalf("error writing content: %v", err)
}
if err := z.Close(); err != nil {
t.Fatal("error closing zip writer: %v", err)
}
b := buf.Bytes()
if _, err = NewReader(bytes.NewReader(b), int64(len(b))); err == nil {
t.Fatal("expected ErrFormat")
}
}
テストの主要なポイントは以下の通りです。
FileHeader
を作成し、Extra
フィールドに意図的に不正なバイト列を設定しています。コメント// missing tag and len
が示すように、Extra Field Data BlockのヘッダID (tag) とデータサイズ (len) の2バイトずつが欠落した形式のデータを設定しています。これにより、readDirectoryHeader
がExtra Fieldをパースする際に、size
が不正な値になる状況を作り出します。z.CreateHeader(&h)
でこの不正なヘッダを持つZIPエントリを作成し、データを書き込み、ZIPファイルを閉じます。NewReader(bytes.NewReader(b), int64(len(b)))
を使って、作成したZIPファイルを読み込もうとします。if _, err = NewReader(...) ; err == nil { t.Fatal("expected ErrFormat") }
の部分で、NewReader
がエラーを返さなかった場合にテストを失敗させています。これは、不正なExtra Fieldが検出された場合にErrFormat
が返されることを期待しているためです。もしErrFormat
が返されずにnil
が返された場合、それは修正が正しく機能していないことを意味します。
このテストは、修正が正しく機能し、不正なExtra Fieldが検出された場合に ErrFormat
が返されることを保証します。
関連リンク
- Go Issue #4302: https://github.com/golang/go/issues/4302
- Go CL 6811048: https://golang.org/cl/6811048 (このコミットに対応するGoのコードレビューシステム上のチェンジリスト)
参考にした情報源リンク
- PKWARE, Inc. APPNOTE.TXT - .ZIP File Format Specification (ZIPファイルフォーマットの公式仕様書)
- GoDoc archive/zip package (Go言語
archive/zip
パッケージの公式ドキュメント) - Go言語のpanicとrecoverについて (Go言語のパニックとリカバリに関する一般的な情報)
- Go言語のテストについて (Go言語のテストに関する一般的な情報)I have generated the commit explanation based on the provided instructions. I will now output it to standard output.
# [インデックス 14246] ファイルの概要
このコミットは、Go言語の標準ライブラリ `archive/zip` パッケージにおける、破損したZIPファイルの「extra data records」の取り扱いに関するバグ修正です。具体的には、ZIPファイルのヘッダに含まれる拡張フィールド(extra data)が不正な形式である場合に、`ErrFormat` エラーを適切に返すように修正し、それまで発生していたパニックや予期せぬ動作を防ぎます。
## コミット
commit 640d818f2ada4997c6cea1ab435de625f5ef944a Author: Dave Cheney dave@cheney.net Date: Wed Oct 31 03:51:59 2012 +1100
archive/zip: handle corrupt extra data records
Fixes #4302.
R=golang-dev, bradfitz, adg
CC=golang-dev
https://golang.org/cl/6811048
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/640d818f2ada4997c6cea1ab435de625f5ef944a](https://github.com/golang/go/commit/640d818f2ada4997c6cea1ab435de625f5ef944a)
## 元コミット内容
`archive/zip: handle corrupt extra data records`
`Fixes #4302.`
このコミットは、`archive/zip` パッケージが破損したextra dataレコードを処理できるようにするものです。Go issue #4302 を修正します。
## 変更の背景
この変更は、Go言語のIssue #4302 に対応するものです。元の問題は、`archive/zip` パッケージが、ZIPファイルのセントラルディレクトリヘッダやローカルファイルヘッダに含まれる「extra field」が破損している場合に、パニック(panic)を引き起こす可能性があったことです。
ZIPファイルフォーマットでは、ファイルヘッダやディレクトリヘッダに可変長の「extra field」と呼ばれる領域を設けています。この領域は、ZIP64拡張情報、AES暗号化情報、Unixタイムスタンプなど、標準フォーマットでは表現できない追加のメタデータを格納するために使用されます。extra fieldは、2バイトのヘッダIDと2バイトのデータサイズ、そしてその後に続くデータで構成されるレコードの集合体です。
Issue #4302では、このextra fieldのデータサイズが、実際に利用可能なバッファサイズを超えている場合に、`archive/zip` パッケージがバッファオーバーリードを試み、結果としてGoランタイムのパニックを引き起こすという脆弱性が報告されました。これは、悪意のある、または単に破損したZIPファイルを処理する際に、アプリケーションがクラッシュする原因となり得ました。このコミットは、このような不正なextra fieldのサイズを検出し、パニックを回避して `ErrFormat` を返すことで、堅牢性を向上させることを目的としています。
## 前提知識の解説
### ZIPファイルフォーマット
ZIPファイルは、複数のファイルやディレクトリを単一のアーカイブにまとめるための一般的なファイルフォーマットです。その構造は、主に以下の要素で構成されます。
1. **ローカルファイルヘッダ (Local File Header)**: 各ファイルデータの前に配置され、ファイル名、圧縮方法、圧縮・非圧縮サイズなどの情報を含みます。
2. **ファイルデータ (File Data)**: 実際のファイルの内容が圧縮されて格納されます。
3. **データ記述子 (Data Descriptor)**: ローカルファイルヘッダで圧縮サイズやCRC-32が不明な場合(ストリーミング圧縮など)に使用されます。
4. **セントラルディレクトリヘッダ (Central Directory File Header)**: ZIPアーカイブの最後にまとめて配置され、アーカイブ内の各ファイルのメタデータ(ファイル名、圧縮方法、サイズ、オフセットなど)を格納します。これにより、アーカイブ全体をスキャンせずに特定のファイルにアクセスできます。
5. **セントラルディレクトリ終了レコード (End of Central Directory Record)**: セントラルディレクトリの終わりを示し、セントラルディレクトリの開始オフセットやエントリ数などの情報を含みます。
### Extra Field (拡張フィールド)
ZIPファイルフォーマットの重要な特徴の一つに「Extra Field」があります。これは、ローカルファイルヘッダとセントラルディレクトリヘッダの両方に存在し、標準のZIPフォーマットではサポートされていない追加情報を格納するための可変長データ領域です。
Extra Fieldは、複数の「Extra Field Data Block」の集合体として構成されます。各ブロックは以下の構造を持ちます。
* **Header ID (2バイト)**: このExtra Field Data Blockの種類を示すID。例えば、`0x0001` はZIP64拡張情報、`0x5455` はUnixタイムスタンプなど。
* **Data Size (2バイト)**: このExtra Field Data Blockのデータ部分のサイズ(バイト単位)。
* **Data (Data Sizeバイト)**: 実際の拡張データ。
このコミットで問題となったのは、この「Data Size」フィールドの値が、実際に読み取り可能なバッファの残りのサイズよりも大きい場合に、`archive/zip` パッケージがその不正なサイズを信じて読み込みを試み、結果としてパニックを引き起こすという点でした。
### ZIP64拡張
ZIP64は、従来のZIPフォーマットの制限(ファイルサイズやアーカイブサイズが4GBまで、エントリ数が65535個までなど)を克服するために導入された拡張です。ZIP64の情報は、Extra Field Data Blockの一つとして格納されます。Header ID `0x0001` を持ち、非圧縮サイズ、圧縮サイズ、ローカルヘッダオフセット、ディスク番号などの64ビット値を格納します。
このコミットの変更箇所では、`zip64ExtraId` (つまり `0x0001`) を持つExtra Field Data Blockを特別に処理していることがわかります。
## 技術的詳細
このコミットの核心は、`archive/zip` パッケージがZIPファイルのextra data recordsをパースする際の堅牢性向上にあります。具体的には、`src/pkg/archive/zip/reader.go` 内の `readDirectoryHeader` 関数において、extra fieldの各レコードを読み込むループ内で、不正な `size` 値を検出するチェックが追加されました。
以前の実装では、extra fieldの各レコードの `tag` (ヘッダID) と `size` (データサイズ) を読み込んだ後、`size` の値が残りのバッファサイズに対して有効であるかどうかの検証が行われていませんでした。そのため、もし `size` が残りのバッファサイズよりも大きな値であった場合、`b` (読み取りバッファ) から `size` バイトを読み取ろうとすると、インデックスが範囲外となり、Goランタイムがパニック(`runtime error: slice bounds out of range` など)を引き起こす可能性がありました。
追加されたコード `if int(size) > len(b) { return ErrFormat }` は、この脆弱性に対処します。
1. `b.uint16()` で `size` (Extra Field Data Blockのデータ部分のサイズ) を読み取ります。
2. `int(size) > len(b)` という条件で、読み取った `size` が現在のバッファ `b` の残りの長さ (`len(b)`) よりも大きいかどうかをチェックします。
3. もし `size` が `len(b)` よりも大きければ、それは不正なextra fieldの形式であることを意味します。この場合、関数は直ちに `ErrFormat` エラーを返して処理を中断します。これにより、不正なデータによるパニックを回避し、呼び出し元に適切なエラーを通知することができます。
この修正により、`archive/zip` パッケージは、破損したZIPファイルや悪意を持って細工されたZIPファイルに対しても、より安全かつ予測可能な動作をするようになります。
テストファイル `src/pkg/archive/zip/zip_test.go` に追加された `TestInvalidExtraHedaer` 関数は、この修正を検証するためのものです。このテストは、意図的に不正なextra fieldを持つZIPファイルを作成し、`NewReader` が `ErrFormat` を返すことを確認します。具体的には、`FileHeader` の `Extra` フィールドに、ヘッダIDとデータサイズが欠落した(つまり、`tag` と `len` がない)バイト列を直接設定することで、不正なextra fieldをシミュレートしています。これにより、`readDirectoryHeader` 関数が `ErrFormat` を返すことを保証しています。
## コアとなるコードの変更箇所
### `src/pkg/archive/zip/reader.go`
```diff
--- a/src/pkg/archive/zip/reader.go
+++ b/src/pkg/archive/zip/reader.go
@@ -241,6 +241,9 @@ func readDirectoryHeader(f *File, r io.Reader) error {
for len(b) > 0 {
tag := b.uint16()
size := b.uint16()
+ if int(size) > len(b) {
+ return ErrFormat
+ }
if tag == zip64ExtraId {
// update directory values from the zip64 extra block
eb := readBuf(b)
src/pkg/archive/zip/zip_test.go
--- a/src/pkg/archive/zip/zip_test.go
+++ b/src/pkg/archive/zip/zip_test.go
@@ -173,3 +173,37 @@ func TestZip64(t *testing.T) {
t.Errorf("UncompressedSize64 %d, want %d", got, want)
}\n}\n+\n+// Issue 4302.\n+func TestInvalidExtraHedaer(t *testing.T) {\n+\tconst timeFormat = "20060102T150405.000.txt"\n+\n+\tvar buf bytes.Buffer\n+\tz := NewWriter(&buf)\n+\n+\tts := time.Now()\n+\tfilename := ts.Format(timeFormat)\n+\n+\th := FileHeader{\n+\t\tName: filename,\n+\t\tMethod: Deflate,\n+\t\tExtra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len
+\t}\n+\th.SetModTime(ts)\n+\n+\tfh, err := z.CreateHeader(&h)\n+\tif err != nil {\n+\t\tt.Fatalf("error creating header: %v", err)\n+\t}\n+\tif _, err := fh.Write([]byte("hi")); err != nil {\n+\t\tt.Fatalf("error writing content: %v", err)\n+\t}\n+\tif err := z.Close(); err != nil {\n+\t\tt.Fatal("error closing zip writer: %v", err)\n+\t}\n+\n+\tb := buf.Bytes()\n+\tif _, err = NewReader(bytes.NewReader(b), int64(len(b))); err == nil {\n+\t\tt.Fatal("expected ErrFormat")\n+\t}\n+}\n```
## コアとなるコードの解説
### `src/pkg/archive/zip/reader.go` の変更点
`readDirectoryHeader` 関数は、ZIPファイルのセントラルディレクトリヘッダを読み込む役割を担っています。このヘッダには、各ファイルのメタデータと、オプションでExtra Fieldが含まれます。
変更が加えられたのは、Extra Fieldをパースするループ内です。
```go
for len(b) > 0 {
tag := b.uint16()
size := b.uint16()
if int(size) > len(b) { // 追加された行
return ErrFormat // 追加された行
}
if tag == zip64ExtraId {
// update directory values from the zip64 extra block
eb := readBuf(b)
ここで b
は、Extra Fieldの残りのバイト列を表す readBuf
型の変数です。
tag := b.uint16()
: Extra Field Data BlockのヘッダID (2バイト) を読み込みます。size := b.uint16()
: Extra Field Data Blockのデータ部分のサイズ (2バイト) を読み込みます。if int(size) > len(b)
: ここが今回の修正の肝です。読み込んだsize
が、現在b
に残っているバイト数 (len(b)
) よりも大きい場合、それは不正なデータであることを意味します。なぜなら、size
バイトのデータを読み込もうとしても、バッファの終端を超えてしまうからです。return ErrFormat
: 上記の条件が真の場合、ErrFormat
エラーを返して処理を中断します。これにより、不正なデータによるパニックを防ぎ、エラーハンドリングを適切に行うことができます。
この修正により、archive/zip
パッケージは、不正なExtra Fieldを持つZIPファイルに対しても、より堅牢な動作をするようになりました。
src/pkg/archive/zip/zip_test.go
の変更点
TestInvalidExtraHedaer
という新しいテスト関数が追加されました。このテストの目的は、不正なExtra Fieldを持つZIPファイルを作成し、archive/zip
パッケージがそれを適切に ErrFormat
として検出することを確認することです。
func TestInvalidExtraHedaer(t *testing.T) {
const timeFormat = "20060102T150405.000.txt"
var buf bytes.Buffer
z := NewWriter(&buf)
ts := time.Now()
filename := ts.Format(timeFormat)
h := FileHeader{
Name: filename,
Method: Deflate,
Extra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len
}
h.SetModTime(ts)
fh, err := z.CreateHeader(&h)
if err != nil {
t.Fatalf("error creating header: %v", err)
}
if _, err := fh.Write([]byte("hi")); err != nil {
t.Fatalf("error writing content: %v", err)
}
if err := z.Close(); err != nil {
t.Fatal("error closing zip writer: %v", err)
}
b := buf.Bytes()
if _, err = NewReader(bytes.NewReader(b), int64(len(b))); err == nil {
t.Fatal("expected ErrFormat")
}
}
テストの主要なポイントは以下の通りです。
FileHeader
を作成し、Extra
フィールドに意図的に不正なバイト列を設定しています。コメント// missing tag and len
が示すように、Extra Field Data BlockのヘッダID (tag) とデータサイズ (len) の2バイトずつが欠落した形式のデータを設定しています。これにより、readDirectoryHeader
がExtra Fieldをパースする際に、size
が不正な値になる状況を作り出します。z.CreateHeader(&h)
でこの不正なヘッダを持つZIPエントリを作成し、データを書き込み、ZIPファイルを閉じます。NewReader(bytes.NewReader(b), int64(len(b)))
を使って、作成したZIPファイルを読み込もうとします。if _, err = NewReader(...) ; err == nil { t.Fatal("expected ErrFormat") }
の部分で、NewReader
がエラーを返さなかった場合にテストを失敗させています。これは、不正なExtra Fieldが検出された場合にErrFormat
が返されることを期待しているためです。もしErrFormat
が返されずにnil
が返された場合、それは修正が正しく機能していないことを意味します。
このテストは、修正が正しく機能し、不正なExtra Fieldが検出された場合に ErrFormat
が返されることを保証します。
関連リンク
- Go Issue #4302: https://github.com/golang/go/issues/4302
- Go CL 6811048: https://golang.org/cl/6811048 (このコミットに対応するGoのコードレビューシステム上のチェンジリスト)
参考にした情報源リンク
- PKWARE, Inc. APPNOTE.TXT - .ZIP File Format Specification (ZIPファイルフォーマットの公式仕様書)
- GoDoc archive/zip package (Go言語
archive/zip
パッケージの公式ドキュメント) - Go言語のpanicとrecoverについて (Go言語のパニックとリカバリに関する一般的な情報)
- Go言語のテストについて (Go言語のテストに関する一般的な情報)