えんでぃの技術ブログ

えんでぃの技術ブログ

ネットワークエンジニアの視点で、IT系のお役立ち情報を提供する技術ブログです。

SELinuxのRBAC、UBAC、MLS、MCS

SELinux_logo

SELinuxシリーズ

本記事は、SELinuxシリーズの5記事目です。

  1. Linuxプロセスアクセス制御の概要
  2. SELinuxの概要
  3. SELinux Type Enforcement
  4. SELinuxの実践
  5. (参考) SELinuxのRBAC、UBAC、MLS、MCS ←今ココ
  6. (参考) SELinux Module Policyのソースコード読解、ビルド
  7. 参考URL

1〜3記事目は、4記事目を理解するための前提知識をカバーしています。
4記事目が最も重要で、SELinuxの具体的な操作方法やコマンド、トラブルシューティング手順を紹介しています。

5記事目以降は参考情報です。

SELinuxの関連記事は、SELinuxタグから探せます。

一連の記事はFedora環境を前提として書いています。
FedoraRHELに類するディストリビューションであればほぼ同等の挙動になると思いますが、他のディストリビューションでは挙動に差異がある可能性があるのでご注意ください。

お伝えしたいこと

本記事は、SELinuxのアクセス制御の中でも、TE (Type Enforcement) を除く以下の技術について概要を説明します。

  • RBAC (Role-Based Access Control)
  • UBAC (User-Based Access Control)
  • MLS (Multi-Level Security)
  • MCS (Multi-Category Security)

正直、これらの技術について知る必要は基本ありません。
なぜなら、targeted policyにおいて実質的に有効なのはTEとMCSのみであり、MCSはKVMやコンテナ仮想化技術でしか使われていないためです。
KVMやコンテナ仮想化技術を使う方にとってはMCSは重要かもしれませんが、大半の方はTEだけ知っていれば十分だと思います。

言ってしまえば、targeted policyを使うのであれば、本記事に書いてある情報はほぼ不要です。
とはいえ、targeted policyを使っている方にとっても以下の情報は気になるかもしれません。
よろしければ、気になるトピックのみ拾い読みいただければと思います。

  • TE以外のアクセス制御技術の概要
  • targeted policyではTEとMCS以外実質無効であると判断できた理由

RBAC (Role-Based Access Control)

RBACの一般論

一般論からお話すると、RBACとは下図のようなアクセス制御です。
具体例として、インフラ自動化システムを想定したRBACイメージを取り上げます。

rbac_general

RBACにおいては、UserとRoleをそれぞれ別に定義します。

Userはログインに使うもので、ユーザー名と認証キー (ログインパスワード) を持ちます。
Roleは役割を表すもので、各種リソースへの読み書き実行権限を保有します。

そして、UserとRoleを紐付けることで、Userはシステムへのアクセス権限を得るという仕組みです。
Userに対して直接権限を付与ということはなく、必ずRoleに対して権限を付与するのがRBACの原則です。
※製品によっては、Roleへの権限付与を原則としつつもUserへの権限付与を許可しているものもあると思います

組織の規模にも依りますが、Userの数は数百〜数万程度の想定です。
それに対してRoleの数は数〜数十程度の想定です。

組織変更や昇格によってUserの役割が変わったときはUserとRoleの紐付けを変更します。
そして組織やシステムの統廃合などによってリソース構成が変わったときは、Role自体の定義や構成を見直します。

このように、様々な理由からユーザーや権限の構成が変わることがあります。
RBACはUser-Role間の紐付けやRole定義を見直すだけでその変化に追従できるという設定変更の容易さ、そして設計思想のわかりやすさにメリットがあります。
組織やシステムの変更のたびに、数万のユーザー全ての権限設定を個別に修正する必要はありません。

また権限構造がわかりやすいので、設計のブラックボックス化によるセキュリティホールも撲滅しやすいのではないかと思います。

さて、一般論はこの辺にしておきます。
次のセクションから、SELinuxにおけるRBACについて説明します。

SELinuxにおけるRBAC

SELinuxにおけるRBAC (Role-Based Access Control) とは、プロセスが持つSELinux Roleに基づいて、追加のアクセス制御を行う機能です。1,2

例えば、以下のようなSecurity Contextがあったとします。

id -Z
# unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

こちらのコマンド出力は、targeted policyの環境においてログインしたときのSecurity Contextがunconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023であることを表しています。
ここで真ん中のunconfined_r:unconfined_tの部分から、unconfined_r Roleとunconfined_t Typeが紐付いていると読み取れます。

Roleは定義の段階で紐付け可能なTypeが決まっています。
Linuxのリソースが特定のRoleを持った段階で、そのリソースが取りうるTypeはRoleと紐づいたもののみに限定されます。

SELinux Type Enforcement - #Security Contextの決まり方でも触れたように、子プロセスが発行されるときは親プロセスのSecurity Contextを引き継ぐのが原則です。
つまり、原則としては今回のユーザーが発行するプロセスは基本的にunconfined_rというRoleを持ちます。
そして、子プロセスはunconfined_rと紐付け可能なTypeしか持てないということになります。
例えtype_transitionルールによってそのプロセスが別のTypeを持つ条件が揃っていたとしても、そのTypeがunconfined_rと共存できない限りType Transitionには失敗します。

まとめると、SELinuxにおけるRBACとは、リソースが持つRole (役割) によって取りうるType (権限) を制限するアクセス制御です。

targeted policyにおけるRBAC

本セクションでは、targeted policyに限定したお話をします。

ログインシェルのRoleの決まり方

SELinux Type Enforcement - #Security Contextの決まり方で触れましたが、Roleは以下のように決まります。

  • プロセス
    • 親プロセスから引き継ぐ
    • Role Transitionの条件を満たせば、Roleが変わることもある
    • default_roleルールは実質定義されていないので関係ない
  • ファイル
    • 基本全てobject_rになる

しかし、ログインシェルのSecurity Contextについては別の仕組みがあります。
本セクションでは、その仕組みを簡単に説明します。

まずは、以下の概要図を見てください。
Linuxログインユーザー、SELinux User、Role、Typeの紐付けが示されています。
こちらの概要図は、私がコマンドをいくつか実行しながらざっくり調べて描いたものですので、「だいたいあってる」といった感覚で見てください。

login_targeted

targeted policyのログインシェルの文脈において関係ないものは全て黒く網掛けしました。
(※) 細かいことを言えばtargeted policyでもunconfined_rからsystem_rにRole Transitionすることはありますし、ファイルは形式的にobject_rを持ちますが...

次のセクションに続きます。

targeted policyにおいてRBACが実質無効である理由

前のセクションより、targeted policyにおいてはどのLinuxユーザーでログインしてもunconfined_u Userとunconfined_r Roleにひも付きます。
一般ユーザーでも、rootユーザーであってもです。

「targeted policyでは実質的にRBACが無効化されている」根拠はそこにあります。
全てのユーザーがrootと同じroleに属するということは、roleによる権限を区別していないということです。
言い換えれば、RBACの観点では全てのユーザーがrootと同等の権限を持っています。

ちなみに、unconfined_r自身はそれほど多くのTypeと紐づいていません。
unconfined_rは圧倒的多数のTypeと紐づくsystem_rにRole Transitionするためのルールが多数定義されていることで、実質的に管理者と同等数のTypeとの紐付けが可能となっています。
Role Transitionとは、新規プロセスが発行されるタイミングで、条件次第でRoleが変化するルールのことです。

以上が、targeted policyにおいてはRBACが実質的に無効化されている理由でした。

以降のRBACに関するセクションは、targeted policyを使う方にとっては関係ない内容ですので、気になる方のみご覧いただければと思います。

(参考) Role構成の調べ方

#ログインシェルのRoleの決まり方 (targeted policyの例)で示した概要図をどのように調べたか、コマンド実行ログを交えて補足します。
似たような説明は、Gentoo Wiki - SELinux/Users and loginsにもあります。

まず、先に結論を示しておきます。
targeted policyの環境にログインすると、unconfined_u:unconfined_r:unconfined_tというSecurity Contextになります。

id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

どうしてこの結果になるか、各種紐付け設定を見ながら説明していきます。

LinuxログインユーザーとSELinux Userの紐付けは、semanage login -lで確認できます3

sudo semanage login -l
# Login Name           SELinux User         MLS/MCS Range        Service

# __default__          unconfined_u         s0-s0:c0.c1023       *
# root                 unconfined_u         s0-s0:c0.c1023       *

rootユーザーでLinuxにログインすると、SELinux Userはunconfined_uになります。
__default__は「その他全てのユーザー」を表し4unconfined_uが紐づきます。
つまり、targeted policyにおいてはどのユーザーでログインしても必ずunconfined_uになります。

semanage loginにオプションを渡して実行すればこの紐付け設定を変えることができますが、mls policyでRBACをフル活用する場合を除いて変える機会はないでしょう。

続いて、SELinux UserとRoleの紐付け情報をsemanage user -lで確認します。

                Labeling   MLS/       MLS/            
SELinux User    Prefix     MCS Level  MCS Range       SELinux Roles

guest_u         user       s0         s0              guest_r
root            user       s0         s0-s0:c0.c1023  staff_r sysadm_r system_r unconfined_r
staff_u         user       s0         s0-s0:c0.c1023  staff_r sysadm_r system_r unconfined_r
sysadm_u        user       s0         s0-s0:c0.c1023  sysadm_r
system_u        user       s0         s0-s0:c0.c1023  system_r unconfined_r
unconfined_u    user       s0         s0-s0:c0.c1023  system_r unconfined_r
user_u          user       s0         s0              user_r
xguest_u        user       s0         s0              xguest_r

