Go Proposal: Secret Mode

2025/12/10 6:10

Go Proposal: Secret Mode

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Go 1.26 では、関数が終了した際に自動的にメモリを消去し、暗号学的シークレットが RAM に残るのを防ぐ実験的ランタイムパッケージ

secret
を導入しました。
secret.Do
でコードをラップすると、その関数がパニックしたり
runtime.Goexit
で終了しても、CPU レジスタ、スタックフレーム、ヒープ割り当てをクリアします。この機能は AMD64 と ARM64 アーキテクチャの Linux のみで利用可能で、ビルドフラグ
GOEXPERIMENT=runtimesecret
を有効にする必要があります。

secret.Do
は通常終了でもパニックしても関数から戻る前にレジスタとスタックを消去しますが、ヒープ割り当てはガベージコレクタが参照を削除し実際に走った後でのみクリアされるため、タイミングは非決定的です。ラップされた関数によって書き込まれたグローバル変数は保護されず、
f
内でゴルーチンを起動するとパニックします。

この実験的パッケージは、暗号ライブラリがフォワードシークレシーを自動化し、メモリダンプによるキー漏洩を減らす手段を提供しますが、プラットフォームサポートの限定性と GC の帳簿情報漏れの可能性により、万能な解決策ではありません。将来の Go リリースでプラットフォーム対応や消去タイミングの調整が拡張される可能性があります

本文

Accepted! シリーズの一部:Go の今後の変更点を簡潔に解説

使用済みメモリを自動で消去して秘密情報漏洩を防止
Ver. 1.26 • Stdlib • 低影響


要約

新しい

runtime/secret
パッケージは、関数を「シークレットモード」で実行できます。関数が終了すると、その関数で使用したレジスタとスタックを直ちに消去(ゼロ化)します。ヒープへの割り当ては、ガベージコレクタが到達不能になった時点で即座に消去されます。

secret.Do(func() {
    // セッションキーを生成し
    // それを使ってデータを暗号化する。
})

これにより、機密情報が必要以上にメモリ上に残るのを防ぎ、攻撃者が取得できるリスクを低減します。パッケージは実験的であり、主に暗号ライブラリ開発者向けです(アプリ開発者ではなく)。


背景

WireGuard や TLS などの暗号プロトコルには フォワードシークレシー が求められます。攻撃者が長期秘密(例:TLS のプライベートキー)を入手しても、過去の通信を解読できないようにするためです。そのためには、使用後にセッションキーをメモリから消去しなければなりません。Go ではランタイムがメモリを管理し、いつ・どのようにクリアされるかは保証されていません。ヒープ割り当てやスタックフレームに残った機密データが、コアダンプやメモリアタックで露出する恐れがあります。開発者はリフレクションを使った不安定な「ハック」で内部バッファをゼロ化することが多いですが、それでも一部のデータは制御外に残ります。

解決策として、機密操作中に使用されるすべての一時領域を自動で消去するランタイムメカニズムを提供します。これによりライブラリ開発者がワークアラウンドを書く手間が省けます。


仕様

runtime/secret
パッケージに
Do
Enabled
を追加:

// Do は f を呼び出す。
//
// Do は、f が使用した一時領域を適切なタイミングで消去します。
// この文脈では「f」は f によって開始された全ての呼び出し木を指します。
//   • f が使ったレジスタは Do が返る前に消去される。  
//   • f の使用したスタックは Do が返る前に消去される。  
//   • f によって行われたヒープ割り当ては、ガベージコレクタが到達不能と判断次第即座に消去される。  
//   • f がパニックしたり runtime.Goexit を呼んだ場合でも Do は機能し、  
//     パニックは実際には Do から発生したように見える。  
func Do(f func())

// Enabled は、Do が現在のコールスタック上に存在するかを報告する。
func Enabled() bool

