Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 にコピーし、実際にコピーされたバイト数を返します。このテストの目的は、forwardCopydst スライスの範囲外に書き込みを行わないことを確認することです。

テストのロジックは以下の通りです。

  1. 初期バッファの拡張: テスト対象のバイトスライス b の初期化が []byte("012345678") から []byte("0123456789") に変更されました。これにより、元のスライスよりも1バイト長いバッファが用意され、オーバーランの検出がより容易になります。
  2. コピー操作の実行: forwardCopy(dst, src) が呼び出され、src の内容が dst にコピーされます。dstsrc は、元の b スライスから特定の範囲を切り出したものです。
  3. オーバーランの検証: コピー操作が完了した後、新しいループが追加されました。このループは、元の b スライス全体を反復処理します。
    • ループ内で、if i >= tc.dst0 && i < tc.dst0+n { continue } という条件で、forwardCopy が実際にデータを書き込んだ dst[:n] の範囲をスキップします。
    • スキップされなかった(つまり、forwardCopy が書き込むべきではなかった)各バイト x について、その値が初期値 ('0'+i) と同じであるかを確認します。
    • もし値が異なっていれば、それは forwardCopydst スライスの境界を超えて書き込みを行ったことを意味し、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}\
 }\

コアとなるコードの解説

  1. b := []byte("0123456789"):

    • 変更前は []byte("012345678") で、長さ9のバイトスライスでした。
    • 変更後は []byte("0123456789") となり、長さ10のバイトスライスになりました。
    • この変更は、テストの基盤となるバッファをわずかに拡張することで、forwardCopy 関数が意図せず境界外に書き込みを行った場合に、その影響をより明確に検出できるようにするためのものです。特に、'9' という新しい文字が追加されたことで、オーバーランが発生した際に、この文字が変更されることで異常を検知しやすくなります。
  2. オーバーランチェックのループの追加:

    		// 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' であるべきです。
    • もし値が初期値と異なっていれば、それは forwardCopydst スライスの境界を超えて、意図しないメモリ領域に書き込みを行ったことを意味します。この場合、t.Errorf が呼び出され、テストが失敗します。エラーメッセージには、どのバイトがオーバーランによって変更されたかを示す詳細な情報が含まれます。

この追加されたテストロジックにより、forwardCopy 関数がメモリ安全性を厳密に遵守し、割り当てられたバッファの範囲外にデータを書き込まないことが保証されます。これは、Go言語の標準ライブラリの堅牢性と信頼性を高める上で重要な改善です。

関連リンク

参考にした情報源リンク