[インデックス 13003] ファイルの概要
このコミットは、Go言語の標準ライブラリ compress/flate
パッケージ内の copy_test.go
ファイルに対する変更です。compress/flate
パッケージは、DEFLATE圧縮アルゴリズムの実装を提供しており、データ圧縮・解凍において効率的なバイト操作が不可欠です。copy_test.go
は、このパッケージ内のバイトコピー操作を行うユーティリティ関数、特に forwardCopy
の正確性と安全性を検証するためのテストコードを含んでいます。今回の変更は、forwardCopy
関数が指定されたコピー範囲を超えてメモリを書き換えてしまう「コピーオーバーラン」が発生しないことを保証するためのテストケースを追加するものです。
コミット
compress/flate
パッケージに、forwardCopy
関数が意図しないメモリ領域に書き込みを行わないことを確認するための「コピーオーバーラン」テストを追加しました。これにより、データコピー操作の堅牢性が向上し、潜在的なメモリ破損や予期せぬ動作を防ぎます。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/86a91539b8acd322800b10db3d64ce42d43dfcde
元コミット内容
commit 86a91539b8acd322800b10db3d64ce42d43dfcde
Author: Nigel Tao <nigeltao@golang.org>
Date: Tue May 1 14:28:33 2012 +1000
compress/flate: add a copy overrun test.
R=rsc, r
CC=golang-dev
https://golang.org/cl/6143043
変更の背景
データ圧縮アルゴリズムの実装において、バイト配列間の効率的かつ正確なデータコピーはパフォーマンスと安定性の両面で極めて重要です。compress/flate
パッケージ内の forwardCopy
のような低レベルのコピー関数は、大量のデータを扱うため、その動作にわずかなバグがあるだけでも、メモリ破損やセキュリティ脆弱性につながる可能性があります。
このコミットの背景には、forwardCopy
関数が、コピー先のバッファの境界を越えてデータを書き込んでしまう「コピーオーバーラン」の潜在的なリスクを排除するという目的があります。このようなオーバーランは、プログラムのクラッシュ、予期せぬデータの変更、さらには悪意のあるコード実行につながる可能性もあるため、厳密なテストによってその発生を未然に防ぐことが求められます。既存のテストでは、コピーされたデータの内容が正しいかどうかの検証は行われていましたが、コピー範囲外のメモリが意図せず変更されていないかどうかの検証は不十分でした。この不足を補うために、本コミットでオーバーランテストが追加されました。
前提知識の解説
compress/flate
パッケージ
Go言語の標準ライブラリ compress/flate
は、RFC 1951で定義されているDEFLATEデータ形式を実装しています。DEFLATEは、LZ77アルゴリズムとハフマン符号化を組み合わせた可逆データ圧縮アルゴリズムであり、ZIP、gzip、PNGなどの多くのファイル形式やプロトコルで広く利用されています。このパッケージは、DEFLATE形式でのデータの圧縮と解凍のためのリーダーとライターを提供します。内部的には、バイト配列間の効率的なデータ操作が頻繁に行われます。
バイト配列とスライス (Go言語)
Go言語では、バイトのシーケンスは []byte
型のスライスとして表現されます。スライスは、基となる配列の一部を参照する軽量なデータ構造です。スライスは [low:high]
の形式で作成され、low
から high-1
までの要素を含みます。スライスを関数に渡す場合、スライスヘッダ(ポインタ、長さ、容量)が値渡しされますが、基となる配列は共有されます。これにより、関数内でスライスの要素を変更すると、元の配列の対応する要素も変更されます。
コピーオーバーラン (Copy Overrun)
「コピーオーバーラン」とは、プログラムがデータをメモリ上のバッファにコピーする際に、そのバッファの割り当てられた境界を超えてデータを書き込んでしまう現象を指します。これは、コピー操作の長さがコピー先のバッファの容量を超えている場合や、コピー関数が誤ったオフセットや長さを計算した場合に発生します。
コピーオーバーランが発生すると、以下のような問題が引き起こされる可能性があります。
- メモリ破損: 意図しないメモリ領域が上書きされ、他の変数やデータ構造が破壊される。
- プログラムのクラッシュ: 破壊されたメモリが後でアクセスされた際に、不正なメモリアクセスとしてプログラムが異常終了する。
- セキュリティ脆弱性: 攻撃者がこの脆弱性を悪用し、任意のコードを実行したり、機密情報を漏洩させたりする可能性がある(バッファオーバーフロー攻撃の一種)。
このため、低レベルのコピー関数においては、コピーオーバーランが発生しないことを厳密に保証するテストが不可欠です。
技術的詳細
このコミットで追加されたテストは、TestForwardCopy
関数内に組み込まれています。forwardCopy
関数は、dst
スライスと src
スライスを受け取り、src
の内容を dst
にコピーし、実際にコピーされたバイト数を返します。このテストの目的は、forwardCopy
が dst
スライスの範囲外に書き込みを行わないことを確認することです。
テストのロジックは以下の通りです。
- 初期バッファの拡張: テスト対象のバイトスライス
b
の初期化が[]byte("012345678")
から[]byte("0123456789")
に変更されました。これにより、元のスライスよりも1バイト長いバッファが用意され、オーバーランの検出がより容易になります。 - コピー操作の実行:
forwardCopy(dst, src)
が呼び出され、src
の内容がdst
にコピーされます。dst
とsrc
は、元のb
スライスから特定の範囲を切り出したものです。 - オーバーランの検証: コピー操作が完了した後、新しいループが追加されました。このループは、元の
b
スライス全体を反復処理します。- ループ内で、
if i >= tc.dst0 && i < tc.dst0+n { continue }
という条件で、forwardCopy
が実際にデータを書き込んだdst[:n]
の範囲をスキップします。 - スキップされなかった(つまり、
forwardCopy
が書き込むべきではなかった)各バイトx
について、その値が初期値 ('0'+i
) と同じであるかを確認します。 - もし値が異なっていれば、それは
forwardCopy
がdst
スライスの境界を超えて書き込みを行ったことを意味し、t.Errorf
を呼び出してテストを失敗させます。
- ループ内で、
このアプローチにより、forwardCopy
関数が、指定されたコピー範囲外のメモリ領域を誤って変更していないことを厳密に検証できます。これは、メモリ安全性を確保し、予期せぬバグを防ぐ上で非常に重要なテストです。
コアとなるコードの変更箇所
変更は src/pkg/compress/flate/copy_test.go
ファイルにあります。
--- a/src/pkg/compress/flate/copy_test.go
+++ b/src/pkg/compress/flate/copy_test.go
@@ -29,7 +29,7 @@ func TestForwardCopy(t *testing.T) {
\t{0, 0, 0, 0, ""},\
}\
for _, tc := range testCases {\
-\t\tb := []byte("012345678")
+\t\tb := []byte("0123456789")
\t\tdst := b[tc.dst0:tc.dst1]\
\t\tsrc := b[tc.src0:tc.src1]\
\t\tn := forwardCopy(dst, src)\
@@ -38,5 +38,15 @@ func TestForwardCopy(t *testing.T) {\
\t\t\tt.Errorf("dst=b[%d:%d], src=b[%d:%d]: got %q, want %q",\
\t\t\t\ttc.dst0, tc.dst1, tc.src0, tc.src1, got, tc.want)\
\t\t}\
+\t\t// Check that the bytes outside of dst[:n] were not modified.\
+\t\tfor i, x := range b {\
+\t\t\tif i >= tc.dst0 && i < tc.dst0+n {\
+\t\t\t\tcontinue\
+\t\t\t}\
+\t\t\tif int(x) != '0'+i {\
+\t\t\t\tt.Errorf("dst=b[%d:%d], src=b[%d:%d]: copy overrun at b[%d]: got '%c', want '%c'",\
+\t\t\t\t\ttc.dst0, tc.dst1, tc.src0, tc.src1, i, x, '0'+i)\
+\t\t\t}\
+\t\t}\
\t}\
}\
コアとなるコードの解説
-
b := []byte("0123456789")
:- 変更前は
[]byte("012345678")
で、長さ9のバイトスライスでした。 - 変更後は
[]byte("0123456789")
となり、長さ10のバイトスライスになりました。 - この変更は、テストの基盤となるバッファをわずかに拡張することで、
forwardCopy
関数が意図せず境界外に書き込みを行った場合に、その影響をより明確に検出できるようにするためのものです。特に、'9'
という新しい文字が追加されたことで、オーバーランが発生した際に、この文字が変更されることで異常を検知しやすくなります。
- 変更前は
-
オーバーランチェックのループの追加:
// Check that the bytes outside of dst[:n] were not modified. for i, x := range b { if i >= tc.dst0 && i < tc.dst0+n { continue } if int(x) != '0'+i { t.Errorf("dst=b[%d:%d], src=b[%d:%d]: copy overrun at b[%d]: got '%c', want '%c'", tc.dst0, tc.dst1, tc.src0, tc.src1, i, x, '0'+i) } }
- この新しいコードブロックは、
forwardCopy
の呼び出し後に実行されます。 for i, x := range b
は、元のバイトスライスb
のすべての要素をインデックスi
と値x
で反復処理します。if i >= tc.dst0 && i < tc.dst0+n { continue }
は、forwardCopy
が実際にデータを書き込んだdst
スライスの範囲 (tc.dst0
からtc.dst0+n-1
まで) をスキップするための条件です。この範囲内のバイトは変更されることが期待されるため、チェックの対象外とします。if int(x) != '0'+i
が、オーバーランを検出する核心部分です。スキップされなかった(つまり、forwardCopy
が書き込むべきではなかった)各バイトx
の値が、その初期値 ('0'
にインデックスi
を加えたASCII値) と等しいかどうかをチェックします。例えば、インデックス0
のバイトは'0'
、インデックス1
のバイトは'1'
であるべきです。- もし値が初期値と異なっていれば、それは
forwardCopy
がdst
スライスの境界を超えて、意図しないメモリ領域に書き込みを行ったことを意味します。この場合、t.Errorf
が呼び出され、テストが失敗します。エラーメッセージには、どのバイトがオーバーランによって変更されたかを示す詳細な情報が含まれます。
- この新しいコードブロックは、
この追加されたテストロジックにより、forwardCopy
関数がメモリ安全性を厳密に遵守し、割り当てられたバッファの範囲外にデータを書き込まないことが保証されます。これは、Go言語の標準ライブラリの堅牢性と信頼性を高める上で重要な改善です。
関連リンク
- Go CL 6143043: https://golang.org/cl/6143043
参考にした情報源リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語の
compress/flate
パッケージドキュメント: https://pkg.go.dev/compress/flate - DEFLATE (Wikipedia): https://ja.wikipedia.org/wiki/DEFLATE
- バッファオーバーフロー (Wikipedia): https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC