Author: Doug, 10+ years embedded development veteran, focusing on: C/C++, embedded, Linux.

Pay attention to the following public account, reply [books], get Linux, embedded field classic books; Reply [PDF] to get all original articles (PDF format).

[IOT town]

directory

[TOC]

On x86 processors, four privilege levels are provided: 0, 1, 2, and 3. The smaller the number, the higher the privilege level!

Generally speaking, the operating system is of the highest importance and reliability and needs to run at privilege level 0.

Applications work at the top level, with a wide range of sources and lowest reliability, working at privilege level 3.

The middle privilege levels 1 and 2 are rarely used.

In theory, programs whose reliability falls between the operating system and applications can be placed on these two privilege levels.

In processors, there are three related terms that are closely related to privilege levels:

  1. CPL: Current Privilege Level Indicates the Current Privilege Level.

  2. DPL: Descriptor Privilege Level Privilege Level of the Descriptor;

  3. RPL: Requestor Privilege Level;

Understand these three privilege protection rules, you understand the operating system to protect the system ultimate password!

CPL: indicates the current privilege level

The current privilege level refers to the privilege level of the currently executing code, which is determined by the bit[1 ~ 0] in the currently executing code segment register CS:

How come the lowest two digits in the CS register are RPL?

The reason is this: before we execute a piece of code, the code segment is in a space in memory, and its code segment descriptor is in the LDT local descriptor table, as shown in the following figure:

Assuming that we now want to execute in this code segment, we need to assign the code segment register CS to 0x0007 (0000_0000_0000_0111).

The lower two bits of the current contents of the CS register are called: current priority, and the value to be assigned to cs is 0x0007, which is called the selector.

According to the structure of the selectors:

  1. RPL: bit[1 ~ 0] = 11B; RPL: bit[1 ~ 0] = 11B; RPL: bit[1 ~ 0] = 11B; RPL: bit[1 ~ 0] = 11B; RPL: bit[1 ~ 0] = 11B;

  2. TI: bit[2] = 1B, indicating that the segment descriptor is searched in LDT;

  3. Index number: the index value of bit[15 ~ 3] is 0, indicating that the LDT offset is 0 (0 = 0 * 4, each descriptor occupies 4 bytes).

When the processor is allowed to enter the code after a series of permission checks, cs = 0x0007 is set.

The lowest 2 bits in the CS register are equal to RPL in the selector, which is 3.

In general, CPL is equal to RPL.

DPL: descriptor privilege level

DPL refers to a segment descriptor that specifies the privilege level of the segment represented by the descriptor.

The structure of the descriptor is as follows:

Bit [14 ~ 13] indicates the privilege level of the segment descriptor.

When requesting access to a segment (whether data or code), the processor finds the segment descriptor in the GDT or LDT and compares the CPL and RPL with the DPL in the descriptor. Access to the segment to which the descriptor points is allowed only if certain rules are met.

The specific comparison rules are described below.

RPL: Requester privilege level

In the CPL, you’ve already described what RPL is, and the two are closely related.

However, sometimes CPL is not the same as RPL.

Such as:

A user program that wants to access a block of its own memory (data segment) in memory through system functions provided by the operating system.

The user program needs to tell the operating system which data segment to access and what the offset is.

This information requires assigning a selector to the data segment register DS via the operating system.

Suppose the selector is 0x000F(binary: 0000_0000_0000_1111):

Index number: 1;

TI: use the LDT;

RPL: 3;

That is, when the operating system accepts the request of the user program and starts to execute the system function, the CPL at this time is the privilege level 0 of the operating system.

At this point, the operating system needs to assign a selector to the data segment register DS, and this selector is passed to the operating system as a parameter by the user program.

In this scenario: CPL = 0, RPL = 3, they are not equal.

The operating system uses this selector sub-0x000F to the LDT of the user program. After finding the data segment descriptor according to the index number 1, the operating system compares CPL(0) and RPL(3) with the DPL in the descriptor to determine whether it has access to the data segment.

  1. The DPL of the user program must be 3, which is determined by the operating system at the beginning of the program loading;

  2. Such access is allowed according to the privilege level checking rules below;

There’s a catch:

Suppose the user program is a malicious program that wants to corrupt the operating system’s data, so it passes in a selector that points to the operating system segment 0x0010(binary: 0000_0000_0001_0000) :

Index number: 2(assuming that some segment of the operating system is known by some other means to be in the second entry of the GDT);

TI: use the GDT.

RPL: 0;

At this point, if the operating system mindlessly receives the call request of the user program as is, it will find the data segment belonging to the operating system through GDT for destructive operations.

