quinnwencn / blog

Apache License 2.0
0 stars 0 forks source link

[SELinux] 01 Warming up with SELinux #14

Open quinnwencn opened 8 months ago

quinnwencn commented 8 months ago

1. 背景

在具有操作系统的环境中,访问控制是必备的一个元素,对维护系统的安全性和稳定性起着至关重要的作用。访问控制可以为操作系统提供:

  1. 资源保护:操作系统管理计算机系统的各种资源,包括CPU、内存、文件、网络等。访问控制限制对这些资源的访问,防止未经授权或不当使用。例如,它阻止用户访问其他用户的个人文件或修改核心系统功能。
  2. 数据安全:操作系统管理用户和应用程序的数据。访问控制可保护数据免遭未经授权的访问、修改或删除。用户有权访问自己的数据,其他用户无法访问该数据。这有助于防止敏感信息泄露。
  3. 权限管理:访问控制有助于为用户分配适当的权限。操作系统识别用户并授予权限以确定用户是否可以执行特定任务。这允许用户执行必要的任务,同时限制对敏感操作的访问。例如,只有具有管理员权限的用户才能更改系统设置或安装软件。
  4. 防止误用:访问控制有助于防止系统误用。用户只能访问其预期任务所需的内容,并且无法执行恶意操作。这保证了系统的稳定性和可靠性。
  5. 审计跟踪:访问控制提供跟踪与系统使用相关的活动的能力。这使得系统管理员能够跟踪谁在何时做了什么,从而使他们能够在必要时解决任何问题。

    2. 访问控制模型

    目前,有多种访问控制模型和策略来加强安全性和管理对资源的访问,常用的主要有:自主访问控制(Discretionary Access Control, DAC)、强制访问控制(Mandatory Access Control, MAC)、基于角色的访问控制(Role-Based Access Control, RBAC)和基于属性的访问控制(Attribute-Based Access Control, ABAC)。

    2.1 DAC

    自主访问控制目前是Linux的默认的访问控制模型,也是一种灵活的访问控制模型。它将访问权限的控制权交由对象的所有者自行决定对它的访问权限。所有者将访问权限划分为读、写和执行三种,同时将访问它的对象划分为所有者,组和其他三种,通过这两种方式的结合,得出一个对象的访问控制: image

但这种访问控制模型在很大程度上依赖于对象的所有者的责任和其他访问对象的诚信上,并不是一个很理想的访问控制模型。

2.2 MAC

MAC是高安全环境中使用的严格访问控制模型,它根据预定义的安全策略,将进程划分为不同的主体,将资源划分为客体,并为主体访问客体制定了不同的标签和敏感级别,访问决策根据主体、客体以及不同的标签来做出访问策略,主体只能访问等于或者低于其指定的级别的对象。 主体、客体和控制策略需要满足基本的安全策略:

一个进程要访问一个资源,会调用一个系统调用,由用户态陷入内核态,在做一些基础检查且通过后,会先检查Linux的DAC规则,如果此次访问不符合DAC的访问控制策略,就会直接返回;只有通过了DAC访问控制策略后,才会由LSM的钩子调用SELinux访问控制策略做检查。因此使能SELinux的系统,并不是说SELinux将代替DAC对访问进行决策。 SELinux有三个工作模式:

SELinux还定义了SELinux的工作策略:

SELinux的核心元素可以由上图给出:

3.2.2 客体(Object)

SELinux中的主体(Subject)只能是进程,但是客体有多重可能,它可以是一个进程,也可以是文件等。但是每一个客体都包含了一个类别标识(class identifier),这个类别标签标识这个客体是一个文件、套接字或者其他。这个类别标识还带有权限集合,SELinux中称为访问向量(Access Vector, AV),访问向量描述了这个客体可以执行的动作(读、写或者发送等),同时,如上所述,客体还包含了安全上下文。我们以SELinux的配置文件举例:/etc/selinux/config 首先他是一个文件,因此类别标识为:file。安全上下文为:system_u:object_t:selinux_config_t,访问向量:read, write, append。 这里举例一条规则,允许unconfined_t类型的进程对selinux_config_t类型的file类别的客体进行读取和写:

alow unconfined_t selinux_config_t : file {read write}

这是一个allow规则,他的规则定义如下:

allow Rule | source_type | target_type : class | permission
### 3.2.3 SELinux的rule
SELinux中的rule共有四种:
* allow:这个规则检查请求的访问在source_domain和target_type之间是否允许,以及class的permission是否也允许,允许则该次请求访问控制被放行
* allowaudit:默认情况下SELinux只记录访问请求失败的操作,如果使用allowaudit定义规则,则该规则下允许的访问操作也会被记录,但是<strong>这个规则只记录,并不实际赋予权限
* dontaudit: 对检查失败的访问请求也不记录日志
* neverallow:这个规则用来表明绝不允许某个操作,即使这个操作已经用allow声明过,可以理解为对已经设置的规则再检查一次,是否有不允许的规则,并且会覆盖已经设置的规则
这四条规则的定义都是一样的:

