草庐IT

[自制操作系统] 第10回 认识保护模式之深入浅出特权级

李知行 2023-03-28 原文

目录
一、前景回顾
二、什么是特权级检查
三、门
四、如何进行特权级检查
五、调用门的跳转执行流程
六、调用门的跳转权限检查

 

一、前景回顾

  我们在前面讲过保护模式较之于实模式的三大特点:分页机制、特权级和分时机制。现在分页机制的坑已经填好了,接下来我们开始填特权级的坑。

二、什么是特权级检查

  首先我们来看看什么是特权级,CPU为了计算机的安全,将程序拥有的权力划分为了4个等级,也就是特权级,特权级按照权力大小划分为了0、1、2、3级,数字越小,权力越大。我们操作系统内核所占的特权级就是0级,处于至高无上的地位,而用户的应用程序处于3级,最低特权级。不过在Linux中,实际只用上了0级和3级,即我们常说的用户态便处于3级,内核态处于0级。

  特权级检查,说到底就是起到一个保护作用,操作系统为了避免用户程序随意访问内核,修改内核数据,所以在“访问者”访问“受访者”那一刹那,体现在实际中就是:段寄存器中的值被重新加载段选择子时(因为我们已经进入保护模式),检查访问者的特权级和受访者的特权级是否匹配。

  在特权级中我们要认识一些特定术语:

  1、CPL:处理器当前的特权级。

  2、DPL:段或者门的特权等级,位于段描述符或者门描述符的DPL字段。

  3、RPL:请求特权级,位于选择子的RPL字段。

  书上其实关于特权级的介绍还是比较详细,只不过对于初学者来说,不是那么好容易理解。我前前后后看了好几遍才算是大体理解了。所以我尝试用我自己的语言来描述一下特权级检查,重点就是CPL、DPL和RPL之间的关系。

  首先CPL是用来描述处理器当前的特权级的,我们的处理器只用上了0和3特权级,也就是说如果处理器在执行用户态程序时,处理器此时的特权级就是3级,如果处理器此时处于内核态,那么处理器的特权级就是0级。

  DPL描述的是段描述符或者门描述符的访问门槛,也就是说如果处理器的特权级低于目标段或者目标门的访问门槛,那么处理器就无法访问段描述符或者门描述符描述的那段内存区域。我相信这个也是比较好理解的。

  重点就是RPL,RPL表示的是请求资源的能力。这个其实是最不好理解的,咋一看RPL其实跟CPL应该是没什么区别的,因为书上都说,当处理器从一个特权级的代码段A转移到另一个特权级的代码段B上运行时(这里假设特权检测通过),处理器的CPL就会变更为代码段B的DPL,也就是目标代码段描述符的DPL将保存在代码段寄存器CS的RPL位,其实还是有区别的。

  这里我自己的理解是:RPL其实并不是指的处理器的请求资源能力,它应该是描述的段的请求资源的能力。我还是举例子来说明,如果用户通过中断门从用户态进入内核态(这个是很常见的低特权级进入高特权级的方式),此时CS段寄存器的CPL和RPL都应该是0,因为此时我们已经进入了内核态,无所不能了。如果用户程序有坏心思,想将代码段C(DPL为0)中存放的内核代码拷贝到指定的数据缓冲区中,这个缓冲区本质上是一个数据段D。重点来了,不管这个数据段是来自用户空间还是内核空间,当段选择子加载到DS寄存器时,该段数据段的RPL都会被操作系统修改为用户进程的CPL,也就是3。此时我们来看看特权检查的规则:

  请求某特权级为DPL级的资源时,参与特权检查的不只是CPL,还要加上RPL,CPL和RPL的特权必须同时大于等于受访者的特权DPL:

  数值上CPL≤DPL并且RPL≤DPL

  显然该数据段就无法接收内核代码,因为它的RPL为3,没有这个能力。关于特权级更详细的介绍我不在赘述,感兴趣的朋友可以参考原书《操作系统真象还原》p229~p251。

三、门

  “门结构”的存在是为了实现从低特权级到高特权级的转变,CPU中实现特权级变换有四种门。门结构就是记录一段程序起始地址的描述符。

  

  他们的使用方式有些许不同:

  1、任务门:任务门以任务段TSS位单位,用来实现任务切换。但是现代操作系统很少使用,我们后面也不用。

  2、中断门:以int指令主动发中断的形式实现从低特权级到高特权级转移,Linux系统调用便是使用此门实现的。

  3、陷阱门:以int3指令主动发中断的形式实现从低特权级到高特权级转移,一般是在编译器调试用。我们也不用。

  4、调用门:call或jmp指令后接调用门选择子作参数,以调用函数例程的方式实现从低特权级到高特权级转移。

四、如何进行特权级检查

  还是照搬书上的总结,当受访者为数据时:

  CPL <= 目标数据段DPL && RPL <=目标数据段DPL,即数据段不允许被比本数据段特权级更低的代码段访问。

  当受访者为代码时,分为如下三种情况:

  1、无门结构且目标为非一致性代码段:CPL = RPL = 目标代码段DPL

  2、无门结构且目标为一致性代码段:CPL >= 目标代码段DPL && RPL >= 目标代码段DPL

  3、有门结构:DPL_GATE >= CPL >= DPL_CODE && RPL <= DPL_GATE