1つのSELinux Userに複数のSELinux Roleが紐付いている箇所があります。
例えば、root Userにはstaff_rsysadm_rsystem_runconfined_rの4つが紐付いています。
実際にログインしたときにどのログインユーザーに紐づくかは、以下のファイルによって制御されているようです。

  • /etc/selinux/{SELINUXTYPE}/contexts/*_contexts
  • /etc/selinux/{SELINUXTYPE}/contexts/users/*

これらの設定ファイルにより、unconfined_uにログインした場合にはsystem_rではなくunconfined_r:unconfined_tが優先的に紐付けられます。
この設定ファイルにより、ログインシェルに紐づくRoleだけでなくTypeも決まります。

SSH経由でunconfined_uにログインする状況を想定して、いくつか関連するファイルを見てみましょう。

  • /etc/selinux/targeted/contexts/default_contexts
  • /etc/selinux/targeted/contexts/openssh_contexts
  • /etc/selinux/targeted/contexts/users/unconfined_u

まずはdefault_contextsの抜粋です。
man default_contextsによると、1列目がログインプロセスのContext、2列目以降がログインシェルに割り当てるRoleとTypeの組み合わせです。
2列目以降はスペース区切りで複数の組み合わせが示されていますが、何度か実験して挙動を確認したところ、左に書いてあるものが優先されるようです。
ここでは、user > staff > sysadm > unconfinedの順ですね。

system_r:sshd_t:s0     user_r:user_t:s0 staff_r:staff_t:s0 sysadm_r:sysadm_t:s0 unconfined_r:unconfined_t:s0

続いてopenssh_contextsを見てみます。
何を意味するかはわかりませんが、紐付け情報ではなさそうなのでスルーします。

privsep_preauth=sshd_net_t

続いて、users/unconfined_uを見てみます。
ここには、unconfinedしか書かれていません。

system_r:sshd_t:s0     unconfined_r:unconfined_t:s0

ということで、default_contextsusers/unconfined_uSSHログインに関するルールが書いてありました。
どちらが優先されるかはmanにも書いてありませんでしたが、mls policyの環境で実際に試してみたところusers/unconfined_uの方が優先されました。
(※) ファイルを書き換えてsshでログインし、id -ZでSecurity Contextを確認するとわかります

mls policyにおけるRBAC

mls policyではどう変わるのか、簡単に示したいと思います。
まずは概要図を示します。

targeted policyとの主な違いは、以下のとおりです。

  • unconfined_rは使われない
  • Linuxユーザーのrootは、SELinuxユーザーのrootと紐づく
  • その他ユーザーは、user_uと紐づく
  • rootstaff_uauditadm_rdbadm_rの紐付けが追加されている

login_mls

guest_uxguest_uなど、全く紐付いていないSELinux Userもあります。
これらについては、semanage userコマンドによって必要に応じてLinux Userとの紐付けを追加して使う想定のようです。

以下にmls policyにおけるコマンド実行結果を示します。

rootユーザーにSSHログインすると、デフォルトではstaff_rになります。
したがって、rootと言えども管理者権限はなく、色々とできません。
staff_rにはsudo権限があるので、sudoerファイルを編集してsudoによってroleを手動で変える運用が基本のようです5
sudoerをどのように編集するかまでは特に調べていません。

id -Z
# root:staff_r:staff_t:s0-s15:c0.c1023

非rootユーザーは、user_rに割り当てられます。

id -Z
# user_u:user_r:user_t:s0

ちなみに、user_rstaff_rとは異なりsudoに対応していないので、sudoコマンドを実行しても何も変わりません。
targeted policyの環境であれば一般ユーザーでも実行できるjournalctlが、mls policyにおいてはsudo -i (≒su -) してもできません。
SELinux Roleが変わっていないため、SELinux的には権限昇格できていないからです。
DAC (Discretionary Access Control) では許可されても、MAC (Mandatory Access Control) では拒否されます。

$ id
uid=1000(endy) gid=1001(endy) groups=1001(endy),10(wheel),1000(shared) context=user_u:user_r:user_t:s0

$ id -Z
user_u:user_r:user_t:s0

$ sudo -i
[sudo] password for endy:

# id
uid=0(root) gid=0(root) groups=0(root) context=user_u:user_r:user_t:s0

# id -Z
user_u:user_r:user_t:s0

# journalctl
No journal files were opened due to insufficient permissions.

sudoの設定はしていないものの、SELinuxユーザーと紐づく範囲であればnewrole -rでRoleを付け替えることは可能です。
紐付けはsemanage user -lで確認できます。

まずはrootユーザーにsuなどを使わず "直接" ログインして、staff_uが割り当てられた状態にします。
その後、newrole -rコマンドでsysadm_rに昇格します。
staff_usysadm_rとも紐付いているので、これが可能となります。

# id -Z
root:staff_r:staff_t:s0-s15:c0.c1023

# newrole -r sysadm_r
Password: 

# id -Z
root:sysadm_r:sysadm_t:s0-s15:c0.c1023

# journalctl
(ログが出る)

最後に、その他の設定を列挙します。

LinuxユーザーとSELinux Userとの紐付けです。

sudo semanage login -l
# Login Name   SELinux User  MLS/MCS Range    Service
# __default__  user_u        s0-s0            *
# root         root          s0-s15:c0.c1023  *

SELinux UserとRoleの紐付けです。

semanage user -l

#               Labeling  MLS/       MLS/                          
# SELinux User  Prefix    MCS Level  MCS Range        SELinux Roles

# guest_u       user      s0         s0               guest_r
# root          user      s0         s0-s15:c0.c1023  auditadm_r secadm_r staff_r sysadm_r system_r
# staff_u       user      s0         s0-s15:c0.c1023  auditadm_r secadm_r staff_r sysadm_r system_r
# sysadm_u      user      s0         s0-s15:c0.c1023  sysadm_r
# system_u      user      s0         s0-s15:c0.c1023  system_r
# user_u        user      s0         s0               user_r
# xguest_u      user      s0         s0               xguest_r

/etc/selinux/mls/contexts/default_contextsには、sshログイン時にはデフォルトでuser_r > staff_rが書かれています。

# (一部抜粋)
system_r:sshd_t:s0      user_r:user_t:s0 staff_r:staff_t:s0 

/etc/selinux/mls/contexts/openssh_contextsには、targeted policyと同様、重要な情報はありませんでした。

/etc/selinux/mls/contexts/users/rootには、以下のようにコメントアウトされた設定がありました。

# (一部抜粋)
# Uncomment if you want to automatically login as sysadm_r
#system_r:sshd_t:s0        unconfined_r:unconfined_t:s0 sysadm_r:sysadm_t:s0 staff_r:staff_t:s0 user_r:user_t:s0

指示通りに上記行のコメントを外すと、sshでrootユーザーにログインしたときに最初からsysadm_r:sysadm_tが割り当てられます。
users/rootの設定は、default_contextsよりも優先されました。

(参考) Roleの意味の調べ方

Roleの意味は、#ログインシェルのRoleの決まり方#mls policyの例の概要図に記載の通りです。

以下に情報ソースを貼っておきます。

RHEL8のマニュアルにtargeted policy, mls policyのRoleの権限の一覧表が書いてあったので、参考にしました。

selinux-policy-docsパッケージをインストールしていれば、一部のRoleの説明をmanでも確認できます。
しかし、manの情報は部分的にしか参考になりません。

system_uobject_rについては、The SELinux Notebookで言及されていました。

psコマンドを実行すると、システムが自動起動しているデーモンやスレッドがsystem_u:system_rを持っていることがわかります。

ps -eZ | head -3
# LABEL                               PID TTY          TIME CMD
# system_u:system_r:init_t:s0           1 ?        00:00:01 systemd
# system_u:system_r:kernel_t:s0         2 ?        00:00:00 kthreadd

適当なフォルダでls -Zを実行すると、全てのファイルがobject_rを持っていることがわかります。

ls -Z1 /
#        system_u:object_r:bin_t:s0 bin
#       system_u:object_r:boot_t:s0 boot
#     system_u:object_r:device_t:s0 dev
#        system_u:object_r:etc_t:s0 etc
#  system_u:object_r:home_root_t:s0 home
#        system_u:object_r:lib_t:s0 lib
#        system_u:object_r:lib_t:s0 lib64
# system_u:object_r:lost_found_t:s0 lost+found
#        system_u:object_r:mnt_t:s0 media
#        system_u:object_r:mnt_t:s0 mnt
#        system_u:object_r:usr_t:s0 opt
#       system_u:object_r:proc_t:s0 proc
# system_u:object_r:admin_home_t:s0 root
#    system_u:object_r:var_run_t:s0 run
#        system_u:object_r:bin_t:s0 sbin
#        system_u:object_r:var_t:s0 srv
#      system_u:object_r:sysfs_t:s0 sys
#        system_u:object_r:tmp_t:s0 tmp
#        system_u:object_r:usr_t:s0 usr
#        system_u:object_r:var_t:s0 var

最後の手段として、アクセス制御ルールをRoleと紐づくTypeなどで検索すると、Roleの役割がざっくりわかります。

# dbadmの役割を調べる
sesearch -A -ds -s dbadm_t

# Role Transitionの傾向を見る
sesearch --role_trans

(参考) RoleとTypeの紐付け制御の実機検証

Roleの定義に含まれないTypeは、Roleと紐付けられないことを実機で確認します。

今回の検証には、プロセスに任意のSecurity Contextを付与するrunconコマンドを使います。
runconは現在のSELinux Userから変更しようとするとエラーになるので、現在のSELinux Userの範囲で試します。

mls policyの環境で非rootユーザーにログインします。
デフォルトでuser_rが割り当てられます。
後続の検証のため、allow Statementでエラーになってもアクセス拒否されないようにPermissiveにしておきます。

id -Z
# user_u:user_r:user_t:s0

setenforce 0
getenforce
# Permissive

引き続き、mls policyの環境でrunconを使ってTypeを変えてみましょう。

以下の例では、user_rと紐付いているssh_home_tを指定しています。
明らかにデタラメなTypeなので本来であればアクセス拒否されそうですが、Permissiveにしてあるので問題なくpsコマンドが通ります。
ssh_home_tが紐付いていることは、予めseinfo -xr user_r | grep ssh_home_tで確認済みです。

runcon -t ssh_home_t ps -ZC ps
# LABEL                               PID TTY          TIME CMD
# user_u:user_r:ssh_home_t:s0        9264 pts/1    00:00:00 ps

次の例では、user_rと紐付かないunconfined_tを割り当てようとしています。
すると、invalid context: ‘user_u:user_r:unconfined_t:s0’とエラーになります。
この検証結果から、Roleの定義時点で対象のTypeと紐付いていない場合、そのRoleとそのTypeは同じリソース上で共存できないということになります。
そしてこの制限はアクセス拒否ではなくSELinuxのその他のエラーなので、Permissive設定であってもエラーになります。

runcon -t unconfined_t ps -ZC ps
# runcon: invalid context: ‘user_u:user_r:unconfined_t:s0’: Invalid argument

UBAC (User-Based Access Control)

UBACの概要

UBAC (User-Based Access Control) は、RBACのオプション的な存在です。

RBACをもってしても、Roleが全く同じSELinux Userが複数存在した場合、それらのUser間の独立性は保たれません。
UBACはSubjectとObjectでSELinux Userが異なる場合に、仮にRoleが同じ場合でも特定のアクセスを拒否する実装です6
特定ユーザーが侵入されたとしても、UBACの制御によって異なるSELinux Userのリソースを覗き見したり改変したりすることができなくなります。

UBACは、SELinuxのBase Policyをビルドするタイミングで有効/無効を切り替えることができます7
Fedoraが管理しているSELinux Policyでは、UBACは有効化されています8

UBACが有効化されると、ソースコード上の随所でif文がTrueになり、追加のconstraintルールが定義されます9
(※) ソースコードを読む必要はありません

UBACが定義する具体的なルールについては、#UBAC関連のConstraintにて紹介します。

targeted policyにおいてUBACが実質無効である理由

#UBAC関連のConstraintに記載の通り、UBACはSubjectとObjectのSELinux Userが不一致の場合に特定のアクセスを拒否する制御です。

RBACの#ログインシェルのRoleの決まり方にて説明したとおり、targeted policyでは常にログインシェルがunconfied_uというSELinux Userになります。
つまり、常に同じSELinux Userになるので、UBACのルールによってアクセス拒否されることは基本ありません。

MLS (Multi-Level Security)

MLSとは

MLS (Multi-Level Security) とは、Security ContextのSensitivity Levelを利用したアクセス制御です。10,11,12
TE、MCSと比較して、最も粒度の細かいアクセス制御を実装します。

mls policyにおいては、Sensitivity Levelはs0からs15まであります。
s0が最もSensitivity Levelが低く、s15が高いです。

s15のファイルは、いわゆるトップシークレットで、絶対に流出を防ぎたいファイルです。
そしてs15のプロセスは、高セキュリティ環境で動作することを許可されており、トップシークレットのファイルを読み取れます。

MLSの原則は "no read up, no write down" です。
例えば、あるプロセスのSensitivity Levelがs5だったとします。
このプロセスは自分よりSensitivity Levelの高い、s6以上のファイルからデータを読み取れません。
また、自分よりSensitivity Levelの低い、s4以下のファイルにデータを書き込めません。
自分以下のレベルのファイルへの読み込み、自分以上のレベルのファイルへの書き込みについて、どのように許可/拒否するかはディストリビューションによって差異があるようです。
RHELについては次のセクションで触れます。

MLSは、情報の機密性に応じてファイルのSensitivity Levelをレベル分けし、高レベルのファイルからの情報流出を防ぐことを重視したいときに使う技術です。
そう聞くと「顧客情報や企業秘密を守りたい」という一般的な用途にマッチしそうな気もしますが、大抵の場合は他のアクセス制御で十分といわれています。
MLSを使う団体は、しばしば「軍隊」と表現されます。
私達が扱うには、恐らく難しいものなのでしょう。

RHELにおけるMLS

RHEL8の場合、Sensitivity Levelに基づくアクセス許可は原則として以下のように実装されているようです13

  • プロセスは自身よりレベルの高いファイルを読み取れない (no read up)
  • プロセスは自身と同じレベルのファイルにしか書き込めない (write equality)

上記ルールは、原則にあった「プロセスは自身よりも低いレベルのファイルに書き込めない (no write down)」よりも更に強力です。
プロセスが自身より高いレベルのファイルを書き換えることを防ぐので、レベルの低いプロセスによる改ざんも防止するということになります。

詳細なルールは、#MLS関連のConstraintにてサンプルを示していますが、MLSがデフォルトでどのようにルール定義されているかの理解は難しすぎるので諦めました。

targeted policyにおいてUBACが実質無効である理由

targeted policyにおいては、Sensitivity Levelがs0しか存在しないためです。
単一の値では大小関係によってルール設定のしようがないので、targeted policyにおいてはMLSは実質無効と判断しました。

以下がtargeted policyにおけるSensitivity Levelの表示結果です。

seinfo --sensitivity

# Sensitivities: 1
#    s0

同じコマンドをmls policyで実行すると、以下のようになります。

seinfo --sensitivity

# Sensitivities: 16
#    s0
#    s1
#    s2
#    s3
#    s4
#    s5
#    s6
#    s7
#    s8
#    s9
#    s10
#    s11
#    s12
#    s13
#    s14
#    s15

Sensitivity Levelの表記

Gentoo Wikiを参考に、Sensitivity Level表記の例を示します。

  • s0 → low=s0, high=s0
  • s0-s0 → low=s0, high=s0 ※上と同じ
  • s0-s2 → low=s0, high=s2

また、mcstransdデーモンが動作している場合、/etc/selinux/{SELINUXTYPE}/setrans.confに従って表示が変わる場合があります。
ファイルの書式は、man setrans.confに書いてあります。

targeted policyにおいては、SystemLowもSystemHighもs0になります。

cat /etc/selinux/targeted/setrans.conf
# s0=SystemLow
# s0-s0:c0.c1023=SystemLow-SystemHigh
# s0:c0.c1023=SystemHigh

mls policyにおいては、また別の意味を持ちます。
SystemLow=s0、SystemHigh=s15です。

cat /etc/selinux/mls/setrans.conf
# s0=SystemLow
# s15:c0.c1023=SystemHigh
# s0-s15:c0.c1023=SystemLow-SystemHigh
# s1=Unclassified
# (以下略)

MCS Categoryも含めてalias名が定義されていますが、MCS Categoryの表記方法については#Categoryの表記を参照してください。

MCS (Multi-Category Security)

MCS (Multi-Category Security) は、SubjectとObjectの間でCategoryの値を含むか含まないかを比較し、それによってアクセス制御を行います。

TEと比較すると、Typeに加えてCategoryによっても区別されるので、より粒度の細かいアクセス制御が実装されます。

MLSと比較すると、Sensitivity Levelには明確な大小関係があるのに対し、Category間には何の関係もありません。
重要なのはCategoryが一致するかしないかです。
更に言うとCategoryは複数持てるので、含むか含まないかとなります。

MCSのCategoryもmlsconstrainのdomdombyの結果に影響するものの、本質的にはMLSとMCSは全く別の概念です。
MCSはtargeted policyのようにMLSが無効 (s0しかない) 環境でも動作します。

デフォルトでは、MCS関連の設定としてはCategoryが1024個作られているのみです。
このCategoryに対して、特に意味づけはされていません。
コマンド実行結果は、targeted policyでもmls policyでも変わりません。

seinfo --category

# Categories: 1024
#    c0
#    c1
# (中略)
#    c1022
#    c1023

categoryに対して意味づけを行うのは、MCSを実装しているアプリケーションによる制御です。

イメージとしては、複数のコンテナプロセスを起動した場合、Type Enforcementでは各プロセスに同じTypeが割り当てられます。
一方、MCSの動作としては各プロセスに異なるCategoryを割り当てます。
結果として、MCSは複数のコンテナを区別し、TEよりも細かい粒度でアクセス制御できるようになります14

targeted policyにおいてMCSが実質無効である理由

targeted policyにおいてMCSを利用しているのは、RHEL8のマニュアルによると以下のみとのことです。

  • OpenShift
  • virt (sVirt)
  • sandbox
  • network labeling
  • containers (container-selinux)

上記製品とSELinuxを併用していない限りは、SELinuxを意識する必要はありません。

もし上記製品を利用する場合には、各製品のドキュメントからMCSの扱い方を調べることになると思います。

Categoryの表記

Gentoo Wikiを参考に、Category表記の例を示します。

Categoryの表記 意味
c0 c0
c0,c15 c0c15
c4.c8 c4c8 (c4,c5,c6,c7,c8)
c2,c6.c9 c2c6c9 (c2,c6,c7,c8,c9)

(参考) Constraint Statement

Constraint Statementは、UBAC、MLSでは特に重要な意味を持ちます。
RBACでも部分的に使われています。

TEの世界では、allow Statementによって各種アクセス許可を定義していました。
Constraint Statementは、このallowに加えて、「allowによってXXを許可する。ただし、許可するのはXXXの場合のみ」の「ただし〜」の条件を定義します。

allow StatementはSubject/ObjectのTypeという条件しか指定できませんでしたが、Constraint StatementはSELinux User, Role, Rangeなども含めてより細かい条件設定が可能です。

Constraint StatementのSyntaxは、以下の構造です15

constrain class perm_set expression | expr ...;

Constraintの対象をclass (Object Class)perm_set (Permissions) で絞り込み、対象のPermissionsを許可する追加の条件式をexpressionの部分に書きます。

expressionsは、「左辺」、「演算子」、「右辺」の3つの部位で構成されます。

左辺/右辺には以下が入ります。

l1,l2,h1,h2のlow、highの意味については、#Sensitivity Levelの表記にて説明します。
これら4つの演算対象は、正確にはMLS関連のConstraintsを規定するmlsconstrain Statementで使うものですが、constrainmlsconstrainは構文が一緒なのでここで併せて説明しています。

演算対象 意味
u1 SubjectのUser
r1 SubjectのRole
t1 SubjectのType
l1 Subjectの "Low" Range
h1 Subjectの "High" Range
u2 ObjectのUser
r2 ObjectのRole
t2 ObjectのType
l2 Objectの "Low" Range
h2 Objectの "High" Range

演算子==(等しい)、!=(異なる) などが登場します。

他にも、式と式の間をandorで連結して複雑な条件式を組むことがしばしばあります。

後続のセクションで具体例を出してみますが、「こういったものがあるんだなぁ」というぐらいの理解で十分かなと思います。

RBAC関連のConstraint

r1とr2でgrepすると、Role関連のConstraintを確認できます。

Type Transitionなど一部の処理は、SubjectとObjectのRoleが一致していなければ許可されないようなルール付けがされています (r1 == r2)
しかし、OR条件でcan_change_process_identity Attributeに含まれていればOKなど、いくつか例外も認めているようです。

seinfo --constrain | grep -P 'r1|r2'
#    constrain process dyntransition (r1 == r2 or ( t1 == can_change_process_identity ) and ( t2 == process_user_target )); 

#    constrain process { transition dyntransition noatsecure rlimitinh siginh } (r1 == r2 or ( t1 == can_change_process_role ) and ( t2 == process_user_target ) or ( t1 == cron_source_domain ) and ( t2 == cron_job_domain ) or ( t1 == can_system_change ) and ( r2 == system_r ) or ( t1 == process_uncond_exempt )); 

UBAC関連のConstraint

UBAC関連のConstraintは、u1 == u2という条件式によって実装されています。
SubjectとObjectのSELinux Userが異なる場合に、特定のアクセスを拒否します。

seinfo --constrain | grep -P 'u1|u2'
#    constrain alg_socket { create relabelto relabelfrom } (u1 == u2 or ( t1 == can_change_object_identity )); 
#    constrain appletalk_socket { create relabelto relabelfrom } (u1 == u2 or ( t1 == can_change_object_identity ));
# (以下略)

MLS関連のConstraint

MLS関連のConstraintは、mlsconstraint Statementによって記述されます。
domdombyという見慣れない演算子が出てきますが、これはdom>= (larger than or equal to)domby<= (less than or equal to)です。

また、RangeにはMCSのCategoryも含みますが、Category間には大小関係の概念はありません。
domの場合は、左辺のCategoryが右辺のCategoryと同じか内包している関係を表します。
dombyの場合は、その逆です。

上記のdomdombyの意味は、各種ドキュメントでdominatesdominated byについて説明している言い回しから推定しました。16,17

MLSのSensitivity Levelは、LowからHighまで範囲で値を取りうるため、以下のようにl1とh2を比較したりと条件が複雑に見えます。
そしてあまりにもルールが多いため、これを理解するのは諦めました。

seinfo --constrain | grep mlsconstrain
#    mlsconstrain association polmatch (l1 dom l2 and ( h1 domby h2 )); 
#    mlsconstrain association recvfrom (l1 dom l2 and ( l1 domby h2 ) or ( t1 == mlsnetreadtoclr ) and ( h1 dom l2 ) or ( t1 == mlsnetread ) or ( t2 == unlabeled_t ));

MCS関連のConstraint

MCS関連のConstraintは存在しません。
Categoryに基づいたアクセス制御は、MCSを認識するアプリケーション側の実装で行います。

詳細は、#MCS (Multi-Category Security)を参照してください。

(参考) mls policyに変更する方法

実用上使うことはないと思いますが、やり方だけ触れておきます。
試す場合は、クローンバックアップを取得の上、VMなどでお試しください。

今回はmlsの例で説明しますが、minimum policyに変更したい場合はmlsの部分をminimumに読み替えれば同じ手順で対応できます。

まず、有効化するpolicyに応じて必要なパッケージをインストールします。
これによって/etc/selinux/mls配下にSecurity Policyなど、mls policyの動作に必要なファイルがインストールされます。

sudo dnf install selinux-policy-mls

続いて、/etc/selinux/configを以下のように書き換えます。

SELINUX=permissive
SELINUXTYPE=mls

いきなりenforcingにするのは危険なので、permissiveにしておきます。
permissiveは、アクセス制御違反が発生したときに監査ログは出すが、実際にはアクセス拒否しないモードです。
Linuxの起動に必要な処理がSELinuxによって拒否され、起動自体に失敗するという最悪の自体を防ぐための保険として、permissiveにします。
(※) SELinux起因でLinuxが起動できなくなったときは、Boot EntryからKernel Command Line Parameterにenforcing=0を追記することで、SELinuxをPermissiveモードにした上でLinuxを起動できます。その後は任意の手順でLinuxを復旧できます。手順の詳細は、SELinuxの実践 - #一時的に無効化する方法を参照してください。

続いて、/.autorelabelファイルを配置します。
mls policyにおいてはType以外のSecurity Contextも重要になるので、-Fを指定することでTypeだけでなくUser, Role, Rangeも全てRelabel対象にします。

sudo fixfiles -F onboot

この状態でOS再起動すると、Linuxブート中にrelabel処理が走ります。

SELINUXTYPEを変えてOS再起動しただけでは、File Contextは書き換わりません。
relabelせずにOS再起動すると、想定外のFile Contextが割り当てられていることによってアクセス拒否が大量に発生します。
SELINUXTYPEを変えるとき、SELinuxをdisabledからenforcingへ変更する時は、必ずrelabelするようにしてください。

relabelについて、詳細はSELinux Type Enforcementの「File Contextとrelabel」を参照してください。

最後に、OS再起動します。

sudo reboot

relabel処理を経て、OSが起動してきます。

journalctl -qen all -t audit -g deniedjournalctl -qen all -t setroubleshootSELinux周りの監査ログの状況を確認しつつ、問題なさそうであればSELinuxを有効化します。
(※) 正直、大量のアクセスエラーが出ます。でも大丈夫です。検証用のVMでしたらそのまま有効化しちゃってください

setenforce 1

mls policyの検証をする場合、/etc/selinux/configにてSELINUX=enforcingにしないで運用するのがおすすめです。
mls policyでLinuxの起動処理が正しく完了するか調べたいのであれば別ですが、そうでなければ必要のときのみEnforcingにし、何があっても電源OFF/ONでPermissiveに戻せる状態にしておくと安心だと思います。
実際、上述の手順でrelabelも含めて対応したとしても、mls policyにおいては大量の監査ログが発生します。

以上でpolicy変更は完了です。

ここで紹介した手順は、以下のリンクを参考にしました。

RHEL8 - Using SELinux - Switching the SELinux policy to MLS

まとめ

RBAC、UBAC、MLS、MCSの概要について紹介しました。
RBAC、UBAC、MLSについては、targeted policyにおいては実質無効である理由についても触れました。

結局のところ、targeted policyにおいてはTEとMCS以外を使うことはありません。
MCSも以下の製品にしか使われないので、MCSも関係なければTEのみ意識すれば十分です。

  • OpenShift
  • virt (sVirt)
  • sandbox
  • network labeling
  • containers (container-selinux)

SELinuxはTEを覚えましょう」というのが本記事の一番言いたいことです。

次の記事

SELinuxのアクセス制御ルールをソースコードから書かなければならない場面がごく稀に存在します。
そういった状況に対処できるよう、基本的なファイル構成やソースコード記述のお作法について概要レベルで紹介します。
基本的にはKernel Policy LanguageとM4をカバーします。
CILについては、2022年1月現在ほとんど使われていないので、概要のみ紹介します。

endy-tech.hatenablog.jp

SELinuxの実践

SELinux_logo

SELinuxシリーズ

本記事は、SELinuxシリーズの4記事目です。

  1. Linuxプロセスアクセス制御の概要
  2. SELinuxの概要
  3. SELinux Type Enforcement
  4. SELinuxの実践 ←今ココ
  5. (参考) SELinuxのRBAC、UBAC、MLS、MCS
  6. (参考) SELinux Module Policyのソースコード読解、ビルド
  7. 参考URL

1〜3記事目は、4記事目を理解するための前提知識をカバーしています。
4記事目が最も重要で、SELinuxの具体的な操作方法やコマンド、トラブルシューティング手順を紹介しています。

5記事目以降は参考情報です。

SELinuxの関連記事は、SELinuxタグから探せます。

一連の記事はFedora環境を前提として書いています。
FedoraRHELに類するディストリビューションであればほぼ同等の挙動になると思いますが、他のディストリビューションでは挙動に差異がある可能性があるのでご注意ください。

お伝えしたいこと

前回のType Enforcementの記事では、デフォルトのtargeted policyに限定しつつもSELinuxを理解するために必要な理論を一通り網羅しました。

本記事では、前回の記事の内容をベースにSELinuxの実践的な知識についてカバーしたいと思います。
具体的には、「SELinuxでアクセス制御エラーのログを検知したときの対処法」を中心に紹介します。

網羅性を重視した結果、本記事には滅多に使わない知識も含まれています。
そういった部分にはセクション名に "(参考)" をつけましたので、関連性が薄いと感じたら必要に応じて読み飛ばし、次に進んでください。

(参考) SELinuxの運用に必要な知識

SELinuxの運用に必要な知識とは、主に「アクセス拒否の対処法」です。
私がそう考えた理由について、以下に補足します。

SELinuxは、ホワイトリスト形式でプロセスのアクセス制御を実装します。
allowルールで許可されないアクセスパターンは、全て拒否されます。

SELinuxのデフォルトの動作モードは、targeted policyです。
targeted policyにおいては、代表的なデーモンプロセスがデフォルトのファイルパスやポート番号で動作するためのアクセス許可設定がある程度網羅的に設定されています。

つまり、特に構成を変更せずにLinuxを使っている限りは、SELinuxのエラーに悩まされることはほぼありません。
Linuxの構成を変更した場合も、多くの場合はsemanageコマンドによってSELinuxのルールを部分的にカスタマイズすることで、新たなアクセスパターンを許可することができます。

場合によっては、デフォルトで許可されたallowルールを取り消して、自分たちが使うルールのみ有効化したいと思うこともあるかもしれません。
しかし、SELinuxにおいて一度許可したルールを後から拒否することはできません。

denyルールというものは存在しませんし、デフォルトでインストールされたModule Policyをアンインストールすることもできません。
(※) Module Policyについては、後続記事の(参考) SELinux Module Policyのソースコード読解、ビルド - Base PolicyとModule Policyに説明があります

SELinuxは「あるプロセスが本来必要なアクセスをすべて許可し、不要なアクセスを拒否する」という動作をします。
脆弱性を突いてroot権限を持つデーモンプロセスの制御を奪取しても、そのデーモンプロセスが本来想定していないような用途のアクセス (※) は拒否されます。
(※) 例えばバックドアのための不正なポート番号のlistenや、無関係なファイルへのアクセスなど

こういった動作によって、システムに侵入された後のリスクを軽減するのがSELinuxの役割であると私は理解しています。
デフォルトで許可されたルールは「そのプロセスにとって本来必要なアクセスを許可するもの」であるため、これを拒否するdenyルールのような仕組みは必要ないと思います。

許可されたアクセスを新たに拒否できない以上、SELinuxの運用においてできることは基本的には「追加でアクセス許可すること」のみです。
言い換えると、SELinux起因のアクセス拒否を直せるようになれればSELinuxの運用知識としては十分だと私は考えています。
アクセス拒否を直す方法は複数ありますが、適切な方法を選択できるようにSELinuxの既存のルール構成も自力で調査できるようになるとなお良いです。

本記事の序盤ではSELinuxの基本操作を扱いますが、メイントピックはアクセス拒否の直し方です。
#エラー切り分けの流れ以降のセクションにて、アクセス拒否の原因調査、そして修正方法について取り扱います。

RPMパッケージ

以下のパッケージはデフォルトで入っていない可能性があるので、必要に応じて導入してください。
Fedoraにおいては、policycoreutils-python-utilssetroubleshoot-serverは初めから入っていました。

特に、policycoreutils-python-utilssetools-consoleは、SELinuxを操作するために必須とも言えるツールです。
またsetroubleshoot-serverpolicycoreutils-restorecondも運用上役に立つので、必要に応じて導入をご検討ください。

パッケージ名 内容
policycoreutils-python-utils semanage, audit2allowコマンドを含む
setools-console seinfo, sesearchコマンドを含む
#(参考) SELinuxの確認コマンド
setroubleshoot-server 通常のアクセス拒否の監査ログ発生直後に、わかりやすい説明文と解決策をシステムログに出力する
#setroubleshoot-serverが出力する追加情報
selinux-policy-doc *_selinuxというmanページを大量に含む
#(参考) manの横断検索
policycoreutils-restorecond restorecondデーモンを含む
#restorecond
policycoreutils-devel Custom Policyをソースコードから記述してビルドするために必要
#(参考) Custom Policyのビルド

SELinuxのステータス

enforcing/permissive/disabled

SELinuxは3つのステータス値のいずれかを取ります1
デフォルトはenforcing (有効) です。

ステータス ラベル付け 監査ログ出力 アクセス制御
enforcing する する する
permissive する する しない
disabled しない しない しない

基本的には、セキュリティを保つためにenforcingのままにしておくべきです。

以下の場合においてはpermissive (拒否しない) が便利です。
permissiveは基本的にSELinux有効なのですが、アクセス拒否をしない部分のみenforcingとは異なります。
アクセス拒否はしませんが、アクセス許可されない場合は監査ログに出力します。

  • permissiveが便利な場面
    • 構築したサーバーの単体試験の一環で、一通りの操作をしてSELinuxのエラーログを洗い出したいとき。エラーログが出たら、出なくなるようにチューニングした上で再度動作確認する
    • 必要なアクセスが拒否されて運用に支障が出ているとき (セキュリティが損なわれるので、あくまで一時的な復旧策として)

disabledは、SELinuxの完全な無効化です。

disabledの状態で作成されたObjectには、Security Contextが割り当てられないということに注意が必要です。
disabledからenforcingに切り替える際は、必ずrelabelしてください。
さもないとファイルのSecurity Contextが想定外の値になり、大量のアクセス拒否が発生する恐れがあります。
relabelの手順は、#全ファイルをrelabelするにて説明します。

disabledにすることはおすすめしません。
上記の通りラベル付けされなくなり、トラブルを誘発するリスクがあるためです。
どうしてもSELinuxによるアクセス拒否を全て止めたい場合には、permissiveを推奨します。
disabledは、「SELinuxを無効化して二度と有効化するつもりはない」ときに使うものです。

SELinuxによるアクセス制御を無効化すると、セキュリティ観点でリスクが上がります。
具体的には、脆弱性を突かれるなどしてroot権限を持つプロセスの制御権を奪われた時の防御手段が1つ失われます。
参考: Linuxプロセスアクセス制御の概要 - #MAC (Mandatory Access Control)

設定ファイルによるステータス変更

/etc/selinux/configを書き換えてOS再起動することで、SELinuxのモードを変更できます。

SELINUX=enforcing
# SELINUX=permissive
# SELINUX=disabled → この設定値は今後サポートされなくなる可能性あり

この設定ファイルを使って設定を書き換えることは、恐らくあまりないでしょう。
あるとしたら、SELinuxによるアクセス拒否を常時無効化したい場合にSELINUX=permissiveに書き換えるぐらいです。

一時的にenforcingからpermissiveに書き換える場合は、sudo setenforce 0コマンドを使う方が便利です。
setenforceコマンドはSELinuxがenforcingかpermissiveの状態であれば使えます。
sudo setenforce 1でenforcingに、sudo setenforce 0でpermissiveになります。

最後に一点補足しますが、SELINUX=disabledというオプションは今後サポートされなくなる可能性があります。
経緯の詳細と代わりの手順については、#(参考) Kernel Command Line ParameterによるSELinuxの無効化を参照してください。
繰り返しになりますが、disabledにすることはそもそもおすすめしません。

ステータス確認

SELinuxのステータスは、2通りのコマンドで確認できます。
どちらを使っても良いです。

getenforce
# Enforcing

sestatus
# SELinux status:                 enabled
# SELinuxfs mount:                /sys/fs/selinux
# SELinux root directory:         /etc/selinux
# Loaded policy name:             targeted
# Current mode:                   enforcing
# Mode from config file:          enforcing
# Policy MLS status:              enabled
# Policy deny_unknown status:     allowed
# Memory protection checking:     actual (secure)
# Max kernel policy version:      33

(参考) Kernel Command-Line ParameterによるSELinuxの無効化

Fedora34、及びRHEL9よりSELINUX=disabledによるSELinuxの無効化はサポートされなくなりました。 2,3
RHEL8においてはまだサポートされていますが、既に非推奨とされています4

なお繰り返しになりますが、SELinuxの無効化はそもそもおすすめしません。

一時的に無効化する方法

今後もサポートされる方法は、Linux KernelのCommand-Line Parameterにselinux=0を渡すことです5
Linux起動時にこのパラメータを指定するには、Linuxの起動中にコンソール画面で以下の操作を行います。

(1) LinuxのBootloaderのKernel選択画面で、eを押すことでBoot Entryの編集画面に入る

boot_parameter1

(2) linuxで始まる行にselinux=0というオプション指定を書き加える。既存のパラメータとはスペースで区切る

boot_parameter2

(3) Ctrl+xを押下してLinuxを起動する

Bootloaderにおけるパラメータ指定はLinux起動前に行うため、SELinuxのエラーによってLinuxの起動自体が失敗する事態においても有効です。
selinux=0の代わりにenforcing=0を指定するとpermissiveモードで起動することも覚えておくと、いざというときに便利です。

永続的に無効化する方法

Kernel Command-Line Parameterのデフォルト値にselinux=0を指定すると、OS再起動してもSELinuxの無効化が持続します。
今回は、grubbyコマンドを使ってBoot Loader (GRUB2) の設定を変更します。
grubbyコマンドを使うには、grubby RPMパッケージが必要です。

まずは現状の設定を確認します。
注目すべきはargs=の行で、デフォルトではselinux=0が入っていません。

ALLを指定すると全Boot Entry分の設定を表示します。
DEFAULTを指定すると、Linux起動時にデフォルトでカーソルのあっているBoot Entryの設定のみ表示します。
1などの数字を指定すると、特定のindexの設定のみ表示します。

# /boot/loader/entries
sudo grubby --info ALL
# (一部抜粋)

# index=0
# kernel="/boot/vmlinuz-5.14.18-300.fc35.x86_64"
# args="ro resume=/dev/mapper/fedora_fedora-swap rd.lvm.lv=fedora_fedora/root rd.lvm.lv=fedora_fedora/swap rhgb quiet"

# index=1
# kernel="/boot/vmlinuz-5.14.18-100.fc33.x86_64"
# args="ro resume=/dev/mapper/fedora_fedora-swap rd.lvm.lv=fedora_fedora/root rd.lvm.lv=fedora_fedora/swap rhgb quiet"

今回の設定変更手順では、以下のファイルも更新されます。

grep GRUB_CMDLINE_LINUX /etc/default/grub
# GRUB_CMDLINE_LINUX="resume=/dev/mapper/fedora_fedora-swap rd.lvm.lv=fedora_fedora/root rd.lvm.lv=fedora_fedora/swap rhgb quiet"

以下のコマンドで全てのBoot Entryにselinux=0オプションをデフォルトで追記します。
ALLの代わりにDEFAULTやindex番号を指定することもできますが、Boot Entryごとにパラメータを分ける使い方をしないのであればALLで良いと思います。

sudo grubby --update-kernel ALL --args 'selinux=0'

変更後のパラメータを確認します。
オプションの末尾にselinux=0が追加されています。

# /boot/loader/entries
sudo grubby --info ALL
# (一部抜粋)

# index=0
# kernel="/boot/vmlinuz-5.14.18-300.fc35.x86_64"
# args="ro resume=/dev/mapper/fedora_fedora-swap rd.lvm.lv=fedora_fedora/root rd.lvm.lv=fedora_fedora/swap rhgb quiet selinux=0"

# index=1
# kernel="/boot/vmlinuz-5.14.18-100.fc33.x86_64"
# args="ro resume=/dev/mapper/fedora_fedora-swap rd.lvm.lv=fedora_fedora/root rd.lvm.lv=fedora_fedora/swap rhgb quiet selinux=0"

grep GRUB_CMDLINE_LINUX /etc/default/grub
# GRUB_CMDLINE_LINUX="resume=/dev/mapper/fedora_fedora-swap rd.lvm.lv=fedora_fedora/root rd.lvm.lv=fedora_fedora/swap rhgb quiet selinux=0"

OS再起動すれば、SELinuxが無効化されます。

sudo reboot

selinux=0オプションを消すには、以下のコマンドを実行します。

sudo grubby --update-kernel ALL --remove-args 'selinux'

Bootloader、/etc/default/grubgrubbyに関するより詳細な情報は、Red Hat社のマニュアルに書いてあります。
RHEL8 - System Design Guide - Configuring kernel command-line parameters

(参考) Boot Entryとは

Boot Entryとは、Linux起動画面に出てくる項目のことです。
下記画像では4つのBoot Entryが存在します。
grubbyがインストールされていれば、sudo grubby --info ALLでも確認できます。

boot_parameter1

Boot EntryはKernel Command-Line Parameterと紐づく概念で、Linux Kernelを更新するたびに1つ追加されます。
更新後のLinux Kernelに不具合があるとLinuxを起動できなくなる恐れがあります。
不具合が起こった場合は、過去バージョンのKernelと紐づくBoot Entryを選択することでLinuxを起動し、復旧操作を行える仕組みになっているようです。
最近のFedora系のディストリビューションでは、systemdが提供するレスキューモードもBoot Entryから選択可能になっています。

Linux Kernelは複数バージョン同時に存在できるようになっており、更新のたびにkernelパッケージが増えていきます。

dnf list kernel --installed
# Installed Packages
# kernel.x86_64  5.8.15-301.fc33  anaconda
# kernel.x86_64  5.14.18-100.fc33  updates 
# kernel.x86_64  5.14.18-300.fc35  @updates 

エラー切り分けの流れ

SELinuxによるアクセス拒否を検知した時、以下の流れで切り分けを行います。
切り分けの方法は固定化されているわけではありません。
ここで紹介するのは、あくまで私のやり方です。

  1. SELinux以外の確認
  2. SELinuxのエラーログ確認
  3. Permissiveで再現させる
  4. File Context周りの確認
  5. ポート番号と紐づくSecurity Contextの確認
  6. Boolean有無の確認
  7. Custom Policyの自力開発の検討

各工程の概要を次のセクションで説明します。
詳細手順は別セクションに書きましたので、リンクから飛んでください。

1. SELinux以外の確認

アクセス拒否された場合、そのアクセスを行っているアプリケーションで問題が発生しているはずです。
まずはそのアプリケーションの状態を確認します。

アプリケーションがPermission deniedかそれに類するエラーを出している場合は、SELinuxによるアクセス拒否が発生している疑いが強まります。

しかし、SELinuxの観点に映る前に、先にDAC (File Permissions + POSIX ACL)の問題が発生していないかを確認しましょう。

過去記事のLinuxプロセスアクセス制御の概要 - DACとMACは両方使われるでも取り上げましたが、LinuxにおいてはDACMACの順にアクセス可否が評価されます。
DACに問題がある場合は、SELinuxに処理が渡るより先にエラーになり、SELinuxのエラーログが出ません。

DACに問題がないことを確認できたら、次に進みます。

2. SELinuxのエラーログ確認

SELinuxがアクセス拒否をすると、syslogと監査ログが出力されます。
以下のコマンドでそれぞれのログを確認できます。
ログが出ていた場合、SELinuxがアクセス拒否していることが確定します。

  • sudo ausearch -m avc,user_avc (監査ログの表示)
  • journalctl -qen all -t setroubleshoot (setroubleshoot-serverによるヒントログの表示)

前者の監査ログからは以下の情報がわかります。

  • 拒否されたアクセスの詳細 (source_type, target_type, class, permission)
  • sourceのプロセス名、プロセスID
  • targetがファイルだった場合はファイル名

後者のsetroubleshootのログには、以下の情報が含まれます。
個人的にはファイルのフルパスを確認できるのが一番便利だと感じます。

  • sourceのプロセス名
  • targetがファイルだった場合はフルパス
  • 解決方法の提案

最後の解決方法の提案ですが、必ずしも最適な解決方法が提案されるとは限りません。
あくまで提案は提案として参考にしつつ、最終的には自分で適切な解決方法を判断することが重要です。

詳細については、#SELinuxのエラーログ確認にてログの読み方も含めて説明します。

3. Permissiveで再現させる

SELinuxでエラーが発生していることを確認できた場合、可能であればPermissiveモードに変更してからエラーメッセージを再現することをおすすめします。

単一コマンドの実行であっても、細かく見れば複数のアクセスが発生しているケースがあります(例: ファイルのopen,read)
Enforcingの場合、openに失敗するとその時点で処理が止まります。
仮にopenを許可した場合、今度はreadに失敗します。
このように、Enforcingのまま切り分けを進めると時間がかかることがあります。

Permissiveであればエラーが発生しても処理を中断しないので、上記の例で言えばopenとreadの両方について監査ログを出力します。
両方の原因に対処して動作確認すれば、次は一発で処理が通ります。

具体的なコマンドは、以下のとおりです。

# EnforcingからPermissiveに変更
sudo setenforce 0

# (エラーの再現)
# (エラーログの確認)

# PermissiveからEnforcingに変更
sudo setenforce 1

セキュリティ観点で本番環境のサーバーをPermissiveにすることは難しいと思います。
少々手間はかかりますが、原則としては検証環境などでこの工程を実施することをおすすめします。

4. File Contextとの差分確認

SELinuxの拒否ログで、targetがファイルである場合はFile Contextとの差分が発生しているかを疑ってください。

ファイルのSecurity ContextがFile Contextと差分がないかを確認します。
最もよく使うコマンドは、以下の2つです。

  • matchpathcon -V ファイル名 (File Contextとの差分チェック)
  • restorecon -v (relabelによる修正)

ファイルのSecurity Contextがアクセス制御ルールの設計と異なる場合、allow Statementとマッチしなくなることでアクセス拒否されてしまいます。
File Context通りのTypeを持っていれば問題ないことがほとんどなので、多くの場合はrelabelによって問題を解決できます。

運用上は、mvコマンドによってファイル移動した時にこの状況がしばしば発生します。
mvはSecurity Contextを含めてファイルの属性情報を全て保持してファイルを移動します。
ホームディレクトリで作ったファイルを/var/etc配下に移動すると、これらのフォルダ配下にuser_home_t Typeのファイルが生成し、アクセス拒否の原因となります。

詳細については、#File Contextとの差分確認にて上記以外のコマンドや修正例も含めて説明します。

5. ポート番号と紐づくSecurity Contextの確認

SELinuxの拒否ログで、targetがTCP/UDPポート番号 (*_port_t) の場合は、ポート番号がデフォルト値から変更されていないかを思い返してみてください。

正しく設定されていれば、source (*_t) とtarget (*_port_t)*に入る文字列が似たものになるはずです。
エラーが発生している場合は、上記の文字列が異なっていることが多いです。

例えば、source=httpd_t, target=http_port_tが正しい姿です。
source=httpd_tに対して、target=ssh_port_tのように明らかに異なる場合は、ポート番号とSecurity Contextの紐付けを修正するか、ポート番号そのものを変更することをご検討ください。

ポート番号とSecurity Contextの紐付けを確認するには、主に以下のコマンドを使用します。

  • seinfo --portcon

ポート番号とSecurity Contextの紐付けを変更して問題を解決するには、以下のコマンドを実行します。

  • sudo semanage port -a ...

詳細については、#ポート番号と紐づくSecurity Contextの確認にて上記以外のコマンドや修正例も含めて説明します。

6. Boolean有無の確認

Object (target) に割り当てられたSecurity Contextが誤っている疑いは、この時点でほぼ払拭されています。
以降は既存のアクセス制御ルールを変更するアプローチになります。

自力でルールを記述するよりも先に、まずは今の状況にマッチしたBooleanが存在しないかを先に確認しましょう。
メジャーなケースであれば、専用のBooleanが用意されていることが少なくありません。

Booleanを探すには、以下のコマンドが有効です。

  • sesearch -A -s ... -t ... -c ... -p ...
  • sudo semanage boolean -l または getsebool -a

まずは、SELinuxの拒否ログと全く同じ条件でsesearchでアクセス許可ルールを検索します。
実際に拒否されているのでアクセス許可ルールは通常ヒットしないのですが、Booleanが存在する場合は以下のような出力を得られます。
以下の例では、httpd_read_user_contentというBooleanがTrueの場合のみ有効化されるallowルールが検索にヒットしました。

sesearch -A -s httpd_t -t user_home_t -c file -p read,open
# allow httpd_t user_home_type:file { getattr ioctl lock open read }; [ httpd_read_user_content ]:True

候補となるBooleanが見つかった場合は、sudo semanage boolean -lなどでBooleanの意味を確認します。
そしてBooleanの意味をより厳密に確認するために、sesearch -bで確認します。

Boolean値が今回の状況にマッチしそうな場合は、以下のいずれかのコマンドでBoolean値を変更します。

  • sudo setsebool -P ...
  • sudo semanage boolean -m ...

詳細については、#Boolean有無の確認にて修正例も含めて説明します。

7. Custom Policyのビルド

上述のいずれの切り分けでも対処できない場合、「最後の手段として」Custom Policyのビルドという手段があります。

自力でallow Statementやtype_transition Statementを記述したソースコードを書き、それをビルド・インストールする方法です。
この方法を使えば、ほぼどんな定義やルールでも追加できます (※)
(※) SID (Security ID) など、Base Policyにしか書けない定義やルールもあります。ここで可能なのは、あくまでModule Policyのソースコードに書けるルールのみです。とはいえ、ほぼ何でもできると思って差し支えありません

具体的には、Custom Policyによって以下のようなことができます。

  • Typeの定義
  • Attributeの定義
  • type_transition Statementの追加
  • allow Statementの追加
  • Booleanの定義
  • File Contextの定義

BooleanやFile Contextについては、semanageコマンドによっても追加できます。
使い分けとしては、Custom PolicyのType定義やallow Statementに関連するものであれば、Custom Policyに含めるのが一般的です。
そうすることで、関連する一連の定義やルールをひとまとめに管理できます。

Custom Policyを追加する状況は、例えば以下が挙げられます。

  • 既存のルール設定にallow Statementを追加したい場合
  • 自作のアプリケーションをSELinuxでアクセス制御したい場合

具体的な手順については、#(参考) Custom Policyのビルドで紹介します。

SELinuxのエラーログ確認

本セクションでは、#2. SELinuxのエラーログ確認の詳細な確認手順を説明します。

一番よく使うのは、以下のコマンドです。

  • sudo ausearch -m avc,user_avc
  • journalctl -qen all -t setroubleshoot

ログの表示コマンド

SELinuxのエラーログは2種類出力されます。

  1. AVC (Access Vector Cache) が出力するアクセス拒否のログ
  2. setroubleshootが出力する追加情報のログ

2のログはsetroubleshoot-server RPMパッケージがインストールされている場合のみ出力されます。

「事象の把握」という意味では、1のAVCから出るログが有用です。
しかし、1のログではファイルへのアクセスが拒否された時のファイルのフルパスが書いてないことがあるので、必要に応じて2のログも確認します。

1のログはaudit2allowコマンドの入力値として利用することがあります。
この使い方については、#(参考) Custom Policyのビルドで説明します。

AVCが出力する監査ログ

SELinuxでアクセス拒否が発生すると、AVCが監査ログ (/var/log/audit/auditd) を出力します。
このログは、sudo ausearch -m avc,user_avcで確認できます。

sudo ausearch -m avc,user_avc

# ----
# time->Sun Nov 21 17:29:41 2021
# type=AVC msg=audit(1637483381.836:583): avc:  denied  { read } for  pid=10602 comm="mandb" name="anki.1" dev="dm-0" ino=919333 scontext=system_u:system_r:mandb_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

上記のログから、以下のような情報を読み取れます。
allow Statementと同等の情報が出力されていることがわかると思います。

  • source (mandb_t)
  • target (user_home_t)
  • class (file)
  • permission (read)

sourceについてはPID (10602) とプロセス名 (mandb) も表示されます。
targetについてはファイル名が表示されますが、フルパスはわかりません (anki.1)
フルパスを確認したい場合は、次のセクションで扱うsetroubleshootのログを見ます。

以下のコマンドは、auditdが停止しているなどの理由でausearchが利用できない場合に使います6
auditdが停止していることは基本ないので、こちらのコマンドを覚える必要はありません。

# dmesg: auditdが動作していない時の代替手段
dmesg | grep -i -e type=1300 -e type=1400

また、Fedora環境の場合は監査ログが/var/log/messagesにも出力されていたので、以下のようにjournalctlでもSELinuxの拒否ログを確認できました。
RHELCentOS Streamでは/var/log/messagesjournalctlでの拒否ログの確認はできませんでした。

journalctl -qen all -t audit -g denied
# Nov 21 17:29:41 pc audit[10602]: AVC avc:  denied  { read } for  pid=10602 comm="mandb" name="anki.1" dev="dm-0" ino=919333 scontext=system_u:system_r:mandb_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

(※) -g avcよりも-g deniedの方がより必要な情報に絞ってフィルタしてくれます

setroubleshoot-serverが出力する追加情報

setroubleshootのログはsetroubleshoot-server RPMパッケージがインストールされている場合のみ出力されます。
AVCから監査ログが出力された少し後のタイミングで、/var/log/messagesに出力されます。

以下のサンプルでは、2つのログが出力されています。
1つ目が発生したエラーの説明、2つ目が解決案の提示です。

journalctl -qen all -t setroubleshoot

# Nov 21 17:29:45 pc setroubleshoot[11309]: SELinux is preventing mandb from read access on the file /usr/local/share/man/man1/anki.1. For complete SELinux messages run: sealert -l 5fe15040-2828-45d4-9e8c-ba8ef5781937


# Nov 21 17:29:45 pc setroubleshoot[11309]: SELinux is preventing mandb from read access on the file /usr/local/share/man/man1/anki.1.

#    *****  Plugin restorecon (99.5 confidence) suggests   ************************

#    If you want to fix the label. 
#    /usr/local/share/man/man1/anki.1 default label should be usr_t.
#    Then you can run restorecon. The access attempt may have been stopped due to insufficient permissions to access a parent directory in which case try to change the following command accordingly.
#    Do
#    # /sbin/restorecon -v /usr/local/share/man/man1/anki.1

#    *****  Plugin catchall (1.49 confidence) suggests   **************************

#    If you believe that mandb should be allowed read access on the anki.1 file by default.
#    Then you should report this as a bug.
#    You can generate a local policy module to allow this access.
#    Do
#    allow this access for now by executing:
#    # ausearch -c 'mandb' --raw | audit2allow -M my-mandb
#    # semodule -X 300 -i my-mandb.pp

1つ目のログにはあまり目新しい情報はありませんが、ファイルのフルパスが書いてあります。

2つ目のログでは、アクセス拒否を修正するための方法を2つ提示しています。
今回の場合は、以下の内容が書かれています。

  • restorecon /usr/local/share/man/man1/anki.1.で直る (99.5%の確度)
  • allowルールを追加し、Custom Moduleとしてインストールする (1.49%の確度)

上記の提案に従えばエラーは解消できますが、最適な解決策とは限りません。
あくまで参考情報として扱い、最終的には自分で判断する必要があります。
setroubleshootが提案しなかった解決策を選択することもしばしばあります。

journalctlが使えない環境では、/var/log/messagesを直接開いてsetroubleshootのログを確認します。

(参考) journalctlの利便性

journalctlは、journaldデーモンが収集したログを一括表示するコマンドです。
以下の点で使い勝手が良いです。

  • journalctlはadmwheel、またはsystemd-journalグループに所属する一般ユーザーでも使える。一方、ログファイルを直接開くにはroot権限が必要
  • /var/log/messages/var/log/audit/audit.logなど複数ファイルのログ情報を横断的に確認できる
  • rotateされず、かなり昔のログまで遡れる
  • コマンドラインオプションのフィルタが便利
  • (SELinuxの文脈では関係ないが、) systemd管理下のサービスの詳細ログが見える

今回のSELinuxのログ確認においても、ログファイルを直接開くよりも扱いやすいのでjournalctlを積極的に活用しています。

(参考) journalctlのオプション

journalctlは複数のログファイルを一元的に表示します。
そのままだとログの量が多くて扱いづらいので、オプションで出力をフィルタして使います。

各種オプションの使い方を下表に示します。

オプション 詳細
-q
  • 制御文字列の挿入を抑制する
  • -qを指定しないと、例えばOS起動のタイミングで-- Boot xxx --という文字列が挿入される
-e
  • 最終行の直近のログから表示する
  • -eを指定しないと、古いログから表示される
  • 暗黙的に-n 1000が指定され、ログ表示の上限が1000行になる
  • -n allなど明示的に指定することで、この挙動を上書きできる
-n [num]
  • 直近のnum行のログを表示する
  • numを省略すると10行になる
  • allで制限なしになる
-t <id>
  • 指定したSyslog Identifier (id) のみに限定して表示する
  • idは、ログ上でタイムスタンプ、ホスト名の隣に書いてある
-g <msg>
  • grep機能
  • メッセージの本文から文字列検索する
  • Perl互換の正規表現を使える
  • 全て小文字で書くと大文字/小文字に限らずヒットする
--no-pager デフォルトのless形式のpagerを抑制する
-f tail -fのようにログが発生するたびに画面を更新する

(※) SELinuxとは無関係ですが、-uで特定サービスの詳細ログを確認するオプションもよく使います。
例: journalctl -u NetworkManager

File Contextとの差分確認

本セクションは、#4. File Contextとの差分確認に関連して、より詳細な確認/修正手順を紹介します。

一番よく使うのは、以下の2コマンドです。

  • matchpathcon -V ファイルパス
  • restorecon -v ファイルパス

本セクションの手順を理解する前提として、File Contextとrelabelの仕組みを理解している必要があります。
参考: SELinux Type Enforcement - #File Contextとrelabel

File Contextの確認

File Contextの定義は、semanage fcontext -lで確認します。

sudo semanage fcontext -l
# SELinux fcontext  type       Context

# /                 directory  system_u:object_r:root_t:s0 
# /.*               all files  system_u:object_r:default_t:s0 
# (以下略)

特定ファイルのFile Context差分確認

ファイルのSecurity ContextとFile Contextの差分を確認するコマンドとして、matchpathcon -Vがあります。
このコマンドにより、指定したファイルのSecurity ContextがFile Contextと一致するかを確認できます。

 touch ~/a
matchpathcon -V ~/a
# /home/endy/a verified.

# Security Contextを変えるとエラーとして検知される
chcon -t tmp_t ~/a
matchpathcon -V ~/a
# /home/endy/a has context unconfined_u:object_r:tmp_t:s0, should be unconfined_u:object_r:user_home_t:s0

rm ~/a

基本matchpathcon -Vだけで事足りますが、他のコマンドも参考情報として紹介します。

ファイルのSecurity Contextを確認するには、ls -Zを使います。

touch ~/a
ls -Z ~/a
# unconfined_u:object_r:user_home_t:s0 /home/endy/a

特定ファイルのFile Contextを確認するには、オプション無しでmatchpathcon ファイルパスを実行します。
正規表現で書かれているsemanage fcontext -lでは探すのが難しいので、この用途ではmatchpathconの方が向いています。

matchpathcon /etc/hosts
# /etc/hosts   system_u:object_r:net_conf_t:s0

matchpathconには、ファイルそのものではなく「File Contextの検索ワード」を指定します。
matchpathconは、File Contextの正規表現で引数の文字列 (ファイルパス) を検索した結果、ヒットしたSecurity Contextを返します。
したがって、matchpathconには、必ずフルパスを指定するようにしてください。
matchpathcon -Vには相対パスを指定しても良いですが、matchpathconには絶対パスを指定する必要があります。

# フルパスを指定しないと想定外の結果になる
cd /etc
matchpathcon hosts
# hosts    <<none>>

(参考) selabel_lookup -k

selabel_lookup -kは、matchpathconと同じ意味を持ちます。
役割が被るので、selabel_lookup -kを使うことは基本ないと思います。

selabel_lookup -k /etc/hosts
# Default context: system_u:object_r:net_conf_t:s0

特定ファイルのrelabel

restorecon -vによって、特定ファイルをrelabelします。
relabelとは、Security ContextをFile Contextの値と揃える処理のことです。

# 事前準備
touch ~/a
chcon -t tmp_t ~/a

# restorecon
ls -Z ~/a
# unconfined_u:object_r:tmp_t:s0 /home/endy/a

restorecon -v ~/a
# Relabeled /home/endy/a from unconfined_u:object_r:tmp_t:s0 to unconfined_u:object_r:user_home_t:s0

ls -Z ~/a
# unconfined_u:object_r:user_home_t:s0 /home/endy/a

# relabelが発生しない場合は、何も出力されない
restorecon -v ~/a
# (出力なし)

-vオプションを指定しない場合、relabelが発生した場合も何も出力されません。

chcon -t tmp_t ~/a

restorecon ~/a
# (出力なし)

restoreconは、fixfilesと同様にデフォルトではTypeしかrelabelしません。
restorecon -FによりUser, Role, Rangeも含めてrelabelできますが、実用上-Fオプションを使うことはないと思います。

全ファイルのFile Context確認

全ファイルのFile Contextを一括確認するには、fixfiles checkを実行します。
restorecon -nvR /とほぼ同じ意味です。

sudo fixfiles check
# Would relabel /home/endy/a from unconfined_u:object_r:tmp_t:s0 to unconfined_u:object_r:user_home_t:s0

デフォルトではTypeの差分しか確認しません。
-FオプションによってUser, Role, Rangeも差分チェックの対象にできますが、実用上使うことはないと思います。

ディレクトリやファイルを指定することで、対象を絞り込むことができます。
ディレクトリを指定した場合、配下のファイルを再帰的に確認します。

sudo fixfiles check ~
# Would relabel /home/endy/a from unconfined_u:object_r:tmp_t:s0 to unconfined_u:object_r:user_home_t:s0

(参考) 複数ファイルのrelabel

全ファイルに対して一括でrelabelを行う手順もありますが、基本的にこの操作を行うことはありません。

relabelは一見すると思考停止で実行すると良さそうにも思えますが、稀に新規作成されてから一度もrelabelされていないファイルのTypeが、relabelによって変化してしまうこともあります。
ファイル新規作成時のLabelとFile Contextの定義は多くの場合整合性が取れていますが、100%とは限りません。
何らかの理由で実行する場合も、事前にfixfiles checkによって影響範囲を確認することをおすすめします。

主なユースケースは、SELinuxをdisabledからenforcing/permissiveに変更した時です。
disabledの場合はファイルがラベル付けされていないので、有効化するタイミングでrelabel処理が必要となります。

システム全体をrelabelする手順はいくつかありますが、代表的なものは以下です。
以下を実行すると、Linuxの再起動中に全ファイルをrelabelします。

sudo fixfiles onboot
sudo reboot

Linuxの起動中に、内部的に以下のコマンドを実行しています。

sudo fixfiles restore
sudo rm /.autorelabel

sudo fixfiles restoreを単体で使うことも可能です。
このコマンド自体はOS再起動を要求しません。
-vオプションをつけることで、restoreconと同様にrelabel処理の詳細を表示します。
sudo fixfiles restoreは、sudo restorecon -R /と同等です。

fixfilesについて、詳細は以下のリンクを参照してください。
参考: SELinux Type Enforcement - #fixfiles

(参考) File Contextの追加

レアケースではありますが、切り分けの結果File Contextの定義が足りないと判明することもあります。
そういった場合は、semanage fcontext -a ...コマンドでFile Contextの定義をLocal Policyとして追加します。

その後、restoreconなどによってrelabelすることで、新しいFile ContextをファイルのSecurity Contextに反映します。

Local Policyで定義したFile Contextを削除するには、sudo semanage fcontext -d ...を実行します。
Module PolicyとしてインストールされたFile Contextを変更/削除することはできないので、そこはご注意ください。

Local Policyの良いところは、ソースコードの記述・ビルド・インストールが不要なところです。
semanageコマンドを1行実行するだけで手軽に実装できます。
Local Policyに対応するのは、semanageのサブコマンドに含まれる以下の要素のみです。
詳細はman semanageを参照してください。

  • File Context
  • Boolean
  • Port
  • Login
  • User

例: 特定ファイルのみ別のSecurity Contextを割り当てたい場合

以下のFile Context定義が示すとおり、デフォルトでは/tmp/直下のファイルはrelabel対象外です。
<<none>>とはrelabel時に対象外とすることを表します7

sudo semanage fcontext -l | grep '^/tmp/\.\*'
# SELinux fcontext  type       Context
# /tmp/.*           all files  <<None>>

今回の例では、/tmp/auser_home_tにrelabelするようにFile Contextを追加定義します。
(※) 良いユースケースを思いつかず、実用性のない例となってしまいました。
(※) 構文について補足ですが、relabel対象外にするためのルールは右記のように指定します sudo semanage fcontext -a -t '<<none>>' '/tmp/a'

sudo semanage fcontext -a -t user_home_t '/tmp/a'

作成したFile Contextを確認します。
Local Policyはsemanage fcontext -lの一番下に表示されます。
今回は、-Cオプションを追加で指定することでLocal Policyのみ表示します。

sudo semanage fcontext -lC
# SELinux fcontext  type       Context

# /tmp/a            all files  system_u:object_r:user_home_t:s0 

実際にrelabelを試してみます。

touch /tmp/a
ls -Z /tmp/a
# unconfined_u:object_r:user_tmp_t:s0 /tmp/a

restorecon /tmp/a
ls -Z /tmp/a
# unconfined_u:object_r:user_home_t:s0 /tmp/a

検証が終わったので元の構成に戻します。
semanage fcontext -dでLocal Policyを削除できます。

rm /tmp/a
sudo semanage fcontext -d '/tmp/a'

sudo semanage fcontext -lC
# (出力なし)

今回の例ではTypeのみ指定しましたが、追加のオプションによってUser, Role, Rangeなども指定してFile Contextを定義できます。
基本的には、Typeのみの指定で十分です。

例: File Contextとの差分によるSELinuxのエラー

File Contextとの差分によってSELinuxのエラーが発生している例を挙げます。
このようなエラーは、mvコマンドでファイル移動した後にrestoreconの実行を忘れることでしばしば発生します。

事前準備として、Apache (WEBサーバ) を事前にインストール・起動しておきます。

sudo dnf install httpd
sudo systemctl start httpd

a.htmlファイルを作成し、curlでアクセス確認します。
以下のやり方であれば問題なく通ります。

sudo bash -c 'echo "aaa" > /var/www/html/a.html'

cat /var/www/html/a.html
# aaa

curl localhost/a.html
# aaa

しかし、以下のように一旦ホームディレクトリでb.htmlを作成してから/var/www/html/にmvで配置した場合はアクセス失敗します。

sudo bash -c 'echo "bbb" > /home/endy/b.html'
sudo mv /home/endy/b.html /var/www/html/

# a.htmlとb.htmlでPermissionに差はない
ls -l /var/www/html
# -rw-r--r--. 1 root root 4 Jan 15 17:35 a.html
# -rw-r--r--. 1 root root 4 Jan 15 17:39 b.html

curl localhost/b.html
# <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
# <html><head>
# <title>403 Forbidden</title>
# </head><body>
# <h1>Forbidden</h1>
# <p>You don't have permission to access this resource.</p>
# </body></html>

403 Forbiddenは、アクセス権限がないことを表します。
ログは割愛しますが、sudo chmod o-r /var/www/a.htmlを実行し、apacheユーザーからa.htmlにアクセスできない状態にしてからcurl /var/www/a.htmlを実行したときも同じエラーになります。

Apacheのログを確認すると、Permission Deniedが発生していました。
先ほどのls -lの実行結果から通常のFile Permissionsはa.htmlとb.htmlに差がないので、SELinuxが原因である疑いが強まります。

sudo tail -1 /var/log/httpd/error_log
# [Sat Jan 15 17:40:44.293322 2022] [core:error] [pid 2795:tid 2895] (13)Permission denied: [client 127.0.0.1:37052] AH00132: file permissions deny server access: /var/www/html/b.html

続いて、SELinuxの監査ログを確認します。
httpd (httpd_t) からb.html (user_home_t) へのreadアクセスが拒否されていることがわかります。
このことから、今回のPermission Deniedエラーは、SELinuxが原因であったことがわかります。

journalctl -qen all -t audit -g denied --no-pager

# AVC avc:  denied  { read } for  pid=2795 comm="httpd" name="b.html" dev="dm-0" ino=131994 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

setroubleshootのログ確認は、ここでは割愛します。

また、Permissiveでの動作確認も省略します。

targetがファイルなので、File Contextとの差分を確認します。
matchpathcon -Vにより、File Contextとの差分が存在することがわかりました。

matchpathcon -V /var/www/html/b.html 
# /var/www/html/b.html has context unconfined_u:object_r:user_home_t:s0, should be system_u:object_r:httpd_sys_content_t:s0

mvでファイル移動した場合、Security Contextが維持されます。
~/b.htmlにファイル生成した段階ではuser_home_t Typeを持っていて、それをmvで移動したためにFile Contextとの差分が生まれています。

relabelによって今回の事態が直ると想定されますが、念のためallowルールを確認します。

上述の監査ログをヒントに、拒否されるアクセスパターンからルール検索してみます。
検索にヒットしているように見えますが、httpd_read_user_contentというBooleanがFalseなのでこのルールは無効化されています。
このBooleanを有効化することでもエラーは解消しますが、アクセス許可の範囲が広すぎるのでセキュリティ観点では好ましくありません。

sesearch -A -s httpd_t -t user_home_t -c file -p read
# allow httpd_t user_home_type:file { getattr ioctl lock open read }; [ httpd_read_user_content ]:True

getsebool httpd_read_user_content
# httpd_read_user_content --> off

続いて、relabelした後の条件でルール検索します (-t httpd_sys_content_t)
今度はヒットしたので、relabelによって今回のエラーが解消できるとわかります。

sesearch -A -s httpd_t -t httpd_sys_content_t -c file -p read
# (一部抜粋)
# allow httpd_t httpd_content_type:file { getattr ioctl lock map open read };

relabelして再度アクセスしてみます。
今度はアクセスに成功しました。

sudo restorecon -v /var/www/html/b.html
# Relabeled /var/www/html/b.html from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0

curl localhost/b.html
# bbb

File Contextとの差分によるSELinuxのエラーは、大抵の場合は操作ミスによって起こります。
今回の場合、以下のいずれかで対策可能です。

  • mvした後はrestoreconでrelabelする
  • mvの代わりにcpを使う

または、次のセクションに示すとおりrestorecondを使うという手もあります。

restorecondによる利便性向上

restorecondデーモンを起動していると、対象のファイルが新規作成/移動されたタイミングで自動的にrestoreconによってrelabelしてくれます。
ユーザーが頻繁にファイルを手動アップロードをするような環境でSELinuxを動作させる場合は便利かもしれません。

restorecondを起動していれば、mvコマンドでファイルを移動した後にrestoreconの実行を忘れても、SELinuxがエラーにならないという効果を見込めます。

詳細は#(参考) restorecondを参照してください。

(参考) restorecond

restorecondとは

restorecondとは、ファイルの更新を検知して自動的にrestoreconを実行するデーモンプロセスです8
/etc/selinux/restorecond.confに書いたファイルのみが対象です。

restorecondは、思いつく範囲では以下のような状況で役に立ちます。

  • SELinuxを知っている管理者が構築したサーバをSELinuxに詳しくないユーザーが操作する場合
    • 例: WEBサーバのファイルアップロードを手動で行う場合、/var/www/配下をrestorecondの監視対象としておくことで「mvの後のrestorecon忘れ」によるエラーを防ぐことができる
  • SELinuxを意識していないソフトウェアをインストールしている場合
    • 例: ダウンロードしたZipファイルを展開して、スクリプト実行によりmvコマンドでファイルを配布する形式のインストーラが世の中にある。そういったソフトを正しく動作させるために利用する

いずれもマイナーなケースかもしれませんが、運用にハマると便利だと思います。

restorecondは、デフォルトではインストールされていません。
policycoreutils-restorecond RPMパッケージをインストールすることで導入できます。

ファイル更新の検知は、inotify9という仕組みを利用しています。

restorecond.conf

restorecondによる監視対象は、/etc/selinux/restorecond.confで指定します10
ファイルの中は、デフォルトで以下のように指定されていました。

/etc/services
/etc/resolv.conf
/etc/samba/secrets.tdb
/etc/updatedb.conf
/run/utmp
/var/log/wtmp
/root/*
/root/.ssh/*

ファイルの記載方法について公式情報はほとんどないのですが、実機で試したところ以下がわかりました。

  • ディレクトリを指定しても、再帰的に配下を監視することはない
  • *によるワイルドカード指定が可能 (例: /var/www/html/*)
  • *は隠しファイルも含む (/var/www/html/*を設定ファイルに書くと、/var/www/html/.cも監視対象になる)
  • #で始まる行はコメント扱いになる
  • **による/を含むワイルドカード指定は不可
  • /home/endy/a aのようにスペースを含むファイル名も認識される
  • "'も純粋に文字列として認識され、特殊な意味を持たない。"/home/endy"のような指定は不可
  • \エスケープ文字として扱われる。/home/endy/a a/home/endy/a\ aは同じ意味
  • restorecond.confに関しては、~は使えない
  • 同一フォルダ内で*と他の文字列の組み合わせは不可 (例: /var/www/htm*のような指定は、ワイルドカードとして動作しない)
  • *を2箇所以上使用すると動作しない (例: /var/www/html/*/*/var/www/htm*/*)

なお、htm*/*/*/のように、対応できない形式を指定した上でrestorecondを起動すると、以下のようなエラーが出ます。

sudo systemctl restart restorecond

journalctl -eu restorecond
# Jan 15 23:37:59 fedora35.test restorecond[5387]: Unable to watch (/var/www/htm*/*) No such file or directory
# Jan 15 23:37:59 fedora35.test restorecond[5387]: Unable to watch (/var/www/*/*) No such file or directory

