大连密封材料 发表于 2025-3-14 12:15:08

关于stac和clac的进一步细节及EFLAGS

一、背景

在之前的博客 内核态代码直接使用用户态数据的注意事项_内核态怎样打开用户态文件-CSDN博客 里,我们x86平台上在内核态里使用用户态数据的干系细节,即必要使用stac和clac函数来打开/关闭内核态访问用户态数据的权限,这里说是权限,实在指的是管控,也就是x86上SMAP特性,防止内核逻辑不测修改用户态数据,仅仅在copy_from_user/copy_to_user等一些地方临时答应内核态逻辑来访问用户态数据。在arm64下,也有类似的机制,如下图里的:
https://i-blog.csdnimg.cn/direct/cf24f0515ff9404898f8d621506b755c.png
这里不睁开说arm64的,但是可以得知的是,不同的硬件平台一些紧张的特性都会覆盖的,如arm64的smap功能在armv8.4-A架构及更高版本中会引入。
回到x86平台,在之前的博客 内核态代码直接使用用户态数据的注意事项-CSDN博客 里,我们自己调用了stac和clac,在stac和clac的中心使用了memcpy使用到了用户态数据,但是实在会遇到一个warning的提示,我们在下面第二章里,睁开说明。然后我们在第三章里,把与stac和clac有关的在做上下文切换时怎样生存和规复的逻辑做说明。

二、stac和clac之间使用memcpy会导致编译warning

如下图,假如在stac和clac之间使用memcpy:
https://i-blog.csdnimg.cn/direct/cb849a7c0f064378851a1dba54fc57ef.png
就会导致编译时如下的warning:
https://i-blog.csdnimg.cn/direct/6818b875416c447a94117a5646c4b8c0.png
提示“call to fortify_panic() with UACCESS enabled”。
2.1 在编译时会使用objtool,在举行validate_call函数时会举行UACCESS使能的检查

如下图可以看到,在objtool的源码里,有对指令举行uaccess的验证,假如当前这条指令不是uaccess安全的话,会举行报错:
https://i-blog.csdnimg.cn/direct/f390e68bd7304c5dadc28dfd140d9a80.png
编译时是可以获悉当前是否在代码里使用了stac这样的打开内核态代码访问用户态数据的管控。
2.2 memcpy里会举行检查,假如检查不外会触发fortify_panic

