気になるmtree

散歩コースの途中から見える脇道の家に、大きなクリスマスツリーが出現してる。 去年までは、そんなの無かったから、家の人が仏教徒からクリスチャンに改宗したの かしらん? それとも、株か何かで儲けて、金の使い道に困り、せめてこの時期は 世の中を明るく照らしてあげようって事なのかしら。 もし、そうなら、そんな無駄電気使うの止め、伊達直人になって、何処かに寄付でもしてください。

そういうお前は、どうなんだ。こういう時は普通、貧乏暇無し と答えるのが 世間相場らしいけど、オイラーは貧乏暇有り だから、なるべく引き籠りして 金に羽が生えて飛んで行かないようにしてる。

クリスマスツリーは、もみの木に飾り付けをするんだっけ? 大きい樹を目印にして 神が降臨するのかな。日本にも同様な信仰があるね。大木にしめ縄を張ったりして あがめている。

大きな樹で思い出されるのが、オアフ島にある、あの有名な樹。 あのー樹なんの樹不思議な樹って、某CMでも流れていたな。

オイラーも気になるmtreeの不思議を解いてみたい。

いよいよ mtree

前回の続き。 参考書は、Advent Calendar 2016 出品作品。

目指せOneFloppyNetBSD

オイラーの所の空runでは、下記。ちょっと見やすいように整形した。

mtree -def /usr/src/distrib/amd64/ramdisk_cd/../../miniroot/mtree.conf -p /mnt/ -u
TOPDIR=/usr/src/distrib/amd64/ramdisk_cd/.. \
CURDIR=/usr/src/distrib/amd64/ramdisk_cd \
OBJDIR=/usr/src/distrib/amd64/ramdisk_cd \
REV=60    TARGDIR=/mnt \
UTILS=/usr/src/distrib/amd64/ramdisk_cd/../../miniroot \
RELEASEDIR= sh /usr/src/distrib/amd64/ramdisk_cd/../../miniroot/runlist.sh \
                    /usr/src/distrib/amd64/ramdisk_cd/../common/list \
                    /usr/src/distrib/amd64/ramdisk_cd/list.local
rm /mnt/instbin

やたらにpathが長い。これはアーキテクチャを越えて共通に使える部分と、アーキテクチャ固有 の部分を分割して、楽しようという魂胆が表面化した結果だ。

そして、こちらはMakefileの該当部分

do_files:
        mtree -def ${MTREE} -p ${MOUNT_POINT}/ -u
        TOPDIR=${TOP} CURDIR=${.CURDIR} OBJDIR=${.OBJDIR} \
            REV=${REV} TARGDIR=${MOUNT_POINT} UTILS=${UTILS} \
            RELEASEDIR=${RELEASEDIR} sh ${UTILS}/runlist.sh ${LISTS}
        rm ${MOUNT_POINT}/${CBIN}

まずは、mtreeを見てく。 mtree.confに従って、dirを作る(設定によってはfileも作れるけど) 簡略版を実例から引いて書いてみた。

[ob: sak]$ cat mtree.conf
                /set type=dir uname=root gname=wheel mode=0755
                .
                bin
                ..
                sbin
                ..
                tmp             mode=01777
                ..
                usr
                bin
                ..
                ..
                var
                db
                ..
                run
                ..
                log
                ..
                ..

冒頭の /set文は、言わずと知れた実行条件だな。次の行のドットは、同階層の所にって意味。 続いてbinが出て来るので、binのdirが作られ、そこへ潜り込むんだな。だから、ドット・ドットで階層を一つ上がる。以下、同様な考え方で、配置していけば良い。

実行例は下記。MYDIRを作って、そこをTOP-dirとして階層を構築。

[ob: sak]$ mkdir MYDIR
[ob: sak]$ sudo mtree -udef mtree.conf -p MYDIR
.:      user (0, 1000, modified)
        gid (0, 1000, modified)
missing: ./bin (created)
missing: ./sbin (created)
missing: ./tmp (created)
missing: ./usr (created)
missing: ./usr/bin (created)
missing: ./var (created)
missing: ./var/db (created)
missing: ./var/run (created)
missing: ./var/log (created)

結果を確認してみる。

