《30天自制操作系统》差不多都过了一遍。感觉操作系统和一般程序的最大的区别是可以随意操作和控制内存。而作为操作系统,最重要的是兼容性和稳定性。稳定的最基本的条件就是安全。所以操作系统作为最灵活的“程序”必须在安全方面做好工作。《30天自制操作系统》这本书上也讲了很多关于保护操作系统的知识,让我记忆深刻。下面就来总结一番。
保护操作系统是一项工程量十分巨大的活,有时候一个死角就能够然操作系统运行错误。所以要想保护操作系统,必须对操作系统的运行的每个流程都十分清楚,这样才能够找到操作系统的弱点,并做好防护措施。下面就来回顾一下操作系统的运行流程以及涉及的相关代码。
1.BIOS找到启动区,执行启动区的代码。这段代码所在文件是ipl10.nas,是用汇编写的,引导区只有512字节,不足以存放操作系统,这段代码的主要作用就是设定分区格式,把操作系统读入内存,然后跳入到操作系统核心代码。说简单一点就是引导操作系统。
2.启动区之后直接跳入HariMari函数开始执行。相关代码在bootpack.c。这部分代码主要是初始化键盘、鼠标等硬件设备设备,初始化GDT、IDT、PIC,开放中断,初始化内存、初始化任务并运行,然后进入到了一个循环,在这个循环里面接收键盘和鼠标的数据,并控制数据的发送。
3.操作系统会启动一个终端。相关代码在console.c中。终端是当前的操作系统最重要的交互工具。主要功能是执行相关命令和外部程序。如果当前选中终端,输入字符在HariMari中被接收之后,会发送到终端做相关的显示,如果发送回车键之后,终端会判断是命令还是程序,并执行。
4.如果执行的是外部程序,则操作系统则会跳到程序的主函数执行其代码。为了程序的方便,程序可以调用操作系统所提供的公开API。
分析上面的内容。前三个部分是操作系统所提供的服务。这些部分是不能够被外部程序所更改的,只需要给操作系统关键部分文件加上一定的修改权限就可以了。比如在win7操作系统上,不能够删除系统关键文件,这应该是一个道理。这部分代码是设计和编写操作系统的人编写的,只要这部分代码稳定,能够保证运行正常功能不会出错就可以了。
第四个部分的代码是可以由其它程序员编写的,所以这部分代码可能会存在bug,或者是恶意代码,如果没有做好一定的防护,操作系统可能会崩溃。
下面再来看一下外部文件被操作系统调用的流程。
用户通过键盘向电脑输入相关外部文件名字,然后回车。HariMari函数接收到键盘数据,发送给终端,终端判断输入是否是命令,如果不是命令,则查找是否存在该文件,如果存在文件则通过file_loadfile载入内存,注册gdt,然后跳转执行。
现在假设操作系统没有任何防备,看看应用程序能够做些什么来破坏操作系统。
1.语言直接修改内存数值。
这是C语言的特性,在操作系统没限制的条件下,可以通过指针修改任意内存的值。
这个本来应该是操作系统的权力。在实现操作系统的时候可以通过在一些内存中写入来保存数据,把数据当全局变量来用。这个在之前的内容中也用到过。但是如果是外部的软件,当然不可能给予它这么大的权限,所以需要把所有软件中的非法内存操作给禁止。
解决方法:专门给应用程序分配数据段,限制应用程序修改的数据范围。如果应用程序需要调用系统函数,则在执行系统函数之前先把段地址切换到操作系统所在段再执行。执行好之后回到应用程序段,切换段以及执行api的操作由操作系统提供,只需要再对应用程序对api给的参数进行合法性校检就可以了。2.用汇编写第三方程序,忽略操作系统指定的数据段。
这个是上面的解决方法的漏洞。c语言编译之后会用系统提供的段,但是汇编更强大,可以自己选定段进行数据修改。
解决方法:让应用程序无法访问操作系统的段。在软件的数据段和代码段上的访问权限加上0x60,这样可以将段设置为应用程序用。该段中的代码如果要访问操作系统所在段代码是非法的。这个0x60是x86架构中提供的,在现在的操作系统中只需要设定好久行了,不需要编写额外的代码。处于好奇,我去查了一下arm,里面有一个MMU,也可以设定访问权限,功能与x86类似。看来这部分内容在操作系统中至关重要,在处理器架构上面就已经把这个问题考虑清楚了。
申明段为应用程序段之后还有其它的一系列好处,比如应用程序不能够直接执行IN和OUT指令,这样可以防止应用程序更改定时器的周期,让定时器运行缓慢。除了这个,在应用程序中,也不能够执行CLI、STI和HLT之类的指令,这些指令在操作系统看来都是危险的,所以0x60的设置极大地提高了系统的安全性。1234//代码段set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);//数据段set_segmdesc(gdt + 1004, segsiz - 1, (int) q, AR_DATA32_RW + 0x60);3.破坏应用程序所在段中的数据。
应用程序不能够对操作系统的段进行修改,所以篡改操作系统是没用的了。但是可以篡改应用程序所在段的内容,让其它应用程序无法正常运行。对于CPU来说,应用程序访问应用程序所在段应该是理所当然的。但是这的确西药改进,如果操作系统中的应用程序很容易受到其它应用程序的干扰,而操作系统对此也不能够做些什么,那这个操作系统也是失败的操作系统。
解决方法:由gdt可以得到一些启示,可以动态修改gdt设置,把正在运行的程序gdt设置为应用程序段,把不在运行的程序设置为操作系统段。这样的话如果运行的应用程序想要破坏其它应用程序,那也只能够破坏和它同时运行的应用程序,大大降低了危害。但是这样有很多缺点,比如说一个程序就要用好几个gdt,而且每次更改起来也比较麻烦。
其实对于这个问题,CPU已经准备好了解决的方案。就是LDT。我们知道,GDT是全局段描述符表,而LDT则是局部段描述符表。每个LDT都是互相独立的,不能够访问各自的资源。所以只需要把应用程序设置在LDT中,这样其它应用程序就不能够访问该应用程序的段,保证了数据的安全。设定LDT也很简单,只需要在任务的tss中给成员变量ldtr赋相应值就可以了。12//设定ldtrtaskctl->tasks0[i].tss.ldtr = (TASK_GDT0 + MAX_TASKS + i) * 8;
最后来谈一下对一些破坏性程序的处理。
作为一个可用的操作系统,如果应用程序具有破坏性,那么运行之后就应该做到适当的提示,以及能够强制关闭应用程序,不要让它继续占用内存和CPU。这样操作系统才能够更加稳定。
对于一些恶意代码,比如在应用程序中修改操作系统段的数据,或者在应用程序中执行STI等操作,由于我之前设定过0x60的段权限,应用程序会自动产生0x0d中断(异常中断),在这个中断中,我可以写一些代码来关闭应用程序做好善后工作。
|
|
现在再来回顾一下操作系统,只要其内部稳定,基本上就很难被外部程序给干扰。因为操作系统和程序已经隔离。应用程序不能够破坏操作系统数据,如果要高权限操作,应用程序可以调用操作系统公开的api来完成。同时应用程序和应用程序间也独立,互不干扰。这样的操作系统才能经受住考验。