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

news/2025/3/17 10:17:18/

一、背景

在之前的博客 内核态代码直接使用用户态数据的注意事项_内核态如何打开用户态文件-CSDN博客 里,我们x86平台上在内核态里使用用户态数据的相关细节,即需要使用stac和clac函数来打开/关闭内核态访问用户态数据的权限,这里说是权限,其实指的是管控,也就是x86上SMAP特性,防止内核逻辑意外修改用户态数据,仅仅在copy_from_user/copy_to_user等一些地方临时允许内核态逻辑来访问用户态数据。在arm64下,也有类似的机制,如下图里的:

这里不展开说arm64的,但是可以得知的是,不同的硬件平台一些重要的特性都会覆盖的,如arm64的smap功能在armv8.4-A架构及更高版本中会引入。

回到x86平台,在之前的博客 内核态代码直接使用用户态数据的注意事项-CSDN博客 里,我们自己调用了stac和clac,在stac和clac的中间使用了memcpy使用到了用户态数据,但是其实会遇到一个warning的提示,我们在下面第二章里,展开说明。然后我们在第三章里,把与stac和clac有关的在做上下文切换时如何保存和恢复的逻辑做说明。

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

如下图,如果在stac和clac之间使用memcpy:

就会导致编译时如下的warning:

提示“call to fortify_panic() with UACCESS enabled”。

2.1 在编译时会使用objtool,在进行validate_call函数时会进行UACCESS使能的检查

如下图可以看到,在objtool的源码里,有对指令进行uaccess的验证,如果当前这条指令不是uaccess安全的话,会进行报错:

编译时是可以获悉当前是否在代码里使用了stac这样的打开内核态代码访问用户态数据的管控。

2.2 memcpy里会进行检查,如果检查不过会触发fortify_panic

在objdump -S导出的vmlinux.txt里,可以搜到如下判断和触发fortify_panic的逻辑:

上图里的代码:
if ((p_size != SIZE_MAX && p_size < size) ||
源码里就一处:

是在fortify-string.h里的fortify_memcpy_chk函数里:

而fortify_memcpy_chk函数是在__fortify_memcpy_chk里使用到:

是在fortify-string.h里定义了memcpy,使用到了上图里的__fortify_memcpy_chk宏:

三、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           0x10char _testmem[102400];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           0x10typedef 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[1]);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:

然后运行 3.1.2 里编出的程序,执行方式如下:

看dmesg里的日志里下图里的红色框里的部分:

可以看到在执行stac前后,eflags的值是有变更的。

3.2 原理分析

3.2.1 eflags的获取方法

eflags是x86下的一个寄存器,是状态标志寄存器。可以通过如下方法来获取eflags的值:

如上图,逻辑还是比较简单的,即通过pushf把eflags压入栈,然后再通过pop弹出它的值到一个寄存器里,作为函数返回值返回。

3.2.2 stac和clac会写EFLAGS的bit18

如下图的dmesg打印可以看到stac是写的EFLAGS的bit18(0x246->0x40246):

改写的是下图里的红色框出的bit位(下图是取自资源 https://download.csdn.net/download/weixin_42766184/90348566?spm=1001.2014.3001.5503 的文档):

bit18也叫做EFLAGS.AC:

该EFLAGS.AC与SMAP功能的相关注释如下:


http://www.ppmy.cn/news/1579785.html

相关文章

kong搭建一套微信小程序的公司研发环境

一、物理架构 微信小程序H5部署在微信公众平台&#xff0c;需要通过外网域名访问到公司机房。 为了区分生产和研发环境&#xff0c;需要创建两个外网域名。 另外&#xff0c;微信小程序需要配置业务域名, 请参考文章- 微信小程序的业务域名配置&#xff08;通过kong网关的pre…

生活中的可靠性小案例11:窗户把手断裂

窗户把手又断了&#xff0c;之前也断过一次&#xff0c;使用次数并没有特别多。上方的图是正常的把手状态&#xff0c;断的形状如下方图所示。 这种悬臂梁结构&#xff0c;没有一个良好的圆角过渡&#xff0c;导致应力集中。窗户的开关&#xff0c;对应的是把手的推拉&#xff…

五子棋小游戏-简单开发版

一、需求分析 开发一个基于 Pygame 库的五子棋小游戏&#xff0c;允许两名玩家在棋盘上轮流落子&#xff0c;当有一方达成五子连珠时游戏结束&#xff0c;显示获胜信息&#xff0c;并提供退出游戏和重新开始游戏的操作选项。 1.棋盘显示 &#xff1a; 显示一个 15x15 的五子棋…

C语言动态内存管理(上)

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;C语言动态内存管理(上) 发布时间&#xff1a;2025.3.16 隶属专栏&#xff1a;C语言 目录 为什么需要动态内存管理静态分配的局限性动态分配的优势 动态内存函数malloc函数介绍函数使用 free函数介绍函数使用 calloc…

基于SSM+Vue+uniapp的科创微应用(可改为研学)小程序+LW示例

1.项目介绍 系统角色&#xff1a;管理员、企业、普通用户功能模块&#xff1a;用户管理、企业管理、场地管理、场地类型管理、预约参观管理、场地预约管理、活动信息管理、报名信息管理、试题管理、试卷管理等技术选型&#xff1a;SSM&#xff0c;Vue&#xff08;后端管理web&…

理解光场模型:uv与st的结合

在计算机图形学和计算机视觉领域&#xff0c;光场模型是一种强大的技术&#xff0c;它允许我们捕捉和呈现复杂的三维场景&#xff0c;以更真实的方式表达光的传播和交互。本文将探讨光场模型的基本概念&#xff0c;并深入分析其中两个关键平面——uv平面和st平面&#xff0c;它…

linux 下消息队列

文章目录 &#x1f4e8; Linux System V 消息队列实战一、消息队列核心概念 &#x1f4a1;1. 消息队列特点 &#x1f31f;2. 生命周期 &#x1f504; 二、项目概述三、完整代码实现1. 公共头文件 common.hpp2. 发送端 sender.cpp3. 接收端 receiver.cpp 三、编译与运行指南1. 编…

初探 Threejs 物理引擎CANNON,解锁 3D 动态魅力

简介 Cannon.js 是一个基于 JavaScript 的物理引擎&#xff0c;它可以在浏览器中模拟物理效果。它支持碰撞检测、刚体动力学、约束等物理效果&#xff0c;可以用于创建逼真的物理场景和交互。 参考文档 官方示例 原理 Cannon.js 使用了欧拉角来表示物体的旋转&#xff0c;…