実際には上記のようなエラーが出ていても、restorecondを再起動した直後だけはrelabelしてくれました。
その後は、ログに書いてある通りWatchできていないため、mvなどで該当パスにファイルを移動してもrelabelしてくれなくなります。

このように、restorecond.confの書き方にはかなり癖があります。
restorecondを使う際は、設定変更のたびにこまめにjournalctl -eu restorecondからエラーが出ていないか確認するようにしましょう。

(参考) restorecond_user.conf

restorecondは一般ユーザー権限でも起動できます。
しかし、手元の環境ではsystemctl --user start restorecondで起動できるような一般ユーザー用のUnitファイルは自動生成していませんでした。
ユーザー権限で常時起動させたい場合は、自前でUnitファイルを作成する必要があります。

一般ユーザー権限で起動した場合、/etc/selinux/restorecond_user.confが読み込まれます。
このファイルでは~がユーザーのホームディレクトリを意味する記号として利用できます。

restorecondをユーザー権限で起動するユースケースは、あまり思いつきませんでした。
強いて言うなら、デスクトップ用途でLinuxを利用している場合に便利かもしれません。

一般ユーザー権限でrestorecondを起動するには、以下のコマンドを実行します。

restorecond -u

restorecondによってrelabelされるタイミング

restorecondによってrelabelされるタイミングは、実機検証で観測できた範囲では以下の通りです。

  • restorecondが起動/再起動したとき
  • mvによってファイル移動したとき
  • cp --archiveによってSecurity Contextも含めて保持しつつコピーしたとき
  • touchなどによってファイルを新規作成したとき

一方で、既に存在するファイルに書き込んだり、touchによってmodified timeを更新しただけではrelabelされませんでした。

【重要】File Contextを更新したら、restorecondを再起動する

restorecondは、起動したタイミングでFile Contextをメモリ上に読み込んでいるようです。
そして、デーモンが再起動するまで再び読みに行くことはありません。
恐らく処理効率化のためだと思います。

ここで注意なのですが、semanage fcontextコマンドなどによってFile Contextを更新した後、restorecondデーモンを再起動する必要があります。
再起動するまでの間、restorecondは古いFile Contextの定義に基づいて動作してしまいますので、くれぐれもご注意ください。

このことはドキュメントには特に書いていないのですが、私が実機操作中に本事象にあたったことで気づきました。

【重要】restorecond起動時に監視対象のファイルが存在すること

restorecondが起動したタイミングで設定ファイルに記述したファイルが存在しなかった場合、後追いでファイルを作成しても監視が開始されません。
この場合、restorecondのログとして以下のエラーメッセージが出ます。

systemctl status restorecond
# (一部抜粋)
# Jan 22 16:07:13 fedora35.test restorecond[770]: Unable to watch (/var/www/html/*) No such file or directory

このエラーが出た場合、ファイルを後追いで作成した上でrestorecondを再起動する必要があります。

例: restorecondによってmv直後に自動relabelする

#例: File Contextとの差分によるSELinuxのエラーと同様の操作を行った時、restorecondが有効の場合にどう変わるかを説明します。

事前準備として、restorecondをインストールします。

sudo dnf install policycoreutils-restorecond

続いて、/etc/selinux/restorecond.confを編集し、管理対象のファイルを追加します。