五、调用门的跳转执行流程

  这里以调用门的跳转执行流程为例讲解门的使用,先看图:

 

  调用门的门描述符依旧是占据4个字节大小,被放置在GDT表中。用户程序中通过“call 调用门选择子”来访问调用门,处理器通过选择子中的高13位索引号加上GDT基址得到门描述符,在门描述符中又得到内核中被调用例程所在代码段的选择子以及偏移,又根据得到的选择子在GDT表中索引得到内核中被调用例程所在代码段的基址,将偏移量和基址结合就得到内核中被调用例程的地址。

六、调用门的跳转权限检查

  援引书中的一个例子,能够更好地说明调用门的特权级检查:

  假设当前处理器正在 DPL为3的代码段上运行,即正在运行用户程序,故处理器当前特权级CPL为 3。此时用户进程想获取安装的物理内存大小,该数据存储在操作系统的数据段中,该段DPL为0。由于当前运行的是用户程序,CPL为3,所以无法访问DPL为0的数据段。于是它使用调用门向系统救助。调用门是操作系统安装在全局描述符表GDT中的,为了让用户进程可以使用此调用门,操作系统将该调用门描述符的DPL设为3。该调用门只需要一个参数,就是用户程序用于存储系统内存容量的缓冲区所在数据段的选择子和偏移地址。调用门描述符中记录的就是内核服务程序所在代码段的选择子及在代码段内的偏移量。用户进程用“call 调用门选择子”的方式使用调用门,此调用门选择子是由操作系统提供的,该选择子的 RPL为3,此时如果用户伪造一个调用门选择子也没用,因为此选择子是用来索引门描述符的,并不用来指向缓冲区的选择子,调用门选择子中的高13位索引值必须要指向门描述符在GDT中的位置,选择子中低2位的RPL伪造也没意义,因为此时CPL为3,是短板,以它为主。此时处理器便进行特权级检查,CPL为 3,RPL为3,门描述符DPL为3,即数值上(CPL≤DPL && RPL≤DPL)成立,初步检查通过。接下来还要再将CPL与门描述符中选择子所对应的代码段描述符DPL比较,这是调用门对应的内核服务程序的DPL,为叙述方便将其记作DPL_CODE。由于DPL_CODE是内核程序的特权级,所以DPL_CODE为0,CPL为3,即数值上满足CPL≥DPL_CODE,CPL比目标特权级低,检查通过,该用户程序可以用调用门,于是处理器的当前特权级CPL的值用DPL_CODE代替,记录在CS.RPL中,此时CPL变为0。接下来,处理器便以0特权级的身份开始执行该内核服务程序,由于该服务程序的参数是用户提交的缓冲区所在的数据段的选择子及偏移量,为避免用户将缓冲区指向了内核的数据区,安全起见,在该内核服务程序中,操作系统将这个用户所提交的选择子的RPL变更为用户进程的CPL,也就是指向缓冲区所在段的选择子的 RPL变成了3。前面说过,参数都是内核在0级栈中获得的,虽然用户进程将缓冲区的选择子及偏移量压在了3特权级栈中,但由于调用门的特权级变换,参数已经由处理器在固件一级上自动复制到0特权级栈中了。用户的代码段寄存器 CS 也在特权级发生变化时,由处理器自动压入到0特权级栈中,所以操作系统需要的参数都可以在自己的0特权级栈中找到。用户缓冲区的选择子修改过后,接下来内核服务程序将用户所需要的内存容量大小写到这个选择子和用户提交的偏移量对应的缓冲区。如果用户程序想搞破坏,所提交的这个缓冲区选择子指向的目标段不是用户进程自己的数据段,而是内核数据段或内核代码段,由于目标段的DPL为0,虽然此时已在内核中执行,CPL为0,但选择子RPL已经被改为3,数值上不满足CPL≤DPL && RPL≤DPL,往缓冲区中的写入被拒绝,处理器引发异常。如果用户程序提交的缓冲区选择子确实指向用户程序自己的数据段,DPL则为3,数值上满足CPL≤DPL && RPL≤DPL,往缓冲区中的写入则会成功。如果中断服务程序内部再有访问内核自己内存段的操作,还会按照数值上(CPL≤DPL && RPL≤DPL)的策略进行新一轮的特权检测。通常,如果不是用户程序向内核提交缓冲区地址来接收数据的话,内核不会主动访问用户的内存段,多是访问自己的数据段或代码段,内核服务程序中若访问内核自己的内存段,由于内存段的DPL为0,所以段选择子的RPL也必须为0。

  好了,本回到此结束了,一两句话是讲不清楚特权级的,要想搞懂还是得认真看书。预知后事如何,请看下回分解。

有关[自制操作系统] 第10回 认识保护模式之深入浅出特权级的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  6. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  7. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  8. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  9. ruby-on-rails - 如何处理 Grape 中特定操作的过滤器之前? - 2

    我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?

  10. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

    在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

随机推荐