2014年5月3日

なぜ cdromグループに入っていないユーザーがCD, DVDを焼けるか、あるいははじめてのsystemd-logind

このあいだ「今度買うハードウェアはDVDマルチライタがついているんだけど、読み込みは許可しつつ焼けないように制限かけたい」って質問されたら、意外に奥が深かったので再現風にまとめ。

事前の予想


自分のデスクトップにLinuxを使いはじめてから15年くらいです。
昔CD-R焼けなくて対応したときの記憶をたどると、たしかこんなかんじだったんじゃないかなと:
  • /dev/cdrom あたりはrootユーザ、cdromグループになっている
  • 使えるユーザはcdromグループに入っているから使える 
これを踏まえて「デバイスファイルを作ったりcdromグループ設定しているのは今はudevだろうから、そのルールを確認してモードを660じゃなくて640にした上でユーザをcdromグループにすれば読み込みのみにできるかな」と予想しました。

確認と謎


自分のマシン(debian testingでinitはsystemdにしてます)でチェックするとこんなかんじ
dragon:~$ ls -l /dev/cdrom
lrwxrwxrwx 1 root root 3 May  2 03:07 /dev/cdrom -> sr0
dragon:~$ ls -l /dev/sr0
brw-rw----+ 1 root cdrom 11, 0 May  2 03:07 /dev/sr0
dragon:~$ id
uid=1000(kankun) gid=1000(kankun) groups=1000(kankun),24(cdrom),25(floppy),29(audio),30(dip),44(video),
46(plugdev),105(scanner),109(bluetooth),111(netdev)
ああcdromグループに入ってるわ…… ということでちょっと安心しましたが、長期間使っている環境なので油断できません。Fedoraマシンでみると
[kankun@snake ~]$ id uid=1000(kankun) gid=1000(kankun) groups=1000(kankun),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
cdromグループは別にあるもののユーザはcdromグループに入っていません。RHEL5,6,7betaもご同様。
しかしいずれの環境でも、普段からCD読み書きできています。気になります。

「ファイルシステムとしてmountするのはudisksあたりが頑張ってくれてるはずだから、直接デバイス読んでみよう」 less -f /dev/sr0 すると CD/DVDの内容が読めました。
brw-rw----+ 1 root cdrom 11, 0 May  2 03:07 /dev/sr0

という状態のデバイスをrootでもcdromグループでもない一般ユーザがアクセスできるのです。
一瞬頭に「???」が浮かびましたがbrw-rw----+」の最後に+があります。確認するとユーザにアクセスを許可するACLが設定されていました。
dragon:~$ getfacl /dev/sr0
getfacl: Removing leading '/' from absolute path names
# file: dev/sr0
# owner: root
# group: cdrom
user::rw-
user:kankun:rw-
group::rw-
mask::rw-
other::---

誰がこのACLを設定しているの?(タイトルでネタバレ)

# ここからの調査は最初RHEL6でやったんですが、より実装がためになるのでFedora20で調べなおして再現してます。なのでちょいフィクションです。

このACLを設定したプログラムに心あたりがないわけです。自分で明示的に指定したこともありません。自分のマシンで自分の知らない権限設定がされているとかいやですね。しらべましょう。誰がこんな設定してるんでしょうか。ちょっと予想します。
  • 今のログインユーザだから(pam?)
  • インストーラで作成したアカウントだから何か特別に設定が書かれている(どこぞの設定ファイルがudevのルールから参照されてる?)
  • GNOMEデスクトップユーザむけのなにかが動いている(udisksとか??)
とりあえずgnomeうんぬんがでてくると登場人物が多すぎて大変なので、状態をクリアするために再起動してXを起動せずにコンソールからログインします。

やはりACLが設定されています。GNOMEは排除できました。

pamをうたがって別のvirtual terminal(VT)でrootでログインします。ACLの状態がかわりました。これでインストーラも排除できました。
getfacl: Removing leading '/' from absolute path names
# file: dev/sr0
# owner: root
# group: cdrom
user::rw-
group::rw-
mask::rw-
other::---
今ログインしてるユーザ全員に許可をだしているわけではなさそうです。一般ユーザのアカウントへの許可は消えてしまいました。最後にログインしたユーザーかな??

VTきりかえてkankunアカウントでもういっぺん確認します。
getfacl: Removing leading '/' from absolute path names
# file: dev/sr0
# owner: root
# group: cdrom
user::rw-
user:kankun:rw-
group::rw-
mask::rw-
other::---
やばい…… VT切り替えしかしてないのにACLが書き変わってる……。pamだけじゃこの動きはできません。予想が全滅してポルナレフごっことかやりたくなります。やっても話が進まないのでぐっと堪えます。

ls -l /dev して同じように+がついているものをみると kvm, rfkill, video0 でも同様のACLが設定されていました。今デスクトップを目の前でつかっているアカウントがデスクトップ用途で使いやすいようにACLを変更する、という意図でやっていそうです。

