RHEL9での変更点(セキュリティ編:Part1 SELinuxの無効化について)

SELinuxを無効にするには「/etc/selinux/configでselinux=disabledとする」と言ったな、あれは嘘だ

こんにちは。SIOS OSSエバンジェリスト/セキュリティ担当で「絶対SELinux無効にするな」の面 和毅です。

まもなくRed Hat Enterprise 9(以下RHEL9)が出るという事で、RHEL9での主な変更点がどうなっているかが気になるところだと思います。

ここではRHEL9 betaを用いて、RHEL9での特にセキュリティ面での変更点を見ていきたいと思います(betaなので勿論リリース時に変更される可能性もありますが)。


参考にしたのは、RHEL9のリリースノートになります。

以下のような主な変更点を詳しく見ていきたいと思います(以下の項目は予定ですので必要に応じて追加・削除が発生します)。

  • SELinuxの変更
  • SCAP Contents(Compliance as Code)の変更
  • OpenSSHの変更

SELinuxの変更

SELinuxに関しては、主な変更点として

  • SELinuxで/etc/selinux/configで「selinux=disabled」が効かなくなる(ハングすることがあります)
  • パフォーマンスの向上

が挙げられています。今回は一番最初の「selinux=disabled」が使えなくなる(システムがハングすることがある)というのを見ていきたいと思います。

当たり前ですが、筆者の見解/立場ではSELinuxは無効化するべきでは無いので、無効化する前に「待て、考え直せ」とは言いたいです。

SELinuxを無効にしたときのハングアップ

まずは事象を見てみたいと思います。/etc/selinux/configで


SELINUX=enforcing

SELINUX=disabled
に設定し、再起動を行います。すると(タイミングの問題だと思いますが)下記のようにブート中にシステムがハングアップします。

10回以上試して一回成功した感じですので、タイミングの問題かもしれませんが結構発生し得ると思われます。

では何でこんな事が発生するのか、まずはSELinuxを無効にするとは何なのかから追いかけてみましょう。

なぜこういう事が起きるのかの追求(Linux Kernel)

SELinuxを無効にするには

  • ブートパラメータで「selinux=0」を指定する
  • /etc/selinux/configで「selinux=disabled」を指定して再起動する

これらの違いを確認してみます。

まず、Linux Kernelをダウンロードしてみます。


[root@rhel9b ~]# dnf download --source kernel
サブスクリプション管理リポジトリーを更新しています。
rhel-9-for-x86_64-appstream-beta-source-rpms リポジトリーの有効化
rhel-9-for-x86_64-baseos-beta-source-rpms リポジトリーの有効化
メタデータの期限切れの最終確認: 0:00:50 時間前の 2022年03月19日 10時51分54秒 に実施しました。
kernel-5.14.0-63.el9.src.rpm                    624 kB/s | 125 MB     03:24  

こちらをインストールしてLinux Kernelを見てみましょう。詳しい手順は省きますが、python3-devel, flex, bison等をインストールしてrpmbuild以下のSPECファイルで「rpmbuild -bp --target=x86_64 kernel.spec」を行うと、rpmbuild/BUILD以下にkernelソースが展開されます(Red Hatのパッチも同時に当てられます)。

以下、/disk/work/rpmbuild以下にカーネル等が展開されているとします。

SELinuxの「無効」を追いかけてみる

SELinuxのLSM部分のソースはlinux-XX/security/selinux以下にあります。


[sios@rhel9b selinux]$ ls
Kconfig   hooks.c   netif.c     netnode.c   selinuxfs.c  xfrm.c
Makefile  ibpkey.c  netlabel.c  netport.c   ss
avc.c     include   netlink.c   nlmsgtab.c  status.c

Kconfigを見てみると

  • config SECURITY_SELINUX_BOOTPARAM

    grub2などのブート時に"selinux=0"を渡すことでSELinuxを無効にする
  • config SECURITY_SELINUX_DISABLE

    selinuxfs(sys/fs/selinuxとしてマウントされる疑似ファイルシステムで、これを介してSELinuxを無効にすることが出来る)を介して/sbin/initやsystemdなどのランタイム処理からSELinuxを無効にする事が出来る

