Date: Tue, 24 Sep 1996 14:36:24 +0200 (MET DST) From: Mark Habersack Reply-To: grendel AT ananke DOT amu DOT edu DOT pl To: "Turpen, Josh -- Josh Turpen" cc: djgpp AT delorie DOT com Subject: PMode rings - small intro Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII This is more detailed info on protected mode rings. Enjoy reading ;-))) Anyone is granted to distribute this article without any restrictions. The only thing forbidden is making fun of it ;-)) I am not responsible for any damage or loss of information resulting from use or misuse of any information contained here. I AM responsible for any typos, errors and mistakes. Text is saved in Unix format. Protection Mechanisms by Mark Habersack ======================================= There are two basic protection schemes built into the i386+ microprocessors. First of them is *task separation*. In i386+ CPUs it is achieved by giving each task its own *virtual address space*. This is done by means of translation tables: on each task switch the LDTR (Local Descriptor Table Register) and CR3 (Control Register 3) are being loaded with some, task-specific, value. This way each and every task has exclusive access to the local descriptor table (LDT), as well as to *page directory*. Task switch does not change the value stored in GDTR (Global Descriptor Table Register). Table pointed to by GDTR is shared accross all tasks in the system. It maps global resources, mainly OS' code and data areas. Different tasks, although using the same virtual address, are referring to different *physical* areas of the host memory. The exact area referenced by task depends on the value stored in their LDTs and in paging mechanism translation tables. Every task uses logical addresses to reference memory and each segment selector may point to a descriptor stored in either LDT or GDT. This approach makes it easy to create a *local address space* for every process (task) in the system, OTOH there is a possibility to share memory between tasks These two techniques are rarely used in concert, though. The most common way of sharing memory between tasks is mapping their memory to the same physical adresse or by copying appropriate descriptors to LDTs of task that are to share memory. Second protection scheme used in i386+ CPUs are so called *rings*. Each memory segment is assigned some *protection level*. Every task, then, has in given moment some Current Privilege Level (CPL) which is equal to the protection level of current code segment. CPL allows to verify tasks rights to access some other area (segment) of memory. There are two kinds of memory accesses: (1) to data, (2) to code (i.e. making far calls to procedures living in different code segment). Protection schemes employed here are as follows: (ad 1) only access to data on protection level *lower* or *equal* to CPL is allowed. (ad 2) it is possible to call procedures from *equal* or *higher* to CPL level of protection. The rules are result of reasoning that it is not acceptable to allow some program (task) to access data which is more protected and that the code on the less protected level might be unreliable and, as such, should not be called by more privileged parts of system software. There are certain simplifications in ring-based protection scheme presented above: (1) CPL is not always equal to protection level of current code segment (2) Ways to call a procedure at different protection level are far more complicated than presented here. (3) It is possible to lower CPL at execution time. In Intel(tm) microprocessors there are 4 protection levels (rings) - 0..3. From these, level 0 is the most privileged. Usual "assignment" of rings is as follows: 0 - operating system kernel 1 - operating system drivers, high-level services, etc. 2 - if used, it may contain intermediate software like database drivers, network front-ends, etc. 3 - application software Gathering together what was said before, task can access data only on its own level of protection or on that which is numerically higher (i.e. on outer one as related to current). This results in ring 0 being the most protected in terms of data access. From the point of view of code segments, ring 0 is the most privileged as one having access to data areas in entire system. Reverse situation exists when looking at the code access: ring 0 can invoke code that lives on its own level only, while ring 3 can call all (unless restricted otherwise) code in entire OS. Protection level of specific segment is stored in its corresponding descriptor in either LDT or GDT. Descriptor contains two-bit field with its associated DPL (Descriptor Privilege Level). CPU fetches the descriptor from memory and stores its contents in a hidden set of registers associated with every segment register in CPU. This cache is then being used instead of reloading descriptors from memory every time segment associated with the given register is being accessed. Data in cache is validated only when code loads some new value into the segment register. When you look at the content of any segment register (of course if it contains a valid selector) you will notice that all of them (I am assuming the task is being executed with CPL == 3) have bits 0-1 set. These bits are called RPL (Requestor's Privilege Level) and provide information on in which ring associated data/code exists. As it can be deduced from this discussion, CPU checks validity of memory access by comparing CPL with DPL of a segment which is to be accessed. This segment (or more precisely descriptor of segment ) is selected by a selector contained in register. Selector's RPL field is used to "weaken" the callee CPL. Checks performed by CPU to authorize access to, let's say, data are: if( (DPL >= CPL) && (DPL >= RPL) ) grant_access; else signal_protection_violation; Consider the following example. Application requests access to some data stored in ring 0. To do that the program has to call some OS procedure and pass it a selector to the segment it wants to visit. In this situation, then, the validity checks should refer not to CPL but to requestor's protection level. CPL will be 0 as the code being executed will pertain to OS kernel living in ring 0 and DPL will be also 0. If CPU checked ONLY whether DPL >= CPL then the application would gain access to any data managed and owned by OS, which is obviously not acceptable. It is very easy for the application to set selector's RPL to 0. To preserve the protection scheme, OS procedure called by the application has to set the passed selector's RPL field to the value of the user program (i.e. 3) and only then use the selector to access data. After these actions are performed CPU sees that DPL < RPL and rejects the access request. Net result of setting selector RPL to 3 is "weakening" of OS kernel's protection level to that of application. The DPL here is compared to EPL (Effective Privilege Level) which is computed by comparing CPL to RPL (EPL = MAX(CPL,RPL)). The protection rings discussed are tightly related to segmentation scheme used in Intel i386+ CPUs. Other techniques involve page attributes and protection levels (there are two: User and Supervisor. The latter one allows access to page only from levels 0, 1, 2; while the former allows access from all rings). Hope this will help a little, Mark --------------------------------------------------------------------------- You can't brush me under the carpet, you can't hide me under the stairs, The custodian of your private fears, your leading actor of yesteryear, Who as you crawled out of the alleys of obscurity, sentenced to rejection in the morass of anonymity. You who I directed with a lover's will, you who I let hypnotise the lens. You who I let bathe in the spotlights glare. You who wiped me from your memory like a greasepaint mask, just like a greasepaint mask... -------------------- http://ananke.amu.edu.pl/~grendel -------------------