メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

挙動変更実験的

runtime/secretパッケージのsecretモードが、Do配下で起動したgoroutineにも自動的に引き継がれるようになった

runtime/secret

この項目の注釈は AI により生成されており、誤りを含む場合があります。
使用例のコンパイル検証: 検証済み

概要

runtime/secretパッケージ(GOEXPERIMENT=runtimesecretでのみ有効な実験的パッケージ)が提供するsecret.Doは、渡した関数の呼び出しツリーで使われるスタック・レジスタ・ヒープ確保領域を、実行後にできるだけ速やかに消去する仕組みである。この変更により、Doに渡した関数の実行中にgo文で新たに起動されたgoroutineも、起動元と同じくsecretモードで実行されるようになった。これまではDo配下で起動したgoroutine自身はsecretモードを引き継がず、保護を続けたい場合は起動先でも改めてsecret.Doに包む必要があった。

使用例

Before

package main

import (
	"fmt"
	"runtime/secret"
)

func handleRequest(key []byte) {
	secret.Do(func() {
		result := process(key)

		// secretモードは自動的には引き継がれないため、
		// 起動するgoroutine側でも改めてDoに包む必要があった。
		go secret.Do(func() {
			logResult(result)
		})
	})
}

func process(key []byte) []byte { return key }
func logResult(b []byte)        {}

func main() {
	handleRequest([]byte("s3cr3t"))
	fmt.Println("done")
}

After

package main

import (
	"fmt"
	"runtime/secret"
)

func handleRequest(key []byte) {
	secret.Do(func() {
		result := process(key)

		// Do配下で起動したgoroutineは自動的にsecretモードを
		// 引き継ぐため、起動先で改めてDoに包む必要がない。
		go func() {
			logResult(result)
		}()
	})
}

func process(key []byte) []byte { return key }
func logResult(b []byte)        {}

func main() {
	handleRequest([]byte("s3cr3t"))
	fmt.Println("done")
}

移行時の注意

secret.Do配下で起動した既存のgo secret.Do(...)のような二重の呼び出しは、ネストしたsecretモードとして扱われるため引き続き動作する(壊れない)が、以後は冗長になる。また、secretモードは起動されたgoroutineすべてに伝播するため、Do配下で起動する非同期処理が増えるほど、スタック消去などのコストが波及する範囲も広がる点に注意が必要である。なお本パッケージはドキュメントに記載の通り現状linux/amd64linux/arm64のみサポートしており、それ以外の環境ではDofを直接呼び出すだけで保護は行われない。

実装解説

goroutineへのsecretモードの伝播は、go文でgoroutineを生成するruntime.newproc1に実装されている。生成元のgoroutine(callergp)のsecretカウンタが0より大きい場合、新しいgoroutine(newg)のsecretフィールドを1にセットすることで、新goroutineが最初からsecretモードに入った状態でスケジュールされる。

goroutineがsecretモードのままruntime.Goexitで終了する場合は、通常のDoの後処理(スタック消去)を経由しないため、runtime.goexit0gp.secret > 0を検知してスタック全体を明示的に消去し、その後gdestroygp.secretを0にリセットしてgoroutine構造体を再利用可能な状態に戻している(該当コード)。

secret.Do自身が使うcount/inc/dec/eraseSecretsgo:linknameruntimeパッケージの実装(getg().secretを読み書きする関数群)に直結しており、これらはruntime/secret.goに定義されている。

関連リンク