の二種類があります。これらのカーネルパラメータはRHELではデフォルトで有効になっており、両方とも使うことが可能です。

ちなみにLinux KernelではLSMの中でどのセキュリティモジュールを使用するかはやはりKernelソースのconfigで指定する必要があり、RHELでは図のようになっています。

当然、TOMOYOやAppArmorのように、SELinuxを「LSMモジュールごと選択しないでKernelに組み込まない」という選択肢もあるため、「SELinuxが無効になっている」という状態は以下の3種類になります。

  • Linux Kernelのconfigを変更してSELinuxを「元々組み込まないKernel」にする
  • Linux Kernelには組み込まれているが、ブートパラメータで「selinux=0」を指定している
  • selinuxfsを通じて、ランタイム処理でdisabledにする

「元々組み込まない」はそのままの処理、つまりLSMで組み込んでいないのでKernelに組み込まれず、SELinuxは「完全に」存在しない、という動作となります。

ブートパラメータで指定する場合と、selinuxfsでdisabledにする方法の2種類について次から見ていきましょう。

bootで「selinux=0」を指定する

SELinuxのLSM部分のソースはlinux-XX/security/selinux以下にあります。まず、「selinux=0」とした時にどの様な処理になっているかを見てみましょう。

「selinux=0/1」の値はselinux/hooks.c中で処理され、「selinux_enabled_boot」という値として扱われます。


    129 int selinux_enabled_boot __initdata = 1;
    130 #ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
    131 static int __init selinux_enabled_setup(char *str)
    132 {
    133         unsigned long enabled;
    134         if (!kstrtoul(str, 0, &enabled))
    135                 selinux_enabled_boot = enabled ? 1 : 0;
    136         return 1;
    137 }
    138 __setup("selinux=", selinux_enabled_setup);
    139 #endif

このselinux_enabled_bootは

  • sel_ib_pkey_init()
  • selinux_nf_ip_init()
  • sel_init_sel_fs()
  • sel_netif_init()
  • sel_netnode_init()
  • sel_netport_init()
でそれぞれ参照されていて、例えばhooks.c中のselinux_nf_ip_init() (netfilterのhookを登録する処理:SELinux: Registering netfilter hooks)では

