【Linux Kernel 5.0特集】Kernelに含まれているセキュリティ機構 - Part1(SELinux編)

こんにちは。SIOS OSSエバンジェリスト/セキュリティ担当の面 和毅です。

2019年初の話題として、次期Linux Kernelのバージョンが4.xから5.0になるという話が話題になっています。

メジャーバージョンも上がるということで、一種まとめの機会ではあると思われますので、これから数回に分けてLinux Kernel 5.0での現状のセキュリティ機構(Kernelに含まれているセキュリティ機構)を整理して見ていこうと思います。

過去にもこのような試みを、雑誌媒体を中心としてLSMでやったことが有りますが、最終的に纏まった形で出たのは2008年前後だったと記憶しています。それからちょうど10年という所で、改めて振り返るにはいい機会になるのではないかと思います。




連載の構成

本連載では、Linux Kernelのセキュリティについて、下記のような順番で紹介したいと思います。

  1. LSM(Linux Security Module)の2019での情報まとめ
    1. SELinux
    2. AppArmor
    3. Yama
    4. IMA
    5. TOMOYO
    6. SMACK
    7. その他(loadpin, Hardened-UserCopy, その他)
  2. ケーパビリティ(Capability)について
    1. ケーパビリティについて、及び使い方(2019年版)
  3. seccompについて
    1. seccompについて、及び使い方
    2. BPFについて、及び使い方
  4. Kernel Self-Protectionについて

第一回(SELinux編)