在objdump -S导出的vmlinux.txt里,可以搜到如下判断和触发fortify_panic的逻辑:
https://i-blog.csdnimg.cn/direct/1cd99da3e3b246e2a123480404985736.png
上图里的代码:
if ((p_size != SIZE_MAX && p_size < size) ||
源码里就一处:
https://i-blog.csdnimg.cn/direct/9253d9c7e7b545ef9d8b96e308d5556d.png
是在fortify-string.h里的fortify_memcpy_chk函数里:
https://i-blog.csdnimg.cn/direct/47c3bd2ef2df47459ddb9511de55be05.png
而fortify_memcpy_chk函数是在__fortify_memcpy_chk里使用到:
https://i-blog.csdnimg.cn/direct/0c8b14a474674e2e84a5315657200742.png
是在fortify-string.h里定义了memcpy,使用到了上图里的__fortify_memcpy_chk宏:
https://i-blog.csdnimg.cn/direct/6a6ff7f55b73491d9957b407aac551ef.png

三、stac和clac会写EFLAGS

我们知道x86下的EFLAGS在做上下文切换时会举行生存和规复。内核里是用pt_regs这个结构体来生存和规复上下文切换时所必要的prev和next的寄存器。有关pt_regs的更多的细节介绍,见之前的博客 监测各个核上cpu上的线程是内核线程还是用户线程,处于内核态还是用户态的方法_怎么查看线程是用户tai还是内核态-CSDN博客里的 3.1 一节。
下面 3.1 一节,我们先贴出测试源码和测试情况,在 3.2 一节里举行干系分析。
3.1 测试源码和测试结果

3.1.1 测试用的内核模块源码

这一节的内核模块源码是改写自 内核态代码直接使用用户态数据的注意事项-CSDN博客 博客里的 2.1 一节。
#include <linux/module.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/stddef.h>
#include <linux/lockdep.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <trace/events/workqueue.h>
#include <linux/sched/clock.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/tracepoint.h>
#include <trace/events/osmonitor.h>
#include <trace/events/sched.h>
#include <trace/events/irq.h>
#include <trace/events/kmem.h>
#include <linux/ptrace.h>
#include <linux/uaccess.h>
#include <asm/processor.h>
#include <linux/sched/task_stack.h>
#include <linux/nmi.h>
#include <asm/apic.h>
#include <linux/version.h>
#include <linux/sched/mm.h>
#include <asm/irq_regs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>

#include <linux/stop_machine.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhaoxin");
MODULE_DESCRIPTION("Module for debug pf error code tasks.");
MODULE_VERSION("1.0");

#define TESTPFERRORCODE_NODE_TYPEMAGIC    0x73
#define TESTPFERRORCODE_IO_BASE         0x10

char _testmem;

typedef struct testpferrorcode_ioctl_memcpy {
    void* memstart;
    int memsize;
} testpferrorcode_ioctl_memcpy;

#define TESTPFERRORCODE_IOCTL_MEMCPY\
    _IOWR(TESTPFERRORCODE_NODE_TYPEMAGIC, TESTPFERRORCODE_IO_BASE + 1, testpferrorcode_ioctl_memcpy)

unsigned long get_eflags(void) {
    unsigned long eflags;
    asm volatile (
      "pushf\n\t"         // 将 EFLAGS 压入栈
      "pop %0\n\t"      // 将 EFLAGS 弹出到 eflags 变量
      : "=r" (eflags)   // 输出约束,eflags 变量作为输出
      :                  // 没有输入
      :                  // 没有被修改的寄存器
    );
    return eflags;
}

static long testpferrorcode_proc_ioctl(struct file *i_pfile, u32 i_cmd, long unsigned int i_arg)
{
    switch (i_cmd) {
      case TESTPFERRORCODE_IOCTL_MEMCPY:
            {
                void __user* parg = (void __user*)i_arg;
                testpferrorcode_ioctl_memcpy mem;
                if (copy_from_user(&mem, parg, sizeof(mem))) {
                  printk("copy_from_user failed\n");
                  return -EFAULT;
                }
               
                {
                  unsigned long eflags = get_eflags();
                  printk("before stac eflags=0x%llx\n", eflags);
                }
               
                stac();

                {
                  unsigned long eflags = get_eflags();
                  printk("after stac eflags=0x%llx\n", eflags);
                }

                {
                  printk("before run memcpy\n");
                  for (int i = 0; i < mem.memsize; i++) {
                        *(_testmem + i) = *((char*)(mem.memstart + i));
                  }
                  printk("after run memcpy\n");
                }
                //memcpy(_testmem, mem.memstart, mem.memsize);
               
                clac();
                printk("after clac()\n");
                return 0;
            }
      default:
            return -EINVAL;
    }
    return 0;
}

static int testpferrorcode_proc_open(struct inode *i_pinode, struct file *i_pfile)
{
        return 0;
}

static int testpferrorcode_proc_release(struct inode *i_inode, struct file *i_file)
{
    return 0;
}

typedef struct testpferrorcode_env {
    struct proc_dir_entry*      testpferrorcode;
} testpferrorcode_env;

static testpferrorcode_env _env;

static const struct proc_ops testpferrorcode_proc_ops = {
    .proc_read = NULL,
    .proc_write = NULL,
    .proc_open = testpferrorcode_proc_open,
    .proc_release = testpferrorcode_proc_release,
    .proc_ioctl = testpferrorcode_proc_ioctl,
};

#define PROC_TESTPFERRORCODE_NAME "testpferrorcode"

static int __init testpferrorcode_init(void)
{
    _env.testpferrorcode = proc_create(PROC_TESTPFERRORCODE_NAME, 0666, NULL, &testpferrorcode_proc_ops);
    return 0;
}

static void __exit testpferrorcode_exit(void)
{
    remove_proc_entry(PROC_TESTPFERRORCODE_NAME, NULL);
}

module_init(testpferrorcode_init);
module_exit(testpferrorcode_exit);
3.1.2 上层的测试步伐

这一节的上层测试代码是改写自 内核态代码直接使用用户态数据的注意事项-CSDN博客 博客里的 2.2 一节。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <sys/types.h>
#include <sys/ioctl.h>

using namespace std;

#define TESTPFERRORCODE_NODE_TYPEMAGIC    0x73
#define TESTPFERRORCODE_IO_BASE         0x10

typedef struct testpferrorcode_ioctl_memcpy {
    void* memstart;
    int memsize;
} testpferrorcode_ioctl_memcpy;

#define TESTPFERRORCODE_IOCTL_MEMCPY\
    _IOWR(TESTPFERRORCODE_NODE_TYPEMAGIC, TESTPFERRORCODE_IO_BASE + 1, testpferrorcode_ioctl_memcpy)

int main(int i_argc, char* i_argv[]) {
    if (i_argc != 2) {
      printf("should be two parameters!\n");
      return -1;
    }
    int convertfd = open("/proc/testpferrorcode", O_RDWR);
    testpferrorcode_ioctl_memcpy mem;
    int size = atoi(i_argv);
    if (size == 4) {
      int testa = 3;
      int testsize = sizeof(testa);
      mem.memstart = &testa;
      mem.memsize = testsize;
    }
    else {
      printf("size=%d\n", size);
      mem.memstart = malloc(size);
      printf("memstart=0x%llx\n", mem.memstart);
      //memset(mem.memstart, 0, size);
      mem.memsize = size;
    }
    if (ioctl(convertfd, TESTPFERRORCODE_IOCTL_MEMCPY, &mem) < 0) {
      perror("ioctl");
      return 0;
    }
    return 1;
}

3.1.3 测试步骤和测试结果

测试步骤是先insmod 3.1.1 里编出后的ko:
https://i-blog.csdnimg.cn/direct/daf80d7b374141589ffdadf2eab525e6.png
然后运行 3.1.2 里编出的步伐,执行方式如下:
https://i-blog.csdnimg.cn/direct/9cd42eea73074e4788e9375ceb2b6968.png
看dmesg里的日志里下图里的赤色框里的部门:
https://i-blog.csdnimg.cn/direct/27589317e04b4ea8abe7dd2e09c4d42d.png
可以看到在执行stac前后,eflags的值是有变更的。

3.2 原理分析

3.2.1 eflags的获取方法

eflags是x86下的一个寄存器,是状态标志寄存器。可以通过如下方法来获取eflags的值:
https://i-blog.csdnimg.cn/direct/094a99cbc9f046cba5741a616d470de4.png
如上图,逻辑还是比较简单的,即通过pushf把eflags压入栈,然后再通过pop弹出它的值到一个寄存器里,作为函数返回值返回。
3.2.2 stac和clac会写EFLAGS的bit18

如下图的dmesg打印可以看到stac是写的EFLAGS的bit18(0x246->0x40246):
https://i-blog.csdnimg.cn/direct/359406fab4c645f6bc55093158a33242.png
改写的是下图里的赤色框出的bit位(下图是取自资源 https://download.csdn.net/download/weixin_42766184/90348566?spm=1001.2014.3001.5503 的文档):
https://i-blog.csdnimg.cn/direct/ea08041f47b04f1292000f3f87e83c69.png
bit18也叫做EFLAGS.AC:
https://i-blog.csdnimg.cn/direct/30255f5da95849f1b74a23f9bbbca239.png
该EFLAGS.AC与SMAP功能的干系解释如下:
https://i-blog.csdnimg.cn/direct/a13016475b324fd89d41d6b828068325.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 关于stac和clac的进一步细节及EFLAGS