what is kernel ring

커널링(“ring”)은 CPU 권한 레벨(Privilege level) 개념이고, x86에서는 보통 Ring 0 = 커널, Ring 3 = 유저만 실사용해요. 핵심은 “지금 실행 중인 코드가 어떤 권한(CPL)로 돌고 있냐”가 바뀐다는 겁니다.

1) 커널링(Ring) = CPL(Current Privilege Level)

  • x86은 전통적으로 Ring 0~3(4단계) 권한을 정의해요.
  • 현재 권한 레벨은 CPL이라고 부르며, 실질적으로는 CS 셀렉터의 하위 2비트로 표현돼요(0~3).
  • Ring 3(유저) 에서는 특권 명령(예: cli, 특정 mov %crX, wrmsr, …)이나 커널 전용 페이지 접근이 막혀요.
  • Ring 0(커널) 은 그 제한이 풀린 상태.

간단 표로 정리하면:

링CPL보통 용도가능한 것(대표)
Ring 00커널커널 페이지 접근, 특권 명령(일부), 인터럽트/예외 처리
Ring 33유저 프로그램유저 페이지 접근, syscall로 커널 서비스 요청

(참고: Ring 1/2는 현대 OS에서 거의 안 씀)

2) Ring 전환은 언제 일어나나? (User → Kernel / Kernel → User)

User → Kernel 로 들어갈 때

대표적으로:

  • syscall / sysenter
  • 인터럽트/예외(페이지 폴트 등)

이때 CPU는 권한을 CPL=0으로 바꾸고, 커널이 지정해둔 커널 스택으로 옮겨 탄 뒤, 유저 상태를 커널 스택에 저장하는 식으로 들어옵니다(구체는 경로마다 다르지만 큰 흐름은 동일).

Kernel → User 로 나갈 때

대표적으로:

  • sysret
  • iretq

특히 iretq는 스택에 저장된 “돌아갈 컨텍스트”를 팝(pop)해서 복귀하는 구조라서, “스택에 무엇이 어떤 순서로 있냐”가 매우 중요해요.

3)

iretq

와 “왜 트랩 프레임(trap_frame)을 만들까?”

질문에서 말한 struct trap_frame 같은 건 보통:

  • 커널에서 유저로 “정상 복귀”할 때 필요한 레지스터 상태를 메모리에 만들어둔 스택 프레임 형태로 저장하려는 목적이에요.
  • iretq가 그 프레임을 그대로 읽어서 RIP/CS/RFLAGS/(RSP/SS)를 복원하니까요.

중요 포인트: 권한 레벨이 바뀌는 복귀(커널→유저)일 때는 iretq가 더 많은 값을 팝합니다.

AMD 매뉴얼 기준으로, iret(64-bit에서 iretq)는:

  • 같은 특권 레벨로 복귀할 때는 RIP, CS, RFLAGS를 팝하고,
  • 더 낮은 특권 레벨(예: CPL0→CPL3)로 복귀할 때는 추가로 RSP, SS까지 팝해요.

즉 커널→유저로 나갈 프레임이면 스택에는 (개념적으로) 이런 게 있어야 해요:

  • RIP, CS, RFLAGS, RSP, SS (순서/사이즈 정확히)

4)

swapgs

는 정확히 뭐고, “GS가 무슨 영역이냐?”

GS는 “메모리 영역”이 아니라 “세그먼트 레지스터”

gs 자체는 레지스터(셀렉터)고, 실제 주소 계산에 쓰이는 건 숨은(hidden) 베이스 값(GS.base) 입니다.

64-bit 모드에서 FS/GS는 특별 취급이라:

  • 일반 세그먼트처럼 “limit 체크”를 하기보단,
  • FS.base / GS.base라는 64-bit 베이스 값을 주소 계산에 더하는 방식으로 주로 쓰여요(TLS, per-cpu 등).

swapgs

의 역할

swapgs는 GS.base ↔ KernelGSbase(MSR) 를 스왑합니다.

AMD 매뉴얼 설명 그대로 요약하면:

  • 커널은 빠르게 커널 데이터(per-cpu 같은)에 접근하려고 GS를 쓰고,
  • 유저모드에서도 GS를 TLS 등으로 쓸 수 있으니,
  • 커널 진입/복귀 때 swapgs로 “지금 GS.base가 어느 쪽(유저/커널) 것을 가리키는지”를 바꿔 주는 거예요.

그래서 pwndbg에서 reg gs가 0으로 보여도 이상하지 않습니다:

  • 그건 “셀렉터 값”이고,
  • 진짜 중요한 건 gs_base / k_gs_base 같은 베이스 값이에요.

    즉 “GS=0인데 swapgs 됐냐?” 판단은 gs_base 쪽을 봐야 합니다.

5) “유저/커널 주소공간이 스레드마다 같냐?”에 대한 큰 그림

  • 같은 프로세스의 스레드들: “유저 가상주소공간”을 공유합니다. 그래서 코드 섹션 주소(예: 0x401010)도 동일한 의미를 가져요.
  • 다른 프로세스: 가상주소 숫자는 같아도, CR3(페이지 테이블 베이스)가 다르면 그 가상주소가 가리키는 물리 페이지가 달라질 수 있어요. 그래서 “다른 프로세스인데 0x401010 내용이 다르다”가 가능합니다.
  • 문맥 전환(context switch) 때 커널이 “프로세스가 바뀌면” 보통 CR3를 바꿔서 다른 주소공간으로 전환합니다.

그리고 커널 영역이 “항상 모두에게 같은가?”는 커널 설정에 따라 달라요:

  • 전통적으로는 커널이 모든 프로세스 주소공간 상단에 매핑되어 “공유”되는 형태가 흔했지만,
  • KPTI 같은 완화가 켜져 있으면 유저/커널 페이지 테이블을 분리해서 커널 진입 시 다른 CR3로 바꾸는 방식도 씁니다(환경 따라 다름).

원하면, 네가 지금 찍어준 레지스터 덤프(예: cs=0x33, ss=0x2b, cr3=...)를 기준으로 **“현재 CPL이 0인지 3인지”, “지금 gs_base/k_gs_base가 어떤 의미인지”, “iretq가 어떤 프레임을 기대하는지”**를 그 자리에서 딱 맞춰서 같이 해석해줄게.