/var/www/html/*

なお、/var/www/html/dir/index.htmlのようなファイルを監視するには、restorecond.conf上の記述でも/var/www/html/dir/*のように階層を表現する必要があるのでご注意ください。

restorecondを起動、または再起動します。

sudo systemctl start restorecond
# またはsudo systemctl restart restorecond

前回の例で使ったファイルが残っている場合は、一旦削除しておきます。

sudo rm /var/www/html/b.html

続いてサンプルファイルを設置します。
一旦ホームディレクトリ配下にファイルを作成してからmvで移動します。
mvはSecurity Contextを保持しますが、今回はrestorecondによってファイル配置直後にrelabelされています。

sudo bash -c 'echo "bbb" > /home/endy/b.html'
ls -Z /home/endy/b.html
# unconfined_u:object_r:user_home_t:s0 /home/endy/b.html

sudo mv /home/endy/b.html /var/www/html/
ls -Z /var/www/html/b.html
# unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/b.html

b.htmlにHTTPアクセスすると、今回は成功します。

curl localhost/b.html
# bbb

このようにrestorecondの働きにより、Apacheの基本オペレーションをSELinuxを意識することなく実施できました。

運用担当者が手動で配置/削除するファイルをrestorecondに監視させると、SELinuxに詳しい人とそうでない人が混在した現場においてもシームレスにLinuxサーバを運用できるようになります。

ポート番号と紐づくSecurity Contextの確認

本セクションは、#5. ポート番号と紐づくSecurity Contextの確認に続けてより具体的なコマンドを説明します。

アプリケーションの設定ファイルを書き換えてデフォルト値以外の待受ポート番号を利用する場合には、本セクションの手順の実施が必要かご検討ください。

ポート番号のSecurity Contextの確認コマンド

seinfo --portconによってポート番号のSecurity Contextを確認できます。

seinfo --portcon
# Portcon: 652
#    portcon sctp 1-511 system_u:object_r:reserved_port_t:s0
# (以下略)

seinfo --portconの後にポート番号を指定することで、表示内容を絞り込みできます。

seinfo --portcon 80

# Portcon: 4
#    portcon sctp 1-511 system_u:object_r:reserved_port_t:s0
#    portcon tcp 1-511 system_u:object_r:reserved_port_t:s0
#    portcon tcp 80 system_u:object_r:http_port_t:s0
#    portcon udp 1-511 system_u:object_r:reserved_port_t:s0

semanage port -lでも確認できますが、root権限が必要です。
また、ポート番号の検索機能もありません。
基本的には、seinfo --portconの方がおすすめです。

sudo semanage port -l
# SELinux Port Type              Proto    Port Number
# afs3_callback_port_t           tcp      7001
# (以下略)

ポート番号とSecurity Contextの紐付け追加コマンド

semanage portコマンドにより、semanage fcontextと同様にLocal Policyを定義してポート番号とSecurity Contextの紐付けを追加定義できます。

以下は、TCP10080をhttp_port_tに紐付けるコマンドです。
-a, --addは追加、-t, --typeはType指定、-p, --protoTCP/UDPプロトコル指定、最後のコマンドライン引数はポート番号の指定です。
-rオプションでRangeも指定できますが、Type Enforcmentのみ利用する環境ではRangeの指定は不要です。

sudo semanage port -a -t http_port_t -p tcp 10080

作成したLocal Policyは、seinfoまたはsemanageで確認できます。
semanage-Cオプションをつけることで、Local Policyのみに限定して表示できるので便利です。

sudo semanage port -lC
# SELinux Port Type  Proto    Port Number
# http_port_t        tcp      10080

他の表示方法の場合、既存ルールと並んで表示されるので少々紛らわしいかもしれませんね。

seinfo --portcon 10080
# Portcon: 6
#    portcon sctp 1024-65535 system_u:object_r:unreserved_port_t:s0
#    portcon tcp 10080 system_u:object_r:http_port_t:s0
#    portcon tcp 10080-10083 system_u:object_r:amanda_port_t:s0
#    portcon tcp 1024-32767 system_u:object_r:unreserved_port_t:s0
#    portcon udp 10080-10082 system_u:object_r:amanda_port_t:s0
#    portcon udp 1024-32767 system_u:object_r:unreserved_port_t:s0

sudo semanage port -l | grep 10080
# SELinux Port   Type  Proto    Port Number
# amanda_port_t  tcp      10080-10083
# amanda_port_t  udp      10080-10082
# http_port_t    tcp      10080, 80, 81, 443, 488, 8008, 8009, 8443, 9000

次のセクションで、具体例を示します。

例: httpdの待受ポート番号を10080に変更する

httpdの待受ポート番号をデフォルトのTCP80からTCP10080に変更する例で、ポート番号のSecurity Contextを変更する必要があるケースの例を示します。

事前準備として、httpdをインストールしておきます。

sudo dnf install httpd

そして/etc/httpd/conf/httpd.confを編集します。
もともとListen 80と書かれていた部分をListen 10080に書き換えます。

#Listen 80
Listen 10080

そしてhttpd.serviceを起動 (または再起動) します。

sudo systemctl start httpd
# または、sudo systemctl restart httpd

すると、httpdの起動に失敗します。
httpdのログを確認すると、Permission deniedによってネットワークソケットのbindに失敗していることがわかります。
root権限でhttpdを起動していることから、SELinuxが怪しいということがわかります (※)
(※) DACの世界では、root権限で起動したプロセスによる操作がPermission Deniedで拒否されることはありません。Permission Deniedになっているということは、MAC、つまりSELinuxによって拒否されているということです

# またはsystemctl status httpd
journalctl -eu httpd
# Jan 16 16:16:24 fedora35.test httpd[1274]: (13)Permission denied: AH00072: make_sock: could not bind to address [::]:10080
# Jan 16 16:16:24 fedora35.test httpd[1274]: (13)Permission denied: AH00072: make_sock: could not bind to address 0.0.0.0:10080

SELinuxのログを確認します。
httpd_t---[name_bind]--->amanda_port_tというアクセスが拒否されていることがわかります。
ポート番号のTypeがhttp_port_tではないことから、ポート番号とSecurity Contextの紐付けに問題があると推測できます。

# 拒否内容: httpd_t---[name_bind]--->amanda_port_t
journalctl -qen all -t audit -g denied
# Jan 16 16:16:24 fedora35.test audit[1274]: AVC avc:  denied  { name_bind } for  pid=1274 comm="httpd" src=10080 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:amanda_port_t:s0 tclass=tcp_socket permissive=0

調べてみると、TCP10080は確かにamanda_port_tでした。

seinfo --portcon 10080
# (一部抜粋)
  #  portcon tcp 10080-10083 system_u:object_r:amanda_port_t:s0

http_port_tにはTCP10080が含まれません。

sudo semanage port -l | grep ^http_port_t
# http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000

AVCのログを参考に、targetをhttp_port_tに変更した場合のallowルールが存在するかを確認します。
当然ながら、httpd_tはhttp_port_tをbindする権限を持っています。

sesearch -A -s httpd_t -t http_port_t -c tcp_socket -p name_bind
# (一部抜粋)
# allow httpd_t http_port_t:tcp_socket name_bind;

これまでの調査で、TCP10080ポートをhttp_port_tに紐付ければ問題が解決することがわかりました。
以下のコマンドでポート番号とSecurity Contextの紐付け設定を変更します。

sudo semanage port -a -t http_port_t -p tcp 10080

sudo semanage port -lC
# SELinux Port Type  Proto    Port Number
# http_port_t        tcp      10080

今回は既存ファイルではなく、まだbindされていないポート番号のSecurity Context定義変更なので、relabelのような反映操作は不要です。
次回ポート番号が生成するときに、上記定義を参照してTCP10080がhttp_port_tと紐づきます。

では、改めてhttpdを起動します。
今度はちゃんと成功します。

sudo systemctl start httpd

curl localhost:10080
# <!doctype html>
# (以下略)

Security Contextの設定を元に戻します。

sudo semanage port -d -p tcp 10080

最後に補足ですが、今回追加したTCP10080amanda_port_tのport rangeの一部として含まれていました。
今回の場合、既存のTCP10080-10083という定義とTCP10080は「範囲が完全に一致するわけではない」ためうまく定義できたようです。
既存のポート番号の範囲と完全に一致するような定義の仕方をしようとすると、以下のようにエラーになります。

sudo semanage port -a -t http_port_t -p tcp 10080-10083
# ValueError: Port tcp/10080-10083 already defined

Boolean有無の確認

本セクションは、#6. Boolean有無の確認に続けてより具体的なコマンドを説明します。

一番よく使うコマンドは、以下の3つです。

  • sesearch -A --type_trans ...
  • semanage boolean -l
  • setsebool -P ...

1つ目は既存のアクセス許可ルール等を検索するコマンドですが、Booleanの検索にも使えます。
拒否されたときの条件を指定して検索すると、「BooleanがTrueなら有効になるルール」がヒットすることがあります。
その場合、Booleanの有効化によってエラーを解消できると気づくことができます。
具体的な使用例は、#例: BooleanによるSELinuxエラー対応で紹介します。

本セクションの手順を理解する前提として、Booleanの概念について理解している必要があります。
参考: SELinux Type Enforcement - #Boolean

Booleanの一覧表示

Booleanの一覧表示をするコマンドは2種類あります。
getsebool -aは一般ユーザー権限で実行可能なのに対し、semanage boolean -lは特権が必要です。
semanage boolean -lの方が詳細な情報を表示できるので、権限があるのであればこちらのほうがおすすめです。

getsebool -a
# abrt_anon_write --> off
# (以下略)

sudo semanage boolean -l
# SELinux boolean  State  Default Description
# abrt_anon_write  (off  ,  off)  Allow ABRT to modify public files used for public file transfer services.

semanage boolean -lによって以下の情報がわかります。

  • Boolean名
  • 現在値
  • OS起動直後にセットされる値 (※)
  • 簡単な説明

(※) コマンドライン上はDefaultと表記されていますが、これは初期インストールされたLinuxのデフォルト値ではなく、PolicyかLocal Policyに書き込まれたBoolean値、つまりLinux再起動時にセットされるBoolean値を表しています。後述のsemanage boolean -mによってBoolean値を変更するLocal Policyを追加すると、ここに表示されるDefault値も変化します。インストール直後の初期値を知りたい場合は、#manの横断検索を参考にしつつ、manコマンドからご確認ください

以下のようにgrepと組み合わせて使うことも多いです。
httpdではなくhttpでフィルタすることで、なるべく多くの情報にヒットするようにしています。

sudo semanage boolean -l | grep -i http
# (一部抜粋)
# awstats_purge_apache_log_files (off  ,  off)  Determine whether awstats can purge httpd log files.
# httpd_can_connect_zabbix       (off  ,  off)  Allow http daemon to connect to zabbix
# mysql_connect_http             (off  ,  off)  Allow mysqld to connect to http port

検索結果より、ApacheをZabbixやMySQLと連携するときに便利そうなBooleanが存在することがわかりました。
また検索ワードをhttpとしたことで、mysql_connect_httpも含めてヒットしました。

Booleanの意味を調べる

Booleanのざっくりとした意味であれば、semanage boolean -lで知ることができます。
しかし、1行の説明でBooleanの意味を正確に理解することは難しいと思います。

sesearch -b Boolean 名を実行することで、Booleanと紐づくアクセス許可ルールを表示できます。
これによりBooleanを変更することによって、どのルールが変化するのか最も直接的に理解できます。

sesearch -A --type_trans -b httpd_can_connect_zabbix
# allow httpd_t zabbix_port_t:tcp_socket name_connect; [ httpd_can_connect_zabbix ]:True

上記出力より、httpd_can_connect_zabbixというBooleanはApache (httpd_t) がZabbixのポート番号 (zabbix_port_t) に対して接続 (name_connect) するallow Statementを有効化していることがわかりました。

Booleanの変更

Booleanを変更するコマンドは2種類あります。
setseboolsemanage boolean -mです。
(※) Booleanの場合はもともと定義された値を書き換えることになるので、semanageのオプションは-a,--addではなく-m,--modifyになります

setseboolsemanage boolean -mの間にあまり違いはないのですが、細かい機能に若干の違いがあります。
やりたいことに合わせて両者を使い分ける必要があります。
ただ、多くの場合はどちらを使っても問題ありません。

まず、両者に共通する機能から紹介します。
Local PolicyにBoolean値を保存しつつ、現在の挙動も変えるには以下のコマンドを実行します。
以下に2つのコマンドを記載していますが、どちらも全く同じ意味を持ちます。

# -1でBooleanに1をセット。-0でBooleanに0をセット
sudo semanage boolean -m -1 httpd_can_connect_zabbix

sudo setsebool -P httpd_can_connect_zabbix 1

設定の変更結果は以下のように確認できます。
semanage boolean -lCのDefault列も書き換わっているので注意してください。
semanageで表示されるDefaultは、SELinux起動時に読み込まれる値のことであり、Security Policyのソースコード上に定義されたデフォルト値のことではありません。

# -CでLocal Policyのみ表示
sudo semanage boolean -lC
# SELinux boolean           State  Default Description
# httpd_can_connect_zabbix  (on   ,   on)  Allow http daemon to connect to zabbix

# getsebool -a | grep httpd_can_connect_zabbixとほぼ同じ
getsebool httpd_can_connect_zabbix
# httpd_can_connect_zabbix --> on

続いて、semanagesetseboolの細かな違いを一部だけ紹介します。

先ほどはsetsebool -PでBoolean値を設定しましたが、-Pは、Persistentを意味します。
-Pを指定するとLocal Policyに書き込むことで設定を永続化します。
-Pを省略すると現在のBoolean値のみ書き換え、Local Policyは書き換えません。
-Pを指定しないsetseboolは、一時的な試験をしたいときに便利かもしれません。
...が、setseboolを-Pなしで使うことは実用上ほぼないと思います。

Booleanの設定を戻したいときは、以下のように再度変更することで元に戻すのが基本です。

sudo semanage boolean -m -0 httpd_can_connect_zabbix
# または、sudo setsebool -P httpd_can_connect_zabbix 0

設定をもとに戻しても、Local Policyには0という値で記録されてしまいます。

sudo semanage boolean -lC
# SELinux boolean           State  Default Description
# httpd_can_connect_zabbix  (off  ,  off)  Allow http daemon to connect to zabbix

semanage booleanには、-dで特定のLocal Policyを削除する機能がありません。
あるのは、-DによってBooleanに関する「全ての」Local Policyを削除する機能のみです。
当然、影響範囲が大きいので気軽には実行できません。

# 「全ての」Booleanに関するLocal Policyを削除する
sudo semanage boolean -D

sudo semanage boolean -lC
# (出力なし)

semanage boolean -Dを実行しても、現在のBooleanの値が書き換わるわけではないのでご注意ください。
事前にBooleanをデフォルト値に手動で戻した上で実行しないと、思わぬ挙動になる懸念があります。
最も確実なのは、semanage boolean -Dを実行した後にOSを再起動することです。
それによってBooleanがPolicyから再読込されて、確実に意図した値に収束します。

例: BooleanによるSELinuxエラー対応

ここでは、httpd_read_user_contentというBooleanを例に紹介します。
(※) セキュリティの観点ではこの機能を使うこと自体望ましくないとは思うのですが、主目的はSELinuxの検証なので目をつぶってください。

事前準備として、Apacheをインストールしておきます。

sudo dnf install httpd

そして/etc/httpd/conf.d/userdir.confを以下のように書き換えます。
元々はuserDir disabledが有効でUserDir public_htmlコメントアウトされていますが、これら2行のコメントを反転させます。

<IfModule mod_userdir.c>
    #UserDir disabled
    UserDir public_html
</IfModule>

この設定により、以下のようにホームディレクトリのHTMLファイルをWEB公開できるようになります。

  • $HOME/public_html/配下に配置したhtmlファイルがhttpdに公開される
  • 上記に公開したファイルは、http://xxx.com/~user/xxx.htmlのようにHTTPアクセスすることで参照できる

具体例は後ほど示します。

ちなみに設定行がIfModuleに囲われていますが、以下の通りデフォルトでモジュールをロードしているので、上記設定はアクティブになります。

httpd -M | grep userdir
#  userdir_module (shared)

ここで、httpdを起動します。
特にエラーは起こりません。

sudo systemctl start httpd

続いて、ファイルを配置してコンテンツを公開します。
また、ACLによってapacheユーザーがホームディレクトリ配下を見えるようにrx権限を付与しておきます (※)
(※)セキュリティホールなので、本番環境ではやらないでください

mkdir ~/public_html
chgrp apache ~/public_html
echo aaa > public_html/a.html

setfacl -m u:apache:rx ~

公開したコンテンツにアクセスを試みます。
すると、403 Forbidden応答が返ります。
403応答は、アクセス権限がないことを表します。

curl localhost/~endy/a.html
# <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
# <html><head>
# <title>403 Forbidden</title>
# </head><body>
# <h1>Forbidden</h1>
# <p>You don't have permission to access this resource.</p>
# </body></html>

エラーの詳細をerror_logから確認します。
Permission Deniedになっていることがわかります。

sudo tail /var/log/httpd/error_log
# (一部抜粋)
# [Sat Jan 22 22:49:19.460845 2022] [core:error] [pid 6034:tid 6077] (13)Permission denied: [client 127.0.0.1:50336] AH00035: access to /~endy/a.html denied (filesystem path '/home/endy/public_html') because search permissions are missing on a component of the path

SELinuxの監査ログを確認します。
ログが出ていることから、原因がSELinuxにあることがわかります。

journalctl -qen all -t audit -g denied --no-pager
# Jan 22 23:26:51 fedora35.test audit[6034]: AVC avc:  denied  { getattr } for  pid=6034 comm="httpd" path="/home/endy/public_html/a.html" dev="dm-0" ino=131994 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:httpd_user_content_t:s0 tclass=file permissive=0

必要なアクセス権限が上記のgetattrのみとは限らないので、Permissiveにしてから再度アクセスを試みます。
そして監査ログを再度確認します。
監査ログから、getattr以外にもread, open, mapが必要であることがわかりました。

sudo setenforce 0

curl localhost/~endy/a.html
# aaa

sudo setenforce 1

journalctl -qen all -t audit -g denied --no-pager
# Jan 22 23:28:20 fedora35.test audit[6034]: AVC avc:  denied  { getattr } for  pid=6034 comm="httpd" path="/home/endy/public_html/a.html" dev="dm-0" ino=131994 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:httpd_user_content_t:s0 tclass=file permissive=1
# Jan 22 23:28:20 fedora35.test audit[6034]: AVC avc:  denied  { read } for  pid=6034 comm="httpd" name="a.html" dev="dm-0" ino=131994 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:httpd_user_content_t:s0 tclass=file permissive=1
# Jan 22 23:28:20 fedora35.test audit[6034]: AVC avc:  denied  { open } for  pid=6034 comm="httpd" path="/home/endy/public_html/a.html" dev="dm-0" ino=131994 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:httpd_user_content_t:s0 tclass=file permissive=1
# Jan 22 23:28:20 fedora35.test audit[6034]: AVC avc:  denied  { map } for  pid=6034 comm="httpd" path="/home/endy/public_html/a.html" dev="dm-0" ino=131994 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:httpd_user_content_t:s0 tclass=file permissive=1

setroubleshootのログを見ると、今回はBooleanの有効化を提案しています。
確度は32.5%と低めです。
今回は練習なので、この提案がなかったものとして先に進みます。

journalctl -qen all -t setroubleshoot
# (一部抜粋)
# ****  Plugin catchall_boolean (32.5 confidence) suggests   ****
# setsebool -P httpd_enable_homedirs 1

File Contextを確認してみます。
今回はmvで移動していないので、基本的に差分はないはずです。
実際に、Object Transitionが正しく働いたことで~/public_html作成時に適切なTypeが割り当てられていることがわかりました。

matchpathcon -V ~/public_html/
# /home/endy/public_html verified.

matchpathcon -V ~/public_html/a.html 
# /home/endy/public_html/a.html verified.

sesearch --type_trans -s unconfined_t -D httpd_user_content_t
# type_transition unconfined_t user_home_dir_t:dir httpd_user_content_t public_html;

次にBooleanの存在を確認します。
sesearchで拒否されたアクセスパターンを条件に指定してルール検索します。
複数のBooleanが出てきますが、httpd_enable_home_dirsが名前的に適切そうで、なおかつ全てのPermissionをカバーできていることがわかります。

sesearch -A --type_trans -s httpd_t -t httpd_user_content_t -c file -p getattr,read,open,map
# allow httpd_t httpd_user_content_type:file map; [ httpd_enable_homedirs ]:True
# allow httpd_t httpd_user_content_type:file { getattr ioctl lock open read }; [ httpd_enable_homedirs ]:True

# allow domain file_type:file map; [ domain_can_mmap_files ]:True
# allow httpd_t httpdcontent:file { append create getattr ioctl link lock open read rename setattr unlink watch watch_reads write }; [ ( httpd_builtin_scripting && httpd_unified && httpd_enable_cgi ) ]:True
# allow httpd_t httpdcontent:file { execute execute_no_trans getattr ioctl map open read }; [ ( httpd_builtin_scripting && httpd_unified && httpd_enable_cgi ) ]:True

Booleanの意味を2通りの方法で確認します。
まずは、semanageでざっくり確認します。
httpdがホームディレクトリを読み込めるようにする」とあるので、今回の意図に合ってそうです。

sudo semanage boolean -l | grep httpd_enable_homedirs
# httpd_enable_homedirs          (off  ,  off)  Allow httpd to read home directories

続いて、Booleanの正確な効果をsesearchで確認します。
-bでBoolean名を指定することで、特定のBooleanに関連するルールのみ表示するようフィルタしています。
合計で56行ものルールがヒットしました。
一部のみ抜粋していますが、他のBooleanと組み合わせることでNFSなどにも対応できるようにルールが書かれていることがわかります。

sesearch -A --type_trans -b httpd_enable_homedirs
# (一部抜粋)
# allow httpd_t httpd_user_content_type:file map; [ httpd_enable_homedirs ]:True
# allow httpd_t httpd_user_content_type:file { getattr ioctl lock open read }; [ httpd_enable_homedirs ]:True
# allow httpd_t nfs_t:dir { ioctl lock read }; [ use_nfs_home_dirs && httpd_enable_homedirs ]:True

以下のコマンドでBooleanを有効化します。
すると、今度はアクセスに成功することがわかります。

sudo setsebool -P httpd_enable_homedirs 1

curl localhost/~endy/a.html
# aaa

以下のコマンドでBooleanを削除します。

0をセットするだけでは、httpd_enable_homedirs=0という情報がLocal Policyに残ってしまうため、全Boolean設定をクリアしています。
非常に影響が大きいコマンドなので、意味を理解した上で実行してください。
このコマンドによって「全ての」Boolean「設定」値がデフォルトに戻ります。
現在のBoolean値は変わりません。

なお、以下の手順の場合は1コマンド目のsetseboolの-Pオプションは無くても結果は変わりません。

sudo setsebool -P httpd_enable_homedirs 0
#または、sudo semanage boolean -m -0 httpd_enable_homedirs

sudo semanage boolean -D

ここで要注意なのですが、sudo semanage boolean -Dを実行しただけでは現在のBoolean値は書き換わりません。
必ず、setseboolによって現在の値もセットで書き換えるようにしてください。

また、途中で設定したACLももとに戻しておきます。

setfacl -b ~

Apacheアンインストールは最後にまとめて行います。
#(参考) Apacheのアンインストール

(参考) Custom Policyのビルド

本セクションは、#7. Custom Policyのビルドに続けてより具体的なコマンドを説明します。

Custom Policyを書く状況は滅多に発生しません。
本セクションに書いてある情報は、「必要なときに改めて詳細を思い出せれば良い」という気持ちで楽に読んでいただけますと幸いです。

Custom Policyのビルドの手順やソースコードの読み方について、詳細は別の記事に詳しくまとめています。
参考: SELinux Module Policyのソースコード読解、ビルド

本セクションでは、具体的なコマンドにフォーカスしてお伝えします。

どんなケースでビルドが必要となるのか?

Custom Policyを書く状況は、主に以下の2つがあると思います。

  1. 既に制御されているプロセス用にallowルールを追加する
  2. 全く制御されていないアプリケーションを新たに制御する

まずは1のケースです。
多くの場合、あるアクセスパターンが拒否されたことをきっかけに検討を始めると思います。
そして既存のType定義やallow Statementなどを確認し、必要なallow Statementが足りていないことを確認します。
そして、relabelやBoolean値の変更によってもこの問題を解決できなかったものとします。

この場合、既存のルールにallow Statementを追加定義することになります。
足りていないallow Statementは、自分で一から書くよりもaudit2allowというプログラムから自動生成するのが簡単です。
audit2allowは、AVC (Access Vector Cache) から発行されたアクセス拒否のログから自動的にallow Statementを発行するプログラムです。
詳細な手順は、#例1: 既に制御されているプロセス用にallowルールを追加するにて説明します。

続いて2のケースです。
自分でソースコードを書き、アプリケーションを開発したとしましょう。
このアプリケーション用のアクセス制御ルールは、デフォルトでは当然存在しません。

今回新たに開発したアプリケーションは、一部のアクセスについては既存のルールで許可されるかもしれません。
例えば、アプリケーションをbashからのコマンド実行によって起動する場合には、unconfined_t Typeが割り当てられるので、大半のリソースには問題なくアクセスが可能です。

しかし、targeted policyによってアクセス制御の対象となっているリソース (※) については、デフォルトでアクセスが拒否されることも考えられます。
また、systemdから起動するように構成した場合、Type Transitionを別途定義しなければsystemdと同じinit_t Typeになってしまうことでほとんどのアクセスに失敗します。
(※) 例えばsshdの設定ファイルなど

こういったケースに対応するために、2の場合は以下のように実装します。

  • 専用のTypeやAttributeを定義する
  • type_transitionルールによってTypeを割り当てる
  • allowルールによってアクセスを許可する (1の例と同じ)
  • File Contextを定義する

こちらの例は非常に応用的な内容であり、私のスキルも超えているので本記事ではビルドの手順のみお伝えします。
ソースコードの記述については触れません。
詳細な手順は、#例2: 全く制御されていないアプリケーションを新たに制御するにてお伝えします。

例1、例2の手順は、以下のRHEL8のマニュアルを参考にしました。
RHEL8 - Using SELinux - 7.2. Creating and enforcing an SELinux policy for a custom application

では、具体的な内容に入っていきます。

例1: 既に制御されているプロセス用にallowルールを追加する

本セクションでは、既存で足りていないallowルールを追加する手順をお伝えします。

正直、デフォルトのルールに足りていない設定を見つけるのは大変です...。
本セクションでは手順をお伝えすることを重視し、実用性のない具体例となっていますがご了承ください。

今回は、「httpd_tからtmp_tへのread権限を追加する」ことを例に説明します。

まずは事前準備として、Apacheをインストールしておきます。

sudo dnf install httpd

続いて、DocumentRootにtmp_t Typeを持つファイルを配置し、Apacheを起動します。

sudo bash -c 'echo aaa > /var/www/html/a'
sudo chcon -t tmp_t /var/www/html/a

ls -lZ /var/www/html/a
# -rw-r--r--. 1 root root unconfined_u:object_r:tmp_t:s0 4 Jan 22 16:43 /var/www/html/a

sudo systemctl start httpd

作成したファイルにアクセス確認します。
アクセス成功すればaaaというファイルの中身が表示されるはずですが、Permission Deniedになります。

curl localhost/a
# <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
# <html><head>
# <title>403 Forbidden</title>
# </head><body>
# <h1>Forbidden</h1>
# <p>You don't have permission to access this resource.</p>
# </body></html>

一旦Permissiveにして再度アクセスを実施し、エラーメッセージを全て出しておきます。

sudo setenforce 0

curl localhost/a
# aaa

sudo setenforce 1

続いて、SELinuxのログを確認します。

journalctl -qen all -t audit -g denied --no-pager

# Jan 22 17:17:14 fedora35.test audit[1714]: AVC avc:  denied  { open } for  pid=1714 comm="httpd" path="/var/www/html/a" dev="dm-0" ino=655778 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:tmp_t:s0 tclass=file permissive=1

# Jan 22 17:17:14 fedora35.test audit[1714]: AVC avc:  denied  { map } for  pid=1714 comm="httpd" path="/var/www/html/a" dev="dm-0" ino=655778 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:tmp_t:s0 tclass=file permissive=1

ログから、以下のアクセスが拒否されていることがわかりました。

  • Subject: httpd_t
  • Object: tmp_t
  • Class: file
  • Permission: { open map }

sesearchで確認すると、確かに該当するルールがないことがわかります。
mapのルールのみ1行表示されていますが、BooleanがFalseなので該当しません。

sesearch -A -s httpd_t -t tmp_t -c file -p open,map
# allow domain file_type:file map; [ domain_can_mmap_files ]:True

getsebool domain_can_mmap_files
# domain_can_mmap_files --> off

allow Statementで表現すると、以下のようになります。
このルールをPolicy Packageファイル (.pp) に変換し、インストールすることでエラーを解消できます。

allow httpd_t tmp_t : file { open map };

今回はエラー件数が少ないので自分でも簡単に書けますが、これをAVCから自動生成する方法もあります。
以下のようにAVCのログをaudit2allowコマンドに渡します。
すると、AVCログに書いてある情報をパースしてallow Statementを表示してくれます。
以下はjournalctlで行っていますが、もちろんsudo ausearch -m avc,user_avc | audit2allowでも同様にallow Statementの表示が可能です。

journalctl -qen all -t audit -g denied --no-pager | tail -2 | audit2allow
# #============= httpd_t ==============
# allow httpd_t tmp_t:file open;

# #!!!! This avc can be allowed using the boolean 'domain_can_mmap_files'
# allow httpd_t tmp_t:file map;

必要に応じてこの情報をファイルに出力し、更にallow httpd_t tmp_t:file { open map };のようにまとめて書くことでallowルールを楽に記述できます。

さて、ここからModule Policyをビルドしてインストールする手順に入ります。
以下の手順を行うために、前提としてpolicycoreutils-devel RPMパッケージをインストールしておきます。

sudo dnf install policycoreutils-devel

今回はコマンドのみ示しますが、処理の中身やソースコードの書き方に興味のある方は後続記事のSELinux Module Policyのソースコード読解、ビルド - #Module Policyのビルドの流れをご覧ください。
上記リンクは参考情報のため、スキップしても問題はありません。

まず、作業用フォルダを作って入っておきます。

mkdir ~/work

以下の内容を持つ~/work/my_httpd.teファイルを作成します。
my_httpdはModule Policyの名前にもなるので、必要に応じて名前を変更してください。

policy_module(my_httpd, 1.0.0)
gen_require(`type httpd_t, tmp_t;')
allow httpd_t tmp_t : file { open map };

以下のコマンドでビルドします。
makeによって必要な中間ファイルが自動生成していることがわかります。
今回はmy_httpd.fcmy_httpd.ifに何も書いていないので、自動生成したファイルの中身も空っぽです。

cd ~/work
make -f /usr/share/selinux/devel/Makefile my_httpd.pp

ls -F
# my_httpd.fc  my_httpd.if  my_httpd.pp  my_httpd.te  tmp/

生成したmy_httpd.ppファイルがCustom PolicyのPolicy Packageです。
このファイルをインストールすることで、Security Policyが更新されて効果を発揮するようになります。

sudo semodule -i my_httpd.pp
# または、sudo semanage module -a my_httpd.pp

sudo semanage module -l | grep my_httpd
# my_httpd                  400       pp 

再びcurlアクセスし、httpd_tからtmp_tにアクセスできるか確認します。
今度はうまくいきました。

curl localhost/a
# aaa

確認できたので、my_httpdをアンインストールしておきます。

sudo semodule -r my_httpd
# libsemanage.semanage_direct_remove_key: Removing last my_httpd module (no other my_httpd module exists at another priority).

# または、sudo semanage module -r my_httpd

作業フォルダも削除しておきます。

rm -rf ~/work

例2では、上記とは異なる方法でビルド・インストールします。
例1についても、例2と同様の方法でインストールすることは可能です。
その場合、以下のようなコマンドでビルド・インストールします。
manファイルやrpmファイルを自動生成したいときには便利かもしれません。

こちらのビルド手順については、次のセクションで詳細に説明します。

sudo dnf install policycoreutils-devel rpm-build

mkdir ~/work
cd ~/work
sepolicy generate --init my_httpd

vi my_httpd.te
# (上述のmy_httpd.teと同じ内容に書き換える)

# デフォルトの記述を削除
echo > my_httpd.fc
echo > my_httpd.if

# ビルド、インストール
sudo ./my_httpd.sh 

# 確認
sudo semodule -l | grep my_httpd
# my_httpd

# 後片付け
sudo semodule -r my_httpd
sudo rm -rf ~/work

例2: 全く制御されていないアプリケーションを新たに制御する

既存で制御されていないアプリケーション用のCustom Policyを記述するには、allow Statementだけでなく以下のような定義が必要になります。

  • Type, Attribute
  • type_transitionルール (Domain Transition, Object Transition)
  • allowルール (例1と同じ)
  • File Context

こちらの例では、ソースコードの書き方までは触れません。
ビルドの手順のみざっとお伝えします。

ご参考までに、ソースコード記述の基礎については後続の記事で軽く触れています。
ソースコードを記述する場面はほぼないと思いますが、必要な方はご覧いただければと思います。
参考: SELinux Module Policyのソースコード読解、ビルド

では、手順の紹介に入ります。

例1と同様、手順の実施にはpolicycoreutils-devel RPMパッケージが必要になります。
またビルド・インストールそのものには必須ではありませんが、RPMファイルの自動生成も行う場合はrpm-buildも必要になります。

sudo dnf install policycoreutils-devel rpm-build

先ほどと同様に、作業フォルダを作ります。

mkdir ~/work

作業フォルダに入り、必要なファイルを一括自動生成します。
本来であれば、引数には制御対象のプロセスを起動するための実行ファイルのパスを指定します。
ただ今回は練習ということで、「カレントディレクトリにあるmy_appファイル」を実行ファイルとして扱い、スクリプトを起動します (※)
(※) 実運用で使う際は、こちらのファイルパスを正確に指定する必要があります (/usr/local/bin/my_appなど)。rpmファイルでインストールしたときに配置するファイルパスや、manページの記述、te/if/fcファイルの記述、シェルスクリプト中で自動実行されるrestoreconの対象ファイルなどに影響します。

my_appファイルは実際に存在しなくても以下の--initコマンドは通りますが、後続のmy_app.shのrestoreconコマンドが失敗してしまいます。
それを防ぐため、今回はtouchコマンドによって事前にmy_appファイルを作っておきます。

touch my_app
sepolicy generate --init my_app

# ***************************************
# Warning /home/endy/work/my_app does not exist
# ***************************************

# Created the following files:
# /home/endy/work/my_app.te # Type Enforcement file
# /home/endy/work/my_app.if # Interface file
# /home/endy/work/my_app.fc # File Contexts file
# /home/endy/work/my_app_selinux.spec # Spec file
# /home/endy/work/my_app.sh # Setup Script

本来であれば、ここでte, if, fcファイルにアクセス制御ルールを記述します。
また、RPMファイルを活用する場合はspecファイルに細かいオプションを指定します。
ただ、今回はビルド手順の説明なのでその工程はスキップします。

ソースコード記述の流れとしては、以下のように進めるのが効率的かなと想定しています。

  • allowルール以外を先に全て記述する
  • Permissiveモードで動作確認する
  • 監査ログをaudit2allowに渡してallowルールを自動生成させたものを参考にallowルールも記述する

ですが、今回はソースコードの記述についても割愛します。
デフォルトの自動生成したソースコードを利用し、このままビルド・インストールします。

sudo ./my_app.sh
# Building and Loading Policy
# + make -f /usr/share/selinux/devel/Makefile my_app.pp
# make: 'my_app.pp' is up to date.
# + /usr/sbin/semodule -i my_app.pp
# + sepolicy manpage -p . -d my_app_t
# ./my_app_selinux.8
# + /sbin/restorecon -F -R -v /home/endy/work/my_app
# ++ pwd
# + pwd=/home/endy/work
# + rpmbuild --define '_sourcedir /home/endy/work' --define '_specdir /home/endy/work' --define '_builddir /home/endy/work' --define '_srcrpmdir /home/endy/work' --define '_rpmdir /home/endy/work' --define '_buildrootdir /home/endy/work/.build' -ba my_app_selinux.spec
# setting SOURCE_DATE_EPOCH=1642896000
# Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.GYx2lV
# + umask 022
# + cd /home/endy/work
# + '[' /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64 '!=' / ']'
# + rm -rf /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64
# ++ dirname /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64
# + mkdir -p /home/endy/work/.build
# + mkdir /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64
# + install -d /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64/usr/share/selinux/packages
# + install -m 644 /home/endy/work/my_app.pp /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64/usr/share/selinux/packages
# + install -d /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64/usr/share/selinux/devel/include/contrib
# + install -m 644 /home/endy/work/my_app.if /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64/usr/share/selinux/devel/include/contrib/
# + install -d /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64/usr/share/man/man8/
# + install -m 644 /home/endy/work/my_app_selinux.8 /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64/usr/share/man/man8/my_app_selinux.8
# + install -d /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64/etc/selinux/targeted/contexts/users/
# + /usr/lib/rpm/check-buildroot
# + /usr/lib/rpm/redhat/brp-ldconfig
# + /usr/lib/rpm/brp-compress
# + /usr/lib/rpm/brp-strip /usr/bin/strip
# + /usr/lib/rpm/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump
# + /usr/lib/rpm/redhat/brp-strip-lto /usr/bin/strip
# + /usr/lib/rpm/brp-strip-static-archive /usr/bin/strip
# + /usr/lib/rpm/check-rpaths
# + /usr/lib/rpm/redhat/brp-mangle-shebangs
# + /usr/lib/rpm/redhat/brp-python-bytecompile '' 1 0
# + /usr/lib/rpm/redhat/brp-python-hardlink
# Processing files: my_app_selinux-1.0-1.fc35.noarch
# Provides: my_app_selinux = 1.0-1.fc35
# Requires(interp): /bin/sh /bin/sh
# Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
# Requires(post): /bin/sh policycoreutils selinux-policy-base >= 35.7-1
# Requires(postun): /bin/sh policycoreutils
# Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64
# Wrote: /home/endy/work/my_app_selinux-1.0-1.fc35.src.rpm
# Wrote: /home/endy/work/noarch/my_app_selinux-1.0-1.fc35.noarch.rpm
# Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.CVjduT
# + umask 022
# + cd /home/endy/work
# + /usr/bin/rm -rf /home/endy/work/.build/my_app_selinux-1.0-1.fc35.x86_64
# + RPM_EC=0
# ++ jobs -p
# + exit 0

シェルスクリプトの中身では、主に以下の処理を実行しています。

  • make -f /usr/share/selinux/devel/Makefile my_app.pp || exit
  • /usr/sbin/semodule -i my_app.pp
    • my_app Moduleのインストール
  • sepolicy manpage -p . -d my_app_t
    • manページファイルを生成。配置はしない
  • /sbin/restorecon -F -R -v /home/endy/work/my_app
  • rpmbuild ...
    • RPMファイルの生成

シェルスクリプトによって、my_appモジュールがインストールされています。

sudo semodule -l | grep my_app
# my_app

RPMパッケージとmanファイルが生成しています。

file my_app_selinux.8
# my_app_selinux.8: troff or preprocessor input, ASCII text

file noarch/my_app_selinux-1.0-1.fc35.noarch.rpm 
# noarch/my_app_selinux-1.0-1.fc35.noarch.rpm: RPM v3.0 bin i386/x86_64 my_app_selinux-1.0-1.fc35

manファイルはカレントディレクトリにありますが、/usr/share/man/man8には配置されていないため、mandbには登録されていません。

man my_app_selinux
# No manual entry for my_app_selinux

man ./my_app_selinux.8
# ファイルを直接指定すれば見える

一旦Moduleをアンインストールして、RPMをインストールしてみます。
すると、Moduleがインストールされ、manも表示できるようになります。
Moduleを配布するときはRPM形式が便利です。

sudo semodule -r my_app
sudo dnf install noarch/my_app_selinux-1.0-1.fc35.noarch.rpm

sudo semodule -l | grep my_app
# my_app

man my_app_selinux
# (表示される)

RPMをアンインストールすると、my_app Moduleもアンインストールされます。

sudo dnf remove my_app_selinux

sudo semodule -l | grep my_app
# (出力なし)

man my_app_selinux
# No manual entry for my_app_selinux

my_app_selinux.specを見るとインストール/アンインストール時に実行されるコマンドが書いてあります。
また、rpm -ql my_app_selinuxからRPMインストール時に配布するファイルを確認できます。
興味のある方はぜひ実機で試してみてください。

最後に、workフォルダを削除しておきます。

sudo rm -rf ~/work

(参考) SELinuxの確認コマンド

ここでは、他のセクションで言及されなかったコマンドや調査方法を中心にここで紹介します。
また、#sesearch#seinfoの詳しい使い方にも軽く触れます。

sesearch

sesearchによって、SELinuxのアクセス制御ルールを検索できます。

検索対象を指定するオプションを1つ、または複数指定する必要があります。
検索対象を指定するオプションは、主に以下です。

オプション 意味
-A allow, allowxpermルールを検索する
--type_trans type_transitionルールを検索する

デフォルトでは、全てのルールを表示します。
フィルタしないと、数万行ものルールが表示されてしまいます。

フィルタを指定するオプションは、主に以下があります。
オプションに指定するパラメータは、カンマ区切りで複数指定できます。

オプション 意味
-s Type source Typeでフィルタする。Attributeも指定可
-t Type target Typeでフィルタする。Attributeも指定可
-c Class Object Class でフィルタする
-p Permission Permission でフィルタする
-b Boolean Boolean に関連するもののみにフィルタする
-D Default --type_transなどと一緒に使い、Default Typeなどでフィルタする

デフォルト動作では、例えば-s Typeを指定するとsourceに指定したType、またはそのTypeを内包するAttributeが指定されたルールのみにフィルタされます。
以下のオプションを-s-tと共に指定することで、上記のフィルタ挙動を変更することができます。

オプション 意味
-ds -s Typeと共に指定すると、そのTypeを内包するAttributeがヒットしなくなる
-dt -dsと同様。-tとセットで使う
-rs -sの検索に正規表現を使用する
-rt -tの検索に正規表現を使用する

他にも色々オプションがあるので、お試しください。

seinfo

seinfoは、Type、Attribute、Class、ポート番号などのObjectの情報を表示するコマンドです。

何も付けずに実行すると、統計情報を表示します。

seinfo
# (一部抜粋)
  # Classes:             134    Permissions:         456
  # Types:              4990    Attributes:          251

代表的なオプションは以下の通りです。

オプション 意味
-t Typeの一覧表示
-a Attributeの一覧表示
-c Classの一覧表示
--common Commonの一覧表示
--portcon ポート番号とSecurity Contextの紐付け一覧表示

-xと併用すると、オプションによってはより詳細な情報が表示されます。
よく使うのは、以下の組み合わせです。

組み合わせ 用途
-xa, -xt TypeとAttributeの紐付け
-xc, -x --common ClassとPermissionの紐付け

例えば、TypeやAttributeの意味を調べたいときは以下のようなコマンドをよく使います。

ClassとPermissionの紐付けを知りたい時はseinfo -xcseinfo -x --commonを使います。
更にPermissionの意味を知りたいときは、The SELinux NotebookのAppendix Aを参照します。

現在のSecurity Contextの調べ方

現在のSecurity Contextを調べるコマンドは、以下のとおりです。

調査対象 コマンド例
ファイル ls -Z
プロセス
  • ps -eZ
  • ps -eo user,cmd,label
ポート番号
  • seinfo --portcon
  • sudo semanage port -l
ユーザー(※) id -Z

(※) id -ZはRBACを利用している環境においては便利なこともありますが、基本的には使いません。id -Zps -Zで表示されるログインシェルのコンテキストは同じなので、id -Zが必須になる場面も実はありません。

(※) ss -Zというコマンドはほぼ使わないため、表に載せていません

(参考) manの横断検索

selinux-policy-doc RPMパッケージがインストール済みであれば、*_selinuxというmanページを参照できます。
情報量はmanページによってまちまちですが、少なくともBooleanの初期値は確実に書いてあります。
他の情報はあまり書いてないことが多いので、「見つかったらラッキー」ぐらいの気持ちで利用するのがちょうど良いと思います。

例えば、cron_userdomain_transitionというBoolean値のデフォルト値を知りたかったとすると、以下のようにキーワード検索します。

man -Kw cron_userdomain_transition
# /usr/share/man/man8/openshift_app_selinux.8.gz
# /usr/share/man/man8/openshift_selinux.8.gz
# /usr/share/man/man8/staff_selinux.8.gz
# /usr/share/man/man8/sysadm_selinux.8.gz
# /usr/share/man/man8/unconfined_selinux.8.gz
# /usr/share/man/man8/user_selinux.8.gz
# /usr/share/man/man8/crond_selinux.8.gz

続いて、検索にヒットしたmanページを確認します。
試しに一番上にあるman openshift_app_selinuxを確認すると、デフォルトがTrueであることがわかります。

cron_userdomain_transition boolean. Enabled by default.

他にもTypeやAttributeでも検索できますが、ヒットしなかったり、必要な情報が書いていないことがほとんどです。
manで情報が見つからなかった場合は、以下のように別のアプローチに切り替える必要があります。

  • #seinfoでTypeとAttributeの紐付け情報を探る
  • #sesearchでどのようなallowルールで使われているか探る

(参考) Local Policyのexport/import

semanageを活用することで、ソースコードを記述すること無くLocal Policyを定義してSELinuxの動作を変えることができます。

このLocal Policyをexport、importすることで、複数のマシンに同じ設定を適用することが可能です。

exportは以下のコマンドで行います。
出力されているのはsemanageのサブコマンドです。
最初に-Dで設定をクリアしてから、一番下の行で今の設定を適用するようなスクリプトになっています。

sudo semanage export
# boolean -D
# login -D
# interface -D
# user -D
# port -D
# node -D
# fcontext -D
# module -D
# ibendport -D
# ibpkey -D
# permissive -D
# boolean -m -1 httpd_can_connect_zabbix

exportした内容は、-fオプションでファイルに保存できます。

sudo semanage export -f semanage.txt
cat semanage.txt
# boolean -D
# login -D
# interface -D
# user -D
# port -D
# node -D
# fcontext -D
# module -D
# ibendport -D
# ibpkey -D
# permissive -D
# boolean -m -0 httpd_can_connect_zabbix

exportしたファイルをsemanage import -fに渡すことで、設定をインポートできます。
exportしたファイルをGit管理して、Ansibleのような自動化ツールによって共通したSELinux設定を配布するような使い方ができるのではないかと思います。

sudo semanage boolean -D
sudo semanage boolean -lC
# (出力なし)

sudo semanage import -f semanage.txt
sudo semanage boolean -lC
# SELinux boolean           State  Default Description
# httpd_can_connect_zabbix  (off  ,  off)  Allow http daemon to connect to zabbix

(参考) Apacheのアンインストール

本記事で検証用にインストールしたApacheのアンインストール手順を書いておきます。

sudo dnf remove httpd
sudo rm -rf /etc/httpd /var/log/httpd /var/www ~/public_html/

今回編集したファイル+αはアンインストール後も残るので、手動である程度消しています。

まとめ

SELinuxでアクセス拒否エラーが出た時の切り分け手順と、原因調査や修正に必要なコマンドを紹介しました。
また、基本操作としてSELinuxを無効化する手順にも触れました。

次の記事

SELinuxシリーズの本編は、本記事で最後です。
次回以降の記事は、全て参考情報です。

次の記事では、Type Enforcement以外のアクセス制御方式であるRBAC, UBAC, MLS, MCSの概要を紹介します。
MCS以外についてはmls policyでしか基本使われないため、多くの方にとってはあまり知らなくても良い内容だと思います。

「なぜ多くの人は知らなくてよいのか」といった部分が気になる方は、その部分を中心にお読みいただければと思います。

endy-tech.hatenablog.jp

SELinux Type Enforcement

SELinux_logo

SELinuxシリーズ

本記事は、SELinuxシリーズの3記事目です。

  1. Linuxプロセスアクセス制御の概要
  2. SELinuxの概要
  3. SELinux Type Enforcement ←今ココ
  4. SELinuxの実践
  5. (参考) SELinuxのRBAC、UBAC、MLS、MCS
  6. (参考) SELinux Module Policyのソースコード読解、ビルド
  7. 参考URL

1〜3記事目は、4記事目を理解するための前提知識をカバーしています。
4記事目が最も重要で、SELinuxの具体的な操作方法やコマンド、トラブルシューティング手順を紹介しています。

5記事目以降は参考情報です。

SELinuxの関連記事は、SELinuxタグから探せます。

一連の記事はFedora環境を前提として書いています。
FedoraRHELに類するディストリビューションであればほぼ同等の挙動になると思いますが、他のディストリビューションでは挙動に差異がある可能性があるのでご注意ください。

お伝えしたいこと

本記事では、SELinuxのアクセス制御において必ず必要となるType Enforcementの理論を説明します。
SELinuxの運用で使う具体的なコマンドやテクニックについては、次の記事であるSELinuxの実践にて紹介します。

SELinuxで実装可能なアクセス制御は、以下のとおり複数存在します。

  • TE (Type Enforcement)
  • RBAC (Role-Based Access Control)
  • UBAC (User-Based Access Control)
  • MCS (Multi-Category Security)
  • MLS (Multi-Level Security)

SELinuxのデフォルト構成 (targeted policy) においては、TEとMCS以外のアクセス制御は実質的に全て無効化されています。
また、MCSを利用するアプリケーションもごく一部のみです (※)
したがって、ほとんどの場合はTEのみ覚えれば十分です。
(※) MCSを使っているのは、主にKVMやコンテナ技術などの仮想化周りです

本記事においてもRBAC、UBAC、MCS、MLSについては概要レベルで軽く触れますが、後半からはTEのみにフォーカスして詳しく説明します。

SELINUXTYPE (policy)

SELINUXTYPE (policy) とは

SELinuxの設定ファイルが/etc/selinux/configにありますが、ここでSELinuxの大まかな設定を変えることができます。
その設定項目の一つがSELINUXTYPEです。
SELINUXTYPEは、単にpolicyと呼ばれることもあります (※)1,2
(※) アクセス制御ルールの定義ファイルであるSecurity Policyと、SELINUXTYPEを表すpolicyは似て非なる言葉なので、混同しないようご注意ください。

SELINUXTYPEが変わることで具体的に何が起こるかというと、読み込まれるSecurity Policyファイルそのものが変わります。
Security Policyのファイルパスは、以下のとおりです。
/etc/selinux/SELINUXTYPE/policy/policy.NN

つまりSELINUXTYPEが変わると、SELinuxのアクセス制御ルールが全く異なる内容に変化します。

SELINUXTYPEは、以下の3種類のいずれかです3
セキュリティは、minimum < targeted < mlsの順に強くなります。
デフォルトはtargetedで、基本的に変更することはありません。
これより先では、特に明記しない限りtargeted policyを前提として説明します。

policy 詳細
minimum
  • MCS policy
  • MCSとTEのみ有効
  • 最低限のModule Policyのみをインストールする
  • ごく一部のプロセスのアクセス制御ルールのみを定義する
targeted
  • MCS policy
  • MCSとTEのみ有効
  • minimumよりも多くのModule Policyをインストールし、より多くのプロセスのアクセス制御ルールを定義する
mls
  • MLS policy
  • TE, RBAC/UBAC, MCS/MLSが全て有効
  • 最も厳しいアクセス制御を実装する
  • デフォルトルールからカスタマイズすることが前提

SELINUXTYPEの確認方法

SELINUXTYPEは、OS起動時に読み込まれる/etc/selinux/configで設定します。
/etc/selinux/configの一部を以下に抜粋します。

# SELINUXTYPE= can take one of these three values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected. 
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

実際のステータス値としてのSELINUXTYPEは、sestatusで確認できます。
以下の出力は、targetedとして動作しているときのものです。

sestatus
# SELinux status:                 enabled
# SELinuxfs mount:                /sys/fs/selinux
# SELinux root directory:         /etc/selinux
# Loaded policy name:             targeted
# Current mode:                   enforcing
# Mode from config file:          enforcing
# Policy MLS status:              enabled
# Policy deny_unknown status:     allowed
# Memory protection checking:     actual (secure)
# Max kernel policy version:      33

minimum, targeted, mlsの違い

3つのpolicyの違いを下表にまとめました。

SELINUXTYPE TE RBAC UBAC MCS MLS
minimum × × ×
targeted × × ×
mls
  • ×:デフォルトは実質無効
  • △:デフォルトは一部有効
  • ◯:デフォルトで有効

基本はデフォルト値のtargeted policyを使います。
targeted policyはMCSとTEがありますが、基本的にはTEのみ意識すれば十分です。

上表で「×」でマークされた機能が実質無効である根拠については、後続記事の(参考) SELinuxのRBAC、UBAC、MLS、MCSで説明します。
本記事については、上表が正しい前提で話を進めます。

(参考) TEの概要

TE (Type Enforcement)とは、SubjectとObject割り当てられたTypeと呼ばれる識別子に基づき、アクセス可否を制御するSELinuxの最も基本的なアクセス制御実装です。

今の時点では、「SELinuxのアクセス制御は基本的にTEで実装されており、他のアクセス制御は別の観点を追加することでセキュリティを高めている」と理解ください。

TEについて、詳細は#TE (Type Enforcement)以降のセクションで説明します。

(参考) MCSの概要

MCSは、プロセスやリソースをCategoryに分けてアクセス制御を行う技術です4
CategoryはTypeとは独立して定義されます。

MCSは、TEでは同じTypeが割り当てられる関係のプロセスに対して、異なるCategoryを割り当ててより細かくアクセス制御します。

例えばコンテナを複数起動した場合、各コンテナプロセスには同じType、異なるCategoryが割り当てられます5
これによって、MCSはTEよりも細かくコンテナ関連のアクセス制御を実装できます。

MCSを使っている主な技術は、以下のとおりです6

  • OpenShift
  • virt (sVirt)
  • sandbox
  • network labeling
  • containers (container-selinux)

上記から、コンテナやKVMなどの仮想化技術を使っているのでない限りは、MCSを意識する必要はほぼありません。

sVirtの場合、MCSのCategory割り当てのロジックは、SELinuxではなくアプリケーション側に実装されているようです。
Categoryの割り当ては基本的にsVirtの機能で自動的に行われますが、ユーザーが手動で設定することもできるようです7

(参考) RBAC、UBAC、MLSの概要

RBAC、UBAC、MLSもMCSと同様、TEが利用するTypeとは別の識別子に基づいてアクセス制御する技術です。
いずれもデフォルトのtargeted policyでは実質無効化されているため、ここでは扱いません。

これらの用語に関する説明は、後続記事の(参考) SELinuxのRBAC、UBAC、MLS、MCSを参照してください。

(参考) targeted policyの概要

#TE (Type Enforcement)以降のセクション、及び後続記事では、targeted policyを前提に説明を進めます。
この後の説明を読んでいく中で、targeted policyの動きについては自然と理解が深まるはずです。

現時点では、targeted policyについて以下の認識を持っていただければ十分です。

  • デフォルト構成
  • TEとMCSが有効。ほかは全て無効
  • 初期状態のLinuxであればアクセス拒否しないよう十分作り込まれている (mls, minimumでは大量のエラーが出る)

(参考) mls policyの概要

mls policyは、TE、MCS、RBAC、UBAC、MLSの全てが有効化で、最もセキュリティの高い構成です。

一部の企業では使用されていますが、targeted policyでもある程度の効果が得られる点、mls policyの運用の難易度の高さからあまり使われていない印象です。
RHEL8のマニュアルでも、利用する団体の例として「軍隊」が挙げられているほどです8

以降は、mls policyがどのように難しいのかを説明します。

まず、MLSが最小限のデフォルトの設定しかなく、自身でカスタマイズする前提の機能であることが難しさのポイントとして挙げられます。

MLSは、プロセスやファイルなどのリソースにSensitivity Levelを割り当て、セキュリティ強度の上下関係によってアクセス制御する技術です。
デフォルトで最低レベルのs0から最高レベルのs15までLevel自体は作成されますが、後はユーザー自身の手で以下のような設計・実装を行う必要があります。

  • 各種リソースをどのようなLevelをつけるか 9
  • Levelの違いによるアクセス許可/拒否の条件をどのように定義するか (※)

(※) デフォルトでも最低限の基本ルールはあります

他にも以下の点で、mls policyは難しいと思います。

  • RBACの権限設計の難易度が高い
  • RBAC前提の運用フローや手順の設計が大変
  • そもそもmls policyを運用している人が少ないので情報が見つかりにくい
  • mls policyは、targeted policyとは異なりデフォルトで大量のアクセス拒否エラーが出る (※)

(※) RBACの設定起因だと思うのですが、私の実体験としてログイン直後の~/.bash_profileの読み込みがPermission Deniedになりました。他にも大量のエラーが数秒おきに発生しました

mls policyが必要な明確な要件がある場合は別ですが、多くの場合はmls policyを使いません。
targeted policyを使います。

(参考) minimum policyの概要

※このセクションでは、まだ説明していない用語を使用します。Base PolicyとModule Policyについては、後続記事の(参考) SELinux Module Policyのソースコード読解、ビルド - Base PolicyとModule Policyにて説明します。Type Transitionについては、後続セクションの#Type Transitionで説明します。

minimum policyは、Base Policy以外は何もインストールされていないモードです10
httpdなどの代表的なミドルウェアに対するSELinuxのルールは、ほぼ定義されていません。
Linux Kernelやsyslogdなど一部の基本機能のみがアクセス制御対象となり、他のプロセスにはデフォルトのTypeが割り当てられます。

具体的には、ユーザー自身が手動で起動したプロセスには、多くの場合unconfined_t (unconfined = 制限されない) Typeがアサインされてアクセス許可されます。
これはtargeted policyと同様です。

systemdによって自動起動されるようなプロセスの大半は、Type Transitionが働かず親プロセスのsystemdと同じinit_tが割り当てられ、アクセスに失敗するケースが多いです。

なお、minimum policyでModule Policyを一覧表示しようとするとエラーになります。
この出力からも、ほぼBase Policyしか有効化されていないのだと理解できます。

sudo semodule -l
# No modules.

minimum policyの主なユースケースは、SELinuxの開発者が最低限のModule Policyのみ有効化した構成で単体試験するときです。

我々ユーザーがminimum policyを利用することは基本ありません。
なぜなら、targeted policyやmls policyで初期インストールされたModule Policyを後からアンインストールすることはできません。
やるとしたら、私達が独自に開発したModule Policyを追加インストールするのみです。
したがって、仮に独自開発したModule Policyの動作確認をしたい要件があったとしても、わざわざminimum policyで最小構成にする必要がないのです。

私達がminimum policyを使うことはまずありません。
使うとしたらtargeted policyか、mls policyです。

(参考) SELINUXTYPEの変更方法

後続記事のSELinuxのRBAC、UBAC、MLS、MCS - (参考) mls policyに変更する方法にて手順を紹介しています。

mls policyに変更する手順を扱っていますが、他のpolicyについても同様です。

TE (Type Enforcement)

TE (Type Enforcement) は、SubjectとObjectに対してTypeと呼ばれる識別子を割り当て、Typeに基づいてアクセス可否を制御するSELinuxの最も基本的なアクセス制御実装です。

特にデフォルトのtargeted policyにおいては、TEとMCSしか使われません。
そしてMCSは一部のアプリケーションでしか使われません。
つまり、SELinuxのアクセス制御はほぼTEのみで実装されています。

TEでは、以下のようなルールを実装します。
関連するキーワードをカッコ付きで付記していますが、これらの用語については後続のセクションで説明します。

  • Typeの定義 (Type, Attribute)
  • Typeの割り当て方に関する規則 (Type Transition, File Context)
  • Typeに基づくアクセス許可ルール (allow Statement)

一番重要なのは、最後のallow Statementです。
allow Statementが、SubjectのTypeとObjectのTypeの組み合わせに対し、許可されたActionを紐付けて定義します。
そして、allow Statementで明示的に許可されなかったアクセスパターンは、暗黙的に全て拒否されます。

この後のセクションで、TEによるアクセス制御ルールがどのように定義されているのか、順を追って紹介します。

allow Statement

allow Statementは、SELinuxが許可するアクセスを定義します。
ここで許可されなかったアクセスは、全てデフォルトで拒否されます。

setools-consoleパッケージがインストールされていれば、sesearch -Aで表示できます。

デフォルトのallow Statementを一部抜粋します。
このアクセス許可ルールでは、sshdプロセス (Subject) がfile_typeに属するディレクトリ群 (Object) に対してgetattr, open, search権限を許可しています。

allow sshd_t file_type:dir { getattr open search };

allow Statementの構文は、以下のとおりです11
ACLのように、source, target を並べて書きます。
(※) source, target, type, class, permissionなどの用語は、この後順を追って説明します。

allow source_type target_type : class perm_set;

type, class, perm_setを複数並べたいときは、{}で囲ってスペース区切りで列挙します。
今回は、perm_set{ getattr open search }と複数指定されています。

なお、SELinuxにはdeny Statementは存在しません。
既に許可されたアクセスパターンは、後から拒否することはできません。
そして、許可されなかったアクセスパターンがデフォルトで拒否されます12

今回のルールについては、以下のような対応関係になっています。

Syntax Syntaxの意味 今回の値
source_type SubjectのType sshd_t
target_type ObjectのType (※) file_type
class Object Class dir
perm_set 許可するAction getattr
open
search

(※) target_typeにselfというキーワードが指定された場合、「source_typeと同じ」という意味になります

少しややこしいですが、allow StatementのSyntaxでは言葉遣いが若干変わります。
以下の言葉は同じ意味を持ちます。

  • Source = Subject
  • Target = Object
  • Class = Object Class
  • Permissions = Actions

更に新しい用語としてTypeとObject Classが出てきました。
この後のセクションで、関連用語も含めつつ順に説明していきます。

  • Security Context (Label)
  • Type
  • Attribute
  • Object Class
  • Common

Security Context (Label)

Typeの説明をするために、まずはSecurity Contextという概念について紹介します。

Security Contextとは、SELinuxが有効な場合に全てのSubjectとObjectに割り当てられる識別子 (文字列) です13
SubjectとObjectとはすなわち、全てのプロセス、ファイル、ネットワークソケット、ファイルシステム、データベース、ユーザーなど、SELinuxのアクセス制御に関わる全てのリソースです。

Security Contextは、Labelとも呼ばれます。
プロセスにSecurity Contextを割り当てることをLabeling (ラベル付け) と表現することもあります。

Security Contextのフォーマットを以下に示します。
Security Contextは、コロンで区切られた文字列で表現されます。

user:role:type[:range]

各要素の意味は、以下のとおりです。

要素名 意味
user
  • SELinux User
  • xxx_uと表記される
  • Linuxユーザーとは別物
  • Linuxユーザーは、ログイン時に1つのSELinux Userと紐づく
  • SELinuxユーザーは1つか複数のroleと紐づく
  • UBACとRBACに使われる
role
  • SELinux Role (役割)
  • xxx_rと表記される
  • roleは1つか複数のtypeと紐づく
  • RBACに使われる
type
  • SELinux Type
  • xxx_tと表記される
  • SubjectとObjectを識別する
  • アクセス許可設定はTypeを指定して定義する
range
  • Sensitivity Level (※) とCategoryの組み合わせ
  • 表記例: s0s0-s0:c0.c1023など
  • range全体、またはCategoryのみが省略されることもある
  • LevelはMLS、CategoryはMCSで使われる

※Sensitivity Levelは、Security Levelや、単にLevelと表記されることもあります

上述の要素の中で重要なのは、typeのみです。
他の要素は無視して結構です。

なぜなら、targeted policyでは基本的にTEしか使われず、TEで使うのは基本Typeのみであるためです。
RBAC/UBAC、MLS/MCSを使う場合のみ、Type以外の要素が重要になります。

例えば、sshdプロセスのSecurity Contextは、以下のように表されます。
Security Contextは非常に長いですが、この中で重要な情報はsshd_tのみです。

ps -o label,uid,command -C sshd
# LABEL                                   USER COMMAND
# system_u:system_r:sshd_t:s0-s0:c0.c1023 root sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups

SELinuxでアクセス制御を実装する際、プロセス名やファイル名を直接指定するのではなく、Security ContextやObject Classを指定してルールを定義します。

例えば今回取り上げた#allow Statementでは、プロセスやファイルが持つTypeを指定して許可設定を記載していました。
冒頭のallow Statementを再掲します。

allow sshd_t file_type:dir { getattr open search };

Type

Typeは、前述のSecurity Contextの3番目の要素です。

Typeの定義は、setools-consoleパッケージが入っていればseinfoコマンドで確認できます。

-xをつけるとより詳細な情報がわかります。
今回はsshd_tに絞って出力していますが、Type名を指定しなければ全て表示されます。

seinfo -t sshd_t

# Types: 1
#    sshd_t

seinfo -xt sshd_t

# Types: 1
#    type sshd_t, polydomain, nsswitch_domain, login_pgm, can_change_object_identity, can_change_process_identity, can_change_process_role, corenet_unlabeled_type, domain, kernel_system_state_reader, netlabel_peer_type, privfd, daemon, syslog_client_type, pcmcia_typeattr_1, ssh_server, unconfined_login_domain, userdom_home_manager_type;

この後もseinfoは何度か出てきますが、以降のセクションでは-xありの出力のみ掲載します。
-xなしの出力は、シンプルに一覧表示するときには便利ですが、先の実行例のように出力を絞っている場合には掲載する意味がないためです。

Type名は以下の命名規則で定義するのが通例です。

  • _tで終わる
  • プロセスのTypeは、プロセス名_t
  • プロセスを起動するための実行ファイルのTypeは、プロセス名_exec_t
  • ログファイルは、プロセス名_log_t
  • ポート番号は、プロトコル名_port_t

Attribute

上述のseinfo -xt sshd_tの出力において、type sshd_tの右に並んでいるのはAttributeと呼ばれるものです。14,15,16
Attributeは、Typeの集合体です。

単一のAttributeに1つ、または複数のTypeを紐付けて定義します。
定義したAttributeは、Typeと同様に扱うことができます。
なお、AttributeにはTypeのような命名規則はありません。

Attributeの定義は以下のコマンドで確認できます。
seinfo -xaは、AttributeとTypeの紐付けを「Attributeごとに」表示します。
紐付けを「Typeごとに」表示するseinfo -xtと情報量は同じですが、表示方法が異なります。

seinfo -xa unconfined_login_domain

# Type Attributes: 1
#    attribute unconfined_login_domain;
#      chroot_user_t
#      crond_t
#      local_login_t
#      remote_login_t
#      rshd_t
#      sshd_t
#      sulogin_t

allow StatementにAttributeを指定することで、複数Typeを含むアクセス許可ルールを少ない文字数で表現できます。

例えば上述のunconfined_login_domainをsource_typeに指定すると、{ chroot_user_t crond_t local_login_t remote_login_t rshd_t sshd_t sulogin_t } を指定したのと同じ意味になります。
そして、後から別のTypeをunconfined_login_domainに追加すると、そのTypeも既存のallow Statementによるアクセス許可の対象に含まれます。

Object Class (OC)

Object Classは、Objectを用途によってざっくり分類する概念です17
単にclassと呼ばれることもあります。

Object Classは、(定義上) TypeやAttributeとは紐付きません18
ニュアンスとしてはObject ClassとType/Attributeに関連性があると意識しても良いのですが、ソースコード的にはそのように書かれていません。

Object Classは、Permissionsと紐付きます。

試しにObject Classの定義を一部見てみましょう。

seinfo -xc dir

# Classes: 1
#    class dir
# inherits file
# {
#  add_name
#  reparent
#  search
#  rmdir
#  remove_name
# }

上記の出力から、dir (ディレクトリ) というObject Classは、add_name, reparent, search, rmdir, remove_nameというPermissionsと紐付いていることがわかります。

また、定義の中にinherits fileという文字列がありますが、これも重要です。
ここで出てくるfileは、Commonと呼ばれるものです。
Commonについては、次のセクションで説明します。

Common

CommonもActionと紐づく概念です。19, 20
Commonがallow Statementで直接指定されることはありません。
CommonはObject ClassのinheritキーワードによってActionを継承させることで、間接的に作用します。

Commonは複数のObject Classと紐づくことで、Object Classの定義を簡潔に表現するのに使われます。

以下のコマンドで、file Commonの定義を確認しましょう。

seinfo -x --common file

# Commons: 1
#    common file
# {
#  watch_mount
#  watch_with_perm
#  ioctl
#  setattr
#  swapon
#  open
#  mounton
#  map
#  append
#  getattr
#  audit_access
#  watch_sb
#  create
#  unlink
#  write
#  lock
#  execmod
#  rename
#  relabelfrom
#  link
#  watch_reads
#  quotaon
#  execute
#  relabelto
#  watch
#  read
# }

file Commonに定義された上記のPermissionsは、fileを継承するdir Classにも紐付けられます。
したがって、dir Object Classの定義上は5つのPermissionsしか定義されていませんでしたが、実際にはfile Commonと紐づく26のPermissionsも追加で扱えます。

ここで、冒頭のallow Statementを再掲します。

allow sshd_t file_type:dir { getattr open search };

Object Classとしてdirが指定された時点で、Object Classの定義上31通り (dir classの5通り + file commonの26通り) のPermissionsの実行に絞り込まれています。
更にallow Statementによって、sshd_tがfile_typeに対して実行できるActionは3つに絞り込まれた、ということになります。

最後に2点補足します。

(1)
Object ClassやCommonと紐づくPermissionsの意味は、以下のリンクで調べることができます。
同じPermission名でもCommonやObject Classによって意味が異なるので、該当箇所のPermissionsを確認するようにしてください。
例えば、同じgetattrでもdirとsocketでは若干意味が異なります。
The SELinux Notebook - Appendix A - Object Classes and Permissions

(2)
CommonとObject Classの一覧を確認すると、SELinuxがざっくり何をアクセス制御できるのかを俯瞰できて便利です。

seinfo --common

# Commons: 7
#    cap
#    cap2
#    database
#    file
#    ipc
#    socket
#    x_device

seinfo -c
# (行数多いので一部のみ抜粋)

# Classes: 134
  #  alg_socket
  #  capability
  #  capability2
  #  context
  #  db_database
  #  db_table
  #  dbus
  #  dir
  #  fd
  #  fifo_file
  #  file
  #  filesystem
  #  icmp_socket
  #  ipc
  #  kernel_service
  #  process
  #  process2
  #  proxy
  #  sock_file
  #  socket
  #  system
  #  tcp_socket
  #  tun_socket
  #  udp_socket
  #  unix_dgram_socket
  #  unix_stream_socket
  #  x_keyboard
  #  x_pointer
  #  x_server

Security Contextの確認

Security Contextの確認方法を下表に整理します。
多くの場合、-ZオプションをつけることでSecurity Contextを表示できます。
特に重要なのはps -Zls -Zseinfo --portconなので、この3つは確実に覚えてください。

確認対象 コマンド
プロセス ps -Z
ファイル ls -Z
ユーザー (ログインシェルのプロセス) id -Z
ネットワークソケット ss -Z
seinfo --portcon

Security Contextが割り当てられているリソースとして、他にもファイルシステムやデータベースなどあると思いますが、代表的なコマンドは上記のみです。
他のリソースのSecurity Contextを調べる方法もあるかもしれませんが、私が調べた範囲では見つかりませんでした。

以下に具体例を示します。

プロセスのLabel表示 (ps -Z)

まずは、プロセスの表示コマンドです。
-eオプションにより、全ユーザーのプロセスを表示できます。
他にも-oによって表示列を指定したり、-Cによって特定プロセスのみ表示することも可能です。
grepawkで頑張らなくても良いのです

ps -eZ
# LABEL                               PID TTY          TIME CMD
# system_u:system_r:init_t:s0           1 ?        00:00:00 systemd
# (以下略)

ps -o label,user,cmd -C sshd
# LABEL                                   USER CMD
# system_u:system_r:sshd_t:s0-s0:c0.c1023 root sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups

ファイルのLabel表示 (ls -Z)

ファイルのLabel表示は、ls -Zで行います。

ls -Z /etc/hosts
# system_u:object_r:net_conf_t:s0 /etc/hosts

getfattrstatでも確認できますが、ls -Zがあるのでほぼ使いません。
ちなみに、getfattrは、ファイルシステムの拡張アトリビュート (Extended Attribute) を確認するコマンドです。
この出力から、ファイルのLabelは拡張アトリビュートとして保持されていることが読み取れます。
拡張アトリビュートについて詳細が気になる方は、man xattrをご覧ください。

# getfattr -n security.selinux /etc/hosts でも良い
getfattr -m - -d /etc/hosts
# getfattr: Removing leading '/' from absolute path names
# # file: etc/hosts
# security.selinux="system_u:object_r:net_conf_t:s0"

stat /etc/hosts
#   File: /etc/hosts
#   Size: 158          Blocks: 8          IO Block: 4096   regular file
# Device: fd00h/64768d Inode: 1048867     Links: 1
# Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
# Context: system_u:object_r:net_conf_t:s0
# Access: 2022-01-02 01:22:36.647398431 +0900
# Modify: 2021-07-16 17:35:49.000000000 +0900
# Change: 2021-11-24 15:36:03.671000000 +0900
#  Birth: 2021-11-24 15:36:03.670000000 +0900

(参考) ユーザーのLabel表示 (id -Z)

ログインユーザーのLabelは、id -Zで表示します。
より正確には、ログインシェルプロセスのLabelを表示します。

id -Z
# unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

SELinuxが有効な環境では、単にidを実行するだけでもSecurity Contextが表示されます。
しかしid -Zの方が見やすいので、あまり使いません。

id
# uid=1000(endy) gid=1001(endy) groups=1001(endy),10(wheel),1000(shared) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

id -Zで表示されるSecurity Contextは、ログインシェルのSecurity Contextと同じです。
つまり、id -Zは以下のpsコマンドと同じ情報を表示しています。

ps -Z | grep bash | grep -v grep
# unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 26530 pts/0 00:00:01 bash

targeted policyにおいては、id -Zで表示されるのは基本unconfined_tのみです。
どのユーザーでも結果は同じなので、このコマンドを叩く機会はそう多くないでしょう。

ネットワークソケットのLabel表示

ネットワークソケットのLabel表示には、ss -Zを使います。

以下の実行例では、sedによって、先頭行と_tを含む行のみ抽出しています。
_tはTypeの一部であり、Security Contextを持つSocketのみを表示するために指定しています。

grepではなくsedを使っているのは、先頭行も抽出するためです。
grep -e 条件1 -e 条件2のようにOR条件で抽出しても良いですが、sedの方が条件指定が簡単です (sed1行目を表示する条件式が1pなので)。

ssコマンドはtcp, udpを含めて様々なタイプのソケットを表示するコマンドですが、ヒットしたのはu_str (Unix Stream) のみでした。
Unix Streamは、平たく言えばローカル通信用で、IPC (Inter-Process Communication) に使われるようです。

すなわち、ssコマンドではネットワーク外部通信用のソケット (tcp, udpなど) のSecurity Contextを表示しません。
割り当てられているのは、IPCに関わる一部のソケットのみでした。
そして、そのソケットもunconfined_tを持つため、実質的にアクセス制御されていないことがわかります。

したがって、targeted policyにおいてはss -Zというコマンドを覚える必要はありません。

ss -nZ | sed -ne 1p -e /_t/p
# Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# u_str ESTAB 0      0      * 20153            * 20914           users:(("systemd",pid=935,proc_ctx=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023,fd=20))
# u_str ESTAB 0      0      * 20869            * 20130           users:(("systemd",pid=935,proc_ctx=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023,fd=2),("systemd",pid=935,proc_ctx=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023,fd=1))

TCP/UDP用のソケットと紐づくSecurity Contextをステータス値として確認するコマンドはありませんでしたが、Typeとの紐付け設定はseinfo --portconで確認できます。
semanage port -aによってローカルポリシーでポート番号のSecurity Contextを追加していたとしても、このコマンドでちゃんと表示されます。

seinfo --portconは、実運用でもよく使う重要なコマンドです。

seinfo --portcon

# Portcon: 652
#    portcon sctp 1-511 system_u:object_r:reserved_port_t:s0
# (以下略)

# ポート番号の指定も可能
# seinfo --portcon 22

# Portcon: 4
#    portcon sctp 1-511 system_u:object_r:reserved_port_t:s0
#    portcon tcp 1-511 system_u:object_r:reserved_port_t:s0
#    portcon tcp 22 system_u:object_r:ssh_port_t:s0
#    portcon udp 1-511 system_u:object_r:reserved_port_t:s0

seinfo --portconと同等の情報を得られるコマンドとしてsudo semanage port -lもありますが、root権限が必要なので使い勝手としてはあまりよくありません。
参照用途のみであれば、seinfoに軍配が上がります。
semanage port -aでポート番号をカスタマイズしている場合は、semanage port -lCでカスタマイズ値のみ表示できるのが便利なぐらいです。

sudo semanage port -l | sed -ne 1p -e /ssh/p
# SELinux Port Type              Proto    Port Number
# ssh_port_t                     tcp      22

Security Contextの決まり方

Security Contextの決まり方は、以下のリンクに書いてあります。
The SELinux Notebook - Computing Security Contexts

本セクションでは、上記リンクで説明されている内容を整理し、噛み砕いてお伝えしたいなと思います。
対象は、targeted policyに関わるプロセスとファイルに絞ります。

プロセスとファイルのSecurity Contextの決まり方は、大きく分けて2種類あります。

  1. Security Policyで定義されたルールで制御する
  2. SELinuxと連携しているアプリケーションがlibselinux APIのライブラリ関数を使って制御する

1のSecurity Policyによる制御では、大まかに言うと以下の順序でSecurity Contextが決まります。

  1. 基本的には親要素のSecurity Contextを引き継ぐ
  2. 特殊なルールによって、親とは異なるSecurity Contextを持つ状況がある
  3. 親要素がない場合など、上記仕組みで決まらない場合には別途定められたデフォルト値に従う

2のlibselinux APIについてはアプリケーション側のソースコード上の実装となるので、Security Contextの決まり方はアプリケーションの作りに依存します。
私の経験では、今までこのようなアプリケーションを見たことはありません。
もしそういったアプリケーションを扱う機会が出てきたら意識しましょう。
本記事では、libselinux APIについては取り扱わないこととします。

では、次のセクションから具体的な内容に入っていきましょう。

プロセスのContext計算

プロセスのSecurity Contextは、以下の流れで決まります21
libselinux APIが関わる部分はあまり意識しないで良い部分なので、文字をグレーにして目立たなくしています。

  1. プロセスがforkされた時、forkされた子プロセスは親プロセスのSecurity Contextを引き継ぐ
  2. プロセスがexecされた時、execされた子プロセスは以下のルールでSecurity Contextが変化する
    • Transition Ruleの条件を満たす場合、そのルールに従って決まる (※1)
    • libselinuxのsetexeccon(3)でSecurity Contextが変化する (※アプリ依存)
    • Default Ruleが指定されている場合、そのルールに従ってデフォルト値がセットされる (※2)
  3. 上記挙動を上書きしてlibselinux APIのsetcon(3) でSecurity Contextが上書きされる。非推奨な実装 (※アプリ依存)

2点補足します。

  • Default Ruleは、本記事で扱うtargeted policyでは実質意識不要です。詳細は#(参考) Default Ruleで補足します
  • ファイル実行によってプロセスが生成する時、fork > execの順に行われます。したがって、上記の1と2の処理は一瞬の間に両方行われます。新規プロセスのSecurity Contextはfork時の親プロセスのものを最初に持ちますが、次の瞬間exec時に変化することがあるとご理解ください

重要な部分を抽出すると、以下のように理解すれば十分です。

  • 親プロセスのSecurity Contextを引き継ぐ
  • ただしType Transitionのルールに該当する場合、Security Contextが書き換わる

forkやexecという用語については、以下の記事を参考にしてください。
前提知識として、exec系のライブラリ関数はexecveというシステムコールと関係することを頭に入れた上でお読みいただけるとスムーズです。

endy-tech.hatenablog.jp

残りのDefault Rule、Transition Ruleといった用語については、ファイルのContext計算のセクションの後でまとめて説明します。

ファイルのContext計算

ファイルのSecurity Contextは、以下の流れで決まります22
Type以外の情報はあまり重要ではないので、グレーで目立たなくしてあります。

  • Userは、ファイルを作成するプロセス (Subject) から引き継ぐ
  • Roleは、role_transitionとdefault_roleの影響を受ける。これらのルールがない場合は、デフォルトでobject_rになる
  • Typeは、type_transitionとdefault_typeの影響を受ける。これらのルールがない場合は、デフォルトで親ディレクトリと同じTypeになる
  • range_transitionとdefault_rangeの影響を受ける。これらのルールがない場合は、デフォルトでファイルを作成したプロセスのlow/current levelになる

勘違いしやすいポイントについて補足しますが、ファイル作成時にはsudo semanage fcontext -lで表示されるFile Contextの値は関係ありません。
ここで表示される値は、restoreconコマンドを実行した時のrelabel処理時に、どのSecurity Contextを割り当てるか判断するために参照されます。
restorecon以外にも、/.autorelabelをつけてOS再起動したとき、fixfiles restorefixfiles relabelコマンドを実行した時、restorecondデーモンがファイル作成時に自動的にrelabelする際などが該当します。
relabelについては、後続の#File Contextとrelabelで説明します。

(参考) Default Rule

この機能をtargeted policyで使うことはありません。
興味のある方以外は、本セクションを丸ごとスキップしても結構です。

プロセスやファイルなどのリソースが生成するとき、プロセスは親プロセス、ファイルは上位ディレクトリのSecurity Contextを引き継ぐのが基本です。
Default Ruleは、このデフォルト挙動を若干変える効果を持ちます23

Default Ruleが発動する条件は単純で、Objectが特定のObject Classだった場合に常に発動します。

default系のStatementには以下のバリエーションがあります。
それぞれuser, role, type, rangeのデフォルト値の決定方法を上書きするのに使われます。

  • default_user
  • default_role
  • default_type
  • default_range

この中で重要なのはdefault_typeのみです。
なぜなら、targeted policyにおいては (MCSと) TEのみ意識すれば良いためです。

その前提で、targeted policyで定義されているDefault Ruleを確認してみましょう。

seinfo --default
# Default rules: 7
#    default_range blk_file target low;
#    default_range chr_file target low;
#    default_range dir target low;
#    default_range fifo_file target low;
#    default_range file target low;
#    default_range lnk_file target low;
#    default_range sock_file target low;

上記出力を見ると、default_typeルールがないことがわかります。

さらに、我々ユーザーが新たにDefault Ruleを追加することはそもそも不可能です。
それは、Module Policyのソースコードにはdefault_typeを記述できないためです。

default_typeの仕様を確認すると、Module Policyの欄に "No" と書かれていることからも読み取れます。

default_typeを変更する影響は非常に大きいので、私達ユーザーが書き換えるようなパラメータではないということだと思います (※)

(※) 厳密にはGitHubからSELinuxソースコード全体をダウンロードし、Base Policyを編集して全体をビルドし直せば、Default Ruleだけでなく何でも書き換えられます。しかし、そこまですることはまずないでしょう。

targeted policyにおいて、Default RuleがTypeを書き換えることはありません。
したがって、私達がDefault Ruleの仕様を気にする必要は全くありません。

Default Ruleは、MLSを使う方のみ気にするようにしましょう。

Transition Rule

Transition RuleもDefault Ruleと同様に、プロセスやファイルが新規作成されたとき、デフォルトのSecurity Contextを指定するルールです。
両者の役割は基本同じですが、Default Ruleと比較してType Transitionはより細かくSecurity Contextを書き換える条件を指定できます。

Transition Ruleには、以下のバリエーションがあります。
それぞれrole、type、rangeの変化に関するルールを記述するStatementです。

  • role_transition24
  • type_transition25
  • range_transition26

これらの中で、重要なのはTypeを変化させるtype_transitionのみです。

Type Transitionについては、#Type Transitionのセクションにて詳細に説明します。

(参考) SID (Security ID)

SID (Security ID) を知ることでSecurity Contextの決まり方について更に理解が進むので、ここで紹介しておきます。
SELinuxを運用する上でSIDは必ずしも必要にはならないので、興味のある方のみご覧いただければと思います。

SIDとは、Security Contextのデフォルト値を決めるパラメータです27
Initial SIDと呼ばれることもあります。

デフォルト値と言えば、プロセスの場合は親プロセス、ファイルの場合は親ディレクトリのSecurity Contextが引き継がれるのでした。
SIDは、上記ルールでもカバーできないような状況でのデフォルト値を定義する、言ってみれば最後のデフォルト値です。

例えば、PPID (Parent Process ID) が0、つまり親プロセスを持たないsystemdプロセスや、SELinuxが無効化された状態で作成されたためSecurity Contextを持たないファイルなどに対して、SIDが利用されます。

SIDのSyntaxは以下の通りです。
やや冗長ですが、1行目でsid_idを定義し、2行目でsid_idにSecurity Contextを紐付けます。

sid sid_id;
sid sid_id context;

SIDの定義を以下に示します。
SID名の後にSecurity Contextが紐付いています。

seinfo -x --initialsid

# Initial SIDs: 27
#    sid any_socket system_u:object_r:unlabeled_t:s0
#    sid devnull system_u:object_r:null_device_t:s0
#    sid file system_u:object_r:unlabeled_t:s0
#    sid file_labels system_u:object_r:unlabeled_t:s0
#    sid fs system_u:object_r:fs_t:s0
#    sid icmp_socket system_u:object_r:unlabeled_t:s0
#    sid igmp_packet system_u:object_r:unlabeled_t:s0
#    sid init system_u:object_r:unlabeled_t:s0
#    sid kernel system_u:system_r:kernel_t:s0
#    sid kmod system_u:object_r:unlabeled_t:s0
#    sid netif system_u:object_r:netif_t:s0
#    sid netmsg system_u:object_r:netlabel_peer_t:s0
#    sid node system_u:object_r:node_t:s0
#    sid policy system_u:object_r:unlabeled_t:s0
#    sid port system_u:object_r:port_t:s0
#    sid scmp_packet system_u:object_r:unlabeled_t:s0
#    sid security system_u:object_r:security_t:s0
#    sid sysctl system_u:object_r:sysctl_t:s0
#    sid sysctl_dev system_u:object_r:unlabeled_t:s0
#    sid sysctl_fs system_u:object_r:unlabeled_t:s0
#    sid sysctl_kernel system_u:object_r:unlabeled_t:s0
#    sid sysctl_modprobe system_u:object_r:unlabeled_t:s0
#    sid sysctl_net system_u:object_r:unlabeled_t:s0
#    sid sysctl_net_unix system_u:object_r:unlabeled_t:s0
#    sid sysctl_vm system_u:object_r:unlabeled_t:s0
#    sid tcp_socket system_u:object_r:unlabeled_t:s0
#    sid unlabeled system_u:object_r:unlabeled_t:s0

各種SIDが使われる条件は、ざっくり以下の通りです28

  • kernelが起動したプロセスやスレッドはkernel SIDのSecurity Contextが割り当てられる
  • file classやdir classがSecurity Contextを持たない場合、file SIDのSecurity Contextが割り当てられる
  • (その他のSIDも同様)
  • Security Contextが (値なしではなく) 不正な値を持っていた場合、unlabeled SIDのSecurity Contextが割り当てられる (滅多にないと思います)

kernel SIDが使われるケースは、例えばこんな状況です29

  1. kernelが起動したsystemd/initプロセス (PID 1) や、その他いくつかのスレッドはkernel SIDのkernel_t Typeが割り当てられる
  2. ただしsystemd/initプロセスは、その後Type Transitionしてinit_t Typeに変わる

file SIDが使われるケースは、例えばこんな状況です30

  1. SELinuxを一時的に無効化した (disabled)
  2. ファイルを作成した。SELinuxは無効なのでラベル付けされない
  3. SELinuxを有効化した。有効化にはOS再起動を伴うので、ファイルシステムも再度読み込まれる
  4. 2で作成したファイルはSecurity Contextを持たないため、file SIDのSecurity Contextであるunlabeled_t Typeが割り当てられる

ただ、最近のディストリビューションではSELinuxをdisabledからenforcingなどに変更したタイミングで自動的にrelabelされるので、File Contextが<<none>> (relabelしない)として定義されていない限りは上記のような状況にはならないと思います31

Type Transition

Type Transitionとは、プロセスやファイルが生成した時にSecurity Contextの一部であるTypeを変化させるアクセス制御ルールです32

Type Transitionがないと、プロセスは全てデフォルトのkernel_tかsystemdと同じinit_tになってしまいます。
実際にはプロセスによって異なるTypeを持っていますが、これはType Transitionの一種であるDomain Transitionの働きによるものです。

Type Transitionは2種類に分類できます。
名前は異なりますが、何のTypeが変化するかで呼び方を分けているだけで、本質は変わりません。

  • Domain Transition: 新規生成するプロセスのTypeが変化するType Transition
  • Object Transition: 新規生成するプロセス以外 (主にファイル) のTypeが変化するType Transition

以降のセクションで、Domain TransitionとObject Transitionについて図解と具体例を交えつつ説明します。

Domain Transition

Domain Transitionの概要

Domain Transitionとは、新規発行された子プロセスが親プロセスとは別のSecurity Contextに遷移する処理のことです。
(※) Default Ruleとは別物なので、区別してください

詳細な説明に入る前に、Domainという言葉をイメージしてみましょう。
以下の図も活用しつつ、説明を進めます。

domain_transition_example

Domainとは、一般に「範囲」を意味する言葉です。
SELinuxにおいては、Subjectであるプロセスのアクセス可能な範囲のことをDomainと呼びます。33,34,35
上図で言うと、init_t Domainとsshd_t Domainが存在します。
それぞれのDomainは、init_tとsshd_tをSubjectとしてアクセス可能なObjectを内包しています。
言い換えると、init_tとsshd_tをSubjectとして、矢印でつながった先をObjectとするallow Statementが存在する範囲のことをDomainと呼びます。

Domain Transitionとは、子プロセスが親プロセスのTypeを引き継がず別Typeを持つことで、異なるDomainに遷移することです。
これにより、子プロセスのsshdは、親プロセスのsystemdとは異なるsshdならではのリソースにアクセスできるようになります。

Entry Pointという言葉も重要です。
Entry Pointとは、子プロセスが別ドメインへ移動する際の入口役となる実行ファイルのことです。
上図ではsshd_exec_tがsshd_t DomainへのEntry Pointということになります。

最後に、図中に出てくるexecute, read, getattr, entrypoint, transitionは、Domain Transitionを実行するためにallow Statementで許可する必要があるPermissionです。
これらのPermissionに加え、type_transition Statementが別途定義されていることでDomain Transitionが可能となります。
Domain Transitionに必要なルール定義については、この後更に詳細に説明します。

Domain Transitionに必要な制御ルール

上記の具体例で言うと、Domain Transitionを行うのに必要なアクセス制御ルールは4行あります36
Domain Transitionを実行するtype_transitionルール1行と、必要なアクセスを許可するallowルール3行です。

前セクションの図において、実線の黒い矢印で表現されている部分に相当します。

# execute
allow init_t sshd_exec_t : file { execute read getattr };

# entrypoint
allow sshd_t sshd_exec_t : file entrypoint;

# transition
allow init_t sshd_t : process transition;
type_transition init_t sshd_exec_t : process sshd_t;

実際にsesearchでルール検索すると、ちゃんとヒットします。
initrc_domainなどAttributeを使って表現しているものもあるので表記が若干異なりますが、アクセス制御ルールとしてはちゃんと内包されています。

sesearchコマンドのオプションは次の記事で詳しく扱いますので、今は「実機でもちゃんとエビデンスが取れているんだな」とご理解いただければ十分です。

sesearch -A -s init_t -t sshd_exec_t -c file -p execute,read,getattr
# allow init_t file_type:file { getattr relabelfrom relabelto };
# allow initrc_domain direct_init_entry:file { execute getattr map open read };

sesearch -A -s sshd_t -t sshd_exec_t -c file -p entrypoint
# allow sshd_t sshd_exec_t:file { entrypoint execute execute_no_trans ioctl lock map open read };

sesearch -A -s init_t -t sshd_t -c process -p transition
# allow initrc_domain daemon:process transition;

sesearch --type_trans -s init_t -t sshd_exec_t
# type_transition init_t sshd_exec_t:process sshd_t;

今回はsystemdプロセスからsshdプロセスが発行される例を取り上げて説明しましたが、Domain Transitionのルールの書き方は一般に同じです。
一般化して、initをparentに、sshdをchildに置き換えたルールも以下に添付しておきます。

# execute
allow parent_t child_exec_t : file { execute read getattr };

# entrypoint
allow child_t child_exec_t : file entrypoint;

# transition
allow parent_t child_t : process transition;
type_transition parent_t child_exec_t : process child_t;

一般化した図も添付します。

domain_transition

type_transition Statement

type_transition Statementは、Type Transition (Domain/Object Transition) を実行する制御ルールです。

type_transitionのSyntaxは以下のとおりです37

type_transition source_type target_type : class default_type;

Syntax上の各要素の意味は、以下のとおりです。

要素 意味
source_type TransitionのトリガーとなったActionのSubject
(※) Transitionの条件1
target_type TransitionのトリガーとなったActionのObject
(※) Transitionの条件2
class Transition対象のTypeのObject Class
default_type Transition後のType
(※) 新規生成するプロセスやファイルのデフォルト値

例えば、先ほどのDomain Transitionの例をモデルにします。

Transitionのトリガーは、以下のallow Statementに対応するexecute命令でした。
以下のallow Statementは、init_tがsshd_exec_tを実行 (execute) することを許可するアクセス制御ルールです。
Subjectがinit_t、Objectがsshd_exec_tです。

allow init_t sshd_exec_t : file { execute read getattr };

それに対応するtype_transition Statementは以下のとおりです。
allow Statementと縦に並べるとわかりやすいですが、上述のallow StatementとSubjectとObjectが揃っています。
type_transitionのSubjectとObjectには、トリガーとなったexecuteのSubjectとObjectを指定します。

type_transition init_t sshd_exec_t : process sshd_t;

Transition後のTypeはsshd_tですが、これに関してはそのままの意味なので説明は不要だと思います。

最後にObject Classがprocessですが、これはTransition先のsshd_tがプロセスであることと対応しています。

なお、Domain TransitionとObject Transitionは全く同じ構文ですが、簡単な見分け方があります。
それは、Object ClassがprocessならDomain Transition、それ以外 (fileなど) ならObject Transitionということです。

Object Classがprocessということは、ファイル実行によって新規生成したプロセスのDomainが変わったということです。
このことから、このTransitionはObject Transitionではなく、Domain Transitionであると言えます。

Object Transition

Object Transitionの概要

Object Transitionとは、新規発行されたプロセス以外のリソース (ファイル、ディレクトリ、データベースのSchemaなど) が親要素とは別のSecurity Contextに遷移する処理のことです。
今回は、ファイルやディレクトリに着目して説明を進めます。

ファイルやディレクトリは、Type Transitionが発生しない場合は、生成したファイルの親ディレクトリのTypeを引き継ぐのがデフォルトの動作です。
Object Transitionが発生すると、生成したファイルやディレクトリが別のTypeを持ちます。
これによって、新規生成したファイル/ディレクトリは親ディレクトリと異なるallow Statementに一致するようになり、異なるアクセス制御パターンを実装できるようになります。

Object Transitionの具体例を下図に示します。

object_transition_example

NetworkManagerが/var/log/wicd.*にログファイルを作成するとします。
Object Transitionがなければ、/var/log/と同じTypeを引き継いで/var/log/wicd.*var_log_tを割り当てようとします。
しかし、今回の場合はObject Transitionによって/var/log/wicd.*というObjectにNetworkManager_log_tが割り当てられました。

上述の挙動を実現するためには、Domain Transitionと同様にtype_transitionルールと、付随するいくつかのallowルールが必要です。
詳細は次のセクションで説明します。

Object Transitionに必要な制御ルール

上記の具体例で言うと、Object Transitionを行うのに必要なアクセス制御ルールは3行あります38
Object Transitionを実行するtype_transitionルール1行と、必要なアクセスを許可するallowルール2行です。

前セクションの図において、実線の黒い矢印で表現されている部分に相当します。

allowルールによって、NetworkManagerプロセスが親ディレクトリにファイル追加する権限 (add_name)と、対象のファイルを作成する権限 (create) を中心にアクセス許可しています。

Object Transitionの場合は、type_transitionに親ディレクトリへのadd_nameと同じSubjectとObjectをセットすることに気をつけてください。
ファイルのcreateの方ではありません。

# add_name
allow NetworkManager_t var_log_t:dir { add_name write search };

# create
allow NetworkManager_t NetworkManager_log_t:file { create write getattr };

# transition
type_transition NetworkManager_t var_log_t:file NetworkManager_log_t;

target policyの実機上では、以下のようにルール定義されていました。

# add_names
sesearch -A -s NetworkManager_t -t var_log_t -c dir -p add_name,write,search
# allow NetworkManager_t var_log_t:dir { add_name ioctl lock read remove_name write };
# allow domain var_log_t:dir { getattr open search };

# create
sesearch -A -s NetworkManager_t -t NetworkManager_log_t -c file -p create,write,getattr
# allow NetworkManager_t NetworkManager_log_t:file { create open setattr };
# allow application_domain_type logfile:file { append getattr ioctl lock };
# allow daemon logfile:file { append getattr ioctl lock };

# transfer
sesearch --type_trans -s NetworkManager_t -t var_log_t -c file
# type_transition NetworkManager_t var_log_t:file NetworkManager_log_t;

今回はNetworkManagerプロセスからログファイルを自動生成する例を取り上げて説明しましたが、Object Transitionのルールの書き方は一般に同じです。
一般化して、NetworkManagerをprocessに、/var/log/parent/dirに置き換えたルールも以下に添付しておきます。

# add_name
allow process_t parent_dir_t:dir { add_name write search };

# create
allow process_t process_yyy_t:file { create write getattr };

# transition
type_transition process_t parent_dir_t:file process_yyy_t;

一般化した図も添付します。

object_transition

Name Transition

Name TransitionはObject Transitionの一種です。

Name Transitionとは、TypeとObject Classだけでなく、ファイル名も含めてTransitionする条件を細かく制御する機能です。

Name Transitionのルール設定例を以下に示します。

sesearch --type_trans -s init_t -t etc_t -c file
# (一部のみ抜粋)
# type_transition init_t etc_t:file syslog_conf_t rsyslog.conf;
# type_transition init_t etc_t:file passwd_file_t passwd;

syslog_conf_tpasswd_file_tなどのTransition後のType指定の後に、rsyslog.confpasswdなどのファイルが追加で指定されています。
このように最後のファイル名を指定することで、作成するファイル名によってTransition後のTypeを区別する書き方が可能となります。

今回の例では、両ルール共にSubject、Object、Classは全て同じです。
通常のObject Transitionでは、このような状況においては2つ以上のTypeを使い分けることはできません。
Name Transitionは、SubjectとObjectのTypeが同じで、それでもTransitionルールを区別したいときに使います。

name_transition_example

Name Transition用のtype_transitionのSyntaxは、以下の構造です39
#type_transition Statementで取り上げたDomain/Object Transitionの構文の末尾にobject_nameが増えています。
object_nameには、ファイル名を完全一致で指定します。

type_transition source_type target_type : class default_type object_name;

Name Transitionの場合も、Object Transitionと同様のallowルールが追加で必要です。

(参考) Typeの意味の見分け方

sshd_tというTypeがあったとします。
このTypeがObjectとなった場合の典型的なObject Classと、具体的にどのようなリソースと紐づくかをすぐに言い当てることはできますでしょうか?

今回は簡単なので、すぐにわかるかもしれません。
しかし、5000強あるTypeとAttribute全てについて用途を言い当てることは難しいと思います。

そこで、ここでは用途を見分けるちょっとしたコツを紹介したいと思います。

Typeの見分け方

Typeは、以下の段階を踏んで見分けていくのが早いと思います。

  • 名前から判断する
  • manで検索する (selinux-policy-docパッケージのインストール推奨)
  • allow Statementから推測する

どういったアプローチか、1つずつ説明します。

名前から判断する

絶対ではありませんが、Typeにはおおよその命名規則があります。
この命名規則から、多くの場合プロセス名とObject Classを判断できます。

Type名 具体例 用途 (class)
プロセス名_t sshd_t sshdプロセス (file)
プロセス名_exec_t sshd_exec_t sshdのentry point、実行ファイル (file)
プロセス名_log_t zabbix_log_t zabbixのログファイル (file)
プロセス名_conf_t syslog_conf_t syslogの設定ファイル (file)
プロセス名_port_t syslogd_port_t syslogdのポート番号 (tcp_socket, udp_socket)

Object Classを判断できたら、後はclassごとに特化した確認コマンドを実行すればより具体的な情報がわかります。

プロセスの場合は、2通りのアプローチがあります。

1つ目の方法は、プロセスが起動している場合に使えます。
プロセス起動中という条件はあるものの、簡単に調べがつきます。

# sshd_t = /usr/sbin/sshd
ps -eo user,label,command | grep sshd_t | grep -v grep
# root     system_u:system_r:sshd_t:s0-s0:c0.c1023 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups

2つ目の方法は、allow Statementから追いかける方法です。
プロセスが起動していなくても調査可能です。

今回はプロセスとわかっているので、entrypointのルールから調べています。
もしプロセスと知らない場合はtype_transitionルールから探すのが確実ですが、これについては後述します。

# sshd_exec_tがentry point
sesearch -A -s sshd_t -p entrypoint
# allow sshd_t sshd_exec_t:file { entrypoint execute execute_no_trans ioctl lock map open read };

# sshd_exec_tは、/usr/sbin/sshd
sudo semanage fcontext -l | grep :sshd_exec_t:
# /usr/sbin/gsisshd  regular file  system_u:object_r:sshd_exec_t:s0 
# /usr/sbin/sshd     regular file  system_u:object_r:sshd_exec_t:s0

余談ですが、関連ファイルが1つでも分かればパッケージを特定して追加の情報を得ることも可能です。

# パッケージはopenssh-server
dnf provides /usr/sbin/sshd
# openssh-server-8.7p1-2.fc35.x86_64 : An open source SSH server daemon
# Repo        : @System
# Matched from:
# Filename    : /usr/sbin/sshd

# openssh-serverとは何か?
dnf info openssh-server
# Installed Packages
# Name         : openssh-server
# Version      : 8.7p1
# Release      : 2.fc35
# Architecture : x86_64
# Size         : 1.0 M
# Source       : openssh-8.7p1-2.fc35.src.rpm
# Repository   : @System
# From repo    : fedora
# Summary      : An open source SSH server daemon
# URL          : http://www.openssh.com/portable.html
# License      : BSD
# Description  : OpenSSH is a free version of SSH (Secure SHell), a program for logging
#              : into and executing commands on a remote machine. This package contains
#              : the secure shell daemon (sshd). The sshd daemon allows SSH clients to
#              : securely connect to your SSH server.

ファイルの場合はFile Contextを確認すれば大体わかります。
zabbix_log_t/var/log/zabbix.*とわかりました。
Zabbixのログファイルという予想は正しそうです。

sudo semanage fcontext -l | grep :zabbix_log_t:
# /var/log/zabbix.*  all files  system_u:object_r:zabbix_log_t:s0 

TCP/UDP Socketの場合は、ポート番号のSecurity Contextを確認すればポート番号を特定できます。
もしサービスが起動していれば、ssから追加の情報を得られます。
そうでなくても、/etc/servicesからポート番号と対応する和名を調べることができます。

(※) /etc/servicesは、現在Activeなポート番号とプロセス名の最新の紐付けを示すファイルではなく、あくまでLinuxがポート番号の和名を表示するためのマッピング情報が静的に記述されているだけのファイルです。参考:man services

sudo semanage port -l | grep syslogd_port_t
# syslogd_port_t                 tcp      601, 20514
# syslogd_port_t                 udp      514, 601, 20514

grep -P ' (601/tcp|20514/tcp|514/udp|601/udp|20514/udp) ' /etc/services
# syslog-conn     601/tcp                 # Reliable Syslog Service
# syslog-conn     601/udp                 # Reliable Syslog Service

manで検索する

このやり方は非常に簡単で、頭を使わずにできます。
man -Kwに検索ワードを指定するだけです。

唯一の弱点は、manに載っていないと使えないことです。

この方法はTypeに限らず、Attribute、Booleanなど何でも使えます。
それどころか、SELinuxに限らず一般的に使えます。

-Kは、man全文からのキーワード検索をするオプションです。
検索にヒットしたmanを順に開いていきます。

-wは検索結果の返し方をチューニングするオプションです。
検索にヒットしたmanファイルのフルパスを一覧表示します。
-Kのみだと検索がたくさんヒットしたときに全体俯瞰しづらいので、-wと組み合わせます。

man -Kw watchdog_t
# /usr/share/man/man8/watchdog_selinux.8.gz
# /usr/share/man/man8/freeipmi_bmc_watchdog_selinux.8.gz

# 載ってないこともある
man -Kw vmware_device_t
# No manual entry for vmware_device_t

watchdog_tについてより詳しく知るには、man watchdog_selinuxman freeipmi_bmc_watchdog_selinuxを実行すれば良いとわかります。

allow Statementから推測する

私達が気にするTypeの大半はプロセスかファイルです。
そしてプロセスかファイルは、systemdなど一部を除いてほぼ全てがDomain/Object TransitionしてそのTypeに変化しています。

つまり、Type Transitionルールを検索すれば何かしらのヒントを得られます。

対象がプロセスだった場合は簡単です。
以下の出力から「sshd_tinit_tからsshd_exec_tをentry pointとしてDomain Transitionしたプロセスである」というところまで一発でわかります。

sesearch --type_trans -D sshd_t
# (一部のみ抜粋)
# type_transition init_t sshd_exec_t:process sshd_t;

後は#名前から判断するの例と同様に、sshd_exec_tのFile Contextを確認して詳細を調べていけます。

対象がファイルやディレクトリだった場合も同様に簡単です。
以下の出力から、「locale_tzabbix_script_tプロセスがetc_tディレクトリ配下にファイルを生成した時にObject Transitionした、ファイルである」ことがわかります。
Object Classがfileなのでファイルと判断しましたが、ここがdirの場合はディレクトリです。

sesearch --type_trans -D locale_t
# type_transition zabbix_script_t etc_t:file locale_t clock;

ファイルであることがわかったので、後は#名前から判断すると同様にFile Contextを調べれば詳細を確認できます。

対象がファイルでもプロセスでもなかった場合は、type_transitionルールにヒットしないと思われます。
こういったTypeの正体を突き止めるのは困難を極めます。
基本的にはmanやGoogleを検索し、わからなければサポートに問い合わせなければ厳しいと思います。

そもそもこういったTypeを調べる場面は想像することも難しいほどレアだと思います。
もしあるとしたら、エラーが発生した場合でしょうか。
その場合は、アプリケーションのエラーログやsetroubleshootのログも合わせて総合的に調査方針を検討する必要があると思います。

File Contextとrelabel

ファイルのSecurity Contextを計算する場面

#ファイルのContext計算にて、ファイルが新規作成されるときのTypeは以下のように決まると説明しました。
以下の他にもDefault Ruleなどの存在はありますが、実際にはほとんど意識する必要がないので割愛しています。

  • 作成されたファイルの親ディレクトリと同じTypeを持つ
  • ただし、Object Transitionの条件を満たす時、type_transitionルールに従ってTypeが決定する

実は、ファイル作成以外にもファイルのSecurity Contextを計算・変更する状況があります。

  1. chconコマンドによって、対象のファイルを任意のSecurity Contextに変更したとき
  2. relabel処理が走ったとき

1のchconコマンドは、テストしたいときを除いて滅多に実行することはありません。
ここで取り上げたいのは、2のrelabel処理についてです。

次のセクションでrelabelとは何かについて説明します。

relabelとは

relabelとは、ファイルを再度ラベル付けする処理のことです。40,41
relabel対象のファイルのSecurity Contextを確認し、File Contextの内容と差分があればFile Contextの定義通りの値に変更します。

File Context (fcontext) とは

File Context (fcontext) とは、ファイルパスとSecurity Contextの紐付け定義です。
relabel処理は、File Contextの紐付け通りになるようにファイルのSecurity Contextを書き換えます。

File Contextは、allow Statementやtype_transition Statementなどと同様に、Module Sources (ソースコード) にて定義されます。
また、semanage fcontextコマンドによってユーザーの手で追加のFile Contextを定義することも可能です。

semanage fcontext -lでFile Contextの定義を一覧表示できます。
File Contextは、ファイルパスはPerl互換の正規表現で定義できます42
grepと組み合わせて、特定Typeと紐づくFile Contextを調べるのによく使います。

sudo semanage fcontext -l

# SELinux fcontext  type       Context
# /                 directory  system_u:object_r:root_t:s0 
# /.*               all files  system_u:object_r:default_t:s0
# (以下略)

sudo semanage fcontext -l | grep :user_home_dir_t:
# /home/[^/]+       directory      unconfined_u:object_r:user_home_dir_t:s0 
# /home/[^/]+       symbolic link  unconfined_u:object_r:user_home_dir_t:s0 

逆にファイルパスを元に対応するSecurity Contextを知りたい場合は、matchpathconコマンドが便利です。
selabel_lookup -kでも同じことはできますが、matchpathconの方が楽に覚えられると思います。

matchpathcon /etc/selinux/config
# /etc/selinux/config  system_u:object_r:selinux_config_t:s0

selabel_lookup -k /etc/selinux/config
# Default context: system_u:object_r:selinux_config_t:s0

これらのコマンドは、引数に渡したPATHをFile Contextと突き合わせてSecurity Contextを返しています。
したがって、matchpathconとselabel_lookup -kの引数はフルパスで指定しないと想定する結果が返ってこないので注意してください。

pwd
# /etc/selinux

matchpathcon config
# config   <<none>>

relabelが発生する場面

relabel処理は、以下の状況で発生します。

  • /.autorelabelを配置してOS再起動
  • restoreconコマンドの実行
  • fixfiles restoreコマンドの実行
  • fixfiles relabelコマンドの実行 (ほぼ使わない)

他にも、ドキュメントには書かれていませんがSELINUXTYPE (targeted/minimum/mls) が変わった時にも自動的にrelabel処理が走りました。
また、RHEL8の場合はSELinuxがdisabledからpermissive/enforcingに変化したときにもrelabel処理が走ります43

次のセクションから、relabelを実行する操作を1つずつ説明します。

/.autorelabel

/.autorelabelファイルを配置してからOS再起動すると、Linux起動中に以下の処理が走ります44

  1. fixfiles restore
  2. rm /.autorelabel

1つ目の処理で、ファイルシステム全体をrelabelします。
2つ目の処理で/.autorelabelファイルを削除し、次回以降の再起動ではrelabelが走らないようにします。

(※) man selinux_configによると空の/.autorelabelを配置するだけでfixfiles -F restoreを実行しそうな説明が書いてありますが、実際にはfixfiles restoreの実行になります。空の/.autorelabelを配置して再起動してもSELinux UserはRelabelされませんでした。fixfiles -F restoreを実行するには、fixfiles -F onbootを実行するか、-Fと書いてある/.autorelabelを配置する必要があります

OS再起動は伴うものの、/.autorelabelを使うのがrelabelのやり方として最も確実とSELinuxのマニュアルに書かれています45
とはいえ、ちょっとした修正であれば#restorecon#fixfiles restoreを利用して問題ありません。
OS再起動をすれば、/devなども含めてより確実にrelabelできるということと理解しています。

/.autorelabelを使うのは、SELinuxを無効 (disabled) から有効 (enforcing/permissive) に変更したときです。
SELinuxが無効の状態で作成されたファイルはSecurity Contextが割り当てられません。
Security Contextが割り当てられていないファイルは、以下のように見えます。

getenforce
# Disabled

touch x
ls -Z x
# ? x

この状態でSELinuxを有効化すると、ファイルに想定外のSecurity Contextが割り当てられることで、SELinuxによるアクセス拒否が大量に発生してしまう恐れがあります46

ただ、実際にはRHEL8の場合は無効から有効に切り替えたタイミングで/.autorelabelが無くてもrelabelが走る仕様です。

とはいえ、過信は禁物です。
SELinuxをdisabledからenforcingに切り替える際は、必ず/.autorelabelを配置してからOS再起動しましょう。
また、滅多にやらないと思いますがSELINUXTYPEの値を変更する際にも/.autorelabelを配置すべきです。

/.autorelabelの使い方の例を以下に示します。
/.autorelabelの配置にはtouchコマンドを使っても良いのですが、今回の例では専用コマンドのfixfiles onbootを使います。
個人的には、専用コマンドの方がタイプミスの心配が少なくて良いかなと思います。
通常のユースケースではType Enforcementしか使わないので、-Fの指定は必要ないと思います。

# sudo touch /.autorelabelと同等
sudo fixfiles onboot

sudo reboot

targeted policyからmls policyに変更する場合など、TE以外のRBAC、UBAC、MLS、MCSなどを使う場合にはType以外のSecuirty Contextも重要になってきます。
以下の例では-Fを指定することで、TypeだけでなくUser,Role,Rangeもrelabel対象としています。
(※) TE以外を使うことは基本的にないので、あくまで参考情報です

# sudo bash -c 'echo -n -F > /.autorelabel'と同等
sudo fixfiles -F onboot

sudo reboot

restorecon

特定のファイルのSecurity Contextを修正したい時、restoreconは便利です47
restoreconは、特定ファイルやディレクトリに対象を絞ったrelabelを実行するコマンドです。
対象ファイルのSecurity ContextをFile Contextに合わせて変更します。

よくあるシナリオとしては、ある場所に作成したファイルをmvで移動したことで、Security Contextがおかしくなった時にrestoreconで修正します。
以下に具体例を示します。

ホームディレクトリ配下にファイルxを作成します。
作成したファイルはuser_home_t Typeを持ちます。

touch /home/endy/x
ls -Z /home/endy/x
# system_u:object_r:user_home_t:s0 /home/endy/x

作成したファイルをmvで移動します。
mvはcpとは異なり、オーナーやタイムスタンプなどのメタ情報を保持します。
これは拡張ファイル属性として保持されるSecurity Contextも例外ではなく、これも保持されます。
しかしmatchpathconが示すとおり、File Contextの値はsystem_u:object_r:usr_t:s0です。

sudo mv /home/endy/x /opt/x

ls -Z /opt/x
# unconfined_u:object_r:user_home_t:s0 /opt/x

matchpathcon /opt/x
# /opt/x   system_u:object_r:usr_t:s0

ここで、restoreconを実行して/opt/xのSecurity ContextをFile Contextの値に合わせて変更します。

restoreconは実行しても標準出力を出しませんが、-vをつけることで変更内容を表示するようになります。
また、-nをつけるとテストモード実行のようにSecurity Contextを変更しません。
-n-vとセットで使うことが多いです。
以下で試してみましょう。

restorecon -nv /opt/x
# Would relabel /opt/x from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:usr_t:s0

ls -Z /opt/x
# unconfined_u:object_r:user_home_t:s0 /opt/x

変更内容は表示されますが、実際には変更されていません。
では、実際に変更してみます。
Typeがusr_tに変化しました。
しかし、Userは変化していないです。

restorecon /opt/x

ls -Z /opt/x
# unconfined_u:object_r:usr_t:s0 /opt/x

matchpathcon /opt/x
# /opt/x   system_u:object_r:usr_t:s0

このように、restoreconはデフォルトでTypeしか変更しません。
-Fをつけて実行すれば、User/Role/Rangeも含めて変更します。
一旦chconで元の状態に戻した上で、今度は-F付きで試してみましょう。

(※) chconは、ファイルに任意のSecurity Contextをセットするコマンドです。今回のようなテスト用途でたまに使います

chcon -t user_home_t /opt/x
ls -Z /opt/x
# unconfined_u:object_r:user_home_t:s0 /opt/x

restorecon -F /opt/x
ls -Z /opt/x
# system_u:object_r:usr_t:s0 /opt/x

restoreconの挙動確認はこれで一旦おしまいです。
/opt/xはもう使わないので、削除しておきます。

sudo rm /opt/x

fixfiles

fixfilesは、内部的にはrestoreconを呼び出しています。
このことは、man fixfilesの以下の文言から読み取れます。

-v     Modify verbosity from progress to verbose. (Run restorecon with -v instead of -p)

fixfilesは、restoreconと比較して大規模なrelabelを行うのに使われます。
デフォルトでrestorecon -Rのように再帰的に実行する上、実行対象のパスを指定しなければ暗黙的に/が指定されます。
つまり、シンプルにfixfiles restoreとだけ実行すると、ファイルシステム全体がrelabelされます。

/etc/selinux/fixfiles_exclude_dirsディレクトリパスを列挙しておくと、そのディレクトリについてはrelabelの対象外とするような制御も可能です。
初期状態ではfixfiles_exclude_dirsファイル自体が存在しないので、対象外のディレクトリは存在しないのがデフォルト動作となります (Fedora35で確認)。

fixfilesは以下のサブコマンドを持ちます。 以降のセクションで一つ一つ説明します。

サブコマンド 意味
onboot /.autorelabelを作成する
check
またはverify
relabelせず、relabelが必要なファイルを列挙する
(≒restorecon -nv)
restore ファイルシステム全体をrelabelする (≒restorecon -Rp /)
relabel restoreとほぼ同じ。
ただし、実行前に/tmp配下を削除できる (≒rm -rf /tmp/*; restorecon -Rp /)

fixfiles onboot

fixfiles onbootは、touch /.autorelabelと同じ意味を持ちます。
fixfiles -F onbootで、echo -n '-F' > /.autorelabelと同じ意味を持ちます。

基本的にはTypeしか使わないので、-Fをつけることはあまりないと思います。

ls /.autorelabel
# ls: cannot access '/.autorelabel': No such file or directory

sudo fixfiles onboot
# System will relabel on next boot

ls /.autorelabel
# /.autorelabel

この後OS再起動すると、#/.autorelabelで説明したとおりfixfiles restore相当のrelabelが実行されます。

fixfiles check (または、fixfiles verify)

fixfiles checkは、relabelを実行したときにSecurity Contextが書き換わるファイルを列挙します。
実際にはSecurity Contextを書き換えません。
fixfiles verifyも全く同じ意味です。
restorecon -Rnvとほぼ同等の挙動です。

試しに、ファイルxのSecurity Contextを書き換えた上でfixfiles checkを実行してみます。

touch /home/endy/x
chcon -u sysadm_u -t tmp_t /home/endy/x
ls -Z /home/endy/x
# sysadm_u:object_r:tmp_t:s0 /home/endy/x

sudo fixfiles check
# Checking / /boot /dev /dev/hugepages /dev/mqueue /dev/pts /dev/shm /run /run/user/1000 /sys /sys/fs/cgroup /sys/fs/pstore /sys/kernel/debug /sys/kernel/debug/tracing /sys/kernel/tracing /tmp
# Would relabel /home/endy/x from sysadm_u:object_r:tmp_t:s0 to sysadm_u:object_r:user_home_t:s0

fixfilesrestoreconと同様、デフォルトではTypeしか確認しません。
-Fを指定することで、User, Role, Rangeを全て確認してくれます。
-Fの挙動は、onboot, restore, relabelなど他のサブコマンドについても同様です。

また、fixfiles checkの後に対象のファイルやディレクトリを指定することで、チェック対象を絞り込むことができます。
ディレクトリを指定した場合は配下のファイルやディレクトリを再帰的にチェックします。
fixfiles restoreも同様にファイルやディレクトリを指定できます。
fixfiles relabelは、常に/が暗黙的に選択され、全てのファイルをrelabelします。

では、これらの特徴を試してみましょう。
対象が/home配下に絞られているので処理が早く終わります。
また、TypeだけでなくUserもrelableするというチェック結果になっていることがわかります。

sudo fixfiles -F check /home/
# Would relabel /home/endy/x from sysadm_u:object_r:tmp_t:s0 to unconfined_u:object_r:user_home_t:s0

fixfiles checkの動作確認はここでおしまいです。
使い終わったファイルを削除しておきます。

rm /home/endy/x

fixfiles restore

fixfiles restoreは、ファイルシステム全体をrelabelします。
このコマンドを単体で使うと、OS再起動することなくrelabelが可能です。

OS再起動を伴わない分/.autorelabelを使った方法よりも気軽に実行しやすいです。
しかし、man selinuxでも触れられている通り/.autorelabelを使った方法が "Best" とのことなので、もしリリース前など、気軽に再起動できる状況であればOS再起動を伴う方法の方が手堅いとは思います。

以下に実行例を示します。

単にfixfiles restoreを実行すると、Typeのみrelabelします。
また、restoreconと同様に-vを指定しないと何が書き換わったかわかりません。
実行時間短縮のため、/home/endy/配下に対象を絞って実行します。

touch /home/endy/x
chcon -u system_u -t tmp_t /home/endy/x
ls -Z /home/endy/x
# system_u:object_r:tmp_t:s0 /home/endy/x

sudo fixfiles restore /home/endy/
ls -Z /home/endy/x
# system_u:object_r:user_home_t:s0 /home/endy/

一旦Typeを元に戻して、今度は-F-vを指定して実行します。
今度は-Fの指定によってTypeだけでなくUser/Role/Rangeも含めてrelabelされます。
また、-vによって実行結果が詳細に出力されます。

chcon -t tmp_t /home/endy/x
ls -Z /home/endy/x
# system_u:object_r:tmp_t:s0 /home/endy/x

sudo fixfiles -Fv restore /home/endy
# Relabeled /home/endy/x from system_u:object_r:tmp_t:s0 to unconfined_u:object_r:user_home_t:s0

(参考) fixfiles relabel

fixfiles relabelを実行すると、fixfiles restoreと同様に対象をrelabelします。
ただし、実行前に/tmpを削除するか聞かれます。

fixfiles relabelユースケースは、正直思い浮かびません。
「基本使わない」と覚えておけば良いと思います。

/tmp配下を削除しないよう指定すると、fixfiles restoreと同様に動作します。

sudo fixfiles relabel

#     Files in the /tmp directory may be labeled incorrectly, this command
#     can remove all files in /tmp.  If you choose to remove files from /tmp,
#     a reboot will be required after completion.

#     Do you wish to clean out the /tmp directory [N]? n
# Relabeling / /boot /dev /dev/hugepages /dev/mqueue /dev/pts /dev/shm /run /run/user/1000 /sys /sys/fs/cgroup /sys/fs/pstore /sys/kernel/debug /sys/kernel/debug/tracing /sys/kernel/tracing /tmp
# / 100.0%
# /boot 100.0%
# /dev 100.0%
# /dev/hugepages 100.0%
# Warning no default label for /dev/mqueue
# /dev/mqueue 100.0%
# /dev/pts 100.0%
# /dev/shm 100.0%
# /run 100.0%
# /run/user/1000 100.0%
# /sys 100.0%
# /sys/fs/cgroup 100.0%
# /sys/fs/pstore 100.0%
# /sys/kernel/debug 100.0%
# Warning no default label for /sys/kernel/debug/tracing
# /sys/kernel/debug/tracing 100.0%
# /sys/kernel/tracing 100.0%
# /tmp 100.0%

# Cleaning up labels on /tmp

/tmp配下を削除するよう指定すると、fixfiles restoreと同様に動作します。
-fを指定することで、/tmpを削除するか聞かれずに無条件で削除するよう選択します。

/tmp配下を削除した後は、Linuxが正しく動作するように一度再起動する必要があります。

sudo fixfiles -f relabel
# Cleaning out /tmp
# Relabeling / /boot /dev /dev/hugepages /dev/mqueue /dev/pts /dev/shm /run /run/user/1000 /sys /sys/fs/cgroup /sys/fs/pstore /sys/kernel/debug /sys/kernel/debug/tracing /sys/kernel/tracing /tmp
# / 100.0%
# /boot 100.0%
# /dev 100.0%
# /dev/hugepages 100.0%
# Warning no default label for /dev/mqueue
# /dev/mqueue 100.0%
# /dev/pts 100.0%
# /dev/shm 100.0%
# /run 100.0%
# /run/user/1000 100.0%
# /sys 100.0%
# /sys/fs/cgroup 100.0%
# /sys/fs/pstore 100.0%
# /sys/kernel/debug 100.0%
# Warning no default label for /sys/kernel/debug/tracing
# /sys/kernel/debug/tracing 100.0%
# /sys/kernel/tracing 100.0%
# /tmp 100.0%

# Cleaning up labels on /tmp

ls /tmp
# (出力なし)

sudo reboot

fixfiles restorefixfiles relabelの違いは、/tmpを削除するか否かです。
正直、/tmpを削除するメリットはいまいちわかりません。

fixfiles relabelを使うことはほぼないと思います。

Boolean

Booleanの概要

SELinuxのModule Policyには、いくつかのBooleanが定義されています48
BooleanはPolicy Source (ソースコード) 上で条件分岐 (if文) として登場し、Booleanが1か0かによって挙動が変わるようにロジックが組まれています。
Booleanが1の場合はTrueを、0の場合はFalseを表します。

このBooleanの値を変更することで、ソースコードを変更すること無く既存のアクセス制御ルールの挙動を変更することができます。

ソースコード上は3種類のBooleanの定義方法がありますが、私達が使う段階においては特に機能に差はありません (※1)49

ソースコード上の定義方法によっては、BooleanのことをTunableと呼ぶこともあります。
私達が扱うBooleanの大半はTunableになります (※2)

(※1) The SELinux Notebookからgen_boolgen_tunabletunable_policyなど、各種Booleanを定義するための構文を参照できます。これらの構文を見比べると、結局のところ同じ使い方をしています。Boolean変数名を宣言して、その先で変数を利用したif文を記述するという使い方です。こういった理由から、各種Booleanは記述場所や記述方法が異なるのみで、機能的な差異は特にないと判断しました。

(※2) Fedoraが管理するSELinux Policyのソースコード上で、Global Booleanを定義するgen_boolよりもTunable Booleanを定義するgen_tunableとその中身を記述するtunable_policyの方が圧倒的にヒット数が多かったためです

Boolean関連コマンド

Booleanの値を調べるには、以下のいずれかのコマンドを使います。

  • getsebool -a
  • sudo semanage boolean -l

Booleanの意味を調べるには、以下のコマンドが有効です。

  • sesearch -A --type_trans -b <boolean>

Booleanの値を1 (True) に変更するには、以下のいずれかのコマンドを使います。

  • sudo setsebool -P <boolean> 1
  • sudo semanage boolean -m -1 <boolean>

Booleanの値を0 (False) に変更するには、以下のいずれかのコマンドを使います。

  • sudo setsebool -P <boolean> 0
  • sudo semanage boolean -m -0 <boolean>

Booleanのより詳細な使い方については、後続記事のSELinuxの実践の「Boolean有無の確認」セクションを参照してください。

(参考) アクセス制御ルールはどこに書いてあるのか

アクセス制御ルールが書いてある場所

アクセス制御ルールの情報は、Security Policyと呼ばれる単一のバイナリファイルに格納されていることは既に説明したとおりです。
このバイナリファイルを作る工程として、開発者はソースコード (Module Sources) を書いています。
したがって、SELinuxのTypeの定義やallow Statementなどの情報は、ソースコードを追うことでも確認することができます。

下図において、右下にある.te.fc.ifの拡張子を持つファイルが主なソースコードです。
これらのソースコードを専用のコマンドによるコンパイル・パッケージ化・インストールという工程を経てSecurity Policyファイルが生成します。

how_security_policies_are_built

ソースコードを読む必要はあるのか?

実用上、ソースコードを読む必要はありません。

必要な定義情報は、全てseinfoやsesearchなどで検索することができます。
わざわざ読みづらいソースコードから理解する必要は全くありません。

非常にレアなケースとして、自分でソースコードを書いてルール定義したくなる場面もあるかもしれません。
ただ、相当なレアケースですので一旦は忘れて良いと思います。

重要なのはseinfoやsesearchコマンドを使いこなすことです。

このあたりのテクニック面のお話は、まとめて次の記事で説明します。
SELinuxの実践

ソースコード周りの話については、別途専用の記事を用意してあります。
もし 不幸にも ソースコードを書く必要な状況が出てきたら、目を通していただければと思います。
(参考) SELinux Module Policyのソースコード読解、ビルド

まとめ

SELinuxの最も重要な要素であるTE (Type Enforcement) について一通りの理論を説明しました。
かなり盛りだくさんの内容となりましたが、実運用をする上では全てを暗記する必要はありません。

本記事で扱った基礎理論をベースに、次の記事ではSELinuxのエラーを修正するための実践的なノウハウを紹介します。
ノウハウ理解のために知識の補完が必要な場合は、改めて本記事を辞書的に活用いただければ幸いです。

次の記事

SELinuxの実運用に必要な知識として、アクセス拒否エラーの切り分け手順と原因別の対処方を紹介します。

endy-tech.hatenablog.jp


  1. The SELinux Notebook - Types of SELinux Policy - #Policy Functionality Based on Name or Type

  2. The SELinux Notebook - The Reference Policy - #Policy Functionality

  3. The SELinux Notebook - The Reference Policy - #Policy Functionality

  4. Red Hat - SELinux Coloring Book - #MCS Enforcement ※MCSはTEよりも少し細かい制御

  5. RED HAT BLOG - Why you should be using Multi-Category Security for your Linux containers

  6. RHEL8 - Using SELinux - Configuring Multi-Category Security for data confidentiality

  7. RHEL7 - Virtualization Security Guide - sVirt Labeling

  8. RHEL8 - Using SELinux - Using Multi-Level Security (MLS)

  9. Gentoo Wiki - SELinux/Tutorials/SELinux Multi-Level Security - #MLS on a default installation

  10. Dan Walsh’s Blog - selinux-policy-minimum

  11. The SELinux Notebook - Access Vector Rules

  12. Gentoo Wiki - SELinux/Quick introduction - #SELinux security subsystem

  13. The SELinux Notebook - Security Context

  14. The SELinux Notebook - Type Statements - #attribute

  15. SELinux by Example: Using Security Enhanced Linux - 5.2.2. Types and Attributes ※書籍

  16. SELinux by Example: Using Security Enhanced Linux - 5.3.1.2. Using Attributes in AV Rules ※書籍

  17. The SELinux Notebook - Objects - #Object Classes and Permissions

  18. The SELinux Notebook - Object Class and Permission Statements - #class-2

  19. The SELinux Notebook - Object Class and Permission Statements - #Associating Permissions to a Class

  20. The SELinux Notebook - Object Class and Permission Statements - #common

  21. The SELinux Notebook - Computing Security Contexts - #Process

  22. The SELinux Notebook - Computing Security Contexts - #Files

  23. The SELinux Notebook - Default Object Rules

  24. The SELinux Notebook - Role Statements - #role_transition

  25. The SELinux Notebook - Type Statements - #type_transition

  26. The SELinux Notebook - MLS Statements - #range_transition

  27. The SELinux Notebook - Security ID (SID) Statement

  28. SELinux by Example: Using Security Enhanced Linux - 10.6. Initial Security Identifiers ※書籍

  29. The SELinux Notebook - Computing Security Contexts - #Process

  30. The SELinux Notebook - Computing Security Contexts - #Files

  31. RHEL8 - Using SELinux - Permanent changes in SELinux states and modes

  32. The SELinux Notebook - Domain and Object Transitions

  33. The SELinux Notebook - SELinux Overview - #Is SELinux useful → “SELinux can confine an application within its own ‘domain’ and allow it to have the minimum privileges required to do its job.”

  34. The SELinux Notebook - Mandatory Access Control → “processes run in domains and the actions on objects are controlled by policy.”

  35. The SELinux Notebook - Type Enforcement → “Basically if the type identifier is used to reference a subject it is referring to a Linux process or collection of processes (a domain or domain type)”

  36. The SELinux Notebook - Domain and Object Transitions - #Domain Transition

  37. The SELinux Notebook - Type Statements ※同じページにtype_changeやtype_memberがありますが、これらはlibselinux APIを使う類のルールなので基本意識不要です

  38. The SELinux Notebook - Domain and Object Transitions - #Object Transition

  39. The SELinux Notebook - Type Statements - #type_transition

  40. man7.org - selinux(8) - #FILE LABELING

  41. man7.org - restorecon(8)

  42. man7.org - semanage-fcontext(8)

  43. RHEL8 - Using SELinux - Permanent changes in SELinux states and modes

  44. man7.org - selinux_config (5)

  45. man7.org - selinux(8) - #FILE LABELING

  46. RHEL8 - Using SELinux - Permanent changes in SELinux states and modes

  47. man7.org - restorecon(8)

  48. Gentoo Wiki - SELinux/Booleans

  49. The SELinux Notebook - The Reference Policy - #Booleans, Global Booleans and Tunable Booleans

SELinuxの概要

SELinux_logo

SELinuxシリーズ

本記事は、SELinuxシリーズの2記事目です。

  1. Linuxプロセスアクセス制御の概要
  2. SELinuxの概要 ←今ココ
  3. SELinux Type Enforcement
  4. SELinuxの実践
  5. (参考) SELinuxのRBAC、UBAC、MLS、MCS
  6. (参考) SELinux Module Policyのソースコード読解、ビルド
  7. 参考URL

1〜3記事目は、4記事目を理解するための前提知識をカバーしています。
4記事目が最も重要で、SELinuxの具体的な操作方法やコマンド、トラブルシューティング手順を紹介しています。

5記事目以降は参考情報です。

SELinuxの関連記事は、SELinuxタグから探せます。

一連の記事はFedora環境を前提として書いています。
FedoraRHELに類するディストリビューションであればほぼ同等の挙動になると思いますが、他のディストリビューションでは挙動に差異がある可能性があるのでご注意ください。

お伝えしたいこと

前回のアクセス制御の一般論に続き、本記事ではSELinuxの概要を紹介します。
アクセス制御ルールについては、ボリュームがあるので次の記事に回します。

本記事でカバーする主なトピックは、以下のとおりです。

SELinuxとは

SELinux (Security Enhanced Linux) とは、MAC (Mandatory Access Control) を実装するアクセス制御技術の1つです。
RHELFedoraなどいくつかのLinuxディストリビューションにおいてデフォルトで有効化されています。

アメリカのUtah大学とアメリカ国防総省が開発したFLASK (Flux Advanced Security Kernel) というセキュリティフレームワークSELinuxの原型です。
その後、FLASKのフレームワークを引き継いでNSA (National Security Agency = アメリカ国家安全保障局) が開発を進め、SELinuxというOSSとしてリリースされたという経緯があります1

MACの特徴については既に前回記事のMAC (Mandatory Access Control)にて紹介しましたが、SELinuxにもMACの特徴が当てはまります。
以降のセクションでは、MACの特徴から更に踏み込み、SELinuxのより具体的な特徴やアーキテクチャに触れていきます。

SELinuxのアクセス制御

SELinuxのアクセス制御の概要を下図に示します。
図は「SubjectがObjectに対してActionを実行する」システムにおいて、SELinuxがどのようなアクセスパターンを制御対象にしているかを表しています。2,3,4

selinux_summary

上図からもわかるように、SELinuxによるアクセス制御は以下の特徴を持ちます。

  • プロセスのアクセス制御技術である (Subject = process)
  • アクセス制御対象がファイルに限らず、多岐にわたる (Object = file, socket, process, device, database, ...)
  • 許可するアクセスの粒度が細かい (Action = read, write, execute, create, open, append, link, ...)

ファイルのみを対象として読み書き実行を中心にアクセス制御を実装するDAC (Discretionary Access Control) と比較すると、SELinuxは細かい粒度でアクセス制御します。
(※) 本記事において、DACという用語は狭義で使用しており、LinuxのFile Access PermissionsとACLのことを指しています

もちろん、SELinuxMACと同様に以下の特徴も持ちます。

  • EUID=0を持つ特権プロセスであってもアクセス制御対象とする
  • アクセス制御ルールを書き換えられるのは基本rootユーザーのみ

仮にEUID=0を持つデーモンプロセスを脆弱性などで乗っ取られたとしても、SELinuxは自身が許可しているアクセス以外を全て拒否します。
EUID=0であれば無条件にアクセスを許可するDACと比較すると、プロセスのアクセスをより厳格に管理するSELinuxは、ゼロデイ攻撃などによってシステムに侵入された後の防御策としてより強力であると言えます。

DACMACに関するより詳細な説明は、前回記事を参照してください。

SELinuxアーキテクチャ

本セクションでは、SELinuxを構成する主なコンポーネントを紹介します。

SELinuxを操作する中で、AVC (Access Vector Cache) という用語は#SELinuxの監査ログに出てきます。
本セクションの内容からは、最低限 「AVCはアクセス制御可否を返すプログラムである」と理解いただければ十分です。

OM (Object Manager) やSecurity Serverについては、用語を覚えなくても問題はありません。
ただ、AVCとやり取りするプログラムですので、これらも併せて理解することでSELinuxの動作を理解する助けになると思います。

システム構成図

以下の図は、SubjectがObjectに対してActionを実行しようとしたとき、SELinuxが許可/拒否を判断するまでの内部動作を示したものです5

component1

処理は以下の流れで進みます。

  • (1) SubjectのプロセスがSELinuxのアクセス許可を得るため、OMにアクセスする
  • (2) OMは、AVCにアクセス権限の有無を問い合わせる
  • (3)〜(4) AVCのキャッシュ上にデータがなければ、AVCからSecurity Serverに問い合わせる。Security Serverは、アクセス制御ルール定義 (Security Policy) の内容に基づいて応答を返す
  • (5) AVCからOMに応答を返す
  • (6)〜(7) 応答に応じて、OMはSubjectにActionの実行を許可または拒否する

図中のSecurity Policyは、単一のバイナリファイルです。
/etc/selinux/SELINUXTYPE/policy/policy.NNに格納されています。
このファイルの中には、アクセス制御ルールの定義情報が入っています。

Security Policyのファイルパスについて補足します。

SELINUXTYPEは/etc/selinux/configに書かれている値で、デフォルトはtargetedです6
SELINUXTYPEは、単にpolicyと呼ばれることもあります。
Security policyとは別の意味なので、ややこしいですが文脈によってどちらのことを指しているか判断する必要があります。

NNはpolicyのバージョンを表し、値が大きいほどSELinuxとして多くの機能をサポートします7
SELinuxを運用する上でこのバージョン番号を意識することはほぼありません。

例えば、Fedora35の場合は以下のファイルパスにSecurity Policyが配置されています。

file /etc/selinux/targeted/policy/policy.33
# /etc/selinux/targeted/policy/policy.33: SE Linux policy v33 8 symbols 9 ocons

(参考) より詳細なシステム構成図

The SELinux Notebook - Core SELinux Componentsにより詳細な図があるので、そちらも添付いたします。

component2

こちらの図を全て理解するのは難しいですが、わかる範囲でポイントを箇条書きにします。

  • 図の上半分がUserSpace、下半分がKernelSpaceになっている
  • UserSpaceのプロセスは、/sys/fs/selinux配下のファイルを通してKernelと連携している
  • /sysはsysfsと呼ばれる疑似ファイルシステムをメモリ上で構成し、UserSpaceからKernelにアクセスするためのインターフェースをファイルという形で提供する (man 5 sysfs)
  • OMとAVCはUserSpaceとKernelSpaceのどちらにも存在しうる。SELinuxと連携するアプリの実装次第
  • Security Serverが読み込んでいるLoaded Policyとは、/etc/selinux/SELINUXTYPE/policy/policy.NNをメモリ上に読み込んだファイルのこと。/sys/fs/selinux/policy

/sys/fs/selinux配下にどのようなファイルがあるかは、以下のリンクに説明があります。
The SELinux Notebook - Linux Security Module and SELinux - #SELinux Filesystem

SELinuxの監査ログ

SELinuxによってアクセスが拒否された場合、Auditdに連携されて監査ログ (/var/log/audit/audit.log) が出力されます8

監査ログは以下のコマンドで確認できます。

sudo ausearch -m avc,user_avc

# ----
# time->Sun Nov 21 17:29:41 2021
# type=AVC msg=audit(1637483381.836:583): avc:  denied  { read } for  pid=10602 comm="mandb" name="anki.1" dev="dm-0" ino=919333 scontext=system_u:system_r:mandb_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

このログは、SELinuxによって拒否されたアクセスの詳細を表現しています。
今回の場合はSubjectがscontext (source context)、Objectはtcontext (target context)、Actionは{ read }として表示されています。
#SELinuxのアーキテクチャで触れたAVCというキーワードは、この監査ログで登場します。

setroubleshoot-server RPMパッケージがインストールされている場合、上記監査ログの発生をトリガーとして追加のログが/var/log/messagesに出力されます。
setroubleshoot-serverは、Fedoraにはデフォルトでインストールされていました。
追加のログは、以下のコマンドで確認できます。

journalctl -qen all -t setroubleshoot --no-pager
# (一部抜粋)

# Nov 21 17:29:45 pc setroubleshoot[11309]: SELinux is preventing mandb from read access on the file /usr/local/share/man/man1/anki.1. For complete SELinux messages run: sealert -l 5fe15040-2828-45d4-9e8c-ba8ef5781937

# Nov 21 17:29:45 pc setroubleshoot[11309]: SELinux is preventing mandb from read access on the file /usr/local/share/man/man1/anki.1.
 
#   *****  Plugin restorecon (99.5 confidence) suggests   *****
#   # /sbin/restorecon -v /usr/local/share/man/man1/anki.1
  
#   *****  Plugin catchall (1.49 confidence) suggests   *****
#   # ausearch -c 'mandb' --raw | audit2allow -M my-mandb
#   # semodule -X 300 -i my-mandb.pp

上記ログは、発生したエラー内容と問題解決のためのヒント情報を含みます。
エラーメッセージから「/sbin/restorecon -v /usr/local/share/man/man1/anki.1を実行すれば直る」と読み取れます。

setroubleshootが提示する解決策は、最適な解決方法とは限りません。
あくまで「ヒント」として活用し、最終的には自分で判断することが重要です。

SELinuxのログの読み方については、後続記事のSELinuxの実践で改めて説明します。

監査ログが出ないケース

DACで拒否された場合

SELinuxのルールが評価されるより手前の段階でエラーになった場合、当然ながらSELinuxの監査ログは発生しません。

特にファイルアクセスについて言うと、SELinuxを含むMACよりもDACが先に評価されます。

したがって、File Permissionの不足が原因でアクセスに失敗した場合は、SELinuxの監査ログは出ないのでご注意ください。

dontauditルールに該当した場合

SELinuxのアクセス制御ルールで、dontaudit ruleというものがあります9
条件に該当したアクセスは、SELinuxによってアクセスを拒否されても監査ログを出力しなくなります。

dontauditルールは拒否されても問題ないアクセスパターンに対して指定するものですが、稀に拒否されてほしくない条件が含まれているかもしれません。
そういった場合には、一時的にdontauditを無効化する切り分けが有効です。
以下のコマンドで一時的に無効化できます。
この無効化状態は、SELinuxのSecurity Policy (アクセス制御のルール定義) が再読込みされるまで継続します。

sudo semodule -DB

再び有効化するには、以下のコマンドでSecurity Policyを再読込みします。

sudo semodule -B

この切り分けが有効なのは、「dontauditルールが不適切に定義されている場合」のみです。
もちろん、こういったことはレアケースで、私はこういった状況にあたったことはありません。

dontauditについては、頭の片隅に入れておく程度で良いと思います。

(参考) 何でもいいので監査ログを出力させる方法

余談ですが、sudo semodule -DBSELinuxの監査ログのサンプルを得たいときにも有効です。
以下の手順で監査ログを得られます。

  • sudo semodule -DBでdontauditを一時的に無効化する
  • journalctl -fでログ監視しながらしばらく待つ
  • 監査ログを確認できたら、sudo semodule -Bでdontauditを再度有効化する

裏を返すと、dontauditを無効化するだけで不要な監査ログが大量に出力されるということなので、何らかの詳細調査のために無効化する時は大量のログ調査する覚悟をしてください。

(参考) その他のトピック

本セクションでは、SELinuxに関して気になりそうなトピックをいくつかピックアップしてカジュアルにまとめます。

本セクションの内容はエビデンスが少なめです。
信憑性についてはそれほど高くないという前提で、気になる部分があれば拾い読みいただければと思います。

SELinuxはどの程度エラーを出すのか

SELinuxは、デフォルトで6万行以上のアクセス許可設定が定義されています。
これらのアクセス許可設定は、デフォルト構成でLinuxを使い続けている限り、必要なアクセスを全て許可するように作られています。

例えば、私はFedoraをメインPCとしてブラウジング、ネットワーク設定、KVMによる仮想マシン起動、各種ソフトウェアのインストール、プログラミングなど行っていますが、SELinuxによる拒否が発生したのは3年間で1回のみです。
その1回も、あるフリーソフトインストーラの動きに癖があったことが原因であり、SELinuxのアクセス許可ルールが足りていなかったわけではありません。

SELinuxを有効化すると色々とうまくいかなくなるのは、RHEL6ぐらいまでの話だと思います。
今はSELinuxにエラーを出させようとするほうが逆に大変です。

SELinuxは、デフォルトのまま使っても十分に運用できる品質になっています。

確かにサーバーの設定ファイルを書き換えてファイルパスやポート番号周りを変えるとエラーが発生しますが、これも正しい知識を持っていれば簡単に対処できます。

アクセス制御ルールの設計方針

SELinuxのアクセス制御ルールは以下の特徴を持ちます。

  • allowルールを追加することでアクセス許可する
  • allowルールに記載のないアクセスは拒否される
  • denyルールは存在しない
  • 既存のallowルールの削除はできない

したがって、既存のアクセス許可ルールを更に厳しくする手段は原則ありません (※)
(※) 厳密にはCustom Policyの追加によって元々アクセス制御対象外だったプロセスをアクセス制御対象に加えることができます。mls policyと呼ばれる設定を有効化することで、セキュリティをガチガチにすることも可能です。しかしいずれも特殊な要件でのみ使われる非常に難易度の高い技術なので、私達が使うことはほぼありません

このことから、アクセス拒否されたときにallowルールを中心に必要な設定を追加することSELinuxの基本的な運用となります。
デフォルトで定義されている6万行以上のアクセス制御ルールを把握する必要はありません。
アクセス拒否が発生したときに、関連するアクセス制御ルールをいくつか検索するためのコマンドを知っていれば十分です。

言い方を考えると、SELinuxはエラーが発生したときに適切な方法で修正できれば十分だと思います。
SELinuxを使うだけであれば、長い時間を割いてSELinuxの奥深くまで理解する必要はありません。

SELinux有効化のメリット

前回記事のMAC (Mandatory Access Control)にて述べたMACの特徴を再掲します。
これらの特徴はSELinuxにも当てはまり、有効化のメリットに繋がります。

  • EUID=0を持つ特権プロセスも特別扱いされない
    • アクセス制御の対象となる
    • アクセス制御ルールを変更できない (※)
  • ファイルアクセス以外も制御される
    • プロセス、ファイル、ポート番号、データベース、カーネル、...

(※) rootユーザーのみ、アクセス制御ルールの変更やSELinuxの無効化操作が可能です。root権限を持つrsyslogなどのプロセスの操作権限を得たとしても、これらの操作はできません

具体的なメリットとしては、以下が挙げられます。

  • 攻撃されたときの被害を「緩和」する10
  • プロセスを乗っ取られたとき、SELinuxの許可範囲でしか操作ができない (※)
  • 特に、未知の脆弱性を利用するゼロデイ攻撃に対して有効

特にコンテナ技術との相性が良いようです。

他にも、少々特殊な設計ではあるものの工夫によって価値を出している事例もあります。
SELinuxによって、file access permissionsでは難しかったきめ細やかな制御を可能としています。
SELinuxによってセキュリティ問題を解決し、セキュリティ観点だけでなく運用観点でもメリットを出せている事例です。

  • ssh秘密鍵sshプロセスは読めるが、人間には読めないようにできる12

SELinuxの守備範囲

SELinuxは、プロセスのアクセス制御技術です。
DACとセットで使われることが想定されています。

あるプロセスが通常動作として必要と想定されたアクセスについては、デフォルトで許可されるようルール定義されています。
仮にデータベースの管理プロセスが乗っ取られたとすると、データベースのクエリや破壊の不正実行は、SELinux観点では許可されます。
バックドアとしてLinuxユーザーと侵入用のポート番号を開くことはデータベースの想定用途に反するため、拒否されます。

世の中には様々なセキュリティ技術がありますが、SELinuxは以下を代替するものではありません。
Linuxカーネルレイヤーにおけるプロセスのアクセス制御」を実装するSELinuxとは、そもそもの役割が異なるためです。

もちろん、SELinuxがプロセスの役割範囲を超えたアクセスを制限することで、万が一システムに侵入された際のリスクを大幅に減らすことは可能です。
したがってSELinuxを有効化する意義はあるのですが、他のセキュリティも併せて実装する必要があるということです。

SELinuxの無効化は非推奨

SELinuxは、デフォルトで有効化されているOS標準のセキュリティ機能です。
そして、無効化することは非推奨とRed Hat社のマニュアルにも書いてあります13

SELinuxが有効であることによって、不正アクセスによるシステム乗っ取りの被害を「軽減」できます (侵入を防ぐことはないと思いますが)
SELinuxを無効化するのは、このメリットを失くすということです。

無効化を検討するのは、SELinuxの有効化がよほど大変な場合なときだと思います。
例えば、SELinux無効化前提の既存設計を変更するのに多大な労力やコストがかかる場合などが考えられます。

しかし、SELinuxの維持管理のシンプルさについては他セクションで既に述べたとおりです。

  • SELinuxはデフォルトで必要なアクセス許可設定が定義済み
  • ポート番号やファイルパスをデフォルトから変えない限り、ほとんどエラーは出ない
  • エラーが出た場合、そこだけを修正すれば良い

以上より、原則としてはSELinuxをデフォルトの有効状態のまま運用するのが良いと考えています。

まとめ

前回記事のMAC (Mandatory Access Control) の特徴から更に踏み込んで、SELinuxの特徴を紹介しました。

SELinuxによるアクセス制御の詳細な仕組みについては、次回記事にて扱います。

次の記事

SELinuxによるアクセス制御のコアとなるType Enforcement (TE) について詳しく説明します。
SELinuxを扱う上で最も重要な知識の理論部分をカバーします。

endy-tech.hatenablog.jp

Linuxプロセスアクセス制御の概要

SELinux_logo

SELinuxシリーズ

本記事は、SELinuxシリーズの1記事目です。

  1. Linuxプロセスアクセス制御の概要 ←今ココ
  2. SELinuxの概要
  3. SELinux Type Enforcement
  4. SELinuxの実践
  5. (参考) SELinuxのRBAC、UBAC、MLS、MCS
  6. (参考) SELinux Module Policyのソースコード読解、ビルド
  7. 参考URL

1〜3記事目は、4記事目を理解するための前提知識をカバーしています。
4記事目が最も重要で、SELinuxの具体的な操作方法やコマンド、トラブルシューティング手順を紹介しています。

5記事目以降は参考情報です。

SELinuxの関連記事は、SELinuxタグから探せます。

一連の記事はFedora環境を前提として書いています。
FedoraRHELに類するディストリビューションであればほぼ同等の挙動になると思いますが、他のディストリビューションでは挙動に差異がある可能性があるのでご注意ください。

お伝えしたいこと

本記事では、SELinuxの位置づけを明確にするために、Linuxプロセスのアクセス制御技術を分類します。

SELinux自体の具体的な特徴は次の記事に譲りますが、SELinuxの上位概念であるMAC (Mandatory Access Control) の特徴はSELinuxにも当てはまるので、これを理解しておくことはSELinuxを理解する上で重要です。

本記事では、以下のようなトピックを扱います。

  • Linuxプロセスのアクセス概要
  • Linuxプロセスのアクセス制御技術
  • DACMACの関係性

Linuxプロセスのアクセス概要

SELinuxはプロセスのアクセス制御を実装するセキュリティ技術の一つです。
Linuxディストリビューションではありません (お約束)。

SELinuxの役割を具体的にイメージするために、本セクションではプロセスが何に対してどうアクセスしているかを図解します。

linux_process_communication_diagram

プロセスは実行ファイルの実行などによって生成し、様々なリソースに対してアクセス、操作します。

例えばユーザーによるCLI操作は、シェル (Process) を介して実行ファイル (File) を実行 (execute) し、別のプロセス (Process) を起動することで様々な処理を実行します。

別の例として、Linuxのブート時にはsystemd (Process) が起動し、systemdが様々なプロセス (Process) を起動します。
各種デーモンプロセス (Process) は設定ファイル (File) を読み込み (read)アーキテクチャによっては子プロセスを複数起動し (fork)、特定のポート番号 (Network Socket) で通信を待ち受けます (listen)

このように、プロセスは様々なリソースにアクセスして処理を実行します。
そして、プロセスからリソースへのアクセスを細かく制御するのがSELinuxという機能です。
例えば、あるプロセスに「File Aを開いて (open) 追記する (append) はできるが、他の書き込み (write) は許可しない」といった制御が可能です。
制御対象となるアクセス先はFileだけでなく、Process (IPC: Inter-Process Communication), Network Socket (ネットワーク通信), Database, Filesystem, Device, Kernel, ...など多岐に渡ります。

Linuxプロセスのアクセス制御

アクセス制御の分類

Linuxのアクセス制御技術は、DACMACに大別されます。
MACのことをnon-discretionary access controlと呼ぶことも稀にあります1

linux_access_control_implementations

DACMACは、いずれもSubjectがObjectに対して何のActionを実行できるかを制御します。

Subjectとは、アクションを実行するもののことです。
Objectとは、Subjectによって何か操作をされる対象のことです。
そしてActionとは、SubjectがObjectに対して実行する操作のことです。

Subjectとは、具体的にはLinuxのプロセスを指します。
Object, Actionが何を指すかは、アクセス制御の種類によって異なります。
詳細は後続のセクションで、DACMACの意味や特徴も含めつつ説明します。

DACMACは両方使われる

DACMACはどちらか片方が動作するというものではありません。
Linuxにおいてプロセスがファイルアクセスをする際、DACMACの順に処理されます2
アクセス先がファイルではない場合、DACは関係ないのでMACのみ処理されます。

  1. まず、DAC観点でアクセス可否を判定する
  2. 1がOKであれば、次にMAC観点でアクセス可否を判定する
  3. 2もOKであれば、ファイルアクセスを許可する

1と2のどちらか片方がNGであれば、多くのプログラムではPermission Deniedエラーになります。

詳しくは次のセクションで触れますが、DACはFile access permissions、MACSELinuxと読み替えてほぼ差し支えありません。

the_order_of_file_access_control

最後に補足ですが、DACMACという処理順序を理解しておくことは実務上でも重要です。
SELinuxでエラーが発生したときは監査ログ (/var/log/audit/audit.log) が発生します。
しかし、仮にSELinuxでアクセス許可されていなくても、先にFile access permissionsで拒否されていた場合には監査ログが発生しません。
アクセス拒否の原因を正確に切り分けるためにも、DACの後にMACという処理順序を理解しておくことが重要です。

DAC (Discretionary Access Control)

DACMACと同様、Linuxプロセスのアクセス制御の一種です。

広義のDACは、以下の全て、または一部が可能であるという特徴を持つアクセス制御を指します3
広義の場合、LinuxWindowsのファイルアクセス制御に限定されない語義となっています。

  • 他のSubjectやObjectに対するデータの受け渡し
  • 他のSubjectに対する権限の付与 (例: chmod)
    ※Discretionary = (非rootユーザーであっても) 裁量を持つ
  • セキュリティ属性の変更 (例: setuid, setgid)
  • 新規作成されたObjectのセキュリティ属性の指定 (例: umask)
  • アクセス制御のルールの変更

しかし実際には、SELinuxの文脈においては狭義のDACで解説されることがほとんどです。
したがって、本ブログにおいても狭義のDACに基づいてこの後の解説を続けます。

狭義のDACは、Linuxにおけるプロセスからファイルへのアクセス制御技術を指します4
DACは、UID (User ID), GID (Group ID) に基づいてLinuxプロセスのファイルアクセスを制御します。
より具体的には、狭義のDACはFile access permissionsとPOSIX ACLという2つの機能を指します

「SubjectがObjectに対して何のActionを実行できるか」という観点で整理すると、Subject、Object、Actionは以下が該当します。

  • Subject = プロセス
  • Object = ファイル
  • Action = read, write, execute, setuid, setgid, deletion (sticky bit)

dac_summary

File access permissionsは、皆さん馴染みがあると思います。
File access permissionsはPOSIXの言い回しですが5、他にもLinux permissionsやUnix permissions、File permissionsなどと呼ばれることもあります。

プロセスを実行したユーザー/グループのID (EUID: Effective UID, EGID: Effective GID) とファイルのオーナー (UID/GID) を突き合わせてUser/Group/Othersを判定し、file mode bits (※) に基づいてアクセス制御する機能です。
(※) 644rwxrw-rw-など、権限を表現するビット列

関連するコマンドとしては、以下が挙げられます。

コマンド 意味
ls -l ファイルのUID/GID, File mode bits の確認
ps -ef プロセスのEUID、PID、コマンドなどを表示 (※)
chmod File mode bitsの変更。
root、またはオーナーユーザーが実行可
chown ファイルのUID/GIDの変更。
rootが実行可

(※) psについては、ps -eo user,group,pid,ppid,commandのように列を指定することで、Groupも含めて確認できます。詳細はman psを参照してください

EUID/EGIDという用語は聞き慣れないかもしれませんが、要するにプロセスを実行したユーザーのUID/GIDです。
例外として、setuid/setgid属性が付与されたファイルを実行したり、rootユーザーの特権を行使することでEUID/EGIDが変わることもあります。
ちなみに、プロセスは他にも2種類のIDを持ちます (SUID/SGID, RUID/RGID)
このあたりの仕組みに興味のある方は、以下の参考URLもご覧ください。

File Permissionsは、「ログインユーザーとファイルのUID/GIDを比較する」と説明されることもありますが、これは実は正確ではありません。
ログインユーザーが実行したプロセスは、「結果的に」上記の説明でも矛盾はありません。
しかし、実際にはLinux起動時にrootユーザー名義 (EUID=0) で実行されるデーモンプロセスなどもあり、これらのプロセスがファイルにアクセスする際もFile Permissionsによって評価されます。
EUIDという言葉を知っていると、デーモンプロセスによるアクセスも本質的にはユーザープロセスと変わらず制御されることを理解できます。

また、ここでEUID/EGIDという小難しい用語に触れたのにはもう一つ理由があります。
それは、DACが「プロセスの」アクセス制御であることを強調するためです。
DACMACも「プロセスから各種リソースへのアクセス制御」という点では同じカテゴリに属する技術です
その観点でDACMACを対比して考えるとSELinuxの必要性について理解が深まるので、ここで改めて意識いただけますと幸いです。

少し話が飛躍しますが、DACだけではセキュリティに穴があるので、MACによって補完しよう」というのがSELinuxを含むMACの基本的な考え方です。

file_access_permissions

POSIX ACLは、File access permissionの高機能版です。
Linuxの文脈では単にACL (Access Control List) と呼ばれることが多いです。
User/Group/Othersに関わらず、特定のユーザーやグループ1つに対して権限を割り当てることができます。
setfaclで設定を変更し、getfaclで設定を確認します。
プロセスとファイルのUID/GIDを対比し、file mode bitsによってアクセス制御するという基本動作は、File access permissionsと同様です。

posix_acl

最後に、冒頭の広義のDACの定義も参考にしつつ、File access permissionsとPOSIX ACLに共通するDACの特徴をまとめます。
次のセクションでDACMACの特徴を対比することで、MAC (つまりSELinux) の必要性を理解するための布石です。

  • ファイルのオーナーユーザーは、一般ユーザーであっても他ユーザーに対してchmodsetfaclで任意の権限を与えることができる (Discretion、つまり裁量を持っている)
  • アクセス制御の粒度は読み書き実行 + Sticky bitによる削除の制御
  • ファイルに対するアクセス制御のみ
  • UID/GIDを利用する
  • rootのEUID (EUID=0) を持っていればアクセス制御の対象にならず、何でもできる

MAC (Mandatory Access Control)

SELinuxMAC (Mandatory Access Control) を実装する技術の一つです。
したがって、本セクションで触れる内容は全てSELinuxについても当てはまります。

MACDACと同様にLinuxプロセスから各種リソースへのアクセスを制御します。
MACは、DACでは許可されている以下の挙動を制限することが特徴です。
これにより、MACDACと比較してより高いセキュリティを提供します。6,7

  • 他のSubjectやObjectに対するファイルデータの受け渡し
  • 他のSubjectに対する権限の付与
  • セキュリティ属性の変更
  • 新規作成されたObjectのセキュリティ属性の指定
  • アクセス制御のルールの変更

MACにおいては特権を持たないリソースオーナーの裁量 (Discretion) によって、chmodのような仕組みで権限を譲渡することはできません。
Kernel Spaceにアクセス制御ルールの定義情報を持ち、EUID=0を持つプロセスであっても例外なく強制的に (Mandatorily) アクセス制御ルールが適用されます。

具体例として、EUID=0を持つデーモンプロセスがゼロデイ攻撃によって脆弱性を付かれ、操作権限を奪われたとします。

DACの場合、EUID=0を持つ限りは権限を絞る手立てはありません。
最悪の場合はroot権限のshellを起動され、任意の操作を実行されます。
例えば、データ取得、バックドアの設置、他のシステムに侵入するための踏み台として利用するなどが考えられます。

一方でMACが実装されていれば、EUIDに関わらず、MACが付与したプロセスを識別するメタデータに基づいてアクセス制御されます(※)
(※) SELinuxの場合は、Security Contextと呼ばれる識別子をプロセスやファイル、ポート番号などに付与します。そして識別子に基づいてアクセス制御します。プロセスが持つEUIDは、SELinuxにおいては意味を持ちません

例えば、侵入されたプロセスがrsyslogdだとすると、実施できる操作がrsyslogサーバーとして必要なアクセスの範囲に限定されます。
MACは侵入を検知したり防いだりすることはできませんが、侵入後の被害を限定することができます。

difference_between_dac_and_mac

MACを実装しているセキュリティ技術は、SELinux以外にも存在します。 以下のMAC実装技術のうち、複数を同じLinux上で同時起動することはできません。
したがって、SELinuxのみを使う場合は他のMACを意識する必要はありません。

(参考) LSM (Linux Security Module)

本セクションは興味のある方向けの補足情報です。
SELinuxを理解する上では、読み飛ばしても問題ありません。

MACはLSM (Linux Security Module) という形でLinux Kernelに組み込まれます。8,9,10
MACを実装しているソフトウェアはSELinuxやAppArmorなど複数存在しますが、LSM Hookというインターフェースを介して共通した方法でLinux Kernelとやり取りします。

lsm

単一のLinuxマシンにMAC機能を提供するソフトウェアを複数インストールし、OS起動時に切り替えて使用することは可能です。
しかし、同時に動作できるMACは1つのみです (※)
したがって、SELinuxを動作するマシンにおいてAppArmorなどの他のMACが同時に動作することはありません。
(※) 厳密には、LSMにはnon-exclusive modules (minor modules) というものがあり、SELinux以外にもyamaやcapabilitiesなどいくつか同時に起動しています。SELinuxやAppArmorはexclusive modules (major modules) と呼ばれ、exclusive modulesが2つ同時に動くことはありません11,12

LSMの切り替えについてはLinux Security Module Usageに詳細が書かれています。

いくつか、Fedora35上で実行した関連ログも添えておきます。

↓ActiveなLSMを表示しています。
SELinuxだけでなく、他にもいくつかのLSMが起動していることがわかります。

cat /sys/kernel/security/lsm
# lockdown,capability,yama,selinux,bpf,landlock

↓Kernelのビルドオプションにより、デフォルトで起動するmajor LSM を指定できます。
自力でLinux Kernelをビルドしているのでなければ、Linux Distributionによってデフォルトで使用するmajor LSMが決まると考えて良いと思います。
以下の出力では、SELinuxがデフォルトにセットされていることがわかります。

grep CONFIG_DEFAULT_SECURITY /boot/config-`uname -r`
# CONFIG_DEFAULT_SECURITY_SELINUX=y

↓現在のkernel command line argumentを確認しています。
kernel command line argumantにsecurity=selinuxのようにMACを指定することで、上記のデフォルト挙動を上書きすることができます。
以下の出力にはsecurity=...という指定がないため、Kernelのビルドオプションの設定に従ってSELinuxで起動することがわかります。

cat /proc/cmdline
# BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.14.18-300.fc35.x86_64 root=/dev/mapper/fedora_pc-root ro rd.lvm.lv=fedora_pc/root rhgb quiet

まとめ

Linuxのアクセス制御技術を分類し、SELinuxの位置づけを示しました。

Linuxプロセスのアクセス制御技術はDACMACに分類され、SELinuxMACの一種です。

MACDACよりも高いセキュリティを実現できる技術ですが、DACと比較して設計や設定変更に労力がかかります。
DACMACをうまく併用し、運用性とセキュリティの良いバランスを検討することが重要です。

具体的には、MACLinuxインストール時の初期値として必要なアクセスを、細かい粒度で許可します。
初期値としてルール設定されているので、ある意味万人向けに、広くアクセス許可されています。
しかし、攻撃でなければ絶対発生し得ないようなアクセスパターンは確実に遮断します。
プロセスの基本動作に変更があった場合に、実情に併せて「root権限を以て慎重に、稀に」アクセス許可設定を変更します。

一方、DACは非rootユーザーでも手軽に変更できる点を活かし、運用者の裁量によって重要なファイルに対して細かく権限を設計し、実情にあわせてこまめにメンテナンスする使い方をします。

次の記事

本記事でカバーしたDACMACの一般論から更に踏み込み、SELinuxの特徴を概要レベルで紹介します。

endy-tech.hatenablog.jp

ファイル実行時に呼ばれるfork,clone,execveについて調べてみた

20200927134745

お伝えしたいこと

Linuxでファイルを実行すると、子プロセスが生成します。
そして実行したプロセスが親プロセスになります。

その際、内部的にはforkとexecveというシステムコールが呼ばれていると聞いたことがあります。
私の環境 (Fedora35) で試してみたところ、forkではなくcloneが呼ばれ、その後execveが呼ばれていました。

本記事では、システムコール、fork、clone、execveという用語を整理したいと思います。

Linuxカーネル周りの技術に触れると、このあたりの用語が当たり前のように使われます。
そんなときに (私のように) 困らないよう、よろしければ読んでいってください。

今回の記事は情報ソースが少なめですが、実機でstraceコマンドを使った実験をすることで情報の信頼性を補完しています。

システムコールとは

システムコールとは、LinuxプロセスがLinuxカーネルに対して行う命令のことです1

Linuxでは、Linuxカーネルが全てのハードウェアに直接アクセスするなどの特権を持っています。

一般的なアプリケーションはこのような特権を直接行使することはできません。
代わりに、アプリケーションからカーネルに対してシステムコールを発行してカーネルに依頼を出します。
そしてカーネルが必要な処理を実行し、アプリケーションに処理結果 (戻り値) を返すという仕組みになっています。

ファイル実行時に呼ばれるシステムコール

bashシェル上でpsコマンド (/usr/bin/ps) を実行すると、bashを親プロセスとしてpsプロセスが生成します。
そして、psプロセスはpsコマンドの処理を実行します。

このようなファイル実行処理の裏では、以下の流れで処理が呼ばれています2

  1. 親プロセスがfork (またはclone) され、新たなPIDを持つ子プロセスが生成する
  2. 子プロセス上でexecveが実行され、親プロセスとは異なる動作を開始する

file_execution_flow

fork、clone、execveは、システムコールの一種です。
次のセクションでそれぞれの意味を紹介します。

fork

forkシステムコールは、親プロセスをコピーして子プロセスを生成します。
親プロセスのメモリ上のデータをそのまま子プロセスのメモリアドレス上にコピーします。
子プロセスはPIDなど一部の情報を除き、親プロセスと全く同じものがコピーされます。
詳細はman 2 forkWikipediaのforkに関する記事の情報が参考になります。

ちなみに、forkはC言語のライブラリ関数としても実装されています。
man 3am forkで仕様を確認できます (確認しなくて大丈夫です)。

forkライブラリ関数を呼び出すと、内部的にはforkシステムコールが呼ばれるという作りになっています。

clone

cloneシステムコールはforkと似ていますが、cloneの方が子プロセスに引き継ぐ情報をより細かく制御できるようです。
forkとcloneのユースケースの違いは理解していませんが、cloneもプロセスをコピーする処理なんだな、とここでは理解しておきます。

詳細はman 2 cloneに書いてあります。

execve

execveシステムコールは、呼び出したプロセス自身のメモリ上でプログラムを実行します。
結果として、execveを実行することで新たなプロセスに "変身" します。
man 2 execveのNOTESセクションにも書かれていますが、execveは新しいプロセスを生成しません。
また、execveの前後でPID (Process ID) は変わりません。
execveは別のアプリケーションを読み込み、同じプロセスの動作を置き換える命令です。

#ファイル実行時に呼ばれるシステムコールの繰り返しになりますが、ファイル実行したときはforkしてからexecveします。
forkで親プロセスから子プロセスをPID以外ほぼ丸ごとコピーし、execveで子プロセスならではの処理をインプットするという流れで処理が進みます。

ちなみに、execveにも関連するライブラリ関数があります。
man 3 execにexec系のC言語ライブラリ関数が列挙されていますが、これらの関数は内部的にexecveを利用しています。
(execl, execlp, execle, execv, execvp, execvpe)

実機確認

「ファイル実行はfork (またはclone) → execveの流れで進む」と説明しましたが、この情報に公式ドキュメントによる裏付けはありません。

ということで、2つだけですが実機ベースのログを添付することで、情報の確かさを補完したいと思います。

ファイル実行するとPPIDに実行元のPIDがセットされることの検証

bash上でpsコマンドを実行する例を考えてみます。

ps -fと入力してEnterキーを押しているのは私達人間ですが、実際に私達のキーボード入力を標準入力として受け取って命令を出しているのはbashプロセスです。

bashプロセスが/usr/bin/psファイルを実行してpsプロセスを起動しているので、psプロセスのPPIDにはbashのPIDがセットされるはずです。

このことは、ps -fを実行すればすぐに検証できます。
ps -fのPPID (Parent PID = 親プロセスのPID) が14429と、/bin/bashのPIDと一致していることがわかります。

ps -f
# UID      PID    PPID  C STIME TTY          TIME CMD
# endy   14429    5207  0 16:51 pts/2    00:00:00 /bin/bash
# endy   20072   14429  0 18:29 pts/2    00:00:00 ps -f

ファイル実行すると、fork (またはclone), execveの順に実行されることの検証

この検証では、straceコマンドを使います。

straceは指定したプロセスに接続し、そのプロセスに関わるシステムコールやシグナルを監視し、詳細なデバッグ情報を表示するツールです。
straceコマンドを実行するには、straceパッケージを事前にインストールしておく必要があります。

sudo dnf install strace

試しにpsプロセスをstraceしてみます。

strace ps -f
# execve("/usr/bin/ps", ["ps", "-f"], 0x7ffed16ab5e8 /* 60 vars */) = 0
# (以下略)

