世界を動かす技術を、日本語で。

メモリセーフなコンテキストスイッチング

2026年6月30日原文(fil-c.org)

概要

  • Fil-Cucontext APIsetjmp/longjmp を安全にサポート
  • スタック破壊や権限モデル違反が Fil-C では発生しない設計
  • jmp_buf は不正アクセス不可な zjmp_buf で管理
  • setjmp/longjmp の危険な挙動を Fil-C がコンパイル時・ランタイムで検出
  • メモリ安全性を維持しつつ、例外処理やコルーチン用途を実現

Fil-Cにおけるucontext APIとsetjmp/longjmpの安全なサポート

  • Fil-Csetcontextgetcontextmakecontextswapcontext (ucontext API)を メモリ安全 にサポート

    • これらAPIは 例外処理コルーチンファイバー の実装に利用
    • longjmp/setjmp はC言語の例外処理やシグナルハンドラからの例外送出に頻用
    • ucontext API はBoostなどでファイバー実装に使用
    • 一部OS(Darwin等)では非推奨だが、 glibc ではサポート継続
  • API誤用時 の危険性

    • setjmp/getcontext 呼び出し後、関数からreturnやスレッド終了で 保存済みスタックフレームが消失
    • makecontext で作成したスタックを解放後に swapcontext/setcontext で実行→ ダングリングスタック実行
    • swapcontext の引数を逆に指定し、現在実行中のコンテキストへ切り替え→ 未定義動作やクラッシュ
  • Fil-C の安全設計

    • 誤用時は panic を発生させ、 ダングリングスタック上での実行不可
    • スタック管理を言語ランタイムが制御し、不正なジャンプや復元時に 安全性を保証
    • Yolo-C等の従来C処理系では スタック破壊・クラッシュ・脆弱性の発生 が容易

setjmp/longjmpの危険性とFil-Cによる安全化

  • setjmp は呼び出し時の レジスタ・スタックポインタ・命令ポインタ 等を保存

    • longjmp で復元し、 setjmp が2回目のreturnを行う
    • 保存したフレームが消失 している場合、 スタック破壊未定義動作
  • volatile修飾子 の必要性

    • setjmp/longjmp 経由で変数値が期待通り動作するには、対象変数を volatile で宣言
    • 最適化やスピルスロット再利用による 値の不整合 を防止
  • コンパイラの対応

    • setjmpを検出し、 spill slot再利用禁止returns_twice属性 で最適化制御
    • setjmp呼び出しを間接参照等で隠蔽すると、 spill slot再利用によるバグや未定義動作 発生
    • Fil-Cはこのパターンを コンパイルエラーやpanic で排除

Fil-Cのjmp_bufとzjmp_bufによる安全設計

  • jmp_bufzjmp_buf という ランタイム管理の不透明オブジェクト へのポインタのみを保持

    • Fil-Cコードからzjmp_bufの中身へアクセス不可
    • longjmp時は zjmp_bufの有効性検証 を実施し、不正な復元は panic
  • setjmp/longjmpの呼び出し制約

    • setjmpシンボルは 直接呼び出しのみ許可、間接参照や関数ポインタ経由は ICE(内部コンパイラエラー)
    • 将来的には 詳細な診断メッセージ で警告予定
  • zjmp_bufのライフサイクル管理

    • setjmp呼び出し時に zjmp_bufの新規生成とスタックフレームへの登録
    • 各スタックフレームは 有効なzjmp_buf集合 を管理
    • ジャンプ先の検証 により、 不正なフレーム復元を排除

まとめ:Fil-Cによるメモリ安全な例外・コンテキスト制御

  • Fil-CC言語の危険な例外制御API (setjmp/longjmp、ucontext)を 安全にラップ
  • スタック・レジスタ・コンテキストの 一貫した管理 により、 未定義動作・脆弱性を根絶
  • 誤用時はpanic で即時検出、 安全な例外処理・コルーチン実装 を実現
  • 従来C処理系で発生する難解なバグや攻撃リスクFil-C が完全に回避

Hackerたちの意見

Fil-Cは今Claudeを使って開発してるの?

Claude.mdは8ヶ月前に追加されたよ。 https://github.com/pizlonator/fil-c/blob/deluge/CLAUDE.md

Claudeはテストを書いただけで、Kimi K2.6コードとGLM 5.2を使ったけど、確認しやすいものだけね。longjmp/ucontextの作業にはLLMは使ってないよ。

特に不満はないけど、setjmp/longjmpを使うコードはメモリ安全性だけじゃなくてリスクが大きいことが多いよね。もしそれを使わざるを得ないなら、できるだけ対策を講じてね。

どんな悪用を想像してるの?メモリ安全の問題じゃないやつ?Fil-Cはそういうのも防いでるかも。結構厳しいから、longjmpは例外みたいにスタックをポップするためだけに使えるよ。

