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

[インデックス 11976] ファイルの概要

このコミットは、Go言語のsyscallパッケージにおけるErrorメソッド内の境界チェックのバグを修正するものです。具体的には、Errno型(uintptrのエイリアス)の値をint型に変換する際の潜在的な問題に対処し、配列errorsへのアクセスが常に安全に行われるように改善しています。

コミット

commit 014568bee123278ae51b0e6f53c909607806568e
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 16 15:23:50 2012 -0500

    syscall: fix bounds check in Error

    Fixes #3042.

    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/5675067

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/014568bee123278ae51b0e6f53c909607806568e

元コミット内容

syscallパッケージのErrorメソッドにおける境界チェックを修正。 Issue #3042を修正。

変更の背景

この変更は、Go言語のsyscallパッケージ内のErrno型が持つError()メソッドにおける潜在的なバグを修正するために行われました。元のコードでは、Errno型の値eerrorsという配列のインデックスとして使用する前に、その値が配列の有効な範囲内にあるかをチェックしていました。しかし、このチェックの記述方法に問題がありました。

具体的には、Errno型はuintptrのエイリアスであり、符号なし整数型です。Go言語では、符号なし整数と符号付き整数を比較する際に、符号なし整数が非常に大きな値である場合、予期せぬ結果を招く可能性があります。元のコード0 <= e && int(e) < len(errors)では、euintptrであるため、0 <= eは常に真となります(uintptrは非負の値しか取らないため)。問題はint(e) < len(errors)の部分で、eint型で表現できる最大値を超える場合、int(e)への変換がオーバーフローを引き起こし、負の値になる可能性がありました。負の値になった場合、int(e) < len(errors)は真となることがあり、結果としてerrors配列への負のインデックスアクセスが発生し、パニック(実行時エラー)を引き起こす可能性がありました。

このバグはGo issue #3042として報告されており、その報告内容から、特定の環境や状況下でErrnoの値が非常に大きくなり、この問題が顕在化することが示唆されています。このコミットは、このような潜在的なパニックを防ぎ、syscallパッケージの堅牢性を向上させることを目的としています。

前提知識の解説

Go言語のsyscallパッケージ

syscallパッケージは、Goプログラムからオペレーティングシステム(OS)のシステムコールを直接呼び出すための機能を提供します。ファイル操作、ネットワーク通信、プロセス管理など、OSレベルの低レベルな機能にアクセスする際に使用されます。OS固有の定数やエラーコードなども定義されており、OSとのインタフェース層として機能します。

Errno

Errno型はsyscallパッケージで定義されている型で、OSが返すエラーコードを表します。Go言語の標準ライブラリでは、通常、エラーはerrorインターフェースを実装した型で表現されますが、システムコールが返すエラーは数値コードであることが多いため、それを表現するためにErrno型が用いられます。Errnouintptrのエイリアスとして定義されており、これはポインタを保持するのに十分な大きさの符号なし整数型です。

Error()メソッド

Go言語では、errorインターフェースを実装するために、通常、Error() stringメソッドを定義します。このメソッドは、エラーに関する人間が読める形式の文字列を返します。Errno型もこのError()メソッドを実装しており、システムコールが返した数値のエラーコードに対応するエラーメッセージを返します。

境界チェック (Bounds Check)

境界チェックとは、配列やスライスなどのデータ構造にアクセスする際に、指定されたインデックスがそのデータ構造の有効な範囲内にあるかどうかを確認する処理のことです。例えば、長さが10の配列に対してインデックス15でアクセスしようとすると、これは範囲外アクセスとなり、プログラムのクラッシュや予期せぬ動作を引き起こす可能性があります。Go言語では、実行時に自動的に境界チェックが行われ、範囲外アクセスがあった場合はパニックが発生します。

符号付き整数と符号なし整数

コンピュータの数値表現には、符号付き整数(正負の数を表現できる)と符号なし整数(非負の数のみを表現できる)があります。Go言語では、int, int8, int16, int32, int64が符号付き整数、uint, uint8, uint16, uint32, uint64, uintptrが符号なし整数です。 符号付き整数と符号なし整数を比較したり、相互に変換したりする際には注意が必要です。特に、符号なし整数が非常に大きな値を持つ場合、それを符号付き整数に変換するとオーバーフローが発生し、負の値として解釈されることがあります。これは、2の補数表現などのコンピュータ内部での数値表現の仕組みに起因します。

