是谁不让访问用户空间
近日在给NDB增加新功能,让它可以访问用户空间。但在ARM平台上遇到一个问题,如果CPU中断时位于内核空间,那么访问任何用户空间的地址都失败。
失败的基本症状是调试器中打印出一串串的问号。
而JTAG库打印出如下错误信息:abort occurred - dscr = 0x03047d47
其中的dscr是ARM CoreSight技术中定义的外部调试寄存器,全称为EDSCR,即外部调试状态和控制寄存器,在ARMv8架构手册中可以找到它的详细定义,结合上面的错误码,解读如下图所示。
本以为低6位的状态值部分可以给出错误原因,但是却始终给出的是“断点”含意,意思是这一次中断到调试器是因为遇到断点。
请老朋友使用ARM官方的DTRACE工具进行测试,也发现类似情况,某些情况下无法读用户空间。比如下图中,当切换到5号CPU后,再访问其它核可以访问的一段地址空间,访问失败,内存窗口显示红色背景,没有数据。
下图是正常能读的情况。
是谁在阻止强大的硬件调试器访问内存呢?根据多年的经验,可能是安全机制在搞鬼。但是这个鬼藏在哪里呢?
今日周末,又想起这个问题。想到了以前在写“猫蛇大战”系列时读过的Linux内核uaccess代码。
虽然内核空间具有高特权,从特权级别来说可以访问用户空间。但其实真的要访问的话,还是有一些复杂的。首先用户空间有很多个,用户空间的内存常常不在物理内存中,另外,内核访问用户空间也要有正当的理由,不可以“擅闯民宅”。所以内核访问用户空间时,也可谓是如履薄冰。
另外,因为这部分逻辑与CPU硬件相关,所以代码也很分散。实现时又常常用宏或者嵌入式汇编,读起来也比较难读。
想到这里,我便打开内核代码,搜索uaccess,搜索整个内核代码树得到的结果太多了,只搜索GDK8使用的arch/arm64。这样一搜,果然有收获。处理页错误的一行printk引起了我的注意。
die_kernel_fault("access to user memory outside uaccess routines",
addr, esr, regs);
这个die系列打印是内核里的一道景观。一执行到这个函数,系统就进入panic了。品味这个错误信息:“在uaccess过程外访问用户内存”,这是调用die的理由,也就是给系统判死刑的原因。
顺着这条消息思考:在uaccess之外不可以访问用户内存。NDB显然属于这种情况啊。因为NDB是用JTAG来访问内存,不是用uaccess。
那么为什么uaccess就能访问呢?
打开arm64下的uaccess代码,很快找到了一个关键函数。
__uaccess_ttbr0_disable,从这个函数名来看,是要禁止访问,“坏事”多半就是它干的。
这个函数上面有个条件编译选项,查这个选项,果然是打开的。
geduer@gdk8:~$ zcat /proc/config.gz | grep TTBR0
CONFIG_ARM64_SW_TTBR0_PAN=y
如此看来就是它在捣鬼。搜索这个宏的文档:
Emulate Privileged Access Never using TTBR0_EL1 switching
configname: CONFIG_ARM64_SW_TTBR0_PAN
Linux Kernel Configuration
└─> Kernel Features
└─> Emulate Privileged Access Never using TTBR0_EL1 switching
Enabling this option prevents the kernel from accessing
user-space memory directly by pointing TTBR0_EL1 to a reserved
zeroed area and reserved ASID. The user access routines
restore the valid TTBR0_EL1 temporarily.
写的很清楚,为了防止内核空间随便访问用户空间,故意把记录用户空间页目录的ttbr0寄存器偷梁换柱了。这招真够损的。^-^
使用ndb读ttbr0寄存器,看到的是这样一个值:
rdmsr ttbr0_el1
msr[182000] = 00000000`02503000
剧透一下,这个值就是假冒的,和后面将看到的有效值差别很大。
既然是uaccess函数做的禁止,那么它也该有方法来启用啊,诚然如此。
阅读这个函数的代码,它是从当前线程信息中读到保存的ttbr0,然后再写到物理CPU中。
如何找到保存的ttbr值呢?
有办法。在NDB中先执行!ps命令显示当前线程的task_struct地址。
task_struct:0xffffffc0f409e740 pid: 179 comm:avahi-daemon
PGD:0xffffffc0f0d2b000 CR3=0x0
state 0 flags:0x404100 stack:0xffffff800b8a8000
上面的地址指向的就是Linux下每个线程都有的task_struct结构体,内核源代码中常常使用著名的current宏来访问(我将其称为Linux内核第一霸)。这个结构体极其庞大,它的起始部分就是架构相关的thread_info子结构。
dt lk!task_struct
+0x000 thread_info : thread_info
+0x020 state : Int8B
+0x028 stack : Ptr64 Void
+0x030 usage :
+0x034 flags : Uint4B
+0x038 ptrace : Uint4B
+0x040 wake_entry : llist_node
+0x048 on_cpu : Int4B
+0x04c cpu : Uint4B
+0x050 wakee_flips : Uint4B
+0x058 wakee_flip_decay_ts : Int8B
+0x060 last_wakee : Ptr64 task_struct
【此处省略数百行】
既然thread_info就在task_struct的开头,那么task_struct的地址就是thread_info的地址。使用dt命令观察:
dt lk!thread_info 0xffffffc0f409e740
+0x000 flags : 0
+0x008 addr_limit : 549755813887
+0x010 ttbr0 : 0xf80000`f0d2b000
+0x018 preempt_count : 0
果然,真实的ttbr0现身了。
接下来,使用NDB的写寄存器命令把这个保存的ttbr0写给CPU:
wrmsr ttbr0_el1 0xf80000f0d2b000
再读回来确认:
rdmsr ttbr0_el1
msr[182000] = 00f80000`f0d2b000
确认ttbr0写成功后,再尝试访问用户空间:
dd 0000007f`82809000
0000007f`82809000 464c457f 00010102 00000000 00000000
0000007f`82809010 00b70003 00000001 000011c0 00000000
0000007f`82809020 00000040 00000000 0001e548 00000000
0000007f`82809030 00000000 00380040 00400007 0019001a
0000007f`82809040 00000001 00000005 00000000 00000000
0000007f`82809050 00000000 00000000 00000000 00000000
0000007f`82809060 0001c4d4 00000000 0001c4d4 00000000
0000007f`82809070 00010000 00000000 00000001 00000006
居然就成功了,困扰多日的问题就这么解决了,在解决的过程中,年轻的NDB调试器和挥码枪发挥了积极作用。给它们个合影吧。
我是在旅途中写的这篇文章。GDK8和挥码枪都很方便携带,所以我就把他们放在背包中,随时可以取出来,快速搭建起一个强大的调试环境。
上图中的蓝色配件叫Nano Display,它可以把GDK8的HDMI输出转为USB信号,送给笔记本电脑,Nano Code中集成了一个视频播放功能,可以把GDK8的桌面显示在主机上。
GDK8的桌面是我深爱的庐山秀峰之龙潭。很多格友曾经与我同游过。
(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以文章和有声读物
也欢迎关注格友公众号
是谁不让访问用户空间相关推荐
- Linux内核访问用户空间文件:get_fs()/set_fs()的使用
测试环境:Ubuntu 14.04+Kernel 4.4.0-31 关键词:KERNEL_DS.USER_DS.get_fs().set_fs().addr_limit.access_ok. 参考代码 ...
- linux包含绝对路径头文件,linux-kernel - 访问用户空间内存访问函数(如access_ok(),get_from_user())需要包含的头文件的确切路径。 - 堆栈内存溢出...
我在linux-headers-3.2.0-49中搜索了用户内存访问功能. 它在uaccess.h头文件中定义,但是uaccess.h文件太多. 下面是我在linux-headers-3.2.0-49 ...
- Linux预备知识(二):进程空间地划分-用户空间/内核空间
查看机器上栈大小命令 ulimit -a 或者 ulimit -s 大小不固定,可以用 ulimit -s 进行调整,默认一般为 8M ** 栈区(stack sagment)**:由操作系统自动分配 ...
- 嵌入式之linux用户空间与内核空间,进程上下文与中断上下文
文章目录 前言 用户空间与内核空间 内核态与用户态 进程上下文和中断上下文 上下文 原子 进程上下文 中断上下文 进程上下文VS中断上下文 原子上下文 前言 之前在学习嵌入式linux系统的时候,一直 ...
- I/O流(包括操作系统与内核,用户空间),I/O工作原理,Java I/O流的设计及Java IO系统
文章目录 一.操作系统与内核 1.1操作系统 1.2内核 1.3 关系图 二.内核空间和用户空间 2.1:目的: 2.2.内核空间(Kernel-space): 2.3.用户空间(User-space ...
- 详细讲解从用户空间申请内存到内核如何为其分配内存的过程
Linux内存管理 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法.力求从外到内.水到渠成地引导网友分析Linux ...
- linux用户空间、内核空间
一.进程理解 进程:资源分配的最小单元,程序在操作系统中运行的实例 线程:最小调度单元 一个进程至少有一个线程或多个线程,一个线程只能属于一个进程,因为进程是最小的资源分配单元,所以线程不存在独立的地 ...
- 用户空间访问I2C设备驱动
2012-01-11 15:33:43 标签:Linux I2C 字符设备 设备驱动 用户空间 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任. ...
- Linux下用户空间访问I/O端口的相关函数
Linux下设置端口权限的系统调用有两个:ioperm和iopl函数. ioperm 功能描述 为调用进程设置I/O端口访问权限,从端口地址from起始,共设置num个值为turn_on.ioperm ...
最新文章
- 工欲善其事,必先利其器。如何玩转 VS Code?
- [LeetCode]#13 3sum
- matlab中的类标转换程序
- 物理机安装ESXI6.7提示No Network Adapters的解决方案
- 线程可警告状态以及APC队列
- Typora入门基本教程
- 双水泵轮换工作原理图_周宁气压给水设备控制柜原理图
- 【干货】神策数据朱德康:用户中台建设实践解析(附PPT下载链接)
- r语言 tunerf函数_R语言非参时间序列(六):波动脉冲响应(VIR)中的关键公式推导...
- Word怎么在空白处添加下划线
- MIPS汇编语言学习笔记27:数组
- 数据结构课程笔记1-水王问题
- cesium获取当前屏幕中心点坐标
- vulntarget-a靶场的学习思考
- ppt扇形图怎么显示数据_高手都是这么做PPT,PPT数据统计扇形图这样做
- 【图像隐藏】基于小波变换DWT实现数字水印嵌入提取含各类攻击附matlab代码
- 智能制造-低时延满足制造需求
- 华为fusion computer虚拟机存储数据恢复
- 如何批量检测查询域名或者网址是否被微信屏蔽拦截
- 边缘AI+视频监控,如何助力企业安全生产监管智能化升级?