• 使用 pam module 与 seccomp 技术禁止用户加载内核模块


    前言

    从 manual 中学习 seccomp 技术 这篇文章中,我谈及了使用 seccomp 让用户无法加载内核模块的功能,在本文中尝试写个简单的 demo 来验证下可行性。

    环境信息与依赖安装

    系统版本:debian 11
    内核版本:Linux debian 5.15.0-0.bpo.2-amd64
    执行 sudo apt install libseccomp-dev libpam0g-dev命令安装依赖库。

    使用 libseccomp 库编写 demo

    #define _DEFAULT_SOURCE
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PAM_SM_SESSION
    
    #include 
    #include 
    
    static int seccomp_filter_install(void)
    {
        int rc = 0;
        scmp_filter_ctx ctx;
    
        ctx = seccomp_init(SCMP_ACT_KILL);
        if (ctx == NULL)
            goto out;
    
        rc = seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(init_module), 0);
        if (rc < 0)
            goto out;
    
        rc = seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(finit_module), 0);
        if (rc < 0)
            goto out;
        
        rc = seccomp_load(ctx);
        if (rc < 0)
            goto out;
    
     out:
        seccomp_release(ctx);
        return -rc;
    }
    
    PAM_EXTERN int pam_seccomp_open_session(pam_handle_t *pamh,
    		int flags,
    		int argc,
    		const char **argv)
    {
        int ret;
        
        ret = seccomp_filter_install();
    
        return ret != 0 ? PAM_SESSION_ERR:PAM_SUCCESS;
    }
    
    PAM_EXTERN int pam_seccomp_close_session(pam_handle_t *pamh,
    		int flags,
    		int argc,
    		const char **argv)
    {
    	return PAM_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    将上述文件保存为 seccomp.c,使用如下编译命令编译之:

    gcc -fPIC -shared -lpam  -lseccomp ./seccomp.c -o pam_seccomp.so
    
    • 1

    编译完成后将 so 权限修改为 0644,然后将文件拷贝到 lib/x86_64-linux-gnu/security/ 目录中,示例命令如下:

    chmod 644 ./pam_seccomp.so 
    sudo cp ./pam_seccomp.so /lib/x86_64-linux-gnu/security/
    
    • 1
    • 2

    此后修改 /etc/pam.d/sshd 文件的内容,在 pam_loginuid.so 配置项目前增加一条 session required pam_seccomp.so配置项目(仅用于测试)。

    测试发现无法生效,使用 strace 跟踪发现在加载 /lib/x86_64-linux-gnu/security/位置的 pam_seccomp.so后,还去加载了 /lib/security/目录中的 pam_seccomp.so文件(此目录不存在),而其它的 pam 模块并不会访问 /lib/security 目录。

    创建相关目录并拷贝文件后重试仍旧不能生效。

    提问

    1. 难道是编译过程中有机关?

    获取 deb 源码包,手动编译发现一个 pam 模块的编译命令如下:

    	make[3]: Entering directory '/tmp/pam-1.4.0/modules/pam_limits'
    	/bin/bash ../../libtool  --tag=CC   --mode=compile gcc -DHAVE_CONFIG_H -I. -I../..    -I../../libpam/include -I../../libpamc/include -DLIMITS_FILE_DIR=\"/etc/security/limits.d/*.conf\" -DLIMITS_FILE=\"/etc/security/limits.conf\" -W -Wall -Wbad-function-cast -Wcast-align -Wcast-qual -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wstrict-prototypes -Wwrite-strings -Winline -Wshadow -g -O2 -MT pam_limits.lo -MD -MP -MF .deps/pam_limits.Tpo -c -o pam_limits.lo pam_limits.c
    	libtool: compile:  gcc -DHAVE_CONFIG_H -I. -I../.. -I../../libpam/include -I../../libpamc/include "-DLIMITS_FILE_DIR=\"/etc/security/limits.d/*.conf\"" -DLIMITS_FILE=\"/etc/security/limits.conf\" -W -Wall -Wbad-function-cast -Wcast-align -Wcast-qual -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wstrict-prototypes -Wwrite-strings -Winline -Wshadow -g -O2 -MT pam_limits.lo -MD -MP -MF .deps/pam_limits.Tpo -c pam_limits.c  -fPIC -DPIC -o .libs/pam_limits.o
    	mv -f .deps/pam_limits.Tpo .deps/pam_limits.Plo
    	/bin/bash ../../libtool  --tag=CC   --mode=link gcc -I../../libpam/include -I../../libpamc/include -DLIMITS_FILE_DIR=\"/etc/security/limits.d/*.conf\" -DLIMITS_FILE=\"/etc/security/limits.conf\" -W -Wall -Wbad-function-cast -Wcast-align -Wcast-qual -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wstrict-prototypes -Wwrite-strings -Winline -Wshadow -g -O2 -no-undefined -avoid-version -module -Wl,--version-script=./../modules.map -Wl,--as-needed -Wl,--no-undefined -Wl,-O1 -o pam_limits.la -rpath /lib64/security pam_limits.lo ../../libpam/libpam.la 
    	libtool: link: rm -fr  .libs/pam_limits.la .libs/pam_limits.lai .libs/pam_limits.so
    	libtool: link: gcc -shared  -fPIC -DPIC  .libs/pam_limits.o   -Wl,-rpath -Wl,/tmp/pam-1.4.0/libpam/.libs ../../libpam/.libs/libpam.so  -g -O2 -Wl,--version-script=./../modules.map -Wl,--as-needed -Wl,--no-undefined -Wl,-O1   -Wl,-soname -Wl,pam_limits.so -o .libs/pam_limits.so
    	libtool: link: ( cd ".libs" && rm -f "pam_limits.la" && ln -s "../pam_limits.la" "pam_limits.la" )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    能够看到编译命令还是有些复杂,但是也没有看到啥怀疑点。同时网上搜索 pam 模块的 demo,没有找到需要特殊编译参数的说明。 一通折腾后发现在 libpam0g-dev 包的安装目录中有一个模块的示例代码的 Makefile 文件,使用的编译命令如下:

    cc -g -fPIC -I"../../include"   -c -o pam_secret.o pam_secret.c
    ld -x --shared -o pam_secret.so pam_secret.o -l
    
    • 1
    • 2

    使用上述编译命令修改 demo 开始的编译命令,测试发现仍旧不生效。

    2. 是否代码实现存在问题?

    获取 pam deb 源码包,阅读 modules/pam_limits/pam_limits.c文件确定关键函数如下:

    1. pam_sm_open_session
    2. pam_sm_close_session

    与上文的代码对比,确认没有异常。

    3. 是否执行问题?

    demo 代码的核心函数为 seccomp_filter_install,我怀疑可能是这个函数的执行出现异常,于是单独将这个函数拷贝出来写了一个测试程序进行测试,strace 跟踪发现有如下错误信息:

    seccomp(SECCOMP_SET_MODE_STRICT, 1, NULL) = -1 EINVAL (Invalid argument)
    seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, NULL) = -1 EFAULT (Bad address)
    seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_LOG, NULL) = -1 EFAULT (Bad address)
    seccomp(SECCOMP_GET_ACTION_AVAIL, 0, [SECCOMP_RET_LOG]) = 0
    seccomp(SECCOMP_GET_ACTION_AVAIL, 0, [SECCOMP_RET_KILL_PROCESS]) = 0
    seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_SPEC_ALLOW, NULL) = -1 EFAULT (Bad address)
    seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, NULL) = -1 EFAULT (Bad address)
    seccomp(SECCOMP_GET_NOTIF_SIZES, 0, 0x7fff5f826ee2) = 0
    seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC_ESRCH, NULL) = -1 EFAULT (Bad address)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    看上去确实执行失败了,可查阅 manual 并没有发现怀疑点,想到可以跳过这个 libseccomp 库,于是暂时放弃。

    不使用 libseccomp 库编写 demo

    源码如下:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    
    #define PAM_SM_SESSION
    
    #include 
    #include 
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    static int install_filter(int nr, int arch, int error) {
      struct sock_filter filter[] = {
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
      };
      struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
        .filter = filter,
      };
      if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
        perror("prctl(PR_SET_SECCOMP)");
        return 1;
      }
      return 0;
    }
    
    int disable_module_init(void) {
      int ret = 0;
      if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("prctl(NO_NEW_PRIVS)");
        return 1;
      }
      ret = install_filter(__NR_finit_module, AUDIT_ARCH_X86_64, EPERM);
      if (ret != 0) {
    	perror("install filter failed\n");
      }
    
      ret = install_filter(__NR_init_module, AUDIT_ARCH_X86_64, EPERM);
      if (ret != 0) {
    	perror("install filter failed\n");
      }
    
      return ret;
    }
    
    PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,
    		int flags,
    		int argc,
    		const char **argv)
    { 
    	int ret = 0;
    	ret = disable_module_init();
    
    	return ret == 0 ? PAM_SUCCESS:PAM_SESSION_ERR;
    }
    
    PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,
    		int flags,
    		int argc,
    		const char **argv)
    {
    	return PAM_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    按照上文的描述进行编译配置,并修改 sshd 配置允许 root 登录,然后重启 sshd 服务,测试通过。测试示例如下:

    [longyu@debian]$ ssh root@localhost
    root@localhost's password: 
    ....................................
    root@debian:~# modprobe uio
    modprobe: ERROR: could not insert 'uio': Operation not permitted
    root@debian:~# strace modprobe uio 2>&1 | grep finit_module
    finit_module(3, "", 0)                  = -1 EPERM (Operation not permitted)
    root@debian:~# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    能够看到,使用 root 用户通过 ssh 登录后加载 uio 模块报无权限,strace 跟踪并过滤 finit_module 系统调用,确定其返回 EPERM 错误,与下发的过滤规则一致。

    扩展思考

    将 pam 与 seccomp 能够实现让用户无法加载内核模块的功能,但是使用 seccomp 带来的一个负面作用是子进程都被设定了 no_now_privs 标志,suid、Linux capabilities 等提权行为也会被禁止,会影响到用户正常的使用过程。

    参考链接

    pam_seccomp.c
    Writing Your First PAM Module

  • 相关阅读:
    论文笔记 - Poisoning and Backdooring contrastive learning
    港科夜闻|香港科大副校长汪扬教授代表第一届联盟联席理事长单位在京港大学联盟2022年理事会作工作总结报告...
    前端食堂技术周刊第 99 期:Remix 2.0、v0、2023 组件库盘点、TS Config 备忘录、Hello 算法
    在openSUSE-Leap-15.4-DVD-x86_64中使用佳能喷墨打印机ip2780
    【系统设计】邻近服务
    死锁问题概述
    【软件开发与重构】基本知识、编程范式
    Centos7构建LNMP平台
    切水果游戏开发1
    【JVM】JVM的内存区域划分
  • 原文地址:https://blog.csdn.net/Longyu_wlz/article/details/126438758