[インデックス 19597] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるLinuxプラットフォームでのSetuid
およびSetgid
関数の挙動に関する重要な変更を導入します。具体的には、これらの関数がLinux上でプロセス全体ではなく、現在のスレッドのみに影響するというGoランタイムの特性を考慮し、誤解を招く挙動を防ぐために、これらの関数を無効化(常にエラーを返すように変更)します。
コミット
commit 343b4ba8c1ad8a29b6dd19cb101273b57a26c9b0
Author: Dave Cheney <dave@cheney.net>
Date: Tue Jun 24 09:16:24 2014 +1000
syscall: disable Setuid/Setgid on linux
Update #1435
This proposal disables Setuid and Setgid on all linux platforms.
Issue 1435 has been open for a long time, and it is unlikely to be addressed soon so an argument was made by a commenter
https://code.google.com/p/go/issues/detail?id=1435#c45
That these functions should made to fail rather than succeed in their broken state.
LGTM=ruiu, iant
R=iant, ruiu
CC=golang-codereviews
https://golang.org/cl/106170043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/343b4ba8c1ad8a29b6dd19cb101273b57a26c9b0
元コミット内容
このコミットは、Goのsyscall
パッケージにおけるSetuid
およびSetgid
関数をLinuxプラットフォーム上で無効化することを目的としています。これは、GoのIssue 1435で長らく議論されてきた問題への対応です。この問題は、Linux上でのSetuid
およびSetgid
のシステムコールが、Goのマルチスレッドランタイムのコンテキストにおいて、プロセス全体ではなく、呼び出し元のスレッドのみにUID/GIDを変更するという予期せぬ挙動を示すことに起因します。この「壊れた状態」での成功を避けるため、これらの関数は常にエラー(EOPNOTSUPP
)を返すように変更されます。
変更の背景
Go言語のランタイムは、内部的に複数のOSスレッド(LWP: Light-Weight Process)を使用するM:Nスケジューリングモデルを採用しています。ここでMはGoのゴルーチン、NはOSスレッドを指します。Goのプログラムが起動すると、ランタイムは必要に応じて複数のOSスレッドを生成し、その上でゴルーチンをスケジューリングします。
Linuxにおけるsetuid(2)
やsetgid(2)
といったシステムコールは、呼び出し元のスレッドのユーザーID(UID)やグループID(GID)を変更します。しかし、Goのプログラムが複数のOSスレッドを使用している場合、これらのシステムコールを呼び出しても、その変更は呼び出しを行った特定のスレッドにのみ適用され、プロセス内の他のスレッドやプロセス全体には伝播しません。
この挙動は、多くのプログラマがsetuid
やsetgid
に期待する「プロセス全体の権限変更」とは大きく異なります。例えば、特権を一時的に放棄して非特権ユーザーとして操作を行いたい場合、プロセス全体が非特権ユーザーになることを期待しますが、実際には一部のスレッドのみが変更され、他のスレッドは依然として元の特権を保持してしまう可能性があります。これはセキュリティ上の脆弱性や、予期せぬ動作を引き起こす原因となります。
GoのIssue 1435では、この問題が長期間にわたって議論されてきました。根本的な解決策(例えば、Goランタイムがsetuid
やsetgid
を呼び出す際に、プロセス内の全スレッドに影響を与えるように調整する、あるいは、これらのシステムコールを呼び出す前に単一スレッドモードに切り替えるなど)は複雑であり、すぐに実装される見込みがありませんでした。
このような状況下で、コメントの一つ(https://code.google.com/p/go/issues/detail?id=1435#c45)で、「壊れた状態での成功」を許容するよりも、明示的にエラーを返す方が良いという提案がなされました。これにより、開発者はSetuid
やSetgid
が期待通りに動作しないことを認識し、代替手段を検討するよう促されます。このコミットは、この提案に基づき、Goのsyscall
パッケージからこれらの関数を実質的に削除し、常にエラーを返すようにすることで、誤用を防ぐことを目的としています。
前提知識の解説
UIDとGID
- UID (User ID): Linux/Unixシステムにおいて、ユーザーを一意に識別するための数値です。プロセスは特定のUIDの下で実行され、そのUIDに基づいてファイルやリソースへのアクセス権が決定されます。
- GID (Group ID): Linux/Unixシステムにおいて、グループを一意に識別するための数値です。ユーザーは一つ以上のグループに属することができ、グループのGIDに基づいてリソースへのアクセス権が決定されます。
setuid(2)
とsetgid(2)
システムコール
これらはLinux/Unixシステムコールであり、プロセスの実効ユーザーID(EUID)や実効グループID(EGID)を変更するために使用されます。
setuid(uid_t uid)
: プロセスの実効UID、保存UID、およびファイルシステムUIDを変更します。通常、特権を放棄するために使用されます。setgid(gid_t gid)
: プロセスの実効GID、保存GID、およびファイルシステムGIDを変更します。
これらのシステムコールは、通常、特権を持つプログラム(例えば、root権限で起動し、その後非特権ユーザーの権限に降格するデーモンなど)が、セキュリティ上の理由から不要な特権を放棄するために利用されます。
マルチスレッド環境とsetuid
/setgid
の挙動
Linuxでは、プロセス内の各スレッドは独立した実行コンテキストを持ちますが、UID/GIDなどのプロセスレベルの属性は通常、プロセス全体で共有されます。しかし、setuid(2)
やsetgid(2)
のようなシステムコールは、LinuxのNPTL (Native POSIX Thread Library) の実装において、呼び出し元のスレッドのコンテキストにのみ影響を与える場合があります。これは、特にGoのようなマルチスレッド(内部的には複数のLWPを使用)なランタイムにおいて、予期せぬ挙動を引き起こす原因となります。
Goのsyscall
パッケージ
Go言語の標準ライブラリの一部であり、OSのシステムコールを直接呼び出すための機能を提供します。これにより、Goプログラムは低レベルのOS機能にアクセスできます。syscall.Setuid
やsyscall.Setgid
は、このパッケージを通じて対応するシステムコールをGoから呼び出すためのラッパー関数です。
EOPNOTSUPP
EOPNOTSUPP
は、Unix系システムで定義されているエラーコードの一つで、「Operation not supported」(操作がサポートされていません)を意味します。このエラーを返すことで、呼び出し元に対して、その操作が現在の環境や設定では実行できないことを明示的に伝えます。
技術的詳細
このコミットの技術的な核心は、Goのsyscall
パッケージがLinux上でSetuid
およびSetgid
関数をどのように扱うかを変更することにあります。
変更前は、src/pkg/syscall/syscall_linux.go
内の//sysnb Setuid(uid int) (err error)
や//sysnb Setgid(gid int) (err error)
といったディレクティブによって、Goのツールチェーンが対応するシステムコールラッパー関数を自動生成していました。これらの自動生成された関数は、RawSyscall
(またはSyscall
)を介して実際のLinuxシステムコールSYS_SETUID
やSYS_SETGID32
(32-bitシステムの場合)を呼び出していました。
しかし、前述の通り、これらのシステムコールはGoのマルチスレッドランタイムのコンテキストでは期待通りの「プロセス全体の権限変更」を行いません。そのため、このコミットでは以下の変更が行われました。
-
自動生成の停止:
src/pkg/syscall/syscall_linux.go
から//sysnb Setuid(uid int) (err error)
と//sysnb Setgid(uid int) (err error)
の行が削除されました。これにより、Goのツールチェーンはこれらの関数のシステムコールラッパーを自動生成しなくなります。 -
手動実装によるエラー返却: 代わりに、
src/pkg/syscall/syscall_linux.go
内にSetuid
とSetgid
関数が手動で実装されました。これらの実装は、引数を受け取りますが、実際のシステムコールを呼び出す代わりに、常にEOPNOTSUPP
エラーを返します。func Setuid(uid int) (err error) { return EOPNOTSUPP } func Setgid(uid int) (err error) { return EOPNOTSUPP }
この変更により、GoプログラムがLinux上で
syscall.Setuid
やsyscall.Setgid
を呼び出そうとすると、常に「操作がサポートされていません」というエラーが返されるようになります。これは、これらの関数がGoのマルチスレッドモデルにおいて安全かつ期待通りに動作しないことを開発者に明示的に伝えるための措置です。 -
アーキテクチャ固有のファイルの変更:
src/pkg/syscall/syscall_linux_386.go
,src/pkg/syscall/syscall_linux_amd64.go
,src/pkg/syscall/syscall_linux_arm.go
といったアーキテクチャ固有のファイルからも、Setgid
のシステムコール定義が削除されました。これは、Setuid
と同様に、これらの関数がもはや直接システムコールを呼び出さないためです。 -
自動生成されたファイルのクリーンアップ:
src/pkg/syscall/zsyscall_linux_386.go
,src/pkg/syscall/zsyscall_linux_amd64.go
,src/pkg/syscall/zsyscall_linux_arm.go
といった、システムコールラッパーが自動生成されるファイルから、既存のSetuid
およびSetgid
関数の定義が削除されました。これらのファイルは、Goのビルドプロセス中にmksyscall.pl
スクリプトによって生成されるため、ソースコードの変更と同期してクリーンアップされる必要があります。
この変更は、Goの設計哲学である「明示的なエラーハンドリング」と「予期せぬ挙動の回避」に沿ったものです。開発者がこれらの関数を誤って使用し、セキュリティ上の問題やバグを引き起こす可能性を低減します。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイルに集中しています。
src/pkg/syscall/syscall_linux.go
:Setuid
とSetgid
関数の手動実装が追加され、常にEOPNOTSUPP
を返すように変更されました。また、これらの関数の自動生成ディレクティブが削除されました。src/pkg/syscall/syscall_linux_386.go
,src/pkg/syscall/syscall_linux_amd64.go
,src/pkg/syscall/syscall_linux_arm.go
: 各アーキテクチャ固有のファイルから、Setgid
のシステムコール定義が削除されました。src/pkg/syscall/zsyscall_linux_386.go
,src/pkg/syscall/zsyscall_linux_amd64.go
,src/pkg/syscall/zsyscall_linux_arm.go
: 自動生成されたシステムコールラッパーファイルから、Setuid
とSetgid
の既存の定義が削除されました。
src/pkg/syscall/syscall_linux.go
の変更点
--- a/src/pkg/syscall/syscall_linux.go
+++ b/src/pkg/syscall/syscall_linux.go
@@ -807,7 +807,20 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
//sysnb Setpgid(pid int, pgid int) (err error)
//sysnb Setsid() (pid int, err error)
//sysnb Settimeofday(tv *Timeval) (err error)
-//sysnb Setuid(uid int) (err error)
+
+// issue 1435.
+// On linux Setuid and Setgid only affects the current thread, not the process.
+// This does not match what most callers expect so we must return an error
+// here rather than letting the caller think that the call succeeded.
+
+func Setuid(uid int) (err error) {
+ return EOPNOTSUPP
+}
+
+func Setgid(uid int) (err error) {
+ return EOPNOTSUPP
+}
+
//sys Setpriority(which int, who int, prio int) (err error)
//sys Setxattr(path string, attr string, data []byte, flags int) (err error)
//sys Symlink(oldpath string, newpath string) (err error)
src/pkg/syscall/syscall_linux_386.go
の変更点
--- a/src/pkg/syscall/syscall_linux_386.go
+++ b/src/pkg/syscall/syscall_linux_386.go
@@ -47,7 +47,6 @@ func NsecToTimeval(nsec int64) (tv Timeval) {
//sys sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) = SYS_SENDFILE64
//sys Setfsgid(gid int) (err error) = SYS_SETFSGID32
//sys Setfsuid(uid int) (err error) = SYS_SETFSUID32
-//sysnb Setgid(gid int) (err error) = SYS_SETGID32
//sysnb Setregid(rgid int, egid int) (err error) = SYS_SETREGID32
//sysnb Setresgid(rgid int, egid int, sgid int) (err error) = SYS_SETRESGID32
//sysnb Setresuid(ruid int, euid int, suid int) (err error) = SYS_SETRESUID32
同様の変更がsyscall_linux_amd64.go
とsyscall_linux_arm.go
にも適用されています。
src/pkg/syscall/zsyscall_linux_386.go
の変更点
--- a/src/pkg/syscall/zsyscall_linux_386.go
+++ b/src/pkg/syscall/zsyscall_linux_386.go
@@ -1005,16 +1005,6 @@ func Settimeofday(tv *Timeval) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func Setuid(uid int) (err error) {
- _, _, e1 := RawSyscall(SYS_SETUID, uintptr(uid), 0, 0)
- if e1 != 0 {
- err = e1
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func Setpriority(which int, who int, prio int) (err error) {
_, _, e1 := Syscall(SYS_SETPRIORITY, uintptr(which), uintptr(who), uintptr(prio))
if e1 != 0 {
@@ -1553,16 +1543,6 @@ func Setfsuid(uid int) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func Setgid(gid int) (err error) {
- _, _, e1 := RawSyscall(SYS_SETGID32, uintptr(gid), 0, 0)
- if e1 != 0 {
- err = e1
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func Setregid(rgid int, egid int) (err error) {
_, _, e1 := RawSyscall(SYS_SETREGID32, uintptr(rgid), uintptr(egid), 0)
if e1 != 0 {
同様の変更がzsyscall_linux_amd64.go
とzsyscall_linux_arm.go
にも適用されています。
コアとなるコードの解説
このコミットの最も重要な変更は、src/pkg/syscall/syscall_linux.go
におけるSetuid
とSetgid
関数の実装です。
変更前は、これらの関数はGoのビルドシステムによって自動生成され、Linuxカーネルの対応するシステムコール(setuid(2)
やsetgid(2)
)を直接呼び出していました。しかし、Goのランタイムが複数のOSスレッドを使用する性質上、これらのシステムコールは呼び出し元のスレッドの権限のみを変更し、プロセス全体の権限には影響を与えませんでした。これは、多くの開発者が期待する「プロセス全体の権限降格」とは異なる挙動であり、誤解やセキュリティ上の問題を引き起こす可能性がありました。
このコミットでは、この問題を解決するために、Setuid
とSetgid
の自動生成を停止し、手動でこれらの関数を実装しました。手動実装された関数は、以下のように非常にシンプルです。
func Setuid(uid int) (err error) {
return EOPNOTSUPP
}
func Setgid(uid int) (err error) {
return EOPNOTSUPP
}
このコードは、引数としてuid
(またはgid
)を受け取りますが、実際のシステムコールを呼び出すことはせず、常にEOPNOTSUPP
というエラーを返します。EOPNOTSUPP
は「Operation not supported」(操作がサポートされていません)を意味するエラーコードです。
この変更の意図は以下の通りです。
- 誤解の防止: 開発者が
syscall.Setuid
やsyscall.Setgid
を呼び出した際に、それがプロセス全体の権限を変更すると誤解するのを防ぎます。明示的にエラーを返すことで、これらの関数がGoのLinux環境では期待通りに動作しないことを伝えます。 - 安全性の向上: 予期せぬ部分的な権限変更によるセキュリティ上の脆弱性や、デバッグが困難なバグの発生を防ぎます。
- 代替手段の促進: 開発者は、これらの関数が利用できないことを知ることで、
Setuid
やSetgid
に依存しない、より堅牢な権限管理メカニズム(例えば、プログラムを非特権ユーザーとして起動する、sudo
などの外部ツールを利用する、あるいはGoのプロセスモデルに合わせた別の方法を検討する)を検討するよう促されます。
この変更は、GoのランタイムがLinux上でどのように動作するかという深い理解に基づいています。GoのマルチスレッドモデルとLinuxのシステムコールの挙動の間の不一致を、ユーザーフレンドリーな方法で解決しようとする試みと言えます。
関連リンク
- Go Issue 1435: https://github.com/golang/go/issues/1435
- Go Code Review 106170043: https://golang.org/cl/106170043
参考にした情報源リンク
- Go Issue 1435のコメント: https://code.google.com/p/go/issues/detail?id=1435#c45
- Stack Overflow: Go issue 1435, setuid/setgid on Linux: https://stackoverflow.com/questions/24400000/go-issue-1435-setuid-setgid-on-linux
- Linux man pages:
setuid(2)
,setgid(2)
- Go
syscall
package documentation (当時のバージョンに基づく) - Go runtime internals (マルチスレッドモデルに関する一般的な知識)