いきなりexecveを呼び出しているように見えます。
fork/cloneせずにexecveを呼び出すと、呼び出し元のbashがpsに置き換わってしまい、bashを維持できなくなってしまいます。
もちろん、そんなことはありません。

straceは、下図のようにbashプロセスがforkされて子プロセスが発行された後に、その子プロセスに接続して監視を開始しているようです。
したがって、上記の方法ではstraceでfork/cloneを観測することはできません。

straceでforkを観測するには、psの親プロセスであるbashを監視する必要があります3

strace_monitoring

では、上図の右側のやり方でfork/cloneを観測します。

まず、ターミナルを2つ起動しておきます。

1つ目のターミナルで現在実行しているシェルのPIDを確認します。
今回は8319だったとします。

※ちなみに、bashにおける$$、つまり$という特殊変数の意味は、man bashのSpecial Parametersセクションに書いてあります

echo $$
# 8319

続いて、2つ目のターミナルでPID 8319のプロセスをstraceで監視します。
-p <pid>で監視対象のプロセスを指定し、-fで子プロセスの情報も監視対象に含めています。
監視対象のbashから見てpsは子プロセスにあたるので、-fが必要となります。

strace -f -p 8319

1つ目のターミナルに戻り、psコマンドを実行します。

ps -f
# UID      PID    PPID  C STIME TTY          TIME CMD
# endy    8319    5207  0 Jan04 pts/1    00:00:00 /bin/bash
# endy   44850    8319  0 01:54 pts/1    00:00:00 ps -f