The operating system will not be so stupid. It will check the parameters passed by the user program when receiving the request.

If it finds that a user program running at privilege 3 passes in a RPL with privilege 0, it will be alarmed: the request privilege is higher than your own run privilege. What do you want?

The operating system then changes the RPL in the selector to CPL, the current privilege level of the user program.

Just like: a village head to the mayor, the appeal is: want to build a factory on the collective land in their own village. The mayor thinks: this is your village own land, you can do whatever you like, permit.

However, if the village chief’s request is: want to build a factory next to the civic square. Then the mayor would shout: this place is not your village, do what you want, go away!

Privilege check rule

Privilege checks for code snippets

In general, only two code segments with the same privileges are allowed to be moved.

Such as:

  1. Jump from one user program segment (CPL = 3) to another DPL = 3;

  2. Jump from one operating system code segment (CPL = 0) to another DPL = 0;

But handlers also provide special ways for lower-privileged code to be moved to higher-privileged code for execution:

  1. If C = 1 is in the TYPE field in the description of the high-privilege code snippet, low-privilege code is allowed to move in;

  2. Through invocation gates, lower-privileged code can also be moved to higher-privileged code segments;

The first case is described here, when C = 1 in the TYPE field of the object snippet descriptor, which is called compliance code, or consistent code.

If type. C = 1, the handler allows code with lower privileges than the DPL of the descriptor to be moved to this code.

The lower the privilege level, the higher the value.

CPL >= DPL

RPL >= DPL

For example, there are two code segments in the operating system with different C flag bits in their descriptors:

A user program is executing at the moment: CPL = 3.

The user program can then be moved to code 2 for execution, not code 1.

Also, after moving to segment 2 of the operating system, the current privilege level CPL remains the same, still 3.

There are two analogies:

1. Similar to the Sudo command in Linux

If a directive requires root privileges, we can use the su – command to convert the identity to root and then execute it.

In this case, all identity information, such as environment variables, belongs to the root user.

We can also use the sudo command directly, which is like temporarily upgrading the user’s permissions, but the environment variables and other information are still the current user, not the root user.

The village head asked the mayor for a loan

The village chief went to the city bank to apply for a loan, but his power is not enough, the bank does not care about him, so the village chief went to the mayor for help.

So, the mayor gave the village head a personal letter of introduction, the village head with this letter to the bank, the bank a look: there is the mayor’s endorsement, so to the village head for loan procedures.

However, in the process of processing, all the places that need to be signed, can only write the village head himself (the privilege level remains unchanged), and cannot write the mayor’s name.

In addition, for code segment 1 in the figure above, since its C flag bit is 0, only programs with the same privileges can be transferred in, which can be expressed numerically as:

CPL == DPL

RPL == DPL

One final point to keep in mind is that higher-privileged code should never be moved to lower-privileged code. The mayor will never act as a village leader.

Data segment privilege check

The rules for checking the privilege level of data segments are simple: a program with a higher privilege level can access data with a lower privilege level, and vice versa.

Numerically, it is:

CPL <= DPL

RPL <= DPL

Stack segment privilege check

The rule for checking the privilege level of the stack segment is also relatively simple. The x86 processor requires that the CPL of the current privilege level be the same as the DPL of the target stack segment.

Numerically, it is:

CPL == DPL

RPL == DPL

To satisfy this requirement, when moving from a user program (CPL = 3) to the operating system (DPL = 0), the current privilege level is unchanged if the transition is made through a dependency (consistency) code segment, and the stack used is still the stack space of the user program.

If the current privilege level changes (CPL = 0), then the stack used must be the stack space below the privilege level of 0.

Therefore, when the operating system loads the user program, it needs to claim a chunk of stack space in advance for use in such scenarios.

In Linux, only the 0 and 3 privilege levels are used, so each user program only needs to prepare the stack for use at the 0 privilege level in advance.

If an operating system uses all four privilege levels from 0 to 3, then the operating system must provide three stack Spaces for user programs running at privilege level 3, which can be used as stack space when the user programs move to privilege levels 0, 1, and 2.





This article mainly from the perspective of privilege, to understand the operating system to the system protection.

In this mechanism, the operating system is well protected from malicious programs, but also provides some channels for the execution of user programs, to invoke lower-level functions.

Recommended reading

[1] C language pointer – from the underlying principle to the tricks, with graphics and code to help you explain thoroughly [2] step by step analysis – how to use C to achieve object-oriented programming [3] The original GDB underlying debugging principle is so simple [4] inline assembly is terrible? Finish this article and end it!

Other series albums: selected articles, C language, Linux operating system, application design, Internet of Things