rule_name | source_type | target_type : class | permission

1. allow

allow kernel_t filesystem_t : filesystem mount;

这条规则定义了一条允许kernel_t类型的主体对客体属于filesystem_t类型的filesystem类别客体执行mount操作。
2. dontaudit 

traceroute_t {port_type -port_t}:tcp_socket name_bind;

这条规则标识:当主体类型为traceroute_t的进程被拒绝对一个除了port_t类型以外的所有port_type类型的tcp_socket执行name_bind操作时,不要记录日志,这里如果tcp_socket是port_t类型,还是会记录日志的。
3. allowaudit

auditallow ada_t self:process execstack; allow ada_t self:process execstack;

这条规则表示:当ada_t类型的进程在栈上执行指令时,记录日志,第二条是运行在ada_t类型的进程在栈上执行指令
4. neverallow

allow appdomain test_data_t:dir search; neverallow app1 test_data_t:dir search;

在这条规则之前,我们定义了一个宏appdomain,这个宏表示所有的app进程。那么这条规则的意思是:首先我们允许所有app进程在test_data_t目录下进行查找操作,但是由于第二条不允许app1对test_data_t目录进行查找,因此整个规则就是允许除了app1外的所有app对test_data_t目录执行查找操作。这条命令也可以替换为:
```allow {appdomain -app1} test_data_t:dir search;

4. How to enable SELinux on ubuntu 20.04

为了在Ubuntu20.04上使用SELinux,首先需要安装依赖:

sudo apt install policycoreutils selinux-utils selinux-basics auditd rpm

安装完依赖后,便可以激活SELinux:

激活后,需要重启才能启用SELinux,启动后可以看到系统在对资源打标签,也就是安全上下文的计算: image

启动后,便可以看到所有文件都被打上了标签: image

进程也都被打上了标签: image

4.1 规则设计

系统的规则位于/etc/selinux/default文件夹中,所有规则会被编译存储于/etc/selinux/default/policy的二进制文件中,用户不能通过修改二进制的方式来增加规则。如果用户要增加规则,需要通过其他方式进行,这里以新增一个app的方式,讲述如何为系统的进程编写规则。

  1. 编写进程代码,并编译:
    
    // selinux_app.cpp
    #include &lt;unistd.h&gt;
    #include &lt;cstdio&gt;
    #include &lt;thread&gt;
    #include &lt;chrono&gt;
    #include &lt;string&gt;

int main(int argc, char *argv) { std::string file { "/var/log/messages" }; FILE f { nullptr };

while (1) {
    f = fopen(file.c_str(), "w");
    if (f == nullptr) {
        std::printf("open file error\n");
        exit(1);
    }
    std::printf("Wait for another turn!\n");
    std::this_thread::sleep_for(std::chrono::seconds(5));
    fclose(f);

}

return 0; }

2. 编译代码:
```bash
g++ selinux_app.cpp -lpthread -o selinux_app
  1. 配置规则前的安全上下文
    ls -Z selinux_app

    output:

    system_u:object_r:user_home_t:s0 selinux_app

    然后执行selinux_app,再查看selinux是否有告警(此时应保证/var/log/messages存在,且selinux_app有权限访问,另外,当前的selinux的模式为permissive)

    
    ./selinux_app
    $ sudo ausearch -m AVC -ts recent

... type=AVC msg=audit(1699435224.165:573): avc: denied { execmem } for pid=1745 comm="gjs" scontext=system_u:system_r:system_dbusd_t:s0-s0:c0.c1023 tcontext=system_u:system_r:system_dbusd_t:s0-s0:c0.c1023 tclass=process permissive=1

time->Wed Nov 8 01:20:31 2023 type=AVC msg=audit(1699435231.529:575): avc: denied { search } for pid=386 comm="systemd-journal" name="user" dev="tmpfs" ino=1023 scontext=system_u:system_r:syslogd_t:s0 tcontext=system_u:object_r:user_runtime_root_t:s0 tclass=dir permissive=1

可见当前并没有拦截selinux_app对``/var/log/messages`的访问
4. 对selinux_app分配类型强制(Type Enforcement)
```bash
$mkdir rules &amp; cd rules
$sudo sepolicy generate --init ../selinux_app

执行后,可以发现生成了几个文件:

├── selinux_app.fc
├── selinux_app.if
├── selinux_app_selinux.spec
├── selinux_app.sh
└── selinux_app.te

