ping コマンドの file capability(2)
(理解が不十分なので間違っている所があれば優しく教えてください)
さて、ping コマンドの file capability(1) - TenForward で紹介したように ping コマンドの File capability は Permitted
のみが有効になっていて、Effective
ビットはオン(+e
)になっていません。これで ping が実行できる理由は説明したとおりですが、+e
が設定されていない理由は「不要だから」というだけの理由ではありません。
「なんで +e
ないんだろう?」とつぶやいていると WhiteAnthrax さんが調べて教えてくれました!ありがとうございます!
これは
に書かれている通りです。Docker コマンド内の ping コマンドの実行で問題が起こるからということです。
まず Docker コンテナでは、普通に docker run
すると cap_net_admin
が許可されません。cap_net_admin
は ping -m
のように実行する際に必要です(man ping
すると -m
は "use mark to tag the packets going out." とのことです)。一方で ping
を普通に実行する際に必要な cap_net_raw
は許可されています。つまり Docker コンテナ内では ping コマンドで -m
をつけても目的は達成できません。
ここで(これは明確に man capabilities
には書いてない気がするけど…)
- File capability で
+e
すると、指定したすべての capability が取得できない場合、コマンドを実行した瞬間に実行は失敗する
ということのようです。
特権が必要なシステムコールの実行が行われる、行われないに関わらず、すべての capability の取得に失敗すると、コマンド自体が実行できないということです。ping --help
のような特権が不要な実行であってもです。
ということは、
- Docker コンテナ内で
cap_net_admin,cap_net_raw+ep
と設定してしまうと、cap_net_admin
が取得できないので、cap_net_raw
のみが必要な実行であってもping
コマンドを実行した瞬間にコマンド実行が失敗する
ということです。
今回の ping
コマンドはプログラム中で自身で capability をチェックして Permitted
がオンであれば capset()
を使って Effective
を有効にしていますので、+p
とだけしておくと、たとえ cap_net_admin
が取得できなくてもコマンド実行は成功します。
つまりプログラム中で権限のチェックを行って capability をセットしていますので、
- capability のセットに失敗したシステムコールだけが失敗する
となります。それ以外のシステムコールは問題なく実行されるわけです。
ping -m
と実行すると、プログラム中で cap_net_admin
のチェックをしているところのみ失敗します。つまり "mark" だけが失敗して、cap_net_raw
が必要な処理は行われます。
https://bugzilla.redhat.com/show_bug.cgi?id=1142311#c24 によると
[root@29b49db23971 /]# /opt/ping/ping -m 123 -c1 10.3.1.1 PING 10.3.1.1 (10.3.1.1) 56(84) bytes of data. Warning: Failed to set mark 123 64 bytes from 10.3.1.1: icmp_seq=1 ttl=62 time=0.327 ms
のように mark 処理の部分だけ失敗して、それ以外の処理は行われるようです(試してませんw)。
というわけで +e
してしまうと、ping コマンドはヘルプすら表示できず、本来の実行できる処理も実行できなくなるので、+p
のみが設定されているということです。
(2019-09-30 追記)
『これは明確に man capabilities
には書いてない気がするけど…』と書きましたが、id:udzura さんから man 2 execve
に
EPERM A "capability-dumb" applications would not obtain the full set of permitted capabilities granted by the executable file. See capabilities(7).
という説明があると教えてもらいました!(これでも曖昧ではあるけど "the full set" とあるのできっと…)
ping コマンドの file capability(1)
(いまさらのお話とは思いますが自分用のメモに)
Linux の Capability、何度も理解しようとして完全に理解できないままなんですが、ちょっとした理由で File capability を調べてます。最近は ambient capability なんてのもあって理解が遠のいてます。:-p
file capability といえば、まず登場するのはこのコマンドではないでしょうか?(違う?) Ubuntu 18.04.3 では次のように setuid されています。file capability は特に設定されていません。
$ ls -l /bin/ping -rwsr-xr-x 1 root root 64424 Jun 28 20:05 /bin/ping $ getcap /bin/ping $
これは想定通りだったんですが、先日リリースしたばかりの CentOS 8 で見てみると、
$ ls -l /bin/ping -rwxr-xr-x. 1 root root 69160 May 11 23:22 /bin/ping $ getcap /bin/ping /bin/ping = cap_net_admin,cap_net_raw+p
setuid されておらず file capability が設定されていて、「おお、イマドキのセキュアな設定っぽい」と思いました(CentOS 7 からそうだったようなので今更感満載です)。でも +p
と設定されています。あれ? +ep
でじゃないとマズくない? +e
がないと実行できなくない? 例のあのややこしい条件式からいっても。
$ man 7 capabilities : (snip) P'(ambient) = (file is privileged) ? 0 : P(ambient) P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient) P'(effective) = F(effective) ? P'(permitted) : P'(ambient) P'(inheritable) = P(inheritable) [i.e., unchanged] P'(bounding) = P(bounding) [i.e., unchanged]
それはそのとおりなのですが、CentOS で採用されている ping が含まれている iputils のコードを確認するとその理由がわかります(たぶん)。
#ifdef HAVE_LIBCAP int modify_capability(cap_value_t cap, cap_flag_value_t on) { cap_t cap_p = cap_get_proc(); cap_flag_value_t cap_ok; int rc = -1; if (!cap_p) { error(0, errno, "cap_get_proc"); goto out; } cap_ok = CAP_CLEAR; cap_get_flag(cap_p, cap, CAP_PERMITTED, &cap_ok); if (cap_ok == CAP_CLEAR) { rc = on ? -1 : 0; goto out; } cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &cap, on); if (cap_set_proc(cap_p) < 0) { error(0, errno, "cap_set_proc"); goto out; } cap_free(cap_p); cap_p = NULL; rc = 0; out: if (cap_p) cap_free(cap_p); return rc; }
(https://github.com/iputils/iputils/blob/4b0a0ab7898b093ef55c4a8cacea9a21c37a451f/ping_common.c#L189)
CAP_PERMITTED
を確認して、付与されていれば CAP_EFFECTIVE
を設定してます(と思う)。
これで +ep
でなく +p
な理由がわかりました。ping
コマンド自体で +p
が付いていれば(というか付いている結果として permitted が設定されていれば、かな?)必要な capability を設定してシステムコールを呼ぶプログラムになっているからでした。
とはいえこれでは +e
が不要な理由はわかりましたが、付けておいてマズい理由はわかりません。ま、セキュリティ観点から不要な権限は付けておく理由はない、というだけでも理由になりますが。
(続く)
セサミ mini を導入してみた(3)
前回、わざわざアダプターを作って送ってもらったけれども改善しなかったセサミminiの鍵を使って施錠した場合の問題。
セサミのサポートのメールで
硬めのクッション(セサミminiの箱の黒いクッションなど)をサムターンに貼って隙間を少し少なくして頂くことも有効かもしれません
とアドバイスをいただいていたのと、ブログへのコメントをいただいた id:UnaStrada さまのアドバイスで、
このブログエントリのように、吸水テープで隙間をなくすと良さそうということで試してみました。
とりあえず百均で隙間テープと防水テープというものを買ってきました(Can Doです)。
隙間テープはスポンジ状の比較的柔らかい材質のものでしたが(捨ててしまって写真ないや)、これだと柔らかすぎてサムターンとセサミに押しつぶされる感じで隙間に入れた意味がありませんでした。少し硬い材質が良いのかもということで、この防水テープを使ってみました。
これだとサムターンの幅より狭いのですが、アダプターをこれ以上広くすると貼り付けた意味がなかったので、狭めにして無理矢理サムターンを隙間に押し込む感じで設置しました。
これで無事鍵で施錠してもサムターンはそれなりの角度になるようになったので、鍵でもアプリでも施錠状態にできるようになりました。
ところで使い続けているとアダプターに結構力がかかるのかアダプターが開いてきませんかね?サムターン受けパーツが開いてくるような。根元に結構力かかってるような…(↑の画像は無理矢理押し込んでるので開くのは仕方ないけど、防水テープ貼る前にしばらく使ってたら結構開いていたような)
P.S. ここに来て WiFi モジュールが不調? WiFi 経由で接続できないことがあったので何度かコンセント抜き差しとリセットしてみて様子見(コンセント抜き差しして復活して数日後またアクセスできず…)
ShiftFS ふたたび 〜 Ubuntu 19.04で導入された ShiftFS を試してみた
ちょうど 2 年ほど前、Open Source Summit で話を聞いて ShiftFS について試したことがありました。
その後はとりあえずカーネルにパッチを当てたりはしてましたが、あまり使わないままでした。少し前に gihyo.jp の Ubuntu の記事を読んでいると、
discoのカーネルフリーズが4月4日に迫る中,興味深いパッチが投稿されています。パッチの中身は「ShiftFS」と呼ばれる,コンテナ環境でのセキュリティ機能を提供するためのラッパーファイルシステムです
https://gihyo.jp/admin/clip/01/ubuntu-topics/201903/29
なんて書かれています。その後の記事で
ShiftFSは, 前回取り上げた後,4月4日に無事にマージに辿り着いています。
https://gihyo.jp/admin/clip/01/ubuntu-topics/201904/19
と書かれていたので、また変わった所がないか調べて試してみたいなと思っていました。
そこでようやく重い腰を上げて、Ubuntu 19.04 環境を用意して試してみました。
ShiftFS について
詳しくは先に紹介した 2 年前の記事を参照してください。
簡単に言うと、非特権コンテナを起動する場合、どの uid/gid で起動するかは一般的に決まっているわけではないですが、世間で流通しているコンテナイメージ内のファイルの所有権は作成時点で一意に決まりますし、自作のコンテナイメージでも同様でしょう。
そこで非特権コンテナを起動するときには、それを起動するユーザの uid/gid に合わせて chown してあげる必要があります。
この問題を解消するために考えられたのが ShiftFS です。
ShiftFS は OverlayFS のように重ね合わせのファイルシステムとして実装されているようです。元のファイルシステムを「下層」(lower filesystem)とし、その上に「上層」(upper filesystem)のファイルシステムを重ねて所有権を調整しているのでしょう(たぶん)。
この ShiftFS は Ubuntu 19.04 の 5.0 カーネルにパッチを適用して使えるようにしており、upstream のカーネルには導入されていない機能です(されそうなのかしら?)。
Ubuntu は 12.04 だったか 14.04 の頃も、まだ upstream な Linux カーネルに導入されていない OverlayFS を先取りして入れていました(カーネルに入ったときには仕様が変わっていて LXC にパッチ送ったりしたのはいい思い出)。また 16.04 なんかでも、かなり後のカーネルで導入される機能がバックポートされていたりして、かなり意欲的に自分たちの求める機能を入れてきます。
ShiftFS によるマウント
マウント方法は2 年前の記事と大きく変わっていません。マウントオプションが増えた程度でしょうか。
マウントは 2 段階で行います。
ShiftFS がないとき
これも2 年前の記事と同じなのですが、簡単に紹介しておきましょう。
ここではファイルシステムとして LXC コンテナのイメージを展開して使用しています(alpineイメージ)。検証のために一部ディレクトリの所有権などをいじっています。root
権限で作成されたコンテナです。
コンテナファイルシステムの root(/)は /var/lib/lxc/ct01/rootfs
です。
$ sudo ls -l /var/lib/lxc/ct01/rootfs total 68 drwxr-xr-x 2 root root 4096 Jul 1 13:00 bin drwxr-xr-x 3 root root 4096 Jul 2 07:17 dev drwxr-xr-x 19 root root 4096 Jul 2 07:17 etc drwxr-xr-x 2 root root 4096 Jun 19 17:14 home drwxr-xr-x 6 root root 4096 Jul 1 13:00 lib drwxr-xr-x 5 root root 4096 Jun 19 17:14 media : (snip)
上の例のように rootfs
以下は root
所有です。
現在のユーザーをコンテナ内の root
にマッピングして User Namespace を作成します。
$ id -u && id -g 1000 1000 $ unshare --pid --user --map-root-user --mount --mount-proc --fork -- /bin/bash # id uid=0(root) gid=0(root) groups=0(root),65534(nogroup) # ls -l /var/lib/lxc/ct01/rootfs/ total 68 drwxr-xr-x 2 nobody nogroup 4096 Jul 1 13:00 bin drwxr-xr-x 3 nobody nogroup 4096 Jul 2 07:17 dev drwxr-xr-x 19 nobody nogroup 4096 Jul 2 07:17 etc drwxr-xr-x 2 nobody nogroup 4096 Jun 19 17:14 home drwxr-xr-x 6 nobody nogroup 4096 Jul 1 13:00 lib drwxr-xr-x 5 nobody nogroup 4096 Jun 19 17:14 media : (snip)
上のように uid/gid=1000/1000 のユーザーをコンテナ内の uid/gid=0/0 にマッピングしただけなので、マッピングされていない uid/gid については nobody/nogroup の 65534/65534 となってしまっています。
ShiftFS があるとき
まずはコンテナの root(/)である /var/lib/lxc/ct01/rootfs
を ShiftFS の下層(lower filesystem)としてマークします。この場合、マウントオプションに mark
を指定します。
$ sudo mount -t shiftfs -o mark /var/lib/lxc/ct01/rootfs /var/lib/lxc/ct01/rootfs [sudo] password for ubuntu: $ grep shiftfs /proc/self/mountstats device /var/lib/lxc/ct01/rootfs mounted on /var/lib/lxc/ct01/rootfs with fstype shiftfs
ShiftFS としてマウントされています(このイメージは root
所有なので、ここでは root
ユーザーで mark しています)。
ここで、さきほどと同じように User Namespace を作成します。
$ unshare --pid --user --map-root-user --mount --mount-proc --fork -- /bin/bash # id uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
そしてさきほど mark した ShiftFS マウントを使って、権限のあるディレクトリに ShiftFS マウントしてみます。
# mount -t shiftfs /var/lib/lxc/ct01/rootfs/ /home/ubuntu/mnt # grep shiftfs /proc/self/mountstats device /var/lib/lxc/ct01/rootfs mounted on /var/lib/lxc/ct01/rootfs with fstype shiftfs device /var/lib/lxc/ct01/rootfs mounted on /home/ubuntu/mnt with fstype shiftfs
マウントされています。マウント先の /home/ubuntu/mnt
を確認してみると、
# ls -l /home/ubuntu/mnt total 68 drwxr-xr-x 2 root root 4096 Jul 1 13:00 bin drwxr-xr-x 3 root root 4096 Jul 2 07:17 dev drwxr-xr-x 19 root root 4096 Jul 2 07:17 etc drwxr-xr-x 2 root root 4096 Jun 19 17:14 home drwxr-xr-x 6 root root 4096 Jul 1 13:00 lib drwxr-xr-x 5 root root 4096 Jun 19 17:14 media : (snip)
ちゃんと root
所有になっており、ShiftFS が効いていることがわかります。
ちなみにこの「下層」(mark)のマウントはホストのNamespaceで行っているので、ホストから見えますが、ホストでは「上層」は見えません。
$ grep shiftfs /proc/self/mountstats (ホスト上で実行)
device /var/lib/lxc/ct01/rootfs mounted on /var/lib/lxc/ct01/rootfs with fstype shiftfs
$
コンテナ起動後は、余計な情報を見せないためにホスト上ではこの mark 用 ShiftFS は umount
できます。
動きは 2 年前の変わっていないようです。
passthrough オプション
Ubuntuのパッチでは ShiftFS のオプションとして passthrough
オプションが追加されているようです。2 年前の記事の時点ではおそらくなかったオプションだと思います。
さきほどの例では何も指定していませんので、passthrough=0
が指定されているのと同じ意味になるようです。
- 1 を指定すると、マウントされたファイルシステムタイプとして「下層」側のファイルシステム名が返るようです
- 2 を指定すると、
ioctl()
でホワイトリストとして指定されたリクエストは「下層」側ファイルシステムに渡されるようです - 3 を指定すると、
1+2
を指定したのと同じになるようです
さきほどの例でマウントした ShiftFS で 1 が指定されていない効果を確認してみます。
# stat -f /home/ubuntu/mnt/root File: "/home/ubuntu/mnt/root" ID: ad6dc6d23bfb15c1 Namelen: 255 Type: UNKNOWN (0x6a656a62) Block size: 4096 Fundamental block size: 4096 Blocks: Total: 4111327 Free: 3014986 Available: 2801214 Inodes: Total: 1048576 Free: 972147
ファイルシステムタイプとして Type: UNKNOWN
と返っています。
(2 の指定は気軽に試すには私にはハードルが高そうなので、)とりあえず 3
を指定してみます。
$ sudo mount -t shiftfs -o mark,passthrough=3 /var/lib/lxc/ct01/rootfs /var/lib/lxc/ct01/rootfs $ grep shiftfs /proc/self/mountinfo 540 28 0:53 / /var/lib/lxc/ct01/rootfs rw,relatime shared:294 - shiftfs /var/lib/lxc/ct01/rootfs rw,mark,passthrough=3
passthrough=3
付きでマウントされています。
では User namespace を作成しましょう。
$ unshare --pid --user --map-root-user --mount --mount-proc --fork -- /bin/bash # mount -t shiftfs -o passthrough=3 /var/lib/lxc/ct01/rootfs /home/ubuntu/mnt # stat -f /home/ubuntu/mnt/root File: "/home/ubuntu/mnt/root" ID: ad6dc6d23bfb15c1 Namelen: 255 Type: ext2/ext3 Block size: 4096 Fundamental block size: 4096 Blocks: Total: 4111327 Free: 3019139 Available: 2805367 Inodes: Total: 1048576 Free: 972149
stat
コマンドで確認すると Type: ext2/ext3
と返ってきています。
2 の ioctl()
周りですが、ShiftFS のパッチ(の一部) を見る限りでは、
static long shiftfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case FS_IOC_GETVERSION: /* fall through */ case FS_IOC_GETFLAGS: /* fall through */ case FS_IOC_SETFLAGS: break; default: return -ENOTTY; } return shiftfs_real_ioctl(file, cmd, arg); } static long shiftfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case FS_IOC32_GETVERSION: /* fall through */ case FS_IOC32_GETFLAGS: /* fall through */ case FS_IOC32_SETFLAGS: break; default: return -ENOIOCTLCMD; } return shiftfs_real_ioctl(file, cmd, arg); }
のような実装になってるので、ここに挙がってるリクエストは許可されているのでしょう(知らんけど)。
LXD
ちなみに LXD ではすでに ShiftFS が使えるようになっています(3.12以降)。
使い方は次で説明されています。
セサミ mini を導入してみた(2)
先日設置したセサミ mini ですが、先日も少し書いたとおり、ちょっとだけ問題がありました。
うちの鍵は
- サムターンが縦(12時)方向で解錠
- サムターンが縦から右に45度回転した状態でチェーンがかかった状態
- サムターンが横(3時)方向で施錠
となります。
サムターンの施錠、解錠状態はそれぞれ少々の遊びがあって、縦から少し行き過ぎた状態と少し手前の状態をぐらぐら動く感じです。
そしてセサミ mini のサムターンアダプターとサムターンの間には少し隙間があり、セサミ mini のつまみ自身も色々な方向に少し遊びがあります(設置が少しズレたときの対応や、色々な鍵への対応を考えてそういう設計になっているようです)。
色々な所に余裕と遊びがある関係で、うちに設置すると:
解錠状態: セサミ mini のつまみがちょうど縦になる状態だと、鍵のサムターンが解錠状態の縦にならず、少し手前で止まってしまい、開場されずにチェーンがかかった状態になる。そこで解錠状態は少し縦より行き過ぎた状態で設定する。
これくらいの角度を解錠に設定します。こちらが問題になることはありません。
施錠状態: 問題は施錠状態です。
セサミのつまみが真横になった状態を「施錠状態」に設定すると、鍵のサムターンは横(3時)の少し手前で止まってしまい、きちんと施錠されずにチェーンがかかった状態になります
そこで少し行き過ぎたところ(次の画像の通り)あたりに設定します。セサミアプリを使って施錠するとこれで施錠できます
ところが鍵を使って施錠すると、鍵のサムターンは横(3時)を向きますが、セサミ miniのつまみは真横のちょっと手前の状態で止まります。
この状態だとセサミは施錠状態のところまでつまみが回っていないと認識し、アプリで見ると "Unlocked" という表示のままです。
セサミminiの施錠位置の調整必要かな。外から普通に鍵で施錠するとつまみは回った状態で解錠状態になってしまう。でもあまり手前で施錠状態にすると遊びがあってアプリから施錠しても完全施錠状態にならないんだよな。 pic.twitter.com/eAVCSesFvb
— 𝕿𝕖𝕟𝔽𝕠𝕣𝕨𝕒𝕣𝕕 (@ten_forward) 28 May 2019
この状態でアプリをタップすると、施錠状態に移行します。再度タップすると解錠されます。つまり 2 度タップしないといけないことになります。
上記ツイートを行うと、公式に補足され、ありがたいことにタダでアダプターを作成し送ってくれるとのことですので、サムターンの詳細なサイズを送りました。
つまみの部分に遊びがあるとセサミのセンサーが認識しない場合がございます。その場合アダプターで解決できるかもしれませんので、よろしければ下記リンクを参考に、サムターンの寸法をsesame@candyhouse.coまで送っていただけないでしょうか?🙇♀️https://t.co/RYwu1KDPrb
— CANDYHOUSE キャンディハウスJP (@candy_house_jp) 29 May 2019
届いたのでアダプターを装着しました。装着したアダプターとセサミのサムターンアダプターとの間にも少し隙間があります。
そして設置。写真のようにアダプターをつけてもサムターンとの間には隙間があります。
このため、
サムターン←(すき間)→作成してもらったアダプター←(すき間)→セサミ miniのサムターンアダプター
という状態になり、やはりいくら調整してもセサミ mini の施錠状態の角度と鍵を使った時の施錠状態の角度の間に差が出来てしまい、症状は改善されませんでした。
とはいうものの、便利さが勝っていますので、作ってもらったアダプターは取り外し、元の状態で再度装着して使っています。
アプリでの対応が待たれますね。
セサミの公式サポート手厚すぎですね。安心できる対応ですね。