2つ目のターミナルにstrace結果が出力されるので、内容を確認します。
出力を抜粋したものを以下に示します。

# clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 44850 attached
# , child_tidptr=0x7f6d2616da10) = 44850

# (中略)

# [pid 44850] execve("/usr/bin/ps", ["ps", "-f"], 0x5573175278a0 /* 60 vars */) = 0

上記より、psを実行するとclone, execveの順にシステムコールが実行されていることがわかりました。
今回のケースでは、forkは呼ばれていませんでした。

まとめ

Linuxにおけるファイル実行時は、fork (またはclone)、execveシステムコールが呼ばれていることについて共有しました。

これからforkという単語が会話に出てきても、この知識でバッチリイメージできると思います。

Linux デスクトップファイルの書き方

desktop-computer
Icons made by Flat Icons from www.flaticon.com

お伝えしたいこと

Linuxデスクトップで使われるDesktop Entry (通称:デスクトップファイル) を自分で書くときに最低限必要な情報をまとめます。

Desktop Entryとは.desktop拡張子を持つテキストファイルです。
デスクトップのMenu上にアプリケーションを表示させるのに使われます。

Desktop Entryの作成は非常に簡単です。
以下の2工程で完結します。

  • 本記事のサンプルをベースに7行ほどのテキストファイルを作成する
  • ~/.local/share/applications/に格納する

