[インデックス 19363] ファイルの概要
このコミットは、Go言語の標準ライブラリである crypto/sha256
および crypto/sha512
パッケージ内のアセンブリコードにおける引数サイズの修正に関するものです。具体的には、block
関数のスタックフレームサイズ計算が誤っていた点を修正しています。
コミット
commit fbd091500818c095c34f7a221aa298efeb13fb9c
Author: Russ Cox <rsc@golang.org>
Date: Thu May 15 15:34:25 2014 -0400
crypto/sha256, crypto/sha512: fix argument size in assembly
The function takes 32 bytes of arguments: 8 for the *block
and then 3*8 for the slice.
The 24 is not causing a bug (today at least) because the
final word is the cap of the slice, which the assembly
does not use.
Identified by 'go vet std'.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/96360043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fbd091500818c095c34f7a221aa298efeb13fb9c
元コミット内容
crypto/sha256
および crypto/sha512
パッケージのアセンブリコードにおいて、block
関数の引数サイズが正しく計算されていなかった点を修正。具体的には、関数がポインタとスライスを受け取る際に、スライスの cap
(容量) 部分がアセンブリコードで利用されないため、これまでの誤ったサイズ指定(24バイト)が偶然にもバグを引き起こしていなかったが、正しい引数サイズ(32バイト)に修正された。この問題は go vet std
によって特定された。
変更の背景
この変更は、Go言語の標準ツールである go vet
の std
オプションによって特定された潜在的な問題に対処するために行われました。go vet
は、Goプログラムの疑わしい構造を報告する静的解析ツールであり、特にアセンブリコードとGoコード間のインターフェースにおける引数の不整合などを検出する能力を持っています。
crypto/sha256
と crypto/sha512
パッケージは、それぞれSHA-256とSHA-512ハッシュアルゴリズムを実装しており、パフォーマンスが重要な部分ではアセンブリ言語で書かれた最適化されたコードを使用しています。これらのアセンブリ関数は、Goの関数から呼び出される際に、Goの呼び出し規約に従って引数を受け取ります。
問題は、block
関数が *block
(8バイトのポインタ) とスライス (Goではポインタ、長さ、容量の3つのフィールドで構成され、それぞれ8バイト、合計24バイト) を引数として受け取るにもかかわらず、アセンブリコード内で引数領域のサイズが誤って計算されていた点にありました。コミットメッセージによると、合計で 8 + 24 = 32
バイトの引数が必要であるにもかかわらず、アセンブリコードでは 24
バイトとして扱われていたようです。
この不整合が「今日までバグを引き起こしていなかった」のは、スライスの3つのフィールドのうち、アセンブリコードが実際に使用していたのは data
(ポインタ) と len
(長さ) のみであり、cap
(容量) の値は参照していなかったためです。しかし、これは潜在的なバグであり、将来のGoのバージョンや異なるコンパイル設定、あるいはアセンブリコードの変更によって問題が顕在化する可能性がありました。go vet
はこのような潜在的な問題を早期に発見し、修正を促す役割を果たしました。
前提知識の解説
Go言語の呼び出し規約とアセンブリ
Go言語は、C言語などと同様に、関数呼び出しにおいて引数をスタックまたはレジスタ経由で渡す呼び出し規約を持っています。Goのランタイムは、Goで書かれた関数とアセンブリで書かれた関数がシームレスに連携できるように、この規約を厳密に定義しています。
アセンブリコードでGoの関数を実装する場合、Goのコンパイラが生成するコードと互換性を持つように、引数のオフセットや戻り値の配置などを正確に理解し、記述する必要があります。特に、Goのスライスは単なるポインタではなく、内部的にデータへのポインタ、長さ、容量の3つのフィールドを持つ構造体として扱われます。64ビットシステムでは、これら各フィールドが8バイトを占めるため、スライス全体としては24バイトのメモリを消費します。
アセンブリコードでGoの関数を定義する際には、TEXT
ディレクティブを使用します。このディレクティブは、関数の名前、フラグ、そしてスタックフレームのサイズを指定します。スタックフレームサイズは、ローカル変数と呼び出し元から渡される引数のための領域を含みます。
TEXT symbol(SB), flags, framesize-argsize
symbol(SB)
: 関数のシンボル名。SB
は静的ベースポインタ (Static Base Pointer) を意味し、グローバルシンボルであることを示します。flags
: 関数の特性を示すフラグ。framesize
: 関数自身のローカル変数などが使用するスタックフレームのサイズ。argsize
: 呼び出し元から渡される引数の合計サイズ。この値は、呼び出し元がスタックに積む引数のサイズと一致している必要があります。
このコミットの文脈では、argsize
の部分が問題となっていました。
go vet
ツール
go vet
は、Go言語のソースコードを静的に解析し、潜在的なバグや疑わしい構造を報告するツールです。GoのSDKに標準で含まれており、開発者がコードの品質を向上させるために広く利用されています。
go vet
が検出できる問題の例としては、以下のようなものがあります。
- フォーマット文字列の不一致 (例:
fmt.Printf("%d", "hello")
) - 到達不能なコード
- ロックの誤用
- 構造体タグの誤り
- アセンブリコードとGoコード間の引数不整合 (今回のケース)
go vet std
は、Goの標準ライブラリに対して go vet
を実行することを意味します。Goのコア開発チームは、標準ライブラリの品質と堅牢性を維持するために、定期的に go vet
を実行し、検出された問題を修正しています。
SHA-256とSHA-512
SHA-256 (Secure Hash Algorithm 256-bit) と SHA-512 (Secure Hash Algorithm 512-bit) は、NIST (National Institute of Standards and Technology) によってFIPS PUB 180-4として公開されている暗号学的ハッシュ関数です。これらは、入力データから固定長のハッシュ値(メッセージダイジェスト)を生成するために使用されます。
- SHA-256: 256ビット(32バイト)のハッシュ値を生成します。
- SHA-512: 512ビット(64バイト)のハッシュ値を生成します。
これらのハッシュ関数は、データの完全性検証、デジタル署名、パスワードの保存など、様々なセキュリティ関連のアプリケーションで広く利用されています。Goの crypto/sha256
および crypto/sha512
パッケージは、これらのアルゴリズムのGo言語実装を提供しています。パフォーマンスが要求されるため、特にブロック処理の部分ではアセンブリ言語による最適化が施されています。
技術的詳細
このコミットの核心は、Goのアセンブリ言語における TEXT
ディレクティブの argsize
パラメータの修正です。
元のコードでは、crypto/sha256/sha256block_amd64.s
と crypto/sha512/sha512block_amd64.s
の両方で、block
関数の定義が以下のようになっていました。
TEXT ·block(SB),0,$264-24 ; sha256
TEXT ·block(SB),0,$648-24 ; sha512
ここで $264-24
や $648-24
の -24
の部分が argsize
を示しています。これは、関数が引数として24バイトを受け取るとアセンブリコードが認識していることを意味します。
しかし、block
関数がGo側から呼び出される際の引数は、以下の2つです。
*block
(ポインタ): 64ビットシステムでは8バイト。[]byte
(スライス): Goのスライスは、データポインタ、長さ、容量の3つのフィールドで構成され、それぞれ8バイトなので合計24バイト。
したがって、合計の引数サイズは 8バイト (ポインタ) + 24バイト (スライス) = 32バイト
となります。
元のコードの -24
は、スライスのサイズのみを考慮し、最初のポインタ引数の8バイトを考慮していませんでした。コミットメッセージにあるように、この不整合が「今日までバグを引き起こしていなかった」のは、アセンブリコードがスライスの cap
(容量) フィールドを使用していなかったためです。Goの呼び出し規約では、引数はスタックに積まれる順序が決まっており、通常は最初の引数から順にオフセットが割り当てられます。もしアセンブリコードが cap
フィールドにアクセスしようとしていた場合、オフセットのずれにより誤ったメモリ領域を読み込み、クラッシュや不正な動作を引き起こす可能性がありました。
この修正では、argsize
を正しい 32
バイトに変更しています。
TEXT ·block(SB),0,$264-32 ; sha256
TEXT ·block(SB),0,$648-32 ; sha512
この変更により、アセンブリコードがGoの呼び出し規約に完全に準拠し、引数領域のサイズを正確に認識するようになります。これにより、将来的なGoランタイムの変更や、アセンブリコードの最適化、あるいはデバッグツールの動作などにおいて、予期せぬ問題が発生するリスクが排除されます。
この修正は、go vet std
によって特定されたという点が重要です。go vet
は、このような低レベルのアセンブリとGoのインターフェースにおける不整合を検出できる高度な静的解析能力を持っていることを示しています。これは、Go言語のツールチェインがコードの品質と堅牢性を維持するためにどれほど強力であるかを示す一例です。
コアとなるコードの変更箇所
変更は、src/pkg/crypto/sha256/sha256block_amd64.s
と src/pkg/crypto/sha512/sha512block_amd64.s
の2つのファイルにわたります。
src/pkg/crypto/sha256/sha256block_amd64.s
--- a/src/pkg/crypto/sha256/sha256block_amd64.s
+++ b/src/pkg/crypto/sha256/sha256block_amd64.s
@@ -140,7 +140,7 @@
MSGSCHEDULE1(index); \
SHA256ROUND(index, const, a, b, c, d, e, f, g, h)
-TEXT ·block(SB),0,$264-24
+TEXT ·block(SB),0,$264-32
MOVQ p_base+8(FP), SI
MOVQ p_len+16(FP), DX
SHRQ $6, DX
src/pkg/crypto/sha512/sha512block_amd64.s
--- a/src/pkg/crypto/sha512/sha512block_amd64.s
+++ b/src/pkg/crypto/sha512/sha512block_amd64.s
@@ -141,7 +141,7 @@
MSGSCHEDULE1(index); \
SHA512ROUND(index, const, a, b, c, d, e, f, g, h)
-TEXT ·block(SB),0,$648-24
+TEXT ·block(SB),0,$648-32
MOVQ p_base+8(FP), SI
MOVQ p_len+16(FP), DX
SHRQ $7, DX
コアとなるコードの解説
両ファイルにおいて、TEXT ·block(SB),0,$FRAMESIZE-ARGSIZE
の行が変更されています。
- 変更前:
TEXT ·block(SB),0,$XXX-24
- 変更後:
TEXT ·block(SB),0,$XXX-32
ここで $XXX
は、block
関数が使用するローカルスタックフレームのサイズを示します(SHA-256では264バイト、SHA-512では648バイト)。この値自体は変更されていません。
変更されたのは -ARGSIZE
の部分で、これは関数が呼び出し元から受け取る引数の合計サイズをバイト単位で指定します。
- 元の
-24
は、スライス引数[]byte
のサイズ(データポインタ8バイト + 長さ8バイト + 容量8バイト = 24バイト)のみを考慮していました。 - 修正後の
-32
は、最初の引数である*block
(ポインタ、8バイト) と、スライス引数[]byte
(24バイト) の合計サイズ8 + 24 = 32
バイトを正しく反映しています。
この修正により、アセンブリコードはGoの呼び出し規約に完全に合致し、スタック上の引数領域を正しく解釈するようになります。これにより、コードの正確性が向上し、将来的な互換性の問題や潜在的なバグのリスクが排除されます。
MOVQ p_base+8(FP), SI
や MOVQ p_len+16(FP), DX
のような命令は、FP
(フレームポインタ) を基準として引数にアクセスしています。p_base
はスライスのデータポインタ、p_len
はスライスの長さを指します。これらのオフセットは、引数サイズが正しく設定されていることを前提として計算されます。今回の修正は、これらのオフセット計算の基盤となる ARGSIZE
を正しくすることで、より堅牢なコードにしています。
関連リンク
- Go言語のアセンブリについて: https://go.dev/doc/asm
go vet
コマンドについて: https://go.dev/cmd/vet/- Go言語の
crypto/sha256
パッケージ: https://pkg.go.dev/crypto/sha256 - Go言語の
crypto/sha512
パッケージ: https://pkg.go.dev/crypto/sha512
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
src/cmd/asm/doc.go
やsrc/cmd/vet/README
) - GoのIssueトラッカーやコードレビューシステム (CL 96360043)
- SHA-256およびSHA-512に関する一般的な暗号学の資料
go vet
の機能に関する技術記事やブログポスト- Go言語の呼び出し規約に関する技術的な議論