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

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

このコミットは、Go言語のsyscallパッケージにおけるネットワーク関連のシステムコール(setsockopt, getsockopt, bind, connectなど)において、引数の型をuintptrからunsafe.Pointerへ変更するものです。これにより、Goのガベージコレクタがポインタの参照を正しく追跡できるようになり、メモリ安全性と堅牢性が向上します。

コミット

  • コミットハッシュ: f00af3da1cd6b3f09f5d61a6d8bca438c77b34c6
  • Author: Brad Fitzpatrick bradfitz@golang.org
  • Date: Tue Jan 21 18:54:49 2014 -0800

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

https://github.com/golang/go/commit/f00af3da1cd6b3f09f5d61a6d8bca438c77b34c6

元コミット内容

syscall: use unsafe.Pointer instead of uintptr in net syscalls

In particular: setsockopt, getsockopt, bind, connect.

There are probably more.

All platforms cross-compile with make.bash, and all.bash still
pases on linux/amd64.

Update #7169

R=rsc
CC=golang-codereviews
https://golang.org/cl/55410043

変更の背景

Go言語のsyscallパッケージは、オペレーティングシステムのシステムコールを直接呼び出すためのインターフェースを提供します。これらのシステムコールは、C言語のAPIと密接に連携しており、しばしばメモリ上のデータ構造へのポインタを引数として受け取ります。

以前のGoのsyscall実装では、これらのポインタをuintptr型として扱っていました。uintptrは、ポインタの値を整数として表現する型であり、Goのガベージコレクタからは単なる数値として認識されます。このため、uintptrを介して参照されているメモリ領域が、Goのガベージコレクタによって誤って解放されてしまう可能性がありました。特に、システムコールが実行中にそのメモリ領域を参照している場合、Use-After-Freeのような深刻なメモリ破壊バグを引き起こすリスクがありました。

このコミットは、この潜在的なメモリ安全性の問題を解決するために導入されました。uintptrの代わりにunsafe.Pointerを使用することで、Goのガベージコレクタに対して、その値がポインタであり、参照先のメモリ領域を保護する必要があることを明示的に伝えます。これにより、システムコールが使用するメモリがガベージコレクションの対象から適切に除外され、プログラムの安定性と信頼性が向上します。

前提知識の解説

Go言語のunsafeパッケージ

unsafeパッケージは、Go言語の型安全性をバイパスするための機能を提供します。通常、Goは厳格な型システムを持ち、ポインタ演算や型変換を制限することでメモリ安全性を保証します。しかし、システムプログラミングや特定の最適化のシナリオでは、これらの制限を緩和する必要が生じます。unsafeパッケージは、そのような高度なユースケースのために提供されており、以下の主要な型と関数を含みます。

  • unsafe.Pointer: 任意の型のポインタを保持できる特殊なポインタ型です。Goのガベージコレクタはunsafe.Pointerが指すメモリを追跡し、そのメモリが使用中である限り解放しません。これは、C言語のvoid*に似ていますが、ガベージコレクタのセーフティネットが提供される点が異なります。
  • uintptr: ポインタの値を符号なし整数として表現する型です。uintptrはポインタの値を保持できますが、Goのガベージコレクタはuintptrが指すメモリを追跡しません。そのため、uintptrを介してメモリを操作する際には、開発者が手動でメモリのライフサイクルを管理する必要があります。

uintptrunsafe.Pointerの違い

特徴uintptrunsafe.Pointer
ガベージコレクションガベージコレクタは追跡しない。ガベージコレクタは追跡し、参照先のメモリを保護する。
型安全性型安全ではない。任意の整数値として扱われる。型安全ではないが、ポインタとしての意味を持つ。
用途ポインタの値を整数として扱う場合(例: アドレス計算)任意の型のポインタを安全に扱う場合(例: CとのFFI、システムコール)

この違いが、システムコールにおけるメモリ安全性の問題の根源でした。uintptrを使用すると、Goのランタイムはそれがポインタであることを認識せず、ガベージコレクションの対象となり得るためです。

Goのsyscallパッケージとシステムコール

syscallパッケージは、GoプログラムからOSのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。これにより、ファイル操作、ネットワーク通信、プロセス管理など、OSカーネルが提供する機能にアクセスできます。システムコールは通常、C言語で定義されたAPIに対応しており、GoからこれらのAPIを呼び出す際には、Goのデータ型をC言語のデータ型にマッピングする必要があります。このマッピングにおいて、ポインタの扱いが重要になります。

ネットワーク関連のシステムコール

setsockopt, getsockopt, bind, connectなどは、ソケットプログラミングにおいて基本的なシステムコールです。

  • setsockopt: ソケットのオプションを設定します。
  • getsockopt: ソケットのオプションを取得します。
  • bind: ソケットにアドレスを割り当てます。
  • connect: ソケットをリモートアドレスに接続します。

