はじめに
最近、とある書籍で binfmt_misc を知ったので、何か面白いことができないか考えてみたところ
shebang の仕組みを再現してみようと思い立ちました。
※あくまで実験的な実装であり、本番環境での使用を推奨するものではありません。
目標
#! はカーネル標準の binfmt_script が既に扱っているので、競合しないように #@ を「擬似 shebang」として扱います。つまり
#@/usr/bin/ls -1
みたいなスクリプトを実行したら
#!/usr/bin/ls -1
を実行した時と同じ出力になることを目指します。
やってみる
1. binfmt_misc にルールを登録
「先頭が #@ のファイルは handler に渡す」という内容を登録します。
binfmt_misc の登録文字列は :name:type:offset:magic:mask:interpreter:flags 形式で、今回の例では
name=myshebang-
offset=0(デフォルト値なので省略可) type=Mmagic=#@-
mask=\xFF\xFF(デフォルトで\xFF埋めなので省略可) interpreter=/usr/local/bin/handler-
flags=(なし)
とすることで
「実行したスクリプトの 0 バイト目から \xFF\xFF でマスク処理した値が
#@ と一致していた場合に /usr/local/bin/handler を実行する」
という内容になります。
#!/usr/bin/env bash
set -euo pipefail
binfmt_misc_dir='/proc/sys/fs/binfmt_misc'
handler='/usr/local/bin/handler'
name='myshebang'
# 機能を有効化
if ! grep -qs "$binfmt_misc_dir" /proc/mounts; then
sudo mount -t binfmt_misc binfmt_misc "$binfmt_misc_dir"
fi
# ルールの登録・更新
if [[ -f $binfmt_misc_dir/$name ]]; then
echo -1 | sudo tee $binfmt_misc_dir/$name
fi
echo ":$name:M::#@::$handler:" | sudo tee $binfmt_misc_dir/register
2. handler の実装
#@ の行を読み取り、本家と同様に exec する処理を実装します。
お手本として、実験環境と同じバージョンの fs/binfmt_script.c を参考にしたところ
- 読み取るのは1行目のみ
-
#!「プログラム名」 「引数」 のスペースまたはタブ区切りを期待する- 「引数」は任意かつ(区切り文字を含んでも)1つとして扱う
- 末尾のスペース / タブはトリムする
- 上記にさらに元のスクリプトのパスと引数群を加えて exec する
と分かったので、下記の通り実装しました。
#!/usr/bin/env bash
set -euo pipefail
script_path="$1"
IFS=$' \t' read -r interp arg < <(sed -n '1s/^#@//p' "$script_path")
exec "$interp" ${arg:+"$arg"} "$script_path" "${@:2}"
なお、 handler の第1引数には元のスクリプトのパスが、それ以降は引数群が渡されます。(flags の値に依って動作が異なるようです。)
3. いざ実験
引数の渡り方を確認しやすいので ls を使っています。
$ cat <<'EOS' >sample
#!/usr/bin/ls -1
EOS
$ chmod +x sample
$ ./sample /dev/zero
./sample
/dev/zero
$ cat <<'EOS' >sample
#@/usr/bin/ls -1
EOS
$ chmod +x sample
$ ./sample /dev/zero
./sample
/dev/zero
同じ出力が確認できました。ちょっと感動!
おわりに
shebang は見た目的にシェルが解釈している雰囲気が漂っていて混乱しやすいのですが、今回の実験で理解が強固になったと思います。
参考文献
河田旺, 小池悠生, 渡邉慶一, 佐伯学哉, & 荒田実樹. (2024). Binary hacks rebooted: 低レイヤの世界を探検するテクニック89選. オライリー・ジャパン.
binfmt_script.c - fs/binfmt_script.c - Linux source code v6.1.158 - Bootlin Elixir Cross Referencer. https://elixir.bootlin.com/linux/v6.1.158/source/fs/binfmt_script.c
Kernel support for miscellaneous binary formats (binfmt_misc) — The Linux kernel documentation. The Linux Kernel Archives. https://www.kernel.org/doc/html/v6.1/admin-guide/binfmt-misc.html
Execve(2) - Linux manual page. Michael Kerrisk - man7.org. https://man7.org/linux/man-pages/man2/execve.2.html