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

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

このコミットは、Go言語のsyscallパッケージにおいて、Google Native Client (NaCl) 環境でWrite(nil)が呼び出された際に発生する問題を修正するものです。具体的には、nilバイトスライスがシステムコールに渡されることを防ぎ、代わりにゼロ長のスライスを使用するように変更することで、NaCl環境での安定性を向上させています。

コミット

commit 82854d7b398ff49896c6b10e954891aa36fb1ade
Author: Russ Cox <rsc@golang.org>
Date:   Tue May 20 11:38:34 2014 -0400

    syscall: fix Write(nil) on NaCl
    
    Fixes #7050.
    
    LGTM=crawshaw, r
    R=golang-codereviews, crawshaw, r
    CC=golang-codereviews
    https://golang.org/cl/91590043

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

https://github.com/golang/go/commit/82854d7b398ff49896c6b10e954891aa36fb1ade

元コミット内容

syscall: fix Write(nil) on NaCl

Fixes #7050.

LGTM=crawshaw, r
R=golang-codereviews, crawshaw, r
CC=golang-codereviews
https://golang.org/cl/91590043

変更の背景

この変更は、Go Issue 7050 (https://golang.org/issue/7050) で報告されたバグを修正するために行われました。問題は、Goプログラムがos.Stdout.Write(nil)のようにnilバイトスライスをWrite関数に渡した場合に、Google Native Client (NaCl) 環境でクラッシュするというものでした。

Go言語の設計上、nilスライスは有効な入力として扱われることが多く、例えばio.Writerインターフェースの実装では、Write(nil)は通常、0バイトを書き込み、エラーを返さないか、あるいはnilエラーを返すことが期待されます。しかし、NaClのシステムコール層は、nilポインタを厳格に扱い、予期せぬ動作やクラッシュを引き起こす可能性がありました。

このコミット以前は、syscall.Write関数がnilバイトスライスを直接NaClの基盤システムコールに渡していました。NaClのシステムコールは、nilポインタを有効なメモリ領域として解釈しないため、これが問題の根本原因となっていました。

前提知識の解説

Go言語におけるnilスライス

Go言語において、スライスは基盤となる配列への参照、長さ、容量を持つデータ構造です。nilスライスは、基盤となる配列を持たないスライスであり、その長さと容量はゼロです。Goの多くのAPIでは、nilスライスは空のスライス([]byte{}など)と同様に扱われることが多く、特に読み書き操作においては、0バイトの操作として成功することが期待されます。例えば、len(nil_slice)は0を返します。

Goのsyscallパッケージ

syscallパッケージは、Goプログラムがオペレーティングシステムの低レベルな機能(システムコール)にアクセスするためのインターフェースを提供します。ファイルI/O、ネットワーク通信、プロセス管理など、OSに依存する操作の多くは、最終的にこのパッケージを介してシステムコールを呼び出します。このパッケージは、異なるOS(Linux, Windows, macOS, Plan 9, NaClなど)のシステムコールを抽象化し、Goプログラムから一貫した方法で利用できるようにします。

Google Native Client (NaCl)

Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術です。NaClは、セキュリティと移植性を確保するために、独自のシステムコールインターフェースとメモリモデルを持っています。NaCl環境で実行されるプログラムは、通常のOS環境とは異なる制約の下で動作します。特に、メモリへのアクセスやポインタの扱いは厳格にチェックされ、不正なアクセスは即座に終了させられます。この厳格なチェックが、GoのnilスライスがNaClのシステムコールに渡された際に問題を引き起こす原因となりました。

技術的詳細

Goのio.Writerインターフェースを実装する多くの型、例えばos.Fileは、Writeメソッドを持ちます。このメソッドは[]byte型の引数を受け取ります。Goの慣習では、Write(nil)は有効な呼び出しであり、通常は0バイトを書き込み、エラーを返しません。これは、nilスライスが長さ0のスライスとして扱われるためです。

しかし、NaCl環境では、GoのsyscallパッケージがNaClの提供する低レベルなシステムコールを呼び出す際に、このnilスライスの扱いが問題となりました。NaClのシステムコールは、引数として渡されるポインタが有効なメモリ領域を指していることを期待します。nilポインタは、NaClのサンドボックス内で有効なメモリ領域として認識されないため、syscall.Writenilバイトスライスを直接NaClのwriteシステムコールに渡すと、NaClランタイムがこれを不正なメモリアクセスと判断し、プログラムがクラッシュしていました。

この問題は、GoのWrite関数がnilスライスを内部的に長さ0のスライスとして処理するものの、最終的にNaClのシステムコールに渡される際には、その「ポインタ」部分がnilのまま渡されてしまうことに起因していました。NaClのシステムコールは、ポインタがnilであること自体を問題視したのです。

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

このコミットでは、以下の2つのファイルが変更されています。

  1. src/pkg/syscall/fd_nacl.go: Write関数の実装が修正されました。
  2. test/fixedbugs/issue7050.go: 問題を再現し、修正を検証するための新しいテストファイルが追加されました。

src/pkg/syscall/fd_nacl.go の変更点

--- a/src/pkg/syscall/fd_nacl.go
+++ b/src/pkg/syscall/fd_nacl.go
@@ -162,7 +162,13 @@ func Read(fd int, b []byte) (int, error) {
 	return f.impl.read(b)
 }
 
+var zerobuf [0]byte
+
 func Write(fd int, b []byte) (int, error) {
+	if b == nil {
+		// avoid nil in syscalls; nacl doesn't like that.
+		b = zerobuf[:]
+	}
 	f, err := fdToFile(fd)
 	if err != nil {
 		return 0, err

test/fixedbugs/issue7050.go の追加

--- /dev/null
+++ b/test/fixedbugs/issue7050.go
@@ -0,0 +1,19 @@
+// run
+
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"fmt"
+	"os"
+)
+
+func main() {
+	_, err := os.Stdout.Write(nil)
+	if err != nil {
+		fmt.Printf("BUG: os.Stdout.Write(nil) = %v\n", err)
+	}
+}

コアとなるコードの解説

src/pkg/syscall/fd_nacl.go の修正

修正の核心は、syscall.Write関数に以下のロジックが追加された点です。

var zerobuf [0]byte

func Write(fd int, b []byte) (int, error) {
	if b == nil {
		// avoid nil in syscalls; nacl doesn't like that.
		b = zerobuf[:]
	}
	// ... 既存のコード ...
}
  • var zerobuf [0]byte: これは、長さ0の配列を宣言しています。この配列は、nilスライスの代わりにシステムコールに渡すための「実体のある」ゼロ長スライスを作成するために使用されます。
  • if b == nil: Write関数に渡されたバイトスライスbnilであるかどうかをチェックします。
  • b = zerobuf[:]: もしbnilであれば、zerobuf配列を基盤とするゼロ長のスライスにbを再割り当てします。zerobuf[:]は、zerobuf配列全体を参照するスライスを作成します。このスライスは長さが0ですが、nilポインタではなく、有効な(ただし空の)メモリ領域を指すポインタを持ちます。

この変更により、syscall.Writeは決してnilポインタをNaClのシステムコールに渡さなくなります。nilスライスが渡された場合でも、それは長さ0の有効なスライスに変換されるため、NaClランタイムはこれを正しく処理し、クラッシュを防ぐことができます。結果として、Write(nil)はNaCl環境でも期待通りに0バイトを書き込み、エラーを返さない動作となります。

test/fixedbugs/issue7050.go の追加

このテストファイルは、os.Stdout.Write(nil)を呼び出すことで、以前のバグを再現しようとします。

func main() {
	_, err := os.Stdout.Write(nil)
	if err != nil {
		fmt.Printf("BUG: os.Stdout.Write(nil) = %v\n", err)
	}
}
  • os.Stdout.Write(nil): 標準出力にnilバイトスライスを書き込もうとします。
  • if err != nil: 以前のバグでは、この呼び出しがクラッシュを引き起こしていました。修正後は、エラーが返されない(またはnilエラーが返される)ことが期待されます。もしエラーが返された場合、それはバグがまだ存在するか、新しい問題が発生したことを示します。

このテストは、// runディレクティブによって、Goのテストフレームワークによって実行されることを示しています。テストがクラッシュせずに正常に完了すれば、修正が成功したことを意味します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (スライス、syscallパッケージ)
  • Google Native Client (NaCl) の公式ドキュメント (システムコールインターフェース、サンドボックスの仕組み)
  • Go言語のソースコード (syscallパッケージの他の実装、io.Writerインターフェースの慣習)
  • Go Issue 7050の議論スレッドI have generated the detailed explanation in Markdown format, following all the specified instructions and chapter structure. I have included comprehensive details on the background, prerequisite knowledge, technical specifics, code changes, and their explanations. I have also included the relevant links.

Now, I will output the generated Markdown content to standard output.

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

このコミットは、Go言語の`syscall`パッケージにおいて、Google Native Client (NaCl) 環境で`Write(nil)`が呼び出された際に発生する問題を修正するものです。具体的には、`nil`バイトスライスがシステムコールに渡されることを防ぎ、代わりにゼロ長のスライスを使用するように変更することで、NaCl環境での安定性を向上させています。

## コミット

commit 82854d7b398ff49896c6b10e954891aa36fb1ade Author: Russ Cox rsc@golang.org Date: Tue May 20 11:38:34 2014 -0400

syscall: fix Write(nil) on NaCl

Fixes #7050.

LGTM=crawshaw, r
R=golang-codereviews, crawshaw, r
CC=golang-codereviews
https://golang.org/cl/91590043

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

[https://github.com/golang/go/commit/82854d7b398ff49896c6b10e954891aa36fb1ade](https://github.com/golang/go/commit/82854d7b398ff49896c6b10e954891aa36fb1ade)

## 元コミット内容

syscall: fix Write(nil) on NaCl

Fixes #7050.

LGTM=crawshaw, r R=golang-codereviews, crawshaw, r CC=golang-codereviews https://golang.org/cl/91590043


## 変更の背景

この変更は、Go Issue 7050 ([https://golang.org/issue/7050](https://golang.org/issue/7050)) で報告されたバグを修正するために行われました。問題は、Goプログラムが`os.Stdout.Write(nil)`のように`nil`バイトスライスを`Write`関数に渡した場合に、Google Native Client (NaCl) 環境でクラッシュするというものでした。

Go言語の設計上、`nil`スライスは有効な入力として扱われることが多く、例えば`io.Writer`インターフェースの実装では、`Write(nil)`は通常、0バイトを書き込み、エラーを返さないか、あるいは`nil`エラーを返すことが期待されます。しかし、NaClのシステムコール層は、`nil`ポインタを厳格に扱い、予期せぬ動作やクラッシュを引き起こす可能性がありました。

このコミット以前は、`syscall.Write`関数が`nil`バイトスライスを直接NaClの基盤システムコールに渡していました。NaClのシステムコールは、`nil`ポインタを有効なメモリ領域として解釈しないため、これが問題の根本原因となっていました。

## 前提知識の解説

### Go言語における`nil`スライス

Go言語において、スライスは基盤となる配列への参照、長さ、容量を持つデータ構造です。`nil`スライスは、基盤となる配列を持たないスライスであり、その長さと容量はゼロです。Goの多くのAPIでは、`nil`スライスは空のスライス(`[]byte{}`など)と同様に扱われることが多く、特に読み書き操作においては、0バイトの操作として成功することが期待されます。例えば、`len(nil_slice)`は0を返します。

### Goの`syscall`パッケージ

`syscall`パッケージは、Goプログラムがオペレーティングシステムの低レベルな機能(システムコール)にアクセスするためのインターフェースを提供します。ファイルI/O、ネットワーク通信、プロセス管理など、OSに依存する操作の多くは、最終的にこのパッケージを介してシステムコールを呼び出します。このパッケージは、異なるOS(Linux, Windows, macOS, Plan 9, NaClなど)のシステムコールを抽象化し、Goプログラムから一貫した方法で利用できるようにします。

### Google Native Client (NaCl)

Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術です。NaClは、セキュリティと移植性を確保するために、独自のシステムコールインターフェースとメモリモデルを持っています。NaCl環境で実行されるプログラムは、通常のOS環境とは異なる制約の下で動作します。特に、メモリへのアクセスやポインタの扱いは厳格にチェックされ、不正なアクセスは即座に終了させられます。この厳格なチェックが、Goの`nil`スライスがNaClのシステムコールに渡された際に問題を引き起こす原因となりました。

## 技術的詳細

Goの`io.Writer`インターフェースを実装する多くの型、例えば`os.File`は、`Write`メソッドを持ちます。このメソッドは`[]byte`型の引数を受け取ります。Goの慣習では、`Write(nil)`は有効な呼び出しであり、通常は0バイトを書き込み、エラーを返しません。これは、`nil`スライスが長さ0のスライスとして扱われるためです。

しかし、NaCl環境では、Goの`syscall`パッケージがNaClの提供する低レベルなシステムコールを呼び出す際に、この`nil`スライスの扱いが問題となりました。NaClのシステムコールは、引数として渡されるポインタが有効なメモリ領域を指していることを期待します。`nil`ポインタは、NaClのサンドボックス内で有効なメモリ領域として認識されないため、`syscall.Write`が`nil`バイトスライスを直接NaClの`write`システムコールに渡すと、NaClランタイムがこれを不正なメモリアクセスと判断し、プログラムがクラッシュしていました。

この問題は、Goの`Write`関数が`nil`スライスを内部的に長さ0のスライスとして処理するものの、最終的にNaClのシステムコールに渡される際には、その「ポインタ」部分が`nil`のまま渡されてしまうことに起因していました。NaClのシステムコールは、ポインタが`nil`であること自体を問題視したのです。

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

このコミットでは、以下の2つのファイルが変更されています。

1.  `src/pkg/syscall/fd_nacl.go`: `Write`関数の実装が修正されました。
2.  `test/fixedbugs/issue7050.go`: 問題を再現し、修正を検証するための新しいテストファイルが追加されました。

### `src/pkg/syscall/fd_nacl.go` の変更点

```diff
--- a/src/pkg/syscall/fd_nacl.go
+++ b/src/pkg/syscall/fd_nacl.go
@@ -162,7 +162,13 @@ func Read(fd int, b []byte) (int, error) {
 	return f.impl.read(b)
 }
 
+var zerobuf [0]byte
+
 func Write(fd int, b []byte) (int, error) {
+	if b == nil {
+		// avoid nil in syscalls; nacl doesn't like that.
+		b = zerobuf[:]
+	}
 	f, err := fdToFile(fd)
 	if err != nil {
 		return 0, err

test/fixedbugs/issue7050.go の追加

--- /dev/null
+++ b/test/fixedbugs/issue7050.go
@@ -0,0 +1,19 @@
+// run
+
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"fmt"
+	"os"
+)
+
+func main() {
+	_, err := os.Stdout.Write(nil)
+	if err != nil {
+		fmt.Printf("BUG: os.Stdout.Write(nil) = %v\n", err)
+	}
+}

コアとなるコードの解説

src/pkg/syscall/fd_nacl.go の修正

修正の核心は、syscall.Write関数に以下のロジックが追加された点です。

var zerobuf [0]byte

func Write(fd int, b []byte) (int, error) {
	if b == nil {
		// avoid nil in syscalls; nacl doesn't like that.
		b = zerobuf[:]
	}
	// ... 既存のコード ...
}
  • var zerobuf [0]byte: これは、長さ0の配列を宣言しています。この配列は、nilスライスの代わりにシステムコールに渡すための「実体のある」ゼロ長スライスを作成するために使用されます。
  • if b == nil: Write関数に渡されたバイトスライスbnilであるかどうかをチェックします。
  • b = zerobuf[:]: もしbnilであれば、zerobuf配列を基盤とするゼロ長のスライスにbを再割り当てします。zerobuf[:]は、zerobuf配列全体を参照するスライスを作成します。このスライスは長さが0ですが、nilポインタではなく、有効な(ただし空の)メモリ領域を指すポインタを持ちます。

この変更により、syscall.Writeは決してnilポインタをNaClのシステムコールに渡さなくなります。nilスライスが渡された場合でも、それは長さ0の有効なスライスに変換されるため、NaClランタイムはこれを正しく処理し、クラッシュを防ぐことができます。結果として、Write(nil)はNaCl環境でも期待通りに0バイトを書き込み、エラーを返さない動作となります。

test/fixedbugs/issue7050.go の追加

このテストファイルは、os.Stdout.Write(nil)を呼び出すことで、以前のバグを再現しようとします。

func main() {
	_, err := os.Stdout.Write(nil)
	if err != nil {
		fmt.Printf("BUG: os.Stdout.Write(nil) = %v\n", err)
	}
}
  • os.Stdout.Write(nil): 標準出力にnilバイトスライスを書き込もうとします。
  • if err != nil: 以前のバグでは、この呼び出しがクラッシュを引き起こしていました。修正後は、エラーが返されない(またはnilエラーが返される)ことが期待されます。もしエラーが返された場合、それはバグがまだ存在するか、新しい問題が発生したことを示します。

このテストは、// runディレクティブによって、Goのテストフレームワークによって実行されることを示しています。テストがクラッシュせずに正常に完了すれば、修正が成功したことを意味します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (スライス、syscallパッケージ)
  • Google Native Client (NaCl) の公式ドキュメント (システムコールインターフェース、サンドボックスの仕組み)
  • Go言語のソースコード (syscallパッケージの他の実装、io.Writerインターフェースの慣習)
  • Go Issue 7050の議論スレッド