技術的詳細

このコミットの技術的な核心は、Go言語における型変換と境界チェックの正確性に関するものです。

元のコード:

if 0 <= e && int(e) < len(errors) {

修正後のコード:

if 0 <= int(e) && int(e) < len(errors) {

ここでeErrno型であり、uintptrのエイリアスです。 len(errors)int型を返します。

元のコードの問題点:

  1. 0 <= e: euintptr(符号なし整数)なので、常に0以上です。したがって、この条件は常に真であり、実質的に意味がありませんでした。
  2. int(e)への変換とオーバーフロー: euintptrの最大値に近い、またはint型で表現できる最大値を超えるような非常に大きな値であった場合、int(e)への型変換はオーバーフローを引き起こす可能性があります。Go言語の仕様では、このような変換は実装依存であり、結果として負の値になることがあります。 例えば、64ビットシステムでintが64ビット、uintptrも64ビットの場合、uintptrの最大値は2^64 - 1です。もしe2^63を超えるような値であった場合、int(e)に変換すると負の値として解釈される可能性があります(2の補数表現の場合)。
  3. 負のインデックスアクセス: int(e)が負の値になった場合、int(e) < len(errors)という条件は真になる可能性があります(len(errors)は非負なので)。これにより、errors[e](実際にはerrors[int(e)])というアクセスが行われた際に、負のインデックスで配列にアクセスしようとすることになり、Goのランタイムがパニックを引き起こします。

修正後のコードの改善点:

if 0 <= int(e) && int(e) < len(errors) {

この修正では、eint型に変換した結果に対して、両方の境界チェックを行っています。

  1. 0 <= int(e): euintptrからintに変換された後、その値が0以上であるかをチェックします。これにより、もしeが大きすぎてintへの変換でオーバーフローし、負の値になったとしても、この条件で捕捉され、errors配列への負のインデックスアクセスが防止されます。
  2. int(e) < len(errors): 変換後のint(e)errors配列の長さ未満であるかをチェックします。

この変更により、eint型で表現できる範囲を超えていたとしても、int(e)が負の値になるケースを適切に処理し、errors配列への安全なアクセスを保証できるようになりました。これにより、Goのsyscallパッケージの堅牢性が向上し、特定のOSエラーコードが非常に大きな値を取るようなエッジケースでのパニックが回避されます。

コアとなるコードの変更箇所

変更はsrc/pkg/syscall/syscall_unix.goファイル内のErrno型のError()メソッドにあります。

--- a/src/pkg/syscall/syscall_unix.go
+++ b/src/pkg/syscall/syscall_unix.go
@@ -95,7 +95,7 @@ func (m *mmapper) Munmap(data []byte) (err error) {
 type Errno uintptr

 func (e Errno) Error() string {
-	if 0 <= e && int(e) < len(errors) {
+	if 0 <= int(e) && int(e) < len(errors) {
 		s := errors[e]
 		if s != "" {
 			return s

コアとなるコードの解説

変更された行は、Errno型のError()メソッド内のif文の条件式です。

元のコード: if 0 <= e && int(e) < len(errors)

修正後のコード: if 0 <= int(e) && int(e) < len(errors)

この変更のポイントは、0 <= e0 <= int(e)に修正した点です。

  • e (Errno型): これはuintptrのエイリアスであり、符号なし整数です。符号なし整数は常に0以上であるため、0 <= eという条件は常に真であり、冗長でした。
  • int(e): eint型に明示的に変換しています。この変換は、eint型で表現できる最大値を超える場合にオーバーフローを引き起こし、結果として負の値になる可能性があります。
  • 0 <= int(e): 修正後のコードでは、eintに変換した結果が0以上であるかをチェックしています。これにより、もしeが非常に大きな値で、int(e)がオーバーフローして負の値になった場合でも、この条件が偽となるため、errors配列への負のインデックスアクセスを防ぐことができます。
  • int(e) < len(errors): この条件は、変換後のint(e)errors配列の有効なインデックス範囲内にあることを確認します。

この修正により、Errnoの値がint型で表現できないほど大きな値であったとしても、errors配列へのアクセスが安全に行われるようになり、潜在的なパニックが回避されます。これは、Go言語の型システムと数値表現の特性を考慮した、堅牢なコードを書く上での重要な教訓を示しています。

関連リンク

参考にした情報源リンク