ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 권한
    프로젝트/운영체제 만들기 2023. 6. 5. 14:33

    글의 참고

    - 64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf

    - https://www.read.seas.harvard.edu/~kohler/class/05s-osp/notes/notes9.html

    - https://en.wikipedia.org/wiki/X86_memory_segmentation#Detailed_segmentation_unit_workflow

    - https://wiki.osdev.org/Security#Rings


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.

    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.

    - `글의 참조`에서 빨간색 볼드체로 체크된 링크는 이 글을 작성하면 가장 많이 참조한 링크다.

    - `운영체제 만들기` 파트에서 퍼온 모든 참조 글들과 그림은 반드시 `이 글과 그림을 소스 코드로 어떻게 구현을 해야할까` 라는 생각으로 정말 심도있게 잠시 멈춰서 생각해봐야 실력이 발전한다.


    글의 내용

     

    - 세그먼트 보안

    : 보호 모드가 초창기에 등장했을 때, GDT 및 LDT를 각각의 프로세스에게 개별적으로 할당시켜 세그먼트 디스크립트안에 작성된 Base와 Limit 영역 안에서만 동작하게 만들었다. 즉, 프로세스가 자신의 영역안에서만 동작시킨 것이다.

     

     

    - 권한 수준

    : 인텔 스펙에서 말하는 하드웨어적인 보호 메커니즘은 `링 레벨`부터 시작된다고 볼 수 있다. 이 링 레벨은 `세그먼트 보호` 메커니즘을 기반으로 한다. 세그먼트 보호 메커니즘은 아래와 같이 4(0 ~ 3)단계로 이루어져 있다. 숫자가 커질 수록 권한이 낮아진다. 즉, 0이 권한이 가장 쌔고, 3이 권한이 가장 낮다.


     

    : 이제 실제 스펙에 내용을 가져와서 인텔의 하드웨어 보호 메커니즘을 살펴보자. 

     

    : 프로세서는 `권한` 관련해서 문제가 발생했다는 감지하면, #GP를 발생시킨다.

    The center (reserved for the most privileged code, data, and stacks) is used for the segments containing the critical software, usually the kernel of an operating system. Outer rings are used for less critical software. (Systems that use only 2 of the 4 possible privilege levels should use levels 0 and 3.)

    The processor uses privilege levels to prevent a program or task operating at a lesser privilege level from accessing a segment with a greater privilege, except under controlled situations. When the processor detects a privilege level violation, it generates a `general-protection exception (#GP)`.

    To carry out privilege-level checks between code segments and data segments, the processor recognizes the following three types of privilege levels:

    Current privilege level (CPL)The CPL is the privilege level of the currently executing program or task. It is stored in bits 0 and 1 of the CS and SS segment registers. Normally, the CPL is equal to the privilege level of the code segment from which instructions are being fetched. The processor changes the CPL when program control is transferred to a code segment with a different privilege level. The CPL is treated slightly differently when accessing conforming code segments. Conforming code segments can be accessed from any privilege level that is equal to or numerically greater (less privileged) than the DPL of the conforming code segment. Also, the CPL is not changed when the processor accesses a conforming code segment that has a different privilege level than the CPL.

    • Descriptor privilege level (DPL) — The DPL is the privilege level of a segment or gate. It is stored in the DPL field of the segment or gate descriptor for the segment or gate. When the currently executing code segment attempts to access a segment or gate, the DPL of the segment or gate is compared to the CPL and RPL of the segment or gate selector (as described later in this section). The DPL is interpreted differently, depending on the type of segment or gate being accessed:


    • Requested privilege level (RPL) — The RPL is an override privilege level that is assigned to segment selectors. It is stored in bits 0 and 1 of the segment selector. The processor checks the RPL along with the CPL to determine if access to a segment is allowed. Even if the program or task requesting access to a segment has sufficient privilege to access the segment, access is denied if the RPL is not of sufficient privilege level. That is, if the RPL of a segment selector is numerically greater than the CPL, the RPL overrides the CPL, and vice versa. The RPL can be used to insure that privileged code does not access a segment on behalf of an application program unless the program itself has access privileges for that segment. See Section 5.10.4, “Checking Caller Access Privileges (ARPL Instruction),” for a detailed description of the purpose and typical use of the RPL.

    Privilege levels are checked when the segment selector of a segment descriptor is loaded into a segment register. The checks used for data access differ from those used for transfers of program control among code segments; therefore, the two kinds of accesses are considered separately in the following sections.

    - 참고 : 5.5 PRIVILEGE LEVELS

    : `CPL`은 코드 세그먼트의 권한 수준이라고 보면 된다. 이 값은 CS 및 SS 세그먼트 레지스터의 [1:0] 비트에 작성되어 있다. 프로세서는 권한 수준이 바뀌면 CPL을 변경한다. 예를 들어, 시스템 콜이 호출되면 CPL은 3에서 0으로 변경된다.

     

    : `DPL`은 세그먼트나 게이트의 권한 수준을 의미한다. 현재 실행중인 코드 세그먼트가 특정 세그먼트 및 게이트에 접근하면, DPL과 CPL 및 RPL과 비교한다. 세그먼트 및 게이트에 접근하려면, MAX(CPL, DPL)의 권한이 DPL 권한보다 높아야 한다.

     

    MAX(CPL, RPL) >= DPL 

    : `RPL`은 데이터 세그먼트 셀렉터에 할당되어 있는 권한 수준이다. `RPL`은 시스템 콜과 같이 유저 애플리케이션의 권한 상승으로 인한 무방비한 공격을 막기 위한 수단으로 사용된다. 

     

    - 데이터 세그먼트 권한 검사

    : `데이터 세그먼트 권한 검사`는 프로세서가 세그먼트 셀렉터를 세그먼테 레지스터의 `Visible Part`에 로드하려고 할 때 수행된다. 즉, 세그먼트 액세스를 하려면, 세그먼트 셀레터가 세그먼트 레지스터에 로드되어야 하는데, 이 때 검사를 한다는 것이다. 검사 내용은 간단하다. 액세스 하려는 세그먼트 디스크립터의 DPL 보다 현재 CS의 CPL이나 세그먼트 셀렉터의 RPL의 권한이 더 높으면 된다. 만약, DPL이 권한이 더 높다면, 프로세서는 해당 요청을 폐기하고 접근하면 안되는 놈이 접근했다는 경고를 보내기 위해 #GP를 발생시킨다.

    To access operands in a data segment, the segment selector for the data segment must be loaded into the datasegment registers (DS, ES, FS, or GS) or into the stack-segment register (SS). (Segment registers can be loaded with the MOV, POP, LDS, LES, LFS, LGS, and LSS instructions.) Before the processor loads a segment selector into a segment register, it performs a privilege check (see Figure 5-4) by comparing the privilege levels of the currently running program or task (the CPL), the RPL of the segment selector, and the DPL of the segment’s segment descriptor. The processor loads the segment selector into the segment register if the DPL is numerically greater than or equal to both the CPL and the RPL. Otherwise, a general-protection fault is generated and the segment register is not loaded.

    - 참고 : [ 5.6 PRIVILEGE LEVEL CHECKING WHEN ACCESSING DATA SEGMENTS ]

     

    : 근데, 문제가 있다. `RPL`은 사실 직접 수정이 가능하다. 즉, CPL이 3인 애플리케이션이 자신의 데이터 세그먼트 셀렉터의 RPL을 0으로 설정할 수 가 있다( `APRL` 명령어를 사용. 이 명령어는 애플리케이션에서도 사용가능). 이렇게 하면, 데이터 세그먼트에 접근할 때, CPL만 체크하게 되는 꼴이다. 그렇면, 다른 애플리케이션의 데이터 세그먼트 셀렉터의 RPL을 수정할 수 있을까? 예를 들어, 멀티 프로세서로 동작하는 환경에서 2개의 프로세서가 각각 A와 B 애플리케이션 프로세스를 동작시키고 있을 때, A와 B는 서로의 데이터 세그먼트 셀렉터 RPL을 변경할 수 있을까? 개인적인 생각이지만, 페이징 테이블이 별도로 존재하기 때문에 그건 쉽지 않을 것 같다.

    It is important to note that the RPL of a segment selector for a data segment is under software control. For example, an application program running at a CPL of 3 can set the RPL for a data- segment selector to 0. With the RPL set to 0, only the CPL checks, not the RPL checks, will provide protection against deliberate, direct attempts to violate privilege-level security for the data segment. To prevent these types of privilege-level-check violations, a program or procedure can check access privileges whenever it receives a data-segment selector from another procedure (see Section 5.10.4, “Checking Caller Access Privileges (`ARPL` Instruction)”).

    - 참고 : [ 5.6 PRIVILEGE LEVEL CHECKING WHEN ACCESSING DATA SEGMENTS ]

    : 근데 정말 `RPL`이 필요할까? 필요성을 알기 위해 `RPL`이 없다고 가정한 시나리오를 보자. `시스템 콜`이 발생하면, 커널로 제어권이 넘어가면서 애플리케이션의 CPL이 `0`이 된다. 즉, 커널과 동등한 권한을 갖게된다. 애플리케이션이 시스템 콜을 호출하면서 넘긴 파라미터에 특정 세그먼트에 값을 변경하는 코드가 들어있을 경우, CPL `0`이므로, 권한 검사가 통과되어 다른 세그먼트의 내용을 변경할 수 있게 된다. 이걸 방지하기 위해 `RPL`이 등장했다. 운영체제는 애플리케이션이 시스템 콜을 호출하면, RPL을 `3`으로 설정한다. 그래서, 다른 세그먼트에 접근조차 하지 못하도록 막는 것이다. 

     

    - 코드 세그먼트 권한 검사

    To transfer program control from one code segment to another, the segment selector for the destination code segment must be loaded into the code-segment register (CS). As part of this loading process, the processor examines the segment descriptor for the destination code segment and performs various limit, type, and privilege checks. If these checks are successful, the CS register is loaded, program control is transferred to the new code segment, and program execution begins at the instruction pointed to by the EIP register.

     

    - 콜 게이트

    : x86에서 `게이트`라는 용어는 권한이 변경이 필요한 경우에 사용된다. 권한 변경이 필요한 이유는 대게 유저 레벨에서 커널 모드의 기능을 사용하기 위해서다. x86에서는 권한을 변경하기 위해 4가지 디스크립터를 제공해준다. 아래 문서를 보자.

    To provide controlled access to code segments with different privilege levels, the processor provides special set of descriptors called gate descriptors. There are four kinds of gate descriptors:

    • Call gates
    • Trap gates
    • Interrupt gates
    • Task gates

    - 참고 : 5.8.2 Gate Descriptors

     

    : 현대에 콜 게이트는 거의 쓰이지 않는다. 운영체제에서 호환성이라는 부분은 굉장히 중요한데, `콜 게이트`는 x86 전용 기술이다. 그래서 잘 쓰이지 않는다.

     

    - 스택 스위칭

    : `스택 스위칭`은 운영 체제 측면에서 굉장히 중요한 내용이다. 보안 관련 토픽에 위치 시켰지만, 인터럽트, 익셉션, 컨택스트 스위치, 시스템 콜 등 정말 빠지는 부분이 없다. 그러므로, 이 글을 말고도 별도로 인텔 문서 및 구글링을 통해 좀 더 자세히 알아볼 것을 권한다. 그럼 이제 아래의 내용을 보자.

    Whenever a call gate is used to transfer program control to a more privileged nonconforming code segment (that is, when the DPL of the nonconforming destination code segment is less than the CPL), the processor automatically switches to the stack for the destination code segment’s privilege level. This stack switching is carried out to prevent more privileged procedures from crashing due to insufficient stack space. It also prevents less privileged procedures from interfering (by accident or intent) with more privileged procedures through a shared stack.
    ....
    - 참고 : 5.8.5 Stack Switching

    : 스택 스위칭은 권한이 낮은 쪽에서 권한 높은쪽으로 액세스 하려고 할 때 발생한다. 스택 스위칭은 목적은 명확하다.

    첫 번째, 부족한 스택의 공간을 더 늘리려는 목적이 있다. 용량이 부족하면, 다른 프로세스의 스택을 침범할 우려가 있기 때문이다.

    두 번째, 스택 스
    악의적이든 아니든 유저 프로세스가 커널 프로세스 
    낮은 쪽에서 권한이 높은 프로세스의 스택을 공유할 수 있기 때문이다. 

     

     

    : 앞에 내용을 토대로 본다면, 권한이 같다면 스택을 공유한다는 소리가 된다. 즉, `유저 <-> 유저`, `커널 <-> 커널`은 스택 스위칭이 발생하지 않는다. 

     

    이러한 상당히 큰 악용의 소지가 존재하게 된다. 그래서 유저 영역 애플리케이션이 커널 영역을 침범할 수 없도록, 유저 영역 애플리케이션들은 무조건 링 레벨 3을 갖게 된다. 커널 영역은 무조건 링 레벨 0을 갖는다. 이러면, 무조건 스택 스위칭이 발생하게 되서 메모리를 공유할 수가 없게 된다 .

     

    : TSS에는 각 권한에 따른 4개의 스택 세그먼트(SS0, SS1, SS2, SS3) 및 스택 포인터(ESP0, ESP1, ESP2, ESP3)의 주소를 명시할 수 있다. 현대의 대부분의 운영체제들은 `0`과 `3`의 권한만을 사용한다. `3`은 유저 레벨 애플리케이션으로 고정이다. `0`은 운영 체제가 사용한다.

     

    : 스택 스위치가 발생한 시점에서 권한 레벨 3코드가 실행되고 있었으면, 호출되는 프로시저(권한이 더 높은 프로시저)의 스택에 권한 레벨 3 코드의 스택(SS3, ESP3)이 저장된다.

    Each task must define up to 4 stacks: one for applications code (running at privilege level 3) and one for each of the privilege levels 2, 1, and 0 that are used. (If only two privilege levels are used [3 and 0], then only two stacks must be defined.) Each of these stacks is located in a separate segment and is identified with a segment selector and an offset into the stack segment (a stack pointer).

    The segment selector and stack pointer for the privilege level 3 stack is located in the SS and ESP registers, respectively, when privilege-level-3 code is being executed and is automatically stored on the called procedure’s stack when a stack switch occurs.

    Pointers to the privilege level 0, 1, and 2 stacks are stored in the TSS for the currently running task (see Figure 7-2). Each of these pointers consists of a segment selector and a stack pointer (loaded into the ESP register). These initial pointers are strictly read-only values. The processor does not change them while the task is running. They are used only to create new stacks when calls are made to more privileged levels (numerically lower privilege levels).

    These stacks are disposed of when a return is made from the called procedure. The next time the procedure is called, a new stack is created using the initial stack pointer. (The TSS does not specify a stack for privilege level 3 because the processor does not allow a transfer of program control from a procedure running at a CPL of 0, 1, or 2 to a procedure running at a CPL of 3, except on a return.)
    ....
    - 참고 : 5.8.5 Stack Switching

    : `TSS` 자료 구조에 권한 레벨 0, 1, 2의 스택 정보들을 저장해야 한다. 만약, `0`과 `3`만 사용한다면 TSS에 SS0, ESP0은 반드시 저장해야 한다. 중요한 부분이 하나있다. SS3, ESP3는 TSS에 저장하지 않는것인가? 맞다. x86에서는 명시적으로 높은 권한에서 낮은 권한으로 코드 점프가 불가능하다. 즉, `0` 레벨 코드에서 `3` 레벨 코드에 `CALL`, `JMP` 명령어를 실행할 수가 없다. 이 말은 x86 기반의 현대 운영체제에서 TSS가 사용되는 경우는 레벨 `3` 에서 레벨 `0`으로 갈 때만 사용된다는 것과 같다. 그리고 스택 스위칭 발생 후, 새로 생성된 스택은 `레벨 0` 코드에서 반환(`RET`, `IRET` 명령어를 사용)하면 자동으로 정리가 된다. 아래는 스택 스위칭 과정을 설명한다.

    ....
    When a procedure call through a call gate results in a change in privilege level, the processor performs the following steps to switch stacks and begin execution of the called procedure at a new privilege level:

    1. Uses the DPL of the destination code segment (the new CPL) to select a pointer to the new stack (segment selector and stack pointer) from the TSS.

    2. Reads the segment selector and stack pointer for the stack to be switched to from the current TSS. Any limit violations detected while reading the stack-segment selector, stack pointer, or stack-segment descriptor cause an invalid TSS (#TS) exception to be generated.

    3. Checks the stack-segment descriptor for the proper privileges and type and generates an invalid TSS (#TS) exception if violations are detected.

    4. Temporarily saves the current values of the SS and ESP registers.

    5. Loads the segment selector and stack pointer for the new stack in the SS and ESP registers.

    6. Pushes the temporarily saved values for the SS and ESP registers (for the calling procedure) onto the new stack (see Figure 5-13).

    7. Copies the number of parameter specified in the parameter count field of the call gate from the calling procedure’s stack to the new stack. If the count is 0, no parameters are copied.

    8. Pushes the return instruction pointer (the current contents of the CS and EIP registers) onto the new stack.

    9. Loads the segment selector for the new code segment and the new instruction pointer from the call gate into the CS and EIP registers, respectively, and begins execution of the called procedure.
    ....

    - 참고 : 5.8.5 Stack Switching

    '프로젝트 > 운영체제 만들기' 카테고리의 다른 글

    Higher Half Kernel  (0) 2023.06.06
    [x86] 인터럽트  (0) 2023.06.05
    C 런타임  (2) 2023.06.04
    크로스 컴파일[작성중]  (0) 2023.06.03
    GCC[작성중]  (0) 2023.06.02
Designed by Tistory.