SELinuxに関しては、ほぼ知らない方はいらっしゃらないかと思いますので詳しい説明は>省略します(概要及び2017年時点での情報に関しては、こちらの日経Linuxの記事を参照してください。以降、この記事では「既に基本的な(CentOS7.x時代の)SELinuxを理解している」という前提で進めていきます。

サンプルで使用するには、やはりFedoraの最新バージョンを使うのが一番簡単です。今回は(執筆時点の01/09/2019では)Fedora29を使用します。

以降、Fedora29をインストールし、パッケージは(01/09/2019時点での)最新バージョンにしている状態を想定して説明していきます。ここでは、日経Linuxの記事の話を踏まえて

  1. 2019年時点での差分(Fedora29で)
  2. CILに関してもう少し
  3. 2019年時点での差分

について説明していきます。

2019年時点でのSELinuxの差分(Fedora29で)と簡単な振返り

  1. バイナリーポリシーファイル
  2. ブーリアン(Boolean)
  3. SELinux CIL
  4. Container対応
  5. Stackable LSM

  1. バイナリーポリシーファイル
  2. こちらの日経の記事にもあるようにSELinuxのポリシーファイルは通常「バイナリーポリシーファイル」としてOSに入っています。Fedora29でのバイナリーポリシーファイルのバージョンは、/etc/selinux/targetd/policy以下に格納されており

    
    [root@localhost policy]# ls -l /etc/selinux/targeted/policy/
    合計 8164
    -rw-r--r--. 1 root root 8358902  1月  9 00:21 policy.31
    [root@localhost policy]# 
    

    と、バイナリポリシーのバージョンは31であることがわかります。

  3. ブーリアン(Boolean)
  4. SELinuxでのブーリアンは、ランタイムでSELinuxポリシーの一部を変更できるようになっているものです。その実態は、SELinuxポリシー中の各モジュール(ポリシは各サービスごとにモジュール化されています)で、

    
    httpd.te)
    policy_module(apache, 2.7.2)
    
    ########################################
    #
    # Declarations
    #
    
    ---省略--
    
    selinux_genbool(httpd_bool_t)
    
    gen_tunable(httpd_anon_write, false)
    
    ---省略--
    

    のようにマクロ"gen_tunable"/"tunable_policy"のセットでブール代数で定義されているものになり、その値(true/false)によってロードされているポリシ中の条件が有効・無効になるというものです。例えば、httpd_can_connect_zabbix(httpdがzabbixに接続できるようにする)に関しては、httpd.te中で

    
    ---省略--
    gen_tunable(httpd_can_connect_zabbix, false)
    ---省略--
    tunable_policy(`httpd_can_connect_zabbix',`
            corenet_tcp_connect_zabbix_port(httpd_t)
    ')
    

    のように、httpd_can_connect_zabbix(デフォルトではfalse)で、"true(zabbixへの接続を許す)"にした場合には、httpd_tドメイン(apacheのドメイン)がzabbixの使用しているポートに接続することを許可するマクロ(corenet_tcp_connect_zabbix_port)が反映されるようになります。

    Fedora29では、ブーリアンで修正できるものはこちらにあるように325種類になります。そのため、SELinux自体を無効にしてセキュリティの強度を下げること無く、特定の条件を許可するようにポリシを動的にカスタマイズすることが出来ます。

    実際にFedora29でZabbix Serverをインストールした際の状況とブーリアンの使い方を見てみましょう。Fedora29に標準のパッケージでZabbix ServerをセットアップするとSELinuxが有効の状態でZabbix Serverが動作しますが、WebUIのDashboardを見るとZabbix Serverが動作しているにも関わらず、"Zabbix Server is running"が"No"になっているのがわかります(下図参照)。これは、httpd_tがzabbix_tドメインで動作しているZabbix Serverから情報を取ってこれないためです。

    そこで、下記のように前述の"httpd_can_connect_zabbix"のブーリアンを有効にします。

    
    [root@localhost ~]# semanage boolean -l |grep -i zabbix
    httpd_can_connect_zabbix       (オフ   ,   オフ)  Allow http daemon to connect to zabbix
    zabbix_can_network             (オフ   ,   オフ)  Determine whether zabbix can connect to all TCP ports
    zabbix_run_sudo                (オフ   ,   オフ)  Allow Zabbix to run su/sudo.
    [root@localhost ~]# semanage boolean --modify --on httpd_can_connect_zabbix
    [root@localhost ~]# semanage boolean -l |grep -i zabbix
    httpd_can_connect_zabbix       (オン   ,   オン)  Allow http daemon to connect to zabbix
    zabbix_can_network             (オフ   ,   オフ)  Determine whether zabbix can connect to all TCP ports
    zabbix_run_sudo                (オフ   ,   オフ)  Allow Zabbix to run su/sudo.
    

    そうすると、システムを再起動したりすることなく、ポリシの動きが動的に変わり、httpd_tがzabbix_tドメインにアクセスできるようになるため、ダッシュボードで"Zabbix server is running"が"Yes"になることがわかります(下図参照)。

  5. CIL
  6. CILに関しては、改めて説明する必要もないと思います。詳しくはSELinuxのCIL (Part3) — | サイオスOSS | サイオステクノロジーを確認してください。

    また、CILのリファレンスガイドを移行しました。こちらの「OSSセキュリティ技術の会(Secure OSS Sig)」の「SELinuxのCILリファレンスガイド」を参照してください。

    1. 下記のようなサンプルプログラム(send_config.c)をsend_configとしてコンパイルし、/opt/test_dir/bin以下に置きます。
      
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <arpa/inet.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <errno.h>
      #include <string.h>
      #include <sys/types.h>
      #include <time.h> 
      
      int main(int argc, char *argv[])
      {
          int listenfd = 0, connfd = 0;
          struct sockaddr_in serv_addr; 
      
          char sendBuff[1025];
          time_t ticks; 
          FILE *fpin;
          char s[256];
      
          listenfd = socket(AF_INET, SOCK_STREAM, 0);
          memset(&serv_addr, '0', sizeof(serv_addr));
          memset(sendBuff, '0', sizeof(sendBuff)); 
      
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
          serv_addr.sin_port = htons(80); 
      
          bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 
      
          listen(listenfd, 10); 
      
          while(1)
          {
              connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 
      
              ticks = time(NULL);
              snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks));
              write(connfd, sendBuff, strlen(sendBuff)); 
      
      	if ((fpin = fopen("/opt/etc_test/df_h.txt","r")) == NULL) {
                      printf("Input file open error!!\n");
              exit(1);
              }
      
      	while (fgets(s, 256, fpin) != NULL) {
      		snprintf(sendBuff, sizeof(sendBuff), "%s", s);
      		write(connfd, sendBuff, strlen(sendBuff)); 
      	}
      	fclose(fpin);
      
      	if ((fpin = fopen("/opt/etc_test/ip_addr.txt","r")) == NULL) {
                      printf("Input file open error!!\n");
              exit(1);
              }
      
      	while (fgets(s, 256, fpin) != NULL) {
      		snprintf(sendBuff, sizeof(sendBuff), "%s", s);
      		write(connfd, sendBuff, strlen(sendBuff)); 
      	}
      
      
      	fclose(fpin);
              close(connfd);
              sleep(1);
           }
      }
      
    2. systemdで動作するように、下記のようなファイルを/etc/systemd/system/send_config.serviceとして登録する。
      
      [Unit]
      Description = Send Config to remote
      
      [Service]
      ExecStart = /opt/test_dir/bin/send_config
      Restart = always
      Type = simple
      
      [Install]
      WantedBy = multi-user.target
      
      
    3. systemdに登録されているか、下記のコマンドで確認し、登録されていたら有効(enabled)にする。
      
      [root@localhost ~]# systemctl list-unit-files --type=service|grep send_config
      send_config.service                         disabled       
      [root@localhost ~]# systemctl enable send_config
      Created symlink /etc/systemd/system/multi-user.target.wants/send_config.service → /etc/systemd/system/send_config.service.
      [root@localhost ~]# systemctl list-unit-files --type=service|grep send_config
      send_config.service                         enabled  
      
    4. systemctlコマンドで起動できること、そして起動時のドメイン(unconfined_service_t)を確認する。
      
      [root@localhost ~]# systemctl start send_config
      [root@localhost ~]# ps axZ|grep send_config
      system_u:system_r:unconfined_service_t:s0 3094 ? S     0:00 /opt/test_dir/bin/send_config
      
    5. 以下のようなドメインを定義する方針にする
      • プロセス(send_config)のドメイン:sendconfig_t
      • プロセスを起動するためのプログラムのType:sendconfig_exec_t
      • プログラムのコンテキスト:system_u:object_r:sendconfig_exec_t
    6. /root/customized_policies/sendconfig.cilとして、下記のようなポリシを作成する。
      
      ; Declare a sendconfig related type
      (type sendconfig_t)
      (type sendconfig_exec_t)
      
      ; Assign the type to the object_r role
      ;(roletype object_r sendconfig_t)
      (roletype system_r sendconfig_t)
      (roletype object_r sendconfig_exec_t)
      
      ; Assign the right set of attributes to the port
      (typeattributeset entry_type sendconfig_exec_t)
      (typeattributeset exec_type sendconfig_exec_t)
      (typeattributeset file_type sendconfig_exec_t)
      (typeattributeset non_security_file_type sendconfig_exec_t)
      (typeattributeset non_auth_file_type sendconfig_exec_t)
      
      ; Assign type entry point
      (allow sendconfig_t sendconfig_exec_t (file (entrypoint)))
      (allow sendconfig_t sendconfig_exec_t (file (ioctl read getattr lock execute execute_no_trans open map)))
      (typetransition initrc_domain sendconfig_exec_t process sendconfig_t)
      
      ; Added for running sendconfig through systemctl
      (allow init_t sendconfig_exec_t (file (read execute execute_no_trans open)))
      (allow init_t sendconfig_exec_t (file (read execute open)))
      (allow init_t sendconfig_t (process (transition rlimitinh signal siginh signull sigkill noatsecure)))
      (allow init_t sendconfig_t (dir (search)))
      (allow init_t sendconfig_t (file (open read getattr)))
      (allow sendconfig_t init_t (fd (use)))
      (allow sendconfig_t init_t (unix_stream_socket (getattr read write)))
      (allow sendconfig_t init_t (process (sigchld)))
      (allow sendconfig_t null_device_t (chr_file (read)))
      (allow sendconfig_t ld_so_t (file (read execute map)))
      (allow sendconfig_t ld_so_cache_t (file (read open getattr map)))
      (allow sendconfig_t etc_t (dir (search)))
      (allow sendconfig_t etc_t (file (open read getattr)))
      (allow sendconfig_t proc_t (dir (search)))
      (allow sendconfig_t proc_t (file (open read getattr)))
      (allow sendconfig_t usr_t (dir (search)))
      (allow sendconfig_t usr_t (file (read open)))
      (allow sendconfig_t lib_t (lnk_file (read)))
      (allow sendconfig_t lib_t (dir (getattr search)))
      (allow sendconfig_t lib_t (file (read open getattr execute map)))
      (allow sendconfig_t locale_t (lnk_file (read)))
      (allow sendconfig_t locale_t (dir (search)))
      (allow sendconfig_t self (tcp_socket (create bind listen accept write)))
      (allow sendconfig_t unreserved_port_t (tcp_socket (name_bind)))
      (allow sendconfig_t node_t (tcp_socket (node_bind)))
      (allow sendconfig_t root_t (dir (search)))
      (allow syslogd_t sendconfig_t (dir (search)))
      (allow syslogd_t sendconfig_t (file (open read getattr)))
      (allow unconfined_t sendconfig_t (dir (open read search getattr)))
      (allow unconfined_t sendconfig_t (file (open read)))
      (allow unconfined_t sendconfig_t (lnk_file (read)))
      (allow unconfined_t sendconfig_t (process (getattr)))
      
      ; Misc
      (allow svc_run_t sendconfig_exec_t (file (read getattr execute open)))
      (allow svc_run_t sendconfig_t (process (transition)))
      (typetransition svc_run_t sendconfig_exec_t process sendconfig_t)
      
      (filecon "/opt/test_dir/bin/send_config" file (system_u object_r sendconfig_exec_t ((s0) (s0))))
      
      Note: このとき、"sendconfig.cil"と必ず拡張子を付けてください。この拡張子部分でポリシの種類を指定します(cil, pp等)。拡張子を付けていないと、下記のようなエラーになります。
      
      [root@localhost customized_policies]# semodule -i sendconfig 
      libsemanage.semanage_direct_install_file: Module does not have a valid extension. (No such file or directory).
      semodule:  Failed on sendconfig!
      
    7. /opt/test_dir/bin/send_configファイルのコンテキストを"system_u:object_r:sendconfig_exec_t:s0"につけ直します。
      
      [root@localhost customized_policies]# chcon system_u:object_r:sendconfig_exec_t:s0 /opt/test_dir/bin/send_config 
      [root@localhost customized_policies]# ls -lZ /opt/test_dir/bin/send_config 
      -rwxr-xr-x. 1 root root system_u:object_r:sendconfig_exec_t:s0 19224  1月 12 08:43 /opt/test_dir/bin/send_config
      
    8. send_configサービスを起動して確認します。
      
      [root@localhost customized_policies]# ps axZ|grep sendconfig
      system_u:system_r:sendconfig_t:s0 3882 ?        Ss     0:00 /opt/test_dir/bin/send_config
      [root@localhost customized_policies]# 
      
    9. 外部からきちんと接続(65000ポートにtelnetでアクセスして情報が出力されるか)を確認します。
      
      sios@external:~/SIOS$ telnet 172.16.148.139 65000
      Trying 172.16.148.139...
      Connected to 172.16.148.139.
      Escape character is '^]'.
      Sat Jan 12 01:14:34 2019
      Filesystem               Size  Used Avail Use% Mounted on
      devtmpfs                 991M     0  991M   0% /dev
      tmpfs                   1001M     0 1001M   0% /dev/shm
      tmpfs                   1001M  500K 1001M   1% /run
      tmpfs                   1001M     0 1001M   0% /sys/fs/cgroup
      /dev/mapper/fedora-root   28G  1.9G   26G   7% /
      tmpfs                   1001M     0 1001M   0% /tmp
      /dev/sda1                477M  121M  327M  28% /boot
      tmpfs                    201M     0  201M   0% /run/user/0
      tmpfs                    201M     0  201M   0% /run/user/1000
      1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default 
          link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
          inet 127.0.0.1/8 scope host lo
             valid_lft forever preferred_lft forever
          inet6 ::1/128 scope host 
             valid_lft forever preferred_lft forever
      2: enp0s3:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
          link/ether 08:00:27:db:a7:d2 brd ff:ff:ff:ff:ff:ff
          inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
             valid_lft 83579sec preferred_lft 83579sec
          inet6 fe80::a00:27ff:fedb:a7d2/64 scope link 
             valid_lft forever preferred_lft forever
      3: enp0s8:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
          link/ether 08:00:27:21:24:fc brd ff:ff:ff:ff:ff:ff
          inet 192.168.56.102/24 brd 192.168.56.255 scope global dynamic enp0s8
             valid_lft 953sec preferred_lft 953sec
          inet6 fe80::a00:27ff:fe21:24fc/64 scope link 
             valid_lft forever preferred_lft forever
      Connection closed by foreign host.
      

[次回に続きます]

切りが良いですので、一旦ここで第一回を終わりにします。次回はSELinuxのコンテナ対応と、Kubernetesでのサポートについての説明を行います。


前へ

Modsecurity (3.0) + Elasticsearch + Kibanaでログを可視化する

次へ

"syzbot"と"syzkaller"について (Part 2) 「Azure上でsyzkallerは動くのか」