HN 日本語サマリー

← 一覧へ戻る
AI・機械学習

Linuxカーネルが$ORIGINをサポート(ある程度)

Linux kernel will support $ORIGIN, sort of (fzakaria.com)

59 pointsby ingve35 コメント

要約

Nixエコシステムにおけるリロケータブルバイナリの実現を目指し、Linuxカーネルに$ORIGIN(実行ファイルと同じディレクトリを指す特殊変数)のサポートを追加する取り組みが行われました。当初はVFSサブシステムへの直接サポートを提案しましたが、eBPFとbinfmt_miscを活用するアプローチが採用され、カーネルの次期バージョンにパッチがマージされる見込みです。これにより、動的リンカの選択をより柔軟に行えるようになり、NixOSでの活用が期待されています。

全文翻訳

何らかの理由で、TacoSprint 2026の間に、Nixにおけるリロケータブルバイナリに取り組めるかどうかを確認することにしました。私は野心的な目標を掲げてNixとその周辺エコシステムを前進させることを楽しんでいます。私は愚かではないにしても大胆です。前回の投稿では、そこに至るための1つの潜在的なアイデアを残しました。それは、カーネルをパッチして、$ORIGINがPT_INTERPとシェバンでサポートされるようにすることです。私は電子メールでパッチを送信する複雑さを乗り越え、Linuxカーネルメーリングリストに提案を送りました。私の最初の試みは、仮想ファイルシステム(VFS)サブシステムで$ORIGINの直接サポートを追加することを提案しました。私は神経質に待ちました。オンラインで読んだことから結果を予想していました。誰かが、私が何かを見落とした、誤解した、または考慮しなかったことがあるため、非礼に「F$#CK OFF」と私に言うだろうと。🤬 結果は全く異なりました。😲 VFSのメンテナーであるChristian Braunerは、誠意をもって私に応答し、変更の根拠を尋ね、最終的にそのようなサポートがサブシステムに組み込まれる可能性のある方法をいくつか提案しました。注:John Ericsonのような人が参加し、固定されていないインタープリタ(PT_INTERP)を持つことがNixやその他のユースケース(例: Buck & Bazel)にどのように役立つかを擁護してくれたことは間違いなく役立ちました。彼は、binfmt_miscを通じてインタープリタを選択するためのプログラム可能な方法としてeBPFを活用できる可能性を示唆しました。すごい!🤯 私は単に$ORIGINを許可したかっただけですが、プログラム可能な選択により何でもできるようになります!このアイデアは彼を本当に魅了したようで、すぐに、休暇中に、Christianはそのようなソリューションの最初のドラフトを提案しました。私たちはメーリングリストで少しやり取りし、最終結果は近いうちに-nextブランチに搭載されるパッチシリーズです。eBPFやbinfmt_miscを知らない場合、私たちは一体何を協力していたのでしょうか?見てみましょう!私はeBPFを十分に説明することはできませんし、現在非常に流行しているため、オンラインには多くの記事があります。要約:カーネル内で実行されている仮想マシン用のコンパイル済みCサブセットでプログラムを作成できます。カーネルは非常に高速であるべきではありませんか?はい、プログラムはネイティブCPUアーキテクチャにJITコンパイルされ、プログラムには固定タイムスライスがあります。これはカーネルにとってクレイジーな脆弱性ではありませんか?コードがロードされる前に、安全であることが「検証」されます。詳細については、このガイドをご覧ください。比較的簡単なeBPFプログラムで$ORIGINをサポートできるようになりました: ```c SEC("struct_ops.s/match") bool BPF_PROG(nix_match, struct linux_binprm *bprm) { return !bpf_strncmp(bprm->buf, 4, "\x7f" "ELF"); } SEC("struct_ops.s/load") int BPF_PROG(nix_load, struct linux_binprm *bprm) { char path[256]; long n; n = bpf_path_d_path(&bprm->file->f_path, path, sizeof(path)); if (n < 0) return n; /* derive the loader location from the binary's path */ return bpf_binprm_set_interp(bprm, path, sizeof(path)); } SEC(".struct_ops.link") struct binfmt_misc_ops nix = { .match = (void *)nix_match, .load = (void *)nix_load, .name = "nix", }; ``` 上記のプログラムがロードされてカーネルに登録されたら、それをトリガーするようにbinfmt_miscサブシステムに依頼します。完全な例については、このスレッドをご覧ください。 ``` > bpftool struct_ops register nix_origin.bpf.o /sys/fs/bpf > echo ':origin:B::::nix:' > /proc/sys/fs/binfmt_misc/register ``` これはどういう意味でしょうか?それは、すべてのバイナリが上記のnix_match関数をトリガーすることを意味します。この場合、ELFファイルですが、PT_INTERP_NIXのような新しいセグメントを持つ実行可能ファイルである可能性もあります。カーネルは、インタープリタを動的に決定するようにnix_loadに依頼します。私たちの特別なBPFプログラムは$ORIGINのサポートを持っています💥 他に何ができるでしょうか?さて、従来のQEMU binfmt_misc登録スクリプトを、このスクリプトのようなBPFプログラムで完全に置き換えることさえできます。他に何ができるでしょうか?ファイル内の任意の情報に基づいてインタープリタをプログラムで選択できるようになったので、かなりのことができます。皆さんの提案やアイデアを聞かせてください💡。より小さな項目としては、ここに示すように、シェバン($ORIGIN/bin/ld.so)で$ORIGINをサポートすることさえ非常に簡単にできます。ファイルの前256バイトを見て、$ORIGINをトリガーするかどうかを確認するだけです。従来のbinfmt_miscハンドオフの1つの欠点または副作用は、目的の最終バイナリがどのように呼び出されるかが不透明であったことです。登録されたインタープリタがプロセスになり、プロセス全体を所有し、実際に実行しようとしたバイナリは引数に格下げされます。WineやQEMUのようなエミュレータの場合は許容できますが、従来のld.soを選択するバイナリごとのBPFローダーではあまり意味がありません。これはいくつかの痛みを伴う方法で漏洩しますが、最も簡単なのは次のとおりです。argv[0]と/proc/<pid>/cmdlineはインタープリタの呼び出しを示しており、実行したものは示していません。/proc/self/exeはインタープリタの名前を付けます。リロケータブルプログラムは通常、/proc/self/exeを通じて自身を配置しますが、代わりにダイナミックリンカを見つけます。😩 Christianもこのための大きなパッチシリーズを送信しました。彼の最新のパッチシリーズは、両端からギャップを埋める2つの新しいディスパッチモードを追加し、これらのモードが修正できる他のいくつかの落とし穴をカバーしています。私がNixで最も興奮しているのは、ローダー置換Lです。Lフラグを使用すると、カーネルは一致したバイナリをメインイメージとしてネイティブに実行し、バイナリのPT_INTERPに名前が付けられたローダーを登録されたインタープリタに置き換えるだけです。binfmt_miscはハンドオフではなくなり、単純なPT_INTERPオーバーライドになります。再構築する契約やIDはないため、標準のダイナミックローダーは変更なく機能します。これは私たちをどこに残しますか?Linuxカーネルのリリースを追跡し、これが-nextに搭載されてタグ付きリリースで出荷されたら、起動時に$ORIGINサポートを登録するNixOSモジュールをアップストリームする予定です🎉。計画は、すべてのELFファイルを一致させるのではなく、新しいPT_INTERP_NIXセグメントをゲートにすることです。これにより、後方互換性が保たれます。BPFハンドラは、新しいセグメントを運ぶことで明示的にオプトインしたバイナリに対してのみ起動します。これは、Nixで生成されたバイナリがBPFハンドラなしで引き続き機能することを意味しますが、それを持つバイナリはリロケータブルステータスに自身を昇格させることができます。港にある船は安全ですが、船はそのためには作られていません。— John A. Shedd