[インデックス 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.Write
がnil
バイトスライスを直接NaClのwrite
システムコールに渡すと、NaClランタイムがこれを不正なメモリアクセスと判断し、プログラムがクラッシュしていました。
この問題は、GoのWrite
関数がnil
スライスを内部的に長さ0のスライスとして処理するものの、最終的にNaClのシステムコールに渡される際には、その「ポインタ」部分がnil
のまま渡されてしまうことに起因していました。NaClのシステムコールは、ポインタがnil
であること自体を問題視したのです。
コアとなるコードの変更箇所
このコミットでは、以下の2つのファイルが変更されています。
src/pkg/syscall/fd_nacl.go
:Write
関数の実装が修正されました。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
関数に渡されたバイトスライスb
がnil
であるかどうかをチェックします。b = zerobuf[:]
: もしb
がnil
であれば、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 Issue 7050: https://golang.org/issue/7050
- Go CL 91590043: https://golang.org/cl/91590043
参考にした情報源リンク
- 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
関数に渡されたバイトスライスb
がnil
であるかどうかをチェックします。b = zerobuf[:]
: もしb
がnil
であれば、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 Issue 7050: https://golang.org/issue/7050
- Go CL 91590043: https://golang.org/cl/91590043
参考にした情報源リンク
- Go言語の公式ドキュメント (スライス、syscallパッケージ)
- Google Native Client (NaCl) の公式ドキュメント (システムコールインターフェース、サンドボックスの仕組み)
- Go言語のソースコード (syscallパッケージの他の実装、io.Writerインターフェースの慣習)
- Go Issue 7050の議論スレッド