こういうのたしかsystemdに含まれているlogindってひとが、ConsoleKitから引きついでました。
コンソール切り替えの情報とか監視してないかなー と、lsof -n |grep ttyとかしてみます。
systemd-l 683 root 5r REG 0,15 4096 4386 /sys/devices/virtual/tty/tty0/active
あった!

systemd-logindによるACL設定


logindが何やってるか知りませんが、こいつかこいつ経由で情報もらった誰かがACLいじっているはず。logindはsystemdのリポジトリにはいってます。
systemdのリポジトリでaclをgrepします。。。./src/login/logind-acl.c ってのがいました。

logind-acl.hで定義されている関数は、 devnode_acl、devnode_acl_all で、デバイスファイルからold_uidとして指定されたuidへの許可ACLを削除して、new_uidとして指定されたuidへの許可ACLを設定します。

devnode_acl_all関数の中で以下のディレクトリを探索してACL設定する対象となるデバイスを決定していました。udevによるタグで絞り込まれています。

       dir = opendir("/run/udev/static_node-tags/uaccess");

このタグづけ対象デバイスは、logindのソースと同じディレクトリにある70-uaccess.rulesの中で定義されていて、たとえばCD/DVDについては以下のようなルールがありました。
# optical drives
SUBSYSTEM=="block", ENV{ID_CDROM}=="1", TAG+="uaccess"
SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="4|5", TAG+="uaccess"
これでさっきのディレクトリに登場しそうです。
$ ls /run/udev/tags/uaccess
b11:0  c10:232    c116:1    c116:10  c116:11  c116:12  c116:2  c116:3  c116:33  c116:4  c116:5  c116:6  c116:7  c116:8  c116:9  c189:257  c189:258    c189:259  c189:260  c189:385  c21:2  c226:0  c81:0
してました。b11:0 です。ブロックデバイスのmajor 11, minor 0ですね。

systemd-logindがACL設定をuaccessタグがついているデバイスにおこなっていそうなことはわかりましたが、どういう契機で行われるかという疑問が残っています。もうひといきです。

1. ログイン時のACL設定 

systemd-logindのソースと同じところにpam-module.cといういかにもあやしいのがいます。これをビルドするとpam_systemd.so というモジュールができてセッション作成時に systemd-logindへセッション作成依頼をdbus経由で投げつけます。
systemd-logindがこの依頼を受けてセッション作成し、必要に応じてACL設定の変更をおこなっています。

2. VTの状態によるACL設定

systemd-logind内での関数呼び出し元をおいかけると、以下のようになっていて、VTの状態を反映したACL設定をおこなっていました。VT切り替え監視そのものもsystemd-logindが見ていてmanager_dispatch_console() がハンドラになっています。

manager_dispatch_console()
-> seat_read_active_vt()
  -> seat_active_vt_changed() 
    -> seat_set_active() 
      -> seat_apply_acls() 
        -> devnode_acl_all() 
          -> devnode_acl()

3. udevdによるACL設定

さらにlogindのソースとおなじディレクトリに追いてある73-seat-late.rules.in をみると
TAG=="uaccess", ENV{MAJOR}!="", RUN{builtin}+="uaccess"
という記載があって、uaccessタグがついているデバイスがつながるとudev組み込みのuaccessという処理が走るように設定されています。これは systemdのsrc/udev/udev-builtin-uaccess.cで定義されていて、既にseatとユーザがひもづいている状態でデバイスを接続した時に、そのデバイスについてdevnode_acl()関数を呼びだしてACL設定をおこなうものでした。

まとめると以下のようになっていて、ログインしたとき、画面切り替えした時、既にログイン済みでデバイスをUSBなどで追加したときにちゃんとACLが設定できるようになっていました。

はじめの疑問にもどる

そして結局一般ユーザにCD, DVDを焼けないように設定したいという当初の質問がどうなったかというと……

/lib/udev/rules.d/70-uaccess.rules を/etc/udev/rules.d にコピーしてCD用のエントリを消すと、uaccessタグが付与されなくなるのでまずは一般ユーザからCD, DVD焼けなくなる。 でも読む分にはudisks2がユーザ権限でmountしてくれるのでこれでOK。

この方法の問題点としては、70-uaccess.rules に将来の変更で対象となるデバイスが追加された場合にうまく反映されない点があるので手放しでおすすめはしづらいところ。アップデート時には注意が必要です。

systemd以前の世代だと類似のしくみがhal+ConsoleKitだったりudev+ConsoleKitで別途実装されているのでこの方法とちょっとかわります。「一般的にこのコマンド叩けばOK」という美しい方法はないのでした。なやましい……。

Ansible 2.3.2のモジュール サポート状況

(2017年9月11日追記) http://docs.ansible.com/ansible/latest/modules_support.html  内のモジュールの分類にNetworkとCertifiedが追加された。あとcoreモジュールの一覧へのリンクも追加された。この記...