longjmp、setjmp、setcontext、getcontext、makecontext、swapcontextとかは安全性には関係ないよ。対処しなきゃいけないのはsigaction(2)で表されるもので、コンテキストスイッチを行うために使うもの、IOでもプリエンプティブでも、かなり後のことだよ。

記事には、通常setjmpを呼び出した同じ関数内(またはその子孫の関数内)でlongjmpをしないと、スタックがクリアされてゴミスタックにlongjmpしちゃうって書いてあった。これってメモリ安全性に関わると思うんだけど?sigactionについてのコメントはちょっと理解できてないから、何か見落としてるかも。追記:追加のコンテキスト- https://usenix.org/legacy/publications/library/proceedings/u...

これらの関数はメモリを壊すのに簡単に悪用されるから、安全性に関わることが多いんだよね。Fil-Cは、これらの関数を使ってメモリが壊れたり、能力モデルに違反したりしないようにめっちゃ気を使ってる。Fil-Cはsigactionもメモリ安全にしてるし、その保護のおかげでシグナルハンドラがlongjmpやsetcontext、swapcontextを使えるようになってる。

面白いね!setjmpとlongjmpはFil-Cとは相性が悪いと思ってたし、ucontextについては全然聞いたことがなかった。スタックの管理は結局メモリの管理だし、普段はそう考えないけど、Fil-Cにはここで何か付け加えることがあるんだね。setjmp/longjmpの複雑さやレジスタの割り当て、スタックのスピリングとの相互作用についてのセクションは本当に読む価値があるよ。難しいって知ってたけど、具体的な話に入ると面白いね。

例えば、Boostはそのファイバー実装の一部としてucontextを使ってるよ。もしかしたら、すごく遅いフォールバックのためにね。Boostのコンテキストとファイバーは、x86_64やARM/ARM64の*nix / MacOS / Windows向けにABIサポートがあるんだ。このサポートを使ったファイバー切り替えのオーバーヘッドは、仮想関数呼び出しと同じくらい重いよ。対照的に、ucontextはすごく重い。自分でC用のファイバーライブラリを書いたんだ。setjmpとlongjmpを使った古い実装を見て、もっと効率的に安全にできる方法を探ることになったんだ。Boostの例に従うことにして、実際に彼らのファイバー切り替えアセンブラの一部を自分のライブラリで使ったよ。

比較すると、ucontextはすごく重い。シグナルマスクを切り替えるから重いんだよね。実際、Fil-Cのucontextロジックは今これをやってる。glibcに依存してるから、glibcがそうするんだ。でも、Fil-Cの内部のzfiber_context APIにsigmasksを保存しないように教えるのは簡単だよ。setcontext/swapcontextのために別のバックエンドを使うだけだから。これをやってるオープンソースプロジェクト(Boostを含む)がいくつもあるから、設定するのは簡単だと思う。でも、今は少しずつ進めてる。最初のステップは、これらの危険なAPIの周りにメモリ安全なラッパーを提供することだね。次のステップは、壊そうとするためにもっとテストを書くことかな。その後で、Boost(や他のほとんどのもの)が欲しがってるsigmaskなしのバージョンを公開するために、代替バックエンドを追加することを考えるよ。

これは数ヶ月前に読んでおきたかった記事だな。 > だから、setjmpに関する最も基本的な安全性の問題は、呼び出してからその関数から戻ると、setjmpによって保存されたコンテキストがlongjmpに対して無効になることだ。 > longjmpは、setjmpによって使われたスタックフレームが上書きされていない時に呼び出される場合にのみ安全だよ。これが、longjmpによって復元されるレジスタの状態がスタックポインタが指すスタックフレームと一致することを保証する唯一の方法だから。この制限は、longjmpの前にスタックフレームをどこかにコピーしておいて、現在のスタックの上にその全体をスピリングすることで解除できるんだ。これが区切られた継続の仕組みだよ!Cにとってこれが厄介なのはポインタの存在だね。スタックは自由に再配置できないから、スタック内のポインタが存在する可能性がある。ほかの言語はこの問題がないんだ。この記事には面白いことがたくさんあるよ!「ucontextを使ったファイバー」、つまりスタックポインタを行き来させることで、僕はジェネレーターを実装したんだ!setjmpを理解するためにmuslのソースコードにも手を伸ばしたけど、理由は違うんだ:レジスタをスタックにスピリングする能力が、僕のガベージコレクターにとって重要だったから。興味がある人のために、これらのことについてもブログに書いたよ: https://www.matheusmoreira.com/articles/delimited-continuati... https://www.matheusmoreira.com/articles/generators-in-lone-l... https://www.matheusmoreira.com/articles/babys-second-garbage...

Hacker Newsで議論の続きを見る