現在の制限点

  • linux/amd64
    linux/arm64
    でのみサポート。その他では
    Do
    はそのまま
    f
    を呼び出す。
  • グローバル変数への書き込みは保護対象外。
  • f
    内でゴルーチンを起動するとパニックになる。
  • runtime.Goexit
    を呼ぶと、消去は全ての defer が実行されるまで遅延する。
  • ヒープ割り当ては
    1. プログラムが参照を落とした瞬間に、
    2. ガベージコレクタがそのことを検知した時点で消去される。
      前者はプログラム側制御、後者はランタイムのタイミングに依存。
  • f
    がパニックすると、そのパニック値が
    f
    内で割り当てられたメモリを参照している場合、
    そのメモリはパニック値が到達不能になるまで消去されない。
  • ポインタアドレスは GC が内部で保持するデータバッファに漏れる可能性があるため、機密情報をポインタに渡さないこと。
    例: 配列
    data
    の 100 番目の要素が秘密なら、
    &data[100]
    を作らないでください。GC がそのポインタを保持するとオフセットが漏洩します。

パッケージは主に暗号ライブラリ開発者向けです。ほとんどのアプリは

secret.Do
を内部で使用する高レベルライブラリを利用すべきです。
Go 1.26 以降、
runtime/secret
は実験的で、ビルド時に
GOEXPERIMENT=runtimesecret
を設定して有効化します。


AES‑GCM でメッセージを暗号化し、セッションキーと内部 AES ステートを確実に消去する方法:

// Encrypt は一時的な鍵を生成し、メッセージを暗号化する。
// secret.Do に機密操作全体を包むことで、  
// 鍵と AES の内部構造がメモリから消えます。
func Encrypt(message []byte) ([]byte, error) {
    var ciphertext []byte
    var encErr error

    secret.Do(func() {
        // 1. 一時的な 32 バイト鍵を生成。  
        key := make([]byte, 32)
        if _, err := io.ReadFull(rand.Reader, key); err != nil {
            encErr = err
            return
        }

        // 2. キーを展開してブロック暗号を作成。  
        block, err := aes.NewCipher(key)
        if err != nil {
            encErr = err
            return
        }

        gcm, err := cipher.NewGCM(block)
        if err != nil {
            encErr = err
            return
        }

        nonce := make([]byte, gcm.NonceSize())
        if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
            encErr = err
            return
        }

        // 3. データを暗号化。  
        // このクロージャから出るのは ciphertext のみ。
        ciphertext = gcm.Seal(nonce, nonce, message, nil)
    })

    return ciphertext, encErr
}

secret.Do
は生鍵だけでなく、関数内で生成される
cipher.Block
構造体(拡張キー表を保持)も保護します。
これは簡易例です。実際の暗号交換では、鍵は安全に受信者へ渡す必要があります。


リンク & クレジット

  • 作者:Dave Anderson, Filippo Valsorda, Jason A. Donenfeld, Russ Cox
  • 参考文献:21865, 704615, Daniel Morsing, Keith Randall

★ 新規投稿を追いかけるには ★


同じ日のほかのニュース

一覧に戻る →

2025/12/14 7:58

Linux Sandboxes and Fil-C

## Japanese Translation: メモリ安全性とサンドボックスはプログラムの異なる部分を保護するため、両方が強力なセキュリティに必要です。純粋な Java プログラムはメモリ安全であってもファイルシステムの syscalls を通じて任意のファイルを書き込むことができるし、逆にすべての能力を取り消したアセンブリプログラムでもメモリバグがある場合がありますが、カーネルが特権 syscalls を殺すためサンドボックスから逃げられません。サンドボックスは意図的に許容範囲を広く設計しているため、攻撃者は残されたメモリ安全性のバグを利用してブローカー・プロセスへ到達することができるので、両方の防御を組み合わせるとより強固な保護が得られます。 本書では、C/C++ 用に設計され、システムコールまで安全性を保証し、init や udevd などの低レベルコンポーネントで使用できるメモリ安全ランタイム「Fil‑C」への OpenSSH の seccomp ベース Linux サンドボックス移植方法について説明します。OpenSSH は既に chroot を採用し、`sshd` ユーザー/グループとして特権なしで実行し、`setrlimit` を使用し、非許可 syscalls を `SECCOMP_RET_KILL_PROCESS` で殺す seccomp‑BPF フィルタを適用しています。Fil‑C はその runtime 内で自動的にこれらの syscalls を許可することで簡素化します。背景スレッドは存続させつつスレッド生成を防ぐため、Fil‑C は API `void zlock_runtime_threads(void)` を追加し、必要なスレッドを事前確保してシャットダウンを無効にします。 OpenSSH の seccomp フィルタは強化されています。失敗時の挙動が `SECCOMP_RET_KILL` から `SECCOMP_RET_KILL_PROCESS` に変更され、mmap 許可リストに新たに `MAP_NORESERVE` フラグが追加され、`sched_yield` が許可されています。サンドボックスは二つの `prctl` コール(`PR_SET_NO_NEW_PRIVS` と `PR_SET_SECCOMP`)で構築され、エラー検出も行われます。Fil‑C のランタイムは `filc_runtime_threads_handshake` で全スレッドとハンドシェイクし、各スレッドが no_new_privs ビットと seccomp フィルタを持つことを保証します。複数のユーザー スレッドが検出された場合、安全エラーが発生します。 メモリ安全性とサンドボックスを組み合わせることで、OpenSSH はより厳格な隔離を実現し、メモリバグによる権限昇格リスクを低減します。このアプローチは他のセキュリティクリティカルプロジェクトにも採用を促す可能性があります。