これらの関数は、ソケットアドレス構造体(例: sockaddr_in, sockaddr_in6)へのポインタを引数として受け取ることが一般的です。

技術的詳細

このコミットの技術的な核心は、Goのガベージコレクタとシステムコール間の相互作用におけるメモリ安全性の保証です。

Goのガベージコレクタは、到達可能なオブジェクトを特定し、到達不能なオブジェクトを解放することでメモリを管理します。ここでいう「到達可能」とは、プログラムがそのオブジェクトにアクセスできるパスが存在することを意味します。Goのポインタは、ガベージコレクタが到達可能性を判断するための重要な情報源です。

しかし、uintptrは単なる整数値であるため、それがメモリ上の特定のアドレスを指しているとしても、ガベージコレクタはそのアドレスがポインタとして使用されていることを認識できません。結果として、uintptrを介してシステムコールに渡されたメモリ領域が、システムコールが完了する前にガベージコレクタによって解放されてしまう可能性があります。これは、特にシステムコールが非同期的に動作する場合や、長時間実行される場合に問題となります。

unsafe.Pointerを使用することで、開発者はGoのガベージコレクタに対して、その値がポインタであり、参照先のメモリ領域を保護する必要があることを明示的に伝えます。これにより、unsafe.Pointerが指すメモリは、そのポインタが有効である限りガベージコレクションの対象から除外されます。

この変更は、特にネットワークシステムコールにおいて重要です。bindconnectのようなシステムコールは、ソケットアドレス構造体へのポインタをカーネルに渡します。カーネルがこのポインタを使用している間に、Goのランタイムがそのメモリを解放してしまうと、カーネルパニックや予期せぬ動作を引き起こす可能性があります。unsafe.Pointerへの変更は、このようなシナリオを防ぎ、Goのネットワークスタックの堅牢性を大幅に向上させます。

また、この変更は、Goのクロスコンパイル環境においても重要です。異なるアーキテクチャやOS間でシステムコールを扱う場合、ポインタのサイズやアライメントが異なることがあります。unsafe.Pointerは、これらのプラットフォーム固有の詳細を抽象化し、より移植性の高いコードを記述するのに役立ちます。

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

このコミットでは、src/pkg/syscallディレクトリ以下の多数のファイルが変更されています。主な変更は、setsockopt, getsockopt, bind, connect, sendtoなどのシステムコール関数のシグネチャにおいて、uintptr型だった引数がunsafe.Pointer型に変更されている点です。また、Sockaddrインターフェースのsockaddr()メソッドの戻り値の型も変更されています。

具体的な変更例をいくつか示します。

src/pkg/syscall/lsf_linux.go:

--- a/src/pkg/syscall/lsf_linux.go
+++ b/src/pkg/syscall/lsf_linux.go
@@ -69,10 +69,10 @@ func AttachLsf(fd int, i []SockFilter) error {
 	var p SockFprog
 	p.Len = uint16(len(i))
 	p.Filter = (*SockFilter)(unsafe.Pointer(&i[0]))
-	return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, uintptr(unsafe.Pointer(&p)), unsafe.Sizeof(p))\n+\treturn setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, unsafe.Pointer(&p), unsafe.Sizeof(p))
 }

 func DetachLsf(fd int) error {
 	var dummy int
-	return setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, uintptr(unsafe.Pointer(&dummy)), unsafe.Sizeof(dummy))\n+\treturn setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, unsafe.Pointer(&dummy), unsafe.Sizeof(dummy))
 }

src/pkg/syscall/syscall_bsd.go:

--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -134,18 +134,18 @@ func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int,\n }\n \n //sys	accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)\n-//sys	bind(s int, addr uintptr, addrlen _Socklen) (err error)\n-//sys	connect(s int, addr uintptr, addrlen _Socklen) (err error)\n+//sys	bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)\n+//sys	connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)\n //sysnb	socket(domain int, typ int, proto int) (fd int, err error)\n-//sys	getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error)\n-//sys	setsockopt(s int, level int, name int, val uintptr, vallen uintptr) (err error)\n+//sys	getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *_Socklen) (err error)\n+//sys	setsockopt(s int, level int, name int, val unsafe.Pointer, vallen uintptr) (err error)\n //sysnb	getpeername(fd int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error)\n //sysnb	getsockname(fd int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error)\n //sys	Shutdown(s int, how int) (err error)\n \n-func (sa *SockaddrInet4) sockaddr() (uintptr, _Socklen, error) {\n+func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) {\n \tif sa.Port < 0 || sa.Port > 0xFFFF {\n-\t\treturn 0, 0, EINVAL\n+\t\treturn nil, 0, EINVAL

