Security

来自osdev
Zhang3讨论 | 贡献2022年1月14日 (五) 02:32的版本
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

操作系统中的安全性是一个非常重要的问题。 它不仅包括来自外部威胁 (例如病毒和黑客) 的安全性,还包括内部安全性。 例如,操作系统负责确保进程不会访问其自己地址空间之外的内存。 如果有程序这样做,则必须将其关闭以保护其他 “行为良好” 的程序。 操作系统安全可分为两大类,高层安全和底层安全。

要查看此类别下的文章,请浏览 此列表

底层保护机制

操作系统程序员可以使用几种不同的底层保护机制。 第一种机制称为 “CPU Ring” 或更简单地称为 “Ring”,它控制允许执行哪些CPU指令。 第二和第三保护机制与内存访问有关。 它们分别被称为 “分页” 和 “分段”。 它们控制允许访问哪些内存区域和/或如何允许访问这些内存区域。

Rings

Ring为程序提供了保护层。 它们允许特定级别的资源访问进程。 这很好,因为它可以防止坏程序弄乱事情。 但是,有几个缺点: 你使用的CPU Ring越多,操作系统与体系结构的绑定联系就越多。 但是,你可以有几种体系结构,每种体系结构都有自己的Ring切换代码。 与此相关的另一个问题是,你的操作系统必须具有 TSS 设置和其他一些功能,这使得Ring切换比仅在内核模式下运行所有程序要困难得多。 在大多数常见的架构中总共有4个Ring。 然而,许多体系结构只有两个Ring (例如x86_64),在本描述中对应于Ring0和3。

Ring 0

这是内核模式(kernel mode)或主管模式(supervisor mode)。 此级别的保护最少,并且可访问资源最多。 启动时,除非关闭,否则操作系统将在此模式下运行。 中断处理程序在此模式下运行。

Ring1和2

这层ring多用于设备驱动程序。 它们提供了更多的保护,但不如ring 3那么多。

Ring 3

这是大多数操作系统用于应用程序的Ring。 此Ring也称为Userland,或Userspace。 它具有最大的保护和最少的资源访问权限。

大多数操作系统只使用0和3Ring。这是因为不需要Ring1和Ring2,这样设备驱动程序可以在需要的任意Ring中运行。

有时应用程序需要访问其Ring不允许的资源。 如果他们尝试访问它们,则会触发一般保护故障 (int 13),并关闭应用程序。 应用程序必须以某种方式与内核接口,并且主要是通过 系统调用 完成的。

I/O 特权层

x86架构上的Ring权限系统的另一个重要方面是I/O特权级别 (IOPL)。它确定哪些Ring可以不受限制地访问I/O端口。 它是在eblags寄存器中设置的两位数字。 数字小于或等于它的Ring具有完整的I/O权限,而大于它的Ring则没有。 它可以在逐个进程的基础上用于授予某些服务器I/O端口访问权限。

分页

See Paging

分段

Segmentation分段

高层保护机制

操作系统中的高层安全性可以通过许多不同的方式来实现。 一种方法是通过 VFS 中的文件权限。 *nix中的文件在其inode条目中具有 'permission' 值。 这个特性用于控制用户可以读取、写入、执行或删除文件。 这些主要由 File Systems 控制。

编程语言特定安全性 (汇编/C/C++ )

OSDev社区周围的许多操作系统完全信任用户输入。 虽然可以理解,大多数用户都在努力使驱动程序工作/稳定,但也应该理解,生产级别代码不应该包含基本的安全问题。 例如,许多系统调用实现从用户的堆栈中取出值并将其存储到寄存器中,然后自由地取消引用它们或将它们用作长度、偏移量等。 更深入地研究以下子主题,读者应该能够理解为什么这会带来问题,以及如何将它们用于在其他 “安全系统” 中获得更高的特权。

还要注意,ELF解析器和网络堆栈实现可能会构成很大的威胁。 许多OSDev项目都信任从ELF文件加载的整数和偏移量,并在这些地址自由执行和存储数据。 同样,网络协议解析器充满了与安全相关的错误 (主要是整数的溢出和下溢),由于基于堆的后果,这些错误通常被用来引起真正的安全问题。 看看下面这段代码:

int size = int_from_user();
char *buf = malloc(size + 1);
memcpy(buf, data_from_user, size);

如你所见,如果用户将大小设置为 ((unsigned) -1),则分配将分配0个字节,这在大多数usermode堆中将为标头分配空间,而没有数据,从而导致用户可控的完整堆溢出。

基于堆栈的溢出

了解x86 堆栈,你应该已经能够推断出为什么这会构成安全威胁。 使堆栈溢出 (相对) 非常容易,因此在操作系统中实现堆栈时必须格外小心。

基于堆的溢出

基于堆的溢出与基于堆栈的溢出根本不同,并且特定于每个分配器。 你可以通过为自由和分配的控制结构的每个成员设置 “poison values” 并在操作前检查它们来帮助在你的操作系统中检测它们。 Linux使用这种方法 (参见LIST_POISON1和LIST_POISON2 [1])。

基于整数的溢出

整数溢出不会导致直接影响,例如基于堆栈或基于堆的溢出。 相反,它们会在代码的后面导致这些类型的错误 (通常是基于堆的)。 例如,假设我们有一个应用程序从协议或文件格式解析数据:

int size = read_int_from_network();
char *buffer = malloc(size + 1);            // leave room for NULL.
memcpy(buffer, data_from_network(), size);  // copies 2^32-1 bytes into buffer.

如你所见,没有对从用户读取的大小进行验证,这可能会在以后的代码中导致较大的问题。 在OSDev中出现这种情况的示例包括ELF解析器 (考虑段/程序/进入文件的入口点偏移量),网络解析器,读/写/lseek实现等。 如果你的代码从用户那里获取输入,则 * 必须 * 验证它是否适合你要处理的数据范围内。 另外,你应该在使用用户提供的数据完成的每个数学运算中检查基于整数的问题。

预防

虽然可能不能直接应用于业余爱好者的操作系统中,但这些想法当然可以被使用和移植到他们身上。 保护系统安全的最佳文档集可以在 PaX Design and Implementation

Pax提供了你所需的几乎所有操作系统安全性。 阅读有关其实现的文档可以让OS开发爱好者人员在其系统中使用大部分这里的思想。 大多数当前的Windows/BSD (内核) 保护都基于这些方案。 用户空间保护可以归因于 ASLR, Heap Cookies (连同general heap hardening) 、Stack Canaries和NX。

使用这些机制并不能防止内存数据的损坏,而是将升高优先级搞破坏的尝试变得非常困难。

另见

文章

帖子

外部链接