「Menuにアプリケーションが表示されない...」と思ったらDesktop Entryを自分で作っちゃいましょう。

作り方を知りたい方は、ぜひ続きをお読みください。

既に慣れている方は、#要点を中心にチェックいただけると効率的です。

要点

Desktop Entryは、以下サンプルを書き換えることで簡単に書けます。

[Desktop Entry]
Name=Virtual Machine Viewer
Exec=virt-viewer
Comment=Display the graphical console
Terminal=false
Icon=virt-viewer
Type=Application

Iconファイルは主に以下のディレクトリから検索されます。
自分で画像を追加する場合は、先頭のホームディレクトリ配下に配置しましょう。

  • ~/.icons/ ★自作アイコンはここに配置
  • /usr/share/icons/
  • /usr/share/pixmaps/

作成したDesktop Entryは以下のディレクトリに配置します。
自分でDesktop Entryファイルを追加する場合は、先頭のホームディレクトリ配下に配置しましょう。

  • ~/.local/share/applications/ ★自作Desktop Entryはここに配置
  • /usr/share/applications/
  • /usr/local/share/applications/

Desktop Entryを作成してもMenuに表示されないときは、以下を疑いましょう。

  • 格納先のディレクトリは正しいか?
  • ファイルパーミッションやオーナーなどの権限情報に問題はないか?
  • Desktop Entryの文法にエラーはないか? → desktop-file-validateコマンドで確認できます