static int __init selinux_nf_ip_init(void)
{
        int err;

        if (!selinux_enabled_boot)
                return 0;

        pr_debug("SELinux:  Registering netfilter hooks\n");
の様に使用されています。つまり、selinux=0を使用すると、以降のnetfilterのhookとしてSELinuxの関数をLSMの飛ばし先に登録する、という処理自体が無くなります。他にもselinuxfs.c中のinit_sel_fs() (selinuxfsの初期化を実施して疑似ファイルシステムを作成する処理)でも

static int __init init_sel_fs(void)
{
        struct qstr null_name = QSTR_INIT(NULL_FILE_NAME,
                                          sizeof(NULL_FILE_NAME)-1);
        int err;

        if (!selinux_enabled_boot)
                return 0;
	err = sysfs_create_mount_point(fs_kobj, "selinux");

と、selinuxfsを作成する事自体を行いません。

この様に、「selinux=0」をgrub2のパラメータとして引き渡すと、システムを起動した時にLinuxカーネル上での最初の処理部分でhookにも登録されず、selinuxfsも作成されなくなり、実質的にSELinuxが機能しなくなります。

selinux=0で起動すると?(initrdでのミニOS起動からsysrootのマウント前までの処理)

Linuxを起動する際には、initrdを展開したミニOSが起動してsysroot(実際の/)をroでマウントしてkernelの処理を行い、systemdがsysroot(/)をrwでRootとして再マウント・Switchして、他のプログラム(systemdや/sbin/init)からの処理を行います。

dmesgの出力や/var/log/messages等で「systemd[1]: Switching root.」というメッセージを見たことがあると思いますが、その前後の処理のことを言っています。

実際にselinux=0で起動した際に、initrdを展開したミニOSがどうなるかをdracutシェルを使用してみてみましょう。

Linux Kernelがブートする際にGrub2のメッセージでbootするカーネルを選択して"e"を押すと、オプションを指定できます。ここでrhgb quietを消して


rd.break rd.shell plymouth.enable=0 rd.plymouth=0 selinux=0
を指定して起動してみます。

shellを立ち上げる際にrootのパスワードが必要なので入力すると、dracutシェルが起動します。ここで/sys/fs/以下を見てみると、selinuxfsである/sys/fs/selinuxが存在していないことがわかります。ここで重要なのは「この時点で存在していない(mountしたものをunmountしたわけではなく作成されていない)」という点です。

つまり、"selinux=0"を指定すると、SELinuxが最初から無効になっている、ということが改めてわかりました(無効というのはhookにregistしていないという事であり、kernel自体にはSELinuxのコードは入っている状態、というのがconfigで選ばなかったときとの差分になりますが)。

/etc/selinux/configで「selinux=disabled」を指定する

次に、/etc/selinux/configで「selinux=disabled」を指定した場合の処理について見てみましょう。

この場合には、前述の"selinux_enabled_boot"が"1"になるので

  • sel_ib_pkey_init()
  • selinux_nf_ip_init()
  • sel_init_sel_fs()
  • sel_netif_init()
  • sel_netnode_init()
  • sel_netport_init()

等で、それぞれの処理が進みます。最終的にselinuxfs.c中のinit_sel_fs()で


        err = register_filesystem(&sel_fs_type);

で、selinuxfsがregistされます。selinuxfsは同じくselinuxfs.c中のsel_fill_super()で


        static const struct tree_descr selinux_files[] = {
                [SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR},
                [SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR},
                [SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO},
                [SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO},
                [SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO},
                [SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO},
                [SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO},
                [SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO},
                [SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR},
                [SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO},
                [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR},
                [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO},
                [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
                [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
                [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
                [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
                [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
                [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
                                        S_IWUGO},
                /* last one */ {""}
        };

として定義されています。ちなみにSELinux=enforcing/permissiveの際には


[root@rhel9b ~]# ls /sys/fs/selinux
access    checkreqprot          context       disable           load    null                 policyvers      ss      validatetrans
avc       class                 create        enforce           member  policy               reject_unknown  status
booleans  commit_pending_bools  deny_unknown  initial_contexts  mls     policy_capabilities  relabel         user

のような疑似ファイルがあり、それぞれを用いてユーザランドからカーネル上のSELinuxの処理とやりとりすることが出来ます。例えば


[root@rhel9b ~]# ls /sys/fs/selinux
access    checkreqprot          context       disable           load    null                 policyvers      ss      validatetrans
avc       class                 create        enforce           member  policy               reject_unknown  status
booleans  commit_pending_bools  deny_unknown  initial_contexts  mls     policy_capabilities  relabel         user
[root@rhel9b ~]# getenforce
Enforcing
[root@rhel9b ~]# echo "0" > /sys/fs/selinux/enforce
[root@rhel9b ~]# getenforce
Permissive
[root@rhel9b ~]# 

の様に、echoを用いて/sys/fs/selinux/enforceの値を0にするとSELinuxをPermissiveモードにすることが出来ます。

このselinuxfsが作成された後、Linuxカーネルの方では通常通りSELinuxの処理が進み、先述の"Switching Root"でルートが切り替わった後にsystemdがポリシをロードする処理を行います。例えばenforcingモードのときには


Mar 18 08:14:47 rhel9b systemd[1]: Switching root.
Mar 18 08:14:47 rhel9b systemd-journald[249]: Journal stopped
Mar 18 08:14:48 rhel9b systemd-journald[249]: Received SIGTERM from PID 1 (systemd).
Mar 18 08:14:48 rhel9b kernel: audit: type=1404 audit(1647558887.415:2): enforcing=1 old_enforcing=0 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=selinux res=1
Mar 18 08:14:48 rhel9b kernel: SELinux:  policy capability network_peer_controls=1
Mar 18 08:14:48 rhel9b kernel: SELinux:  policy capability open_perms=1
Mar 18 08:14:48 rhel9b kernel: SELinux:  policy capability extended_socket_class=1
Mar 18 08:14:48 rhel9b kernel: SELinux:  policy capability always_check_network=0
Mar 18 08:14:48 rhel9b kernel: SELinux:  policy capability cgroup_seclabel=1
Mar 18 08:14:48 rhel9b kernel: SELinux:  policy capability nnp_nosuid_transition=1
Mar 18 08:14:48 rhel9b kernel: SELinux:  policy capability genfs_seclabel_symlinks=0
Mar 18 08:14:48 rhel9b kernel: audit: type=1403 audit(1647558887.466:3): auid=4294967295 ses=4294967295 lsm=selinux res=1
Mar 18 08:14:48 rhel9b systemd[1]: Successfully loaded SELinux policy in 53.398ms.

のようになります。

しかし、この処理を行う際(Switching rootで/を切り替えた後)に/etc/selinux/config中に"selinux=disabled"を見つけると、selinuxfs(/sys/fs/selinux)をumountし、SELinuxが使えない状態にします。/var/log/messagesの出力だと


Mar 20 14:43:16 rhel9b kernel: SELinux:  Disabled at runtime.

の様になっているところです。この後の処理は、selinuxfs.c中のselinux_disable()からunregister_selnuxfs()を呼び出して


int selinux_disable(struct selinux_state *state)
{
--省略=-
        pr_info("SELinux:  Disabled at runtime.\n");

        /*
         * Unregister netfilter hooks.
         * Must be done before security_delete_hooks() to avoid breaking
         * runtime disable.
         */
        selinux_nf_ip_exit();

        security_delete_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks));

        /* Try to destroy the avc node cache */
        avc_disable();

        /* Unregister selinuxfs. */
        exit_sel_fs();

のように、LSMのフックからSELinuxへの飛び先を削除(SELinuxを終了)してからselinuxfsをunregistします。

つまり、システムとしては「SELinuxを有効として一旦起動しておきながら、systemd等の処理からselinux_disableを呼び出してSELinuxへのLSMフックを外しSELinuxを無効化した」という、ちょっと歪な状態になっています。

/etc/selinux/configでselinux=disabledとして起動すると?(initrdでのミニOS起動からsysrootのマウント前までの処理)

先程の"selinux=0"をbootパラメータに渡した際と同様に、dracutシェルを起動して確認してみましょう。

/etc/selinux/configで"selinux=disabled"として再起動し、grub2上で先述の手順で


rd.break rd.shell plymouth.enable=0 rd.plymouth=0

を起動してdracutシェルを起動してみます。すると

のように、dracutシェルを起動した時点(つまりrootファイルシステムを/に切り替えする前の時点)では、/sys/fs/selinuxが存在することがわかります。

処理としてはこの後、systemdがlibselinuxを用いて/etc/selinux/configを読み出し、disabledになっているためselinuxfsをアンマウントさせることになります。しかし、まれにタイミングの問題で、SELinuxポリシのロードが出来ずにハングアップしていた様です。

理由と今後

/etc/selinux/configによる無効化をやめるという話は、Fedoraプロジェクトのwiki「Changes/Remove Support For SELinux Runtime Disable」に経緯が詳しく載っています。やはり「ランタイムでSELinuxを無効化出来る様にしているのは、セキュリティ上のトレードオフになるから」というのが理由のようです。

/etc/selinux/configによる無効化(disabled)は現時点では非推奨であり、将来無くなる可能性があります。

そこで今後は、

SELinuxを無効化にしたいという場合には「bootパラメータで"selinux=0"を付けてください」

が現在の所正しい案内となっています。

この辺はRHEL9betaのChange Logでこちらにも書かれています。

次回予告

今回は/etc/selinux/configのお話をしました。次回はRHEL9上のSELinuxのパフォーマンス向上部分を見てみます。

日々のメモを更新しています。

セキュリティ関係ニュースを更新しています。個別で情報出せるようになる前の簡単な情報・リンクなんかも載せていきます。

前へ

BIND 9の脆弱性情報(Medium: CVE-2021-25220, CVE-2022-0396, High: CVE-2022-0635, CVE-2022-0667)と新バージョン(9.11.37, 9.16.27, 9.18.1 )

次へ

Linux Kernelの脆弱性(Important: CVE-2022-0995)