其中,te文件是类型强制文件,里面描述了对selinux_app的类型设置;if文件为接口文件;fc描述了是文件上下文文件;sh脚本是要运行进行规则生成的:

./selinux_app.sh
  1. SELinux生效
    system_u:object_r:selinux_app_exec_t:s0 selinux_app

    此时再执行selinux_app,可以看到审计日志里有了记录:

    
    ./selinux_app 

sudo ausearch -m AVC -ts recent

type=AVC msg=audit(1699435247.640:590): avc: denied { open } for pid=1977 comm="selinux_app" path="/var/log/messages" dev="sda5" ino=4336677 scontext=system_u:system_r:selinux_app_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=file permissive=1 type=AVC msg=audit(1699435247.640:590): avc: denied { write } for pid=1977 comm="selinux_app" name="messages" dev="sda5" ino=4336677 scontext=system_u:system_r:selinux_app_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=file permissive=1 type=AVC msg=audit(1699435247.640:590): avc: denied { search } for pid=1977 comm="selinux_app" name="log" dev="sda5" ino=4325384 scontext=system_u:system_r:selinux_app_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=dir permissive=1

原来可以访问的日志,开始因为SELinux规则而被记录日志,因为是permissive模式,因此没有报错,而是记录审计报告,原因是我们没有设置selinux_app_t 对`system_u:object_r:var_log_t:s0 /var/log/messages`的访问运行。
```bash
ps -efZ | grep seinux_app
system_u:system_r:selinux_app_t:s0 kane     2915    1946  0 01:35 pts/0    00:00:00 ./selinux_app
  1. 设置允许规则 首先查看文件夹/var, /var/log/和文件`/var/log/messages``的安全上下文:

    ls -Z /
    system_u:object_r:var_t:s0 var
    
    ls -Z /var/
    system_u:object_r:var_log_t:s0 log
    
    ls -Z /var/log/messages
    system_u:object_r:var_log_t:s0 messages

    因此,要确保selinux_app_t对var_t、var_log_t文件夹和var_log_t文件有读写权限,由于打开文件还需要ioctl的权限,因此,要补充:

    allow selinux_app_t var_t:dir {open search getattr};
    allow selinux_app_t var_log_t:dir {getattr search open read lock ioctl};
    allow selinux_app_t var_log_t:file { open {getattr write append lock ioctl} };

    但是,由于selinux_app.te中没有声明var_t和var_log_t类型,因此需要在文件开始处增加声明:

    type var_t;
    type var_log_t;

    最终的selinux_app.te的增量为:

    
    policy_module(selinux_app, 1.0.0)

######################################## #

Declarations

#

type selinux_app_t; type selinux_app_exec_t; +type var_t; +type var_log_t; init_daemon_domain(selinux_app_t, selinux_app_exec_t)

permissive selinux_app_t;

######################################## #

selinux_app local policy

# allow selinux_app_t self:fifo_file rw_fifo_file_perms; allow selinux_app_t self:unix_stream_socket create_stream_socket_perms;

+allow selinux_app_t var_t:dir {open search getattr}; +allow selinux_app_t var_log_t:dir {getattr search open read lock ioctl}; +allow selinux_app_t var_log_t:file { open {getattr write append lock ioctl} };

domain_use_interactive_fds(selinux_app_t)

files_read_etc_files(selinux_app_t)

miscfiles_read_localization(selinux_app_t)