Desktop Entryとは?

Desktop Entryとは、.desktopという拡張子を持つテキストファイルです。
Desktop Fileや、Application Fileとも呼ばれます。

Desktop Entryを特定のディレクトリ配下に格納すると、LinuxデスクトップのMenu (WindowsでいうStart Menu) でアプリケーションとして表示され、クリック操作で起動できるようになります。

GUIでの実行を想定したアプリケーションをインストールすると、多くの場合インストール処理の一環で.desktopファイルも配布されます。
これによって、Menuでアプリケーション名を検索して起動できるようになります。

下記画像において "Google Chrome" という表示名でアイコンを用意し、クリックするとchromeを起動するのがDesktop Entryの役割です。

linux_desktop_menu

(参考) Desktop Entryの仕様を知る必要はあるのか?

デフォルトでDesktop Entryが用意されない場合に、自分で追加したいときに仕様がわかると便利です。

しかし、この知識が必要になる場面は限られているので、デスクトップアプリケーションを日常的に開発する方でなければ暗記する必要はありません。
必要なときに「こんな仕様があった」と思い出し、その場で調べながら書ければ十分です。

上記を踏まえ、本記事もDesktop Entryをちょこっと書きたい方向けに要点が短時間で伝わるように説明します。
より詳細な仕様については、freedesktop.orgを参照するように誘導します。

freedesktop.orgは公的な認証を受けている団体ではありませんが、Linuxデスクトップの標準的な仕様をいくつも定義しています。
そしてGnomeKDEなどのLinuxデスクトップがそれにある程度準拠しているため、実質的にLinuxデスクトップの仕様を知りたいときに重宝する情報源となっています。

Desktop Entryの書き方

まずはサンプルを示します。
私の環境ではvirt-viewerというプログラムにDesktop Entryファイルが付属しなかったので、Desktop Entryを自作しました。

このファイルは、~/.local/share/applications/virt-viewer.desktopに格納しています。
Desktop Entryの格納先については、#Desktop Entryの配置先で触れます。

[Desktop Entry]
Name=Virtual Machine Viewer
Exec=virt-viewer
Comment=Display the graphical console
Terminal=false
Icon=virt-viewer
Type=Application

Desktop EntryはINI形式で書くようです。
ざっくり、意味は以下のようになります。

Iconについては、#Iconの指定方法で別途補足します。

項目 詳細
Name
Menu上の表示名
Virtual Machine Viewer
Exec
クリック時に実行されるコマンド
virt-viewer
Comment
アプリケーションの説明文
Display the graphical console
Terminal
ターミナル上で実行するか?
false
Icon
Menu上のアイコンファイル
virt-viewer.svg
Type
タイプ
アプリケーション。
他にLink, Directoryがあるが、基本Applicationにする

作成したファイルを~/.local/share/applications/virt-viewer.desktopに配置すると、Menu上で検索してヒットするようになります。
以下はアプリケーション名で検索していますが、Exec (virt-viewer) やComment (graphical console) でもヒットします。
このあたりはMenuの実装にも依ると思いますが、とても便利です。

virtual_machine_manager_on_the_menu

Desktop Entryの基本的な書き方については、上表の説明で全てです。
オプションの意味を理解した上で上記サンプルを書き換えれば、Desktop Entryファイルを簡単に作成できると思います。

より詳細な仕様については、freedesktop.orgのDesktop Entry Specificationに記載があります。
また、/usr/share/applications/に既存のDesktop Entryが数多くあるので、見ると参考になると思います。
マニアックですが、デスクトップの言語に併せてNameやCommentを使い分けることもできるようです。

ただよほど凝りたいのでない限りは、詳細な仕様を知る必要はあまりないと思います。

Iconの指定方法

サマリ

Iconの指定方法は2通りあります1

  • 画像ファイルのフルパス指定
  • キーワード指定

キーワードを指定すると、Icon Theme Specificationの仕様に従って画像ファイルを検索し、それを使います。
内部的には難しい仕組みが動いていますが、ざっくり言うと以下の通りです。

  • ファイル名を指定すると指定のディレクトリから画像ファイルを探してきてそれを使う
  • ファイルの拡張子は省略して良い

実用上は、以下のディレクトリ配下の画像ファイルを、拡張子を省いたファイル名で指定する使い方が多いと思います。
上2つのディレクトリには、アプリケーションのインストーラが自動配置する画像ファイルが格納されます。
自前で画像を用意したい場合は、3番目のディレクトリにSVG形式などで画像ファイルを格納します。

  • /usr/share/icons/
  • /usr/share/pixmaps/
  • ~/.icons/

(参考) Iconの詳細仕様

本セクションは参考情報のため、興味のない方はスキップしてください。

Iconをキーワードで指定した際、画像ファイルの検索対象となるディレクトリは以下の3箇所です。
上から順に検索されます。

  • $HOME/.icons/
  • $XDG_DATA_DIRS/icons/
  • /usr/share/pixmaps/

環境変数XDG_DATA_DIRSについては、以下の中身でした。
PATHのように、複数のディレクトリが:で順に並んだ構造です。

echo $XDG_DATA_DIRS
/home/endy/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share

XDG_DATA_DIRSを展開すると、デフォルトでは以下の順で画像ファイルを探索します。
flatpakを使っていない方は、2番目と3番目のディレクトリは無視して問題ありません。

  • ~/.icons/
  • ~/.local/share/flatpak/exports/share/icons/ ※flatpak
  • /var/lib/flatpak/exports/share/icons/ ※flatpak
  • /usr/local/share/icons/
  • /usr/share/icons/
  • /usr/share/pixmaps/

実用上は、特に以下の3箇所をチェックすればほぼ事足ります。

  • /usr/share/icons/, /usr/share/pixmaps/: 既存の画像ファイルの主な格納先
  • ~/.icons/: 自前で用意したアイコンの格納先

今回は目的のファイルがvirt-viewer.svgとして存在することがわかったので、それを使います。

find ~/.icons /usr/share/icons /usr/share/pixmaps -iname 'virt-viewer*'
# /usr/share/icons/Paper/24x24@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/16x16/apps/virt-viewer.png
# /usr/share/icons/Paper/32x32@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/48x48@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/48x48/apps/virt-viewer.png
# /usr/share/icons/Paper/512x512@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/32x32/apps/virt-viewer.png
# /usr/share/icons/Paper/512x512/apps/virt-viewer.png
# /usr/share/icons/Paper/24x24/apps/virt-viewer.png
# /usr/share/icons/Paper/16x16@2x/apps/virt-viewer.png
# /usr/share/icons/hicolor/22x22/apps/virt-viewer.png
# /usr/share/icons/hicolor/16x16/apps/virt-viewer.png
# /usr/share/icons/hicolor/256x256/apps/virt-viewer.png
# /usr/share/icons/hicolor/48x48/apps/virt-viewer.png
# /usr/share/icons/hicolor/32x32/apps/virt-viewer.png
# /usr/share/icons/hicolor/scalable/apps/virt-viewer.svg
# /usr/share/icons/hicolor/24x24/apps/virt-viewer.png

画像の見た目はGUIでフォルダ階層を下ってファイルを見ても良いですが、せっかくフルパスがわかっているのでCLIから開いちゃいます。

xdg-open /usr/share/icons/hicolor/scalable/apps/virt-viewer.svg

余談ですが、xdg-openは既定のアプリケーションで目的のファイルやURLを開くコマンドです。
xdg-open .でカレントディレクトリを開いたり、xdg-open https://xxxというコマンドを紐付けたDesktop Launcherを作成してショートカット代わりに使うと便利です。

Desktop Entryの配置先

サマリ

Desktop Entryは主に以下に格納されています。

  • ~/.local/share/applications/
  • /usr/share/applications/
  • /usr/local/share/applications/

自前で作成したDesktop Entryは、基本的に1番目のパスに格納します。
システム全体にインストールされたデスクトップファイルは、2番目か3番目のパスに格納されています。

今回サンプルで作成したvirt-viewer.desktopは、1番目のリンクに格納します。

(参考) Desktop Entry配置先の詳細

本セクションは参考情報のため、興味のない方はスキップしてください。

作成したDesktop Entry (*.desktop) は、$XDG_DATA_DIRS/applications/配下に設置するようです。

$XDG_DATA_DIRSの中身を再掲します。

echo $XDG_DATA_DIRS
/home/endy/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share

したがって、Desktop Entryファイルの格納先は以下のとおりです。

  • ~/.local/share/flatpak/exports/share/applications/
  • /var/lib/flatpak/exports/share/applications/
  • /usr/local/share/applications/
  • /usr/share/applications/

ただ、実際には以下のディレクトリにもDesktop Entryを格納できます。
公式の情報ソースはありませんが、Arch Wikiには記載がありました。

  • ~/.local/share/applications/

補足ですが、経験上ディレクトリには以下のようなルールがあります。

  • /usr//usr/local/~/.local/配下のファイル構成は類似している
  • /usr/にはディストリビューションのパッケージマネージャが配布したファイルが格納される
  • /usr/local/にはその他の方法でシステムにインストールされたファイルが格納される (pipやmake installなど)
  • ~/.local/はホームディレクトリ配下なので、ユーザー固有のリソースが配置される

従って、/usr/share/applications/が使えるということは、~/.local/share/applications/も恐らく使えるだろうと連想できます。

(参考) desktop-file-validate

滅多に使うことはありませんが、Desktop Entryのエラーを検出するプログラムがあります。
もし作成したデスクトップファイルに正しいPermissionを付与し、正しいディレクトリに配置したにも関わらずMenuに表示されない場合は、以下のコマンドでDesktop Entryの定義が正しいかを確認してみると良いかもしれません。

desktop-file-validateコマンドは、以下のように.desktopファイルを指定して使います。
特に何の警告もでなければ、文法上は問題ありません。

desktop-file-validate virt-viewer.desktop
# (出力なし)

試しに、virt-viewer.desktopのNameオプションを以下のようにコメントアウトしてみます。

[Desktop Entry]
#Name=Virtual Machine Viewer
Exec=virt-viewer
Comment=Display the graphical console
Terminal=false
Icon=virt-viewer
Type=Application

そして、再びdesktop-file-validateを実行してみます。
必須パラメータのNameが指定されていないことでエラーになりました。

desktop-file-validate virt-viewer.desktop
# virt-viewer.desktop: error: required key "Name" in group "Desktop Entry" is not present

まとめ

Desktop Entry (デスクトップファイル) の書き方、配置先について書きました。 既にDesktop Entryの概要を知っている方は、#要点を中心にチェックいただけると効率的です。

Linux PC関連まとめ

endy-tech.hatenablog.jp