src/pkg/syscall/syscall_unix.go:

--- a/src/pkg/syscall/syscall_unix.go
+++ b/src/pkg/syscall/syscall_unix.go
@@ -160,7 +160,7 @@ func Write(fd int, p []byte) (n int, err error) {
 var SocketDisableIPv6 bool

 type Sockaddr interface {
-\tsockaddr() (ptr uintptr, len _Socklen, err error) // lowercase; only we can define Sockaddrs\n+\tsockaddr() (ptr unsafe.Pointer, len _Socklen, err error) // lowercase; only we can define Sockaddrs
 }

 type SockaddrInet4 struct {
@@ -209,7 +209,7 @@ func Getpeername(fd int) (sa Sockaddr, err error) {
 func GetsockoptInt(fd, level, opt int) (value int, err error) {
 	var n int32
 	vallen := _Socklen(4)
-\terr = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), &vallen)\n+\terr = getsockopt(fd, level, opt, unsafe.Pointer(&n), &vallen)
 	return int(n), err
 }

@@ -234,40 +234,40 @@ func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error) {
 }

 func SetsockoptByte(fd, level, opt int, value byte) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value)), 1)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&value), 1)
 }

 func SetsockoptInt(fd, level, opt int, value int) (err error) {
 	var n = int32(value)
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), 4)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&n), 4)
 }

 func SetsockoptInet4Addr(fd, level, opt int, value [4]byte) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value[0])), 4)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&value[0]), 4)
 }

 func SetsockoptIPMreq(fd, level, opt int, mreq *IPMreq) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), SizeofIPMreq)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(mreq), SizeofIPMreq)
 }

 func SetsockoptIPv6Mreq(fd, level, opt int, mreq *IPv6Mreq) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), SizeofIPv6Mreq)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(mreq), SizeofIPv6Mreq)
 }

 func SetsockoptICMPv6Filter(fd, level, opt int, filter *ICMPv6Filter) error {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(filter)), SizeofICMPv6Filter)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(filter), SizeofICMPv6Filter)
 }

 func SetsockoptLinger(fd, level, opt int, l *Linger) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(l)), SizeofLinger)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(l), SizeofLinger)
 }

 func SetsockoptString(fd, level, opt int, s string) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&[]byte(s)[0])), uintptr(len(s)))\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&[]byte(s)[0]), uintptr(len(s)))
 }

 func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(tv)), unsafe.Sizeof(*tv))\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(tv), unsafe.Sizeof(*tv))
 }

 func Socket(domain, typ, proto int) (fd int, err error) {

コアとなるコードの解説

上記の変更箇所は、GoのsyscallパッケージがOSのシステムコールを呼び出す際に、メモリ上のデータへのポインタをどのように扱うかを根本的に変更しています。

  1. システムコール関数の引数型の変更: bind, connect, getsockopt, setsockopt, sendtoといった関数は、ソケットアドレス構造体やオプション値へのポインタを引数として受け取ります。これらの引数の型がuintptrからunsafe.Pointerに変更されました。

    • 変更前 (uintptr): uintptrは単なる整数であり、Goのガベージコレクタはそれが指すメモリを追跡しませんでした。これにより、システムコールがそのメモリを使用している間に、ガベージコレクタがメモリを解放してしまう可能性がありました。
    • 変更後 (unsafe.Pointer): unsafe.Pointerは、ガベージコレクタに対して、その値がポインタであり、参照先のメモリを保護する必要があることを明示的に伝えます。これにより、システムコールが完了するまで、そのメモリ領域が安全に保持されることが保証されます。
  2. Sockaddrインターフェースのsockaddr()メソッドの戻り値の変更: Sockaddrインターフェースは、様々な種類のソケットアドレス(IPv4, IPv6, Unixドメインなど)を抽象化するためのものです。このインターフェースのsockaddr()メソッドは、具体的なソケットアドレス構造体へのポインタと、その長さを返します。このメソッドのポインタの戻り値の型もuintptrからunsafe.Pointerに変更されました。

    • この変更は、bindsendtoなどのシステムコールにソケットアドレスを渡す際に、unsafe.Pointerを直接使用できるようにするためです。これにより、uintptr(unsafe.Pointer(&sa.raw))のような冗長な変換が不要になり、コードがよりクリーンになります。また、sockaddr()メソッドが返すポインタもガベージコレクタによって追跡されるようになり、メモリ安全性が向上します。

これらの変更は、Goのネットワークスタックの基盤となるsyscallパッケージのメモリ管理を改善し、より堅牢で信頼性の高いネットワークアプリケーションの開発を可能にします。特に、低レベルなシステムコールを扱う際に発生しうる、ガベージコレクションに起因する潜在的なバグを排除することを目的としています。

関連リンク

参考にした情報源リンク