编译规则:
```bash
sudo ./selinux_app.sh

再访问:

./selinux_app

此时除了系统的一些告警外,没有任何关于selinux_app的告警日志记录了:

time->Wed Nov  8 02:03:10 2023
type=PROCTITLE msg=audit(1699437790.532:916): proctitle="/usr/bin/gnome-shell"
type=SYSCALL msg=audit(1699437790.532:916): arch=c000003e syscall=10 success=yes exit=0 a0=3fc693a44000 a1=c000 a2=5 a3=0 items=0 ppid=1411 pid=1645 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=(none) ses=3 comm="gnome-shell" exe="/usr/bin/gnome-shell" subj=system_u:system_r:init_t:s0 key=(null)
type=AVC msg=audit(1699437790.532:916): avc:  denied  { execmem } for  pid=1645 comm="gnome-shell" scontext=system_u:system_r:init_t:s0 tcontext=system_u:system_r:init_t:s0 tclass=process permissive=1

5. SELinux and Android Automotive

当前的智能汽车中,座舱域控制器为座舱提供了许多高阶娱乐功能,例如:座舱游戏、座舱音乐、座舱电影等,这些功能的引入也对座舱的基础设施提出了更高的要求。安卓系统中引入了Android Automotive的概念,借助各种总线拓扑,很多汽车子系统都可以实现互连以及与车载信息娱乐 (IVI) 系统的连接。不同的制造商提供的确切总线类型和协议之间有很大差异(甚至同一品牌的不同车型之间也是如此),例如控制器局域网 (CAN) 总线、区域互连网路 (LIN) 总线、面向媒体的系统传输 (MOST) 总线以及汽车级以太网和 TCP/IP 网络(如 BroadR-Reach)。

Android Automotive 的硬件抽象层 (HAL) 为 Android 框架提供了一致的接口(无需考虑物理传输层)。此车载 HAL 是开发 Android Automotive 实现的接口。

系统集成商可以将特定于功能的平台 HAL 接口(如 HVAC)与特定于技术的网络接口(如 CAN 总线)连接,以实现车载 HAL 模块。典型的实现可能包括运行专有实时操作系统 (RTOS) 的专用微控制器单元 (MCU),该微控制器单元用于 CAN 总线访问或类似操作,可通过串行链路连接到运行 Android Automotive 的 CPU。除了专用 MCU,还可以将总线访问作为虚拟 CPU 来实现。只要实现符合车载 HAL 的接口要求,每个合作伙伴都可以选择适合硬件的架构。

但是,座舱功能的增加同时也增加了座舱的风险暴露面,尤其是Android系统可能会安装第三方应用,甚至是未授权安装的应用,如何限制第三方应用和未授权应用对车辆控制系统的访问,是一个亟需解决的问题。因此Android Automotive的车载适配层HAL得以提出,车载HAL是汽车和车辆网络服务之间的一些接口定义: image

其中,CAR Service是通过CAR API的方式为OEM APPs提供服务的,因此,如何禁止第三方应用或者未授权应用访问车辆控制系统的问题就缩小到如何限制第三方应用或者未授权应用访问CAR API的问题。Android系统提供的应用权限功能首先提供了一层保护。

5.1 安卓应用权限

应用APK中包含了一个应用权限申请的manifest文件,该文件中详细描述了该应用所要使用到的权限,可以通过这个接口定义的方式强制要求CAR API对应的功能只有拥有特定的权限的应用才允许调用:

<permission-group
 android:name=”android.support.car.permission.CAR_MONITORING />;

<permission
 android:name=”android.support.car.permission.CAR_MILEAGE”
 android:protectionLevel=”signature|privileged” />;
<permission
 android:name=”android.support.car.permission.CAR_SPEED”
 android:permissionGroup=”android.permission-group.LOCATION”
 android:protectionLevel=”dangerous” />;
<permission
 android:name=”android.support.car.permission.CAR_VENDOR_EXTENSION”
 android:permissionGroup=”android.support.car.permission.CAR_INFORMATION”
 android:protectionLevel=”signature|privileged” />;

当某个应用调用对应的API时,通过AIDL封装的接口能获取进程的PID和permission,再由binder模块检查对应的权限是否满足,以此来达到权限控制的目的:

public IBinder onBind(Intent t) {
        int check = checkCallingPermission("com.example.zs.ipcdemo.permission.ACCESS_BOOK_SERVIC"); 
        if (check == PackageManager.PERMISSION_DENIED) {
             return null;
        } 
        return mBinder;
}

这虽然能达到控制权限的目的,但是由于权限是否需要是由APK自行定义,控制权并不在系统中,因此这个方案必须和证书绑定,才能发挥作用,且自由度较低。

5.2 SELinux

由于CAR SERVICE都是通过Android调用对应的硬件驱动去发送控制报文(例如CAN报文、LIN报文等)来实现控制车辆(例如打开空调、打开车门等)的目的,因此可以通过SELinux的策略配置,只允许特定的系统进程才允许对这些驱动的访问,从而从系统上禁止第三方应用或者未授权应用对CAR SERVICE的未授权访问。 在安卓系统中,使能SELinux后,会在system/sepolicy中生成SELinux的规则,然后整合到SELinux内核策略,并覆盖上游的Android操作系统。但是,用户如果需要自定义SEPolicy,却无法通过修改system/sepolicy的方式进行,Android提供了另外的机制给用户自行定义SELInux规则。用户自行定义的SELinux规则位于/device/manufacturer/device-name/sepolicy目录中,在Android8.0以及更高的版本中,对这个目录的SELinux规则的修改,只会影响供应商目录的政策,这个举措使得系统默认SELinux规则和供应商的规则独立,使得系统隔离更彻底。 image 因此,SELinux在Android Automotive上的应用,主要是控制不同APP对车辆接口的访问,即限制APP对CAN INTERFACE、ETH INTERFACE、LIN INTERFACE和MOST INTERFACE的访问。

6. Reference

  1. https://source.android.com/docs/devices/automotive?hl=zh-cn
  2. https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/using_selinux/getting-started-with-selinux_using-selinux
  3. SELinux_Notebook.pdf