2025/12/14 9:34

An Implementation of J

## Japanese Translation: ## 改訂版要約 本書は、技術仕様の構造化された目次であり、以下のように整理されています。 1. **第0章 – はじめに** 2. **第1章 – 文を解釈する** - 1.1 単語生成 - 1.2 構文解析 - 1.3 トレイン(列車) - 1.4 名前解決 3. **第2章 – 名詞** - 2.1 配列 - 2.2 型 - 2.3 メモリ管理 - 2.4 グローバル変数 4. **第3章 – 動詞** - 3.1 動詞の構造 - 3.2 ランク - 3.3 原子(スカラー)動詞 - 3.4 オブヴァース、同一性、および変種 - 3.5 エラー処理 5. **第4章 – 副詞と接続詞** 6. **第5章 – 表現** - 5.1 原子表現 - 5.2 ボックス化された表現 - 5.3 木構造表現 - 5.4 線形表現 7. **第6章 – ディスプレイ** - 6.1 数値表示 - 6.2 ボックス化表示 - 6.3 フォーマット済み表示 主要セクションの後に、付録A〜F(インキュナブルム、スペシャルコード、テストスクリプト、プログラムファイル、外国接続詞、およびシステム概要)が補足資料として提供されます。書末には参考文献・用語集・索引が付されています。 この構成(目次 → 詳細セクション → 付録 → 参照資料)は、読者に全体枠組みを最初に把握させたうえで、必要に応じて詳細へ掘り下げたり補足資料を参照したりできる明確かつ階層的な道筋を提供します。

2025/12/14 8:39

Closures as Win32 Window Procedures

## Japanese Translation: **改訂版要約:** この記事では、Win32 のウィンドウプロシージャに追加のコンテキストポインタを渡す方法を示しています。これは、WndProc が通常 4 つしか引数を取らないため、ネイティブ API には備わっていない機能です。著者は x64 アセンブラで小さなトランスペイル(trampoline)を作成し、実行時に JIT コンパイルして 5 番目の引数スロットを挿入し、呼び出し前に必要なコンテキストを格納します。これにより、各ウィンドウがグローバル変数や `GWLP_USERDATA` を使わずに独自の状態を保持できるようになります。トランスペイルは GNU アセンブラで書かれ、`.exebuf` セクション(`bwx` フラグ付き)から 2 MiB の実行可能バッファが確保されます。C ヘルパー関数 `make_wndproc(Arena *, Wndproc5, void *arg)` は 2 つのバイトオフセットプレースホルダーを修正してトランスペイルを生成します。作成後は `set_wndproc_arg(WNDPROC p, void *arg)` を使ってコンテキストを変更できます。アロケータ例では、異なる状態オブジェクト用に複数のトランスペイルを生成したり、動的に切り替えたりする方法を示しています。この手法は、トランスペイルがアンウインドテーブルを持たないため Windows Control Flow Guard 下でも安全に機能し、グローバル変数を使わずにウィンドウごとのデータを付与する低レベルの手段を示しています。