[ob: sak]$ tree MYDIR/
MYDIR/
|-- bin
|-- sbin
|-- tmp
|-- usr
|   `-- bin
`-- var
    |-- db
    |-- log
    `-- run

配置

カッコよく言うとデブロイと言う事になるかな。こういう横文字は、あの知事が大好き。 IT業界なら、専門用語も許されると言うか推奨されるだろうけど、おじいちゃん、おばあちゃん 相手に横文字のオンパレードはいかがなものかと思うぞ。

そんなに横文字大好きなら、某社みたいに、東京エリアに入ったら、英語を標準とします。 日本語をしゃべったら、田舎者と言う名札を付けてください。違反者には、罰として、ごみを 10kg回収して清掃局へ届ける事。

これで、日本自慢の綺麗な日本が無料で実現出来るぞ。どうでっしゃろ、都議会へ案件を 提出してみなはれ。

たまにはいいね、こういう馬鹿話。され、本題へ戻る。mtreeを使って、それぞれのコマンドの 収納場所は出来た。次は、そこにコマンドを格納と言うか配置する番。

やたらに、色々な前書きで、環境変数の設定があり、その環境の元で、shellスクリプトを 実行する。スクリプト名は、runlist.sh。二つの引数、listとlist.localを取る。

この引数は、配置情報が入ったファイルだ。このファイルは、前々回にも、instbinを 作る時に使われたものだ。集中管理で、配置するファイルをひとまとめにする情報と、配置 情報を同時に記述してる訳だ。

以下、listの一部

LINK    instbin                                 usr/sbin/chroot
LINK    instbin                                 usr/sbin/pwd_mkdb

# various files that we need in /etc for the install
COPY    ${DESTDIR}/etc/group                    etc/group
COPY    ${CURDIR}/../miniroot/master.passwd     etc/master.passwd

chrootやpwd_mkdbをinstbinに混ぜてください、ってのは、前々回の話。

instbinをusr/sbin/chrootに配置(この場合はリンクで)して下さいとか、${DESTDIR}/etc/groupにあるファイルをetc/groupにコピーして下さいってのが、今回の話。 スクリプトでもないのに、いきなり、shellスクリプトの構文の一部(変数展開)が出てきて おやって思うけど、種明かしはこれからするね。

[ob: sak]$ cat runlist.sh
#       $OpenBSD: runlist.sh,v 1.6 2014/02/21 19:14:23 deraadt Exp $

if [ "X$1" = "X-d" ]; then
        SHELLCMD=cat
        shift
else
        SHELLCMD="sh -e"
fi

( while [ "X$1" != "X" ]; do
        cat $1
        shift
done ) | awk -f ${UTILS:-${CURDIR}}/list2sh.awk | ${SHELLCMD}

これが配置用のshellスクリプト。細かい事はlist2sh.awkに振って、大まかな流れだけが 記されている。-dを付けて起動すると、dry runするようなので、やってみる。

[ob: sak]$ CURDIR=`pwd` sh ./runlist.sh -d list
  :
# copy the crunched binary, link to it, and kill it
echo 'COPY      ${OBJDIR}/instbin                       instbin'
test -f ${TARGDIR}/instbin && rm -fr ${TARGDIR}/instbin
cp ${OBJDIR}/instbin ${TARGDIR}/instbin
echo 'LINK      instbin                                 bin/arch'
test -f ${TARGDIR}/bin/arch && rm -f ${TARGDIR}/bin/arch
(cd ${TARGDIR}; ln instbin bin/arch)
echo 'LINK      instbin                                 bin/cat'
test -f ${TARGDIR}/bin/cat && rm -f ${TARGDIR}/bin/cat
(cd ${TARGDIR}; ln instbin bin/cat)
  :
echo 'SYMLINK   install.sub                             upgrade'
test -f ${TARGDIR}/upgrade && rm -f ${TARGDIR}/upgrade
(cd ${TARGDIR}; ln -s install.sub upgrade)

echo 'TZ'
(cd ${TARGDIR}; sh $UTILS/maketz.sh $DESTDIR)

exit 0

echo行で表示されるのは、listに有った行。次の行からの文は、list2sh.awkが挿入したもの。 それが、サブshellとして実行され、所定の配置が行われるわけだ。

ちょいとlist2sh.awkを見ておく。

BEGIN {
        printf("cd ${OBJDIR}\n");
        printf("\n");
}

$1 == "COPY" {
        printf("echo '%s'\n", $0);
        printf("test -f ${TARGDIR}/%s && rm -fr ${TARGDIR}/%s\n", $3, $3);
        printf("cp %s ${TARGDIR}/%s\n", $2, $3);
        next;
}

$1 == "LINK" {
        printf("echo '%s'\n", $0);
        for (i = 3; i <= NF; i++) {
                printf("test -f ${TARGDIR}/%s && rm -f ${TARGDIR}/%s\n", $i, $i);
                printf("(cd ${TARGDIR}; ln %s %s)\n", $2, $i);
        }
        next;
}

END {
        printf("\n");
        printf("exit 0\n");
        exit 0;
}

こんな風に、listの第一フィールドに文字列によってディスパッチしながら、適当なshellコマンドを生成してる。awkでは、awkの中からshellを呼べないので、ちょっと面倒な事になってる。

そんな不便さを解消しようってんで、perlが生まれた。便利な機能は、pythonにもrubyにも 引き継がれたって訳だな。

mtreeをもう少し

mtreeを使って、dirの階層を作る例は上で見たけど、トロイの木馬の件とは余り関係なさそう だなあ。他の応用は無いの?

家探ししたら、/usr/src/regress/usr.sbin/mtree こんなのが出てきた。階層名にregress って入ってる所を見ると、回帰テスト用かしら? 置いてあるのは、Makefileとspec1って ファイルが一つ。何でもmake駆動なんで、走らせてみる。そしてspec1も見ておく。

[ob: mtree]$ cat spec1
# $OpenBSD: spec1,v 1.1 2008/06/13 21:33:00 espie Exp $
/set \
type=dir
.
/set type=file
spec1
[ob: mtree]$ make
mtree -e -f /usr/src/regress/usr.sbin/mtree/spec1 -p /usr/src/regress/usr.sbin/mtree

現在のdirにspec1が有る事を期待してるのかな? spec1を別の所に持って行って、やってみる。

[ob: sak]$ mtree -ef spec1 -p .
[ob: sak]$ mtree -ef spec1 -p MYDIR
missing: ./spec1

見る所を変えたら、そんな所にはファイルが無いんで、文句垂れてきた。なる程。

じゃ、ファイルのサイズを調べて、それをスペックファイルに記述し、確認してみるか。

missing: ./spec1
[ob: sak]$ wc -c spec1
      46 spec1
[ob: sak]$ cat spec1
/set type=dir
.
/set type=file
spec1 size=46

[ob: sak]$ mtree -ef spec1 -p .

あらかじめ調べた、ファイルサイズを、spec1に埋め込んでから、確認。何も言ってこない。 頼りが無いのは、良い証拠ってのは、unixの流儀です。

今度は、spec1を改変(単にスペース文字を追加しただけですが)してから、確認。

[ob: sak]$ mtree -ef spec1 -p .
spec1:  size (46, 49)

今度は、悪い知らせが届いた。タプルに結果が入って、一見Python風。左側の値が期待値で、 右側が現在値。これで、改変もばっちり検査出来るぞ。今世の中に不足してると言う、 セキュリティエンジニアになれるか。BSD由来のmtreeを持って行けば。 主戦場のリナには、こんな便利なコマンドは無いからね。

まてまて、悪い人はこういう単純なチェックはすり抜けるもの。さてどうする? そんな事もあろうかと、

[ob: sak]$ md5 spec1
MD5 (spec1) = 92648f2ed13f6d51f3d84e1ec965eede
[ob: sak]$ cat ck
/set type=dir
.
/set type=file
spec1  md5digest=92648f2ed13f6d51f3d84e1ec965eede

[ob: sak]$ mtree -ef ck -p.
[ob: sak]$ vi spec1   ;; modify it
[ob: sak]$ mtree -ef ck -p.
spec1: MD5 (92648f2ed13f6d51f3d84e1ec965eede, 00a6a1274d8010bed39117baa386903b)

さすがに、チェックコードを自分の所には埋め込めない(埋め込み==ファイル改変ですから)ので、別ファイルにした。簡便で、md5にしたけど、rmd160digest, sha256digest等も サポートされているので、今ならそちらを使うべき。そのうちに、sha512もmtreeでサポート されるだろう。

やる気の問題か? mtreeのソースから、変更の必要そうな所を洗い出してみた。

[ob: mtree]$ grep sha256 *.[ch]
compare.c:              } else if (strcmp(new_digest, s->sha256digest)) {
compare.c:                      printf("%sSHA256 (%s, %s)\n", tab, s->sha256digest,
create.c:               char *sha256digest, buf[SHA256_DIGEST_STRING_LENGTH];
create.c:               sha256digest = SHA256File(p->fts_accpath,buf);
create.c:               if (!sha256digest)
create.c:                       output(indent, &offset, "sha256digest=%s", sha256digest);
misc.c: {"sha256digest",F_SHA256,       NEEDVALUE},
mtree.h:        char    *sha256digest;                  /* SHA-256 digest */
spec.c:                 ip->sha256digest = strdup(val);
spec.c:                 if (!ip->sha256digest)

sha2(3)を見ると、SHA512File(const char *filename, char *buf)を使えってなってるぞ。

mtree の実例

上の例は、オイラーが妄想逞しく、ちょいとした実験をしたんだけど、実例は無いか? ちょいと考えてみた。

トロイの木馬って一種のウィルス。BSDは毎日、毎週、毎月管理スクリプトが走っている。 ウィルスのチェックなら、毎日やるのが筋だろう。

ってな事で、daily(8) すると、Runs the system security(8) check script. と、 振られた訳ですよ。そこを辿ってみると、脅威になりそうな点を色々な角度からチェック してますって、事細かい説明がされていた。 その説明の一環として

     •   Check for permission changes in special files and system binaries
         listed in /etc/mtree/special.  security also provides hooks for
         administrators to create their own lists.  These lists should be kept
         in /etc/mtree/ and filenames must have the suffix “.secure”.  The
         following example shows how to create such a list, to protect the
         programs in /bin:

             # mtree -cx -p /bin -K sha256digest,type > /etc/mtree/bin.secure
             # chown root:wheel /etc/mtree/bin.secure
             # chmod 600 /etc/mtree/bin.secure

         Note: These checks do not provide complete protection against Trojan
         horse binaries, as the miscreant can modify the tree specification to
         match the replaced binary.  For details on really protecting yourself
         against modified binaries, see mtree(8).

で、このひとまとまりのセキュリティチェックはどうやってるのか、ざっと /etc/dalyを見ると

start_part "Running security(8):"
export SUIDSKIP
/usr/libexec/security
end_part

という事で、/usr/libexec/securityへと、視点が移動します。

懐かしいperl語で書かれていましたよ。ひっそりとこういう底辺で頑張っているのね。 生憎オイラーは、perl語は受け付けない体になってしまったので、十分に読めないけど 冒頭部分だけ。

sub check_mtree {
        nag !-d '/etc/mtree', '/etc/mtree is missing' and return;

        if (open my $fh, '-|', qw(mtree -e -l -p / -f /etc/mtree/special)) {
                nag 1, $_ for map { chomp; $_ } <$fh>;
                close_or_nag $fh, "mtree special";
        } else { nag 1, "cannot spawn mtree: $!"; }
        :

mtreeの例が埋め込まれていた。-lは、パーミションのチェックか。ちょいと走らせてみる。

[ob: libexec]$ sudo ./security
[ob: libexec]$ sudo chown sakae /etc/rc.conf.local
[ob: libexec]$ sudo ./security

Checking special files and directories.
Output format is:
        filename:
                criteria (shouldbe, reallyis)
etc/rc.conf.local:
        user (0, 1000)
mtree special: exit code 2

最初は勿論チェックはOKになった。それじゃ、つまらないので、感でconfファイルを オイラーの持ち物にしてみた。これで、色々な悪さを自在に出来る事になる。

はて、この事態を検出してくれるか? 答えは、ちゃんと検出した、、でした。 持ち主のuidが0を期待してる所、uidが1000になってますぜってね。

果たして、その閻魔帳は? 勿論rootしか読み書き出来ない秘密のファイル。

[ob: libexec]$ sudo lv /etc/mtree/special
 :
rc              type=file mode=0644 uname=root gname=wheel
rc.conf         type=file mode=0644 uname=root gname=wheel
rc.conf.local   type=file mode=0644 uname=root gname=wheel optional
rc.local        type=file mode=0644 uname=root gname=wheel optional
rc.securelevel  type=file mode=0644 uname=root gname=wheel optional
 :

ちゃんと登録されてましたね。他にどんなものがチェック対象になってるか見ておくと吉。

だって、防御の要が分かりますから、悪い人はそこを回避すれば良い訳で。。。

防御するより攻撃する方が、絶対的に有利です。

ですから、攻撃されても被害が大きくならないように、減災って政府がさかんに言ってる。 都民に告ぐ。直下地震から逃れる方法はただ一つ。田舎へ疎開しましょう。

そうすれば、田舎も活性化しますよ。東京オリンピックなんて言ってワクワクしてる 場合ですかね!!