解释器类应用程序安全防护

背景介绍

业界主要使用Linux IMA机制对系统运行的程序发起完整性检查,一方面可以检测文件是否被篡改,另一方面通过IMA的白名单机制可以保证只有经过认证(如签名或HMAC)的文件才可以被运行。

Linux IMA机制支持对通过read()、exec()、mmap()等系统调用访问的文件进行完整性度量/评估。尽管从功能上而言,IMA能够配置对解释器运行脚本所采用的read()系统调用的完整性保护功能,但是在实际场景中,IMA摘要列表主要配置为针对exec()、mmap()进行拦截,而无法有效拦截未授权的恶意脚本执行。原因IMA无法将脚本文件和其他可变的数据文件进行区分。一旦针对read()系统调用配置完整性保护,则会将其他可变的配置文件、临时文件、数据文件等纳入保护范围,而这些文件无法预先生成基线或认证凭据,从而导致完整性检查失败。因此实际场景无法配置基于read()的度量/评估策略,无法针对性地实现拦截和保护:

执行方式实际应用场景下能否通过IMA保护
./test.sh
bash test.sh

特性介绍

本特性旨在通过内核系统调用保证通过执行方式运行脚本文件(如./test.sh)和通过解释器运行脚本文件(bash ./test.sh)具备相同的权限检查流程,具体说明如下:

execveat()支持AT_CHECK检查参数

execveat()是于Linux 3.19/glibc 2.34版本开始支持的系统调用函数,该函数允许传入一个已打开的文件描述符并执行该文件。本特性针对execveat()系统调用扩展AT_CHECK检查机制,实现对某个文件是否可执行进行检查操作,而不真正地执行该文件。

当调用者通过execveat()系统调用函数,传入目标文件描述符并指定AT_CHECK参数时,在内核的系统调用执行逻辑中,首先针对传入的文件描述符进行权限检查,该流程与普通的文件执行流程一致,包含文件的DAC权限位、LSM访问控制规则、IMA等检查,如果检查不通过则退出并返回错误码-EACCSS。直到所有权限检查流程完成后,execveat()系统调用判断参数是否包含AT_CHECK,如果包含,则不执行后续的执行流程,退出并返回0,表示该文件的可执行权限检查通过。

解释器支持调用execveat对程序进行权限检查

在支持基于execveat()的AT_CHECK检查机制的基础上,当解释器打开待运行的脚本后,可主动调用execveat()系统调用函数发起对文件进行可执行权限检查,只有当权限检查成功后,才可继续运行脚本文件。

接口介绍

系统调用接口说明

execveat()系统调用的函数类型为:

int execveat(int dirfd, const char *pathname,
             char *const _Nullable argv[],
             char *const _Nullable envp[],
             int flags);

本特性涉及新增flags参数AT_CHECK,说明如下:

参数取值说明
AT_CHECK0x10000对目标文件进行可执行权限检查,而不真正地执行该文件

内核启动参数说明

本特性支持如下内核启动参数:

参数取值说明
exec_check.bash=0/1默认为0,设置为1时,bash解释器进程运行脚本时前调用execveat进行脚本文件的可执行权限检查
exec_check.java=0/1默认为0,设置为1时,jdk运行脚本时class和jar文件时,需要调用execveat进行脚本文件的执行权限检查
exec_check.<other>=0/1后续可扩展其他解释器

注意:上述启动参数实际由各个解释器进程进行读取解析,内核并不实际使用这些参数。

特性范围

本特性于openEuler 24.03 LTS SP1(6.6内核)版本支持,需要内核版本。特性支持的解释器类型如下:

解释器目标文件说明
bashshell脚本文件bash进程对打开的shell文件进行可执行权限检查
jdkclass文件/jar文件java虚拟机对加载的class文件和jar包进行可执行权限检查

社区开发人员或用户可基于该机制,自行扩展其他解释器或类似机制的支持。

使用说明

AT_CHECK参数使用示例

前置条件

内核版本大于6.6.0-54.0.0.58,glibc版本大于等于2.38-41。

glibc-2.38-41.oe2403sp1.x86_64
kernel-6.6.0-54.0.0.58.oe2403sp1.x86_64

操作指导

可编写如下测试程序(test.c)进行参数功能测试:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define AT_CHECK               0x10000

