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

[インデックス 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 vetstd オプションによって特定された潜在的な問題に対処するために行われました。go vet は、Goプログラムの疑わしい構造を報告する静的解析ツールであり、特にアセンブリコードとGoコード間のインターフェースにおける引数の不整合などを検出する能力を持っています。

crypto/sha256crypto/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.scrypto/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つです。

  1. *block (ポインタ): 64ビットシステムでは8バイト。
  2. []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.ssrc/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), SIMOVQ p_len+16(FP), DX のような命令は、FP (フレームポインタ) を基準として引数にアクセスしています。p_base はスライスのデータポインタ、p_len はスライスの長さを指します。これらのオフセットは、引数サイズが正しく設定されていることを前提として計算されます。今回の修正は、これらのオフセット計算の基盤となる ARGSIZE を正しくすることで、より堅牢なコードにしています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/cmd/asm/doc.gosrc/cmd/vet/README)
  • GoのIssueトラッカーやコードレビューシステム (CL 96360043)
  • SHA-256およびSHA-512に関する一般的な暗号学の資料
  • go vet の機能に関する技術記事やブログポスト
  • Go言語の呼び出し規約に関する技術的な議論