更安全的用户进程?
本节分支:
data_limit_3gb
在实现用户进程一文的文尾,笔者留下了一个思考题:既然要求用户不能直接访问内核,那为什么不将用户代码段的界限设置为 3GB 呢?正如之前所演示的那样,如果用户代码段的界限为 4GB,则用户就可以随意修改内核,包括直接访问显存:
其实笔者也不知道准确的答案,我粗略参考了 Linux 0.11 的代码,发现 Linux 0.11 似乎也是直接将段界限设置为 4GB 。至于规范的防止用户修改内核的方式,咋们以后遇上了再说,现在我们来看看到底能不能通过将数据段界限改为 3GB 来防止用户直接修改内核数据。
首先将用户数据段的段界限改为 0xbfffffff
:
1 | //文件说明:tss.c |
还要修改 syscall:
1 | //..... |
为什么要将之前的静态变量方式改成扩展内联汇编呢?说到这,笔者不禁流下了悔恨的泪水…当初我也是为了少学这点看起来复杂的扩展汇编,所以使用了全局静态变量这样的“巧计”来代替这种复杂的汇编。没办法,未来某些场景必须使用到它,所以,该来的早晚会来。话说回来,目前而言,由于我们还没有真正的加载用户进程(真正的用户进程是从文件中读取,进而载入内存的,而当前我们使用函数代替用户进程的),所以一切代码和数据(的标号,即地址),不论用户进程还是内核,都被链接成了 3GB 以上,这都是下面这行 makefile 指令造成的:
1 | #文件说明:makefile |
这条指令将目前的所有文件全部链接成 kernel.bin,并指定入口的虚拟地址为 0xc0001500
,因此,所有的函数和全局、静态变量的地址实际上都在 0xc0000000
以上!所以,我们将用户段的界限限制在 3GB 以下时,在用户态中就不能再使用任何全局变量和静态变量!所以这里的 _syscallX 就不能使用静态变量来中转啦!于是乎,只有改成扩展内联汇编喽。
最后,还得修改系统调用入口函数,因为进入中断并不会自动切换 ds、es、fs(只会切换cs),所以咋们必须手动将这几个寄存器修改为内核态的数据段选择子,如下:
1 | ;..... |
ok,来试试看这下能不能直接在用户态访问显存:
显然,发生了 GP 异常,禁止访问。说明这样是能够起到一定的保护作用的。来看看正常运行的效果:
上图中用户只打印了一次,我就使用 Bochs 断点指令将其打断了,因为后面任务切换时引发了程序崩溃,原因就不细说了。这说明通过限制用户数据段的界限来禁止访问内核的这种方式是不可行的。实际上,现代操作系统的内存保护机制并非在段上,而是在页上 ,关于这点,以后再说吧。