int main(void)
{
    int fd;
    int access_ret;

    fd = open("./", O_RDONLY);
    access_ret = execveat(fd, "test.sh", NULL, NULL, AT_CHECK);
    perror("execveat");
    printf("access_ret = %d\n", access_ret);
    close(fd);
    return 0;
}

步骤1: 编译测试代码:

gcc test.c -o test

步骤2: 创建测试脚本test.sh:

echo "sleep 10" > test.sh

步骤3: 如果测试脚本具备合法的可执行权限,则execveat返回0:

# chmod +x test.sh
# ./test
execveat: Success
access_ret = 0

步骤4: 如果测试脚本不具备合法的权限,则execveat返回-1,错误码为Permission denied:

# chmod -x test.sh
# ./test 
execveat: Permission denied
access_ret = -1

bash解释器支持脚本可执行权限检查

前置条件

内核版本大于6.6.0-54,glibc版本大于等于2.38-41,bash版本大于等于5.2.15-13

bash
bash-5.2.15-13.oe2403sp1.x86_64
glibc-2.38-41.oe2403sp1.x86_64
kernel-6.6.0-54.0.0.58.oe2403sp1.x86_64

操作指导

步骤1: 设置系统中所有脚本文件的权限为可执行

bash
find / -name "*.sh" --exec chmod +x {} \;

步骤2: 设置启动参数并重启系统,添加的启动参数为:

exec_check.bash=1

步骤3: 验证只有具备可执行权限的脚本才可被bash解释器运行:

bash
# echo "echo hello world" > test.sh
# bash test.sh
bash: line 0: [1402] denied sourcing non-executable test.sh
# chmod +x test.sh
# bash test.sh
hello world

jdk支持脚本可执行权限检查

前置条件

获取支持该特性的jdk代码:

https://gitee.com/openeuler/bishengjdk-8/tree/IMA_Glibc2_34

按照如下流程编译:

https://gitee.com/openeuler/bishengjdk-8/wikis/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3/%E6%AF%95%E6%98%87JDK%208%20%E6%BA%90%E7%A0%81%E6%9E%84%E5%BB%BA%E8%AF%B4%E6%98%8E

操作指导

步骤1: 确保系统中所有.class文件和.jar文件的可执行权限

find / -name "*.class" chmod +x {} \; 
find / -name "*.jar" chmod +x {} \;

步骤2: 设置启动参数并重启系统,添加的启动参数为:

exec_check.java=1

步骤3: 验证只有具备可执行权限的class文件或jar文件才可被jvm运行:

可编写如下测试程序(HelloWorld.java)进行参数功能测试:

public class HelloWorld {    
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
bash
# javac HelloWorld.java
Access denied to /home/bishengjdk/bishengjdk-8/install/jvm/openjdk-1.8.0_432-internal/lib/tools.jar
# chmod +x /home/bishengjdk/bishengjdk-8/install/jvm/openjdk-1.8.0_432-internal/lib/tools.jar
# javac HelloWorld.java
# java HelloWorld
Access denied to HelloWorld.class

# chmod +x HelloWorld.class
# java HelloWorld
Hello, World!

结合IMA摘要列表实现解释器类应用完整性保护

前置条件

开启IMA摘要列表功能,详见内核完整性度量(IMA)文档章节。

操作指导

步骤1: 为目标应用程序生成IMA摘要列表(过程略,摘要列表生成方式详见内核完整性度量(IMA)文档章节)。

步骤2: 开启IMA摘要列表功能(过程略,摘要列表生成方式详见内核完整性度量(IMA)文档章节),以开启摘要列表+shell脚本校验为例,配置的内核启动参数如下:

bash
ima_appraise=enforce ima_appraise_digest_list=digest-nometadata ima_policy="appraise_exec_tcb" initramtmpfs module.sig_enforce exec_check.bash=1

步骤3: 验证IMA对bash脚本完整性保护

bash
# echo "echo hello world" > test.sh
# chmod +x test.sh
# bash test.sh
bash: line 0: [2520] denied sourcing non-executable test.sh

# 生成摘要列表后签名并导入(略)
# echo /etc/ima/digest_lists/0-metadata_list-compact-test.sh > /sys/kernel/security/ima/digest_list_data
# bash test.sh
hello world