Chapter 20

PMA·PMP·ePMP: 물리 메모리 보호

19장의 가상 메모리는 운영체제가 응용 프로그램끼리를 떼어 놓는 장치다. 그런데 운영체제 자신이 건드려서는 안 되는 메모리, 이를테면 M 모드 펌웨어나 보안 모니터는 누가 지킬까? 답은 M 모드가 물리 주소 공간에 직접 치는 울타리, 곧 물리 메모리 보호(PMP)다. 이 장에서는 하드웨어가 정해 둔 주소별 성질인 PMA, M 모드가 설정하는 PMP, M 모드 자신까지 묶는 Smepmp(ePMP), 그리고 주소 윗비트를 태그로 쓰게 해 주는 포인터 마스킹을 차례로 살펴본다.

왜 물리 메모리에 울타리가 필요한가

17장에서 보았듯이 RISC-V 하트는 M·S·U 세 특권 모드 가운데 하나에서 돈다. 리눅스 같은 운영체제는 S 모드에서, 그 아래 펌웨어(OpenSBI 같은 SBI 구현)는 M 모드에서 돈다. S 모드 운영체제는 페이지 테이블로 어떤 물리 페이지든 자기 주소 공간에 매핑할 수 있다. 그렇다면 운영체제가 실수로, 혹은 공격을 받아 펌웨어의 코드나 비밀 키가 있는 물리 주소를 매핑해 버리면 어떻게 막을까? 페이지 테이블은 운영체제가 직접 쓰는 것이므로 이 문제를 풀 수 없다. 운영체제보다 더 높은 곳에서, 물리 주소를 기준으로 검사하는 장치가 필요하다.

그 장치가 물리 메모리 보호(PMP, Physical Memory Protection)다. PMP는 하트마다 있는 M 모드 전용 CSR 묶음으로, 물리 주소 영역별로 읽기·쓰기·실행 권한을 정한다. 이와 나란히, 하드웨어가 처음부터 정해 둔 주소별 성질을 검사하는 물리 메모리 속성(PMA, Physical Memory Attributes) 검사기가 있다. 두 검사기는 주소 변환이 끝난 물리 주소를 두고 동시에 판정한다.

하트 load · store · fetch 주소 변환 가상 → 물리 (19장) M 모드·Bare면 그대로 PMA 검사기 “이 주소는 무엇인가” 플랫폼이 정한 속성 PMP 검사기 “누가 써도 되는가” M 모드가 설정 AND 메모리·장치 접근 수행 접근 오류(access fault) 예외 코드 1 · 5 · 7 (18장) 하나라도 거부
그림 20-1. 메모리 접근 한 번이 거치는 검사. 가상 주소는 먼저 물리 주소로 바뀌고(M 모드와 Bare 모드에서는 그대로), 그 물리 주소를 PMA 검사기와 PMP 검사기가 나란히 판정한다. 둘 다 통과해야 접근이 일어나며, 하나라도 거부하면 명령어·load·store 접근 오류 예외가 된다.

세 가지 검사 장치는 질문이 서로 다르다. 표로 정리하면 다음과 같다.

PMAPMP페이지 기반 가상 메모리
질문이 물리 주소에 그런 접근이 가능한가지금 모드가 이 물리 주소에 접근해도 되는가이 가상 주소는 어느 물리 주소이며 권한은 무엇인가
누가 정하나칩·보드 설계 (대부분 고정)M 모드 소프트웨어 (pmpcfg·pmpaddr)S 모드 운영체제 (페이지 테이블, satp)
모드에 따라달라지지 않는다S·U 모드에 적용, M 모드는 선택적S·U 모드 (M 모드는 변환 없음)
위반 시접근 오류 (정밀 트랩 권장)접근 오류 (항상 정밀 트랩)페이지 폴트 (코드 12·13·15)
M과 U만 있는 칩에서도 쓸모가 있다

특권 규격은 M·U 두 모드만 있는 “보안 임베디드 시스템” 구성을 따로 꼽는다. 가상 메모리가 없는 마이크로컨트롤러에서는 PMP가 U 모드 응용 프로그램을 가두는 유일한 장치다. M 모드만 있는 칩에서도 PMP의 잠금 기능으로 약간의 보호(예: 부트 ROM을 실수로 덮어쓰지 않기)를 얻을 수 있다.

PMA: 주소마다 타고난 성질

물리 주소 공간은 균일하지 않다. 어떤 구간은 DRAM이고, 어떤 구간은 UART 같은 장치의 제어 레지스터이며, 어떤 구간은 비어 있다. 장치 레지스터는 읽기만 해도 값이 바뀌기도 하고(수신 버퍼에서 한 글자를 꺼내는 읽기), 원자적 연산을 지원하지 않기도 한다. 규격은 주소 구간마다 가진 이런 성질을 PMA(Physical Memory Attributes)라 부르고, 그것을 검사하는 하드웨어를 PMA 검사기라 부른다.

다른 ISA는 이런 속성 일부를 페이지 테이블 항목에 넣기도 한다. RISC-V는 플랫폼 고유 정보가 가상화 계층(페이지 테이블)으로 새어 들어가지 않도록 PMA를 별도의 하드웨어 구조로 분리했다. 대부분은 설계 때 고정되어 PMA 검사기에 배선되고, 실행 중 바꿀 수 있는 속성은 플랫폼 고유의 메모리 맵 레지스터로 설정한다.

가상의 예시 SoC — 주소 값은 설명용이다 종류 R / W / X 원자적 연산 캐시 · 멱등성 0x0000_1000부트 ROM 주 메모리R · – · X없음캐시 가능 · 멱등 0x0200_0000타이머 · IPI 레지스터 I/OR · W · –AMONone캐시 불가 · 강한 순서 0x0C00_0000인터럽트 컨트롤러 I/OR · W · –AMONone읽기 부작용 있음 0x1000_0000UART I/OR · W · –AMONone비멱등 (읽으면 꺼냄) 0x8000_0000DRAM 주 메모리R · W · XAMOArithmetic캐시·일관성 · 멱등
그림 20-2. 가상의 예시 SoC에서 물리 주소 구간별 PMA. 같은 load 명령이라도 DRAM에서는 언제든 다시 읽어도 되지만, UART 수신 레지스터에서는 읽을 때마다 글자가 하나씩 빠져나가므로 투기적(speculative) 읽기를 해서는 안 된다. 이런 차이를 하드웨어에 알려 주는 것이 PMA다.
PMA 종류규격이 정하는 내용
주 메모리 vs I/O가장 중요한 구분. 주 메모리는 정해진 성질을 모두 갖춰야 하고, 장치 스크래치패드처럼 그 성질을 못 갖춘 구간은 I/O로 분류한다. 접근할 수 없는 구간도 “R·W·X가 없는 I/O”로 표현한다.
접근 종류지원하는 접근 폭(바이트 ~ 버스트), 정렬 안 된 접근 지원 여부, 명령어 가져오기 가능 여부, 하드웨어 페이지 테이블 읽기·쓰기 가능 여부.
원자성 (AMO)AMONone < AMOSwap < AMOLogical(and·or·xor 추가) < AMOArithmetic(A 확장 전부). Zacas가 AMOCASW·AMOCASD·AMOCASQ 단계를 더한다.
원자성 (예약)LR/SC 지원 정도: RsrvNone, RsrvNonEventual(되긴 하지만 결국 성공 보장 없음), RsrvEventual.
정렬 안 된 원자성 단위MAGnn: 접근한 바이트가 모두 같은 nn바이트 정렬 구간 안에 있으면 정렬 예외 없이 원자적으로 실행된다.
메모리 순서주 메모리는 RVWMO 또는 RVTSO. I/O는 느슨한 순서 또는 강한 순서이며, 강한 순서 구간은 순서 채널 번호를 가진다(채널 0: 그 구간 하나만, 채널 1: 모든 I/O 구간에 걸친 전역 순서).
일관성 · 캐시 가능캐시 가능 여부는 소프트웨어에 보이는 동작을 바꾸지 않아야 하므로 M 모드만 관리하는 플랫폼 설정으로 다룬다. 캐시 불가 구간의 접근은 캐시가 아니라 메모리 자체가 답해야 한다.
멱등성주 메모리는 멱등이라고 가정한다. I/O는 읽기·쓰기 멱등성을 따로 정한다. 비멱등 구간에는 투기적·중복 접근을 해서는 안 된다.
PMA 위반은 어떻게 보이나

규격은 PMA 위반을 가능하면 정밀하게 트랩하라고 강하게 권한다. 그러면 명령어·load·store 접근 오류 예외로 나타나며, 페이지 폴트와는 구별된다. 다만 오류 응답으로 장치를 찾아내는 옛 버스처럼 정밀 트랩이 불가능한 경우에는 부정확한 버스 오류 인터럽트로 보고될 수도 있다. 또 정렬 안 된 접근을 비멱등 구간에 하면 정렬 예외 대신 접근 오류를 내는 것이 좋다고 규격은 말한다. 정렬 예외를 받은 트랩 처리기가 작은 접근 여러 번으로 흉내 내면 장치에 부작용이 생기기 때문이다.

페이지 단위로 메모리 종류를 덮어쓰는 Svpbmt 확장은 19장(가상 메모리)에서 다룬다. PMA가 “이 물리 주소의 원래 성질”이라면, Svpbmt는 운영체제가 페이지마다 “이 매핑은 캐시하지 않는 메모리 또는 I/O처럼 다뤄라”라고 덧붙이는 장치다.

PMP 엔트리와 CSR 배치

PMP는 엔트리 여러 개로 이루어진다. 엔트리 하나는 8비트 설정 레지스터 pmpNcfg와 주소 레지스터 pmpaddrN의 짝이다. 구현은 엔트리를 0개, 16개, 64개 중 하나만큼 가질 수 있고, 번호가 낮은 엔트리부터 구현해야 한다. 모든 필드는 WARL이라 읽기 전용 0일 수도 있으며, PMP CSR은 M 모드에서만 접근할 수 있다.

pmpNcfg 76543210 L 0 A X W R 잠금예약주소 일치 방식실행쓰기읽기 RV32 pmpcfg0 pmp3cfg31:24 pmp2cfg23:16 pmp1cfg15:8 pmp0cfg7:0 RV64 pmpcfg0 pmp7cfg63:56 pmp6cfg55:48 pmp5cfg47:40 pmp4cfg39:32 pmp3cfg31:24 pmp2cfg23:16 pmp1cfg15:8 pmp0cfg7:0 RV64는 짝수 CSR(pmpcfg0, 2, …, 14)만 쓴다 · 엔트리 8–15는 pmpcfg2에 · 홀수 번호는 불법
그림 20-3. 엔트리 설정 바이트 pmpNcfg와 그 묶음. RV32에서는 CSR 하나에 4개, RV64에서는 8개씩 빽빽하게 담아 문맥 전환 비용을 줄인다. RV64가 pmpcfg1 대신 pmpcfg2를 쓰는 까닭은, 엔트리 8–11의 설정이 RV32와 RV64 모두에서 pmpcfg2[31:0]에 놓이게 하기 위해서다.
CSR주소내용
pmpcfg0 – pmpcfg150x3A0 – 0x3AF엔트리 설정 바이트 묶음 (RV64는 짝수 번호만)
pmpaddr0 – pmpaddr630x3B0 – 0x3EF엔트리 주소 (물리 주소를 4로 나눈 값)
mseccfg / mseccfgh0x747 / 0x757Smepmp·포인터 마스킹 등의 보안 설정 (뒤에서)

pmpaddr: 물리 주소를 4로 나눈 값

주소 레지스터는 물리 주소 그 자체가 아니라 비트 2부터 위쪽을 담는다. RV32에서는 32비트 레지스터가 34비트 물리 주소의 비트 33–2를, RV64에서는 비트 55–2를 담는다(레지스터의 63–54번 비트는 0). 왜 RV32가 34비트일까? Sv32 페이지 테이블이 34비트 물리 주소를 지원하므로 PMP도 XLEN보다 넓은 주소를 다뤄야 하기 때문이다. Sv39·Sv48이 지원하는 56비트 물리 주소가 RV64의 한계가 된 것도 같은 이유다.

물리 주소 (RV32) 33210 address[33:2] [1:0] 버림 RV32 pmpaddr 310 address[33:2] (WARL) RV64 pmpaddr 6354530 0 address[55:2] (WARL)
그림 20-4. pmpaddr 형식. 레지스터 값은 바이트 주소를 오른쪽으로 2비트 민 값이므로, 영역의 실제 바이트 주소는 pmpaddr × 4로 읽어야 한다. 구현된 물리 주소 비트가 더 적으면 남는 비트는 WARL 규칙에 따라 고정된다.

설정 바이트의 R·W·X는 이 엔트리가 각각 읽기·쓰기·명령어 실행을 허용하는지를 뜻한다. R=0, W=1인 조합은 예약되어 있다(쓰기만 되고 읽기는 안 되는 메모리는 의미가 없다). 권한이 없는 접근은 종류에 따라 다른 예외가 된다.

거부된 접근예외mcause 코드
명령어 가져오기 (X 없음)Instruction access fault1
load, LR, 캐시 블록 관리 명령 (R 없음)Load access fault5
store, SC, AMO, cbo.zero (W 없음)Store/AMO access fault7

주소 일치 방식: OFF · TOR · NA4 · NAPOT

설정 바이트의 2비트 A 필드가 pmpaddr를 어떻게 해석할지 정한다.

A이름뜻
0OFF엔트리를 끈다. 아무 주소와도 일치하지 않는다.
1TOR (Top Of Range)바로 앞 엔트리의 주소를 바닥, 자기 주소를 꼭대기로 쓰는 임의 구간 pmpaddr[i-1] ≤ y < pmpaddr[i]
2NA4자연 정렬된 4바이트 구간
3NAPOT자연 정렬된 2의 거듭제곱 크기(8바이트 이상) 구간. 크기는 pmpaddr 하위 비트로 표현
물리 주소 → pmpaddr0 × 4 엔트리 0 (OFF) 엔트리 1: TOR [pmpaddr0, pmpaddr1) pmpaddr1 × 4 엔트리 2: TOR [pmpaddr1, pmpaddr2) pmpaddr2 × 4 엔트리 3: NA4 4바이트 엔트리 4: NAPOT 크기 2ⁿ · 크기로 정렬 TOR의 바닥은 앞 엔트리의 pmpaddr이다 · 앞 엔트리의 A 설정과는 무관하다
그림 20-5. 네 가지 일치 방식. 연속된 TOR 엔트리는 경계값을 공유하므로 N개의 경계로 N−1개의 이웃 구간을 만든다. 엔트리 0이 TOR이면 바닥은 0이다. 꼭대기가 바닥보다 작거나 같으면(pmpaddr[i-1] ≥ pmpaddr[i]) 그 TOR 엔트리는 아무 주소와도 일치하지 않는다.

NAPOT: 꼬리의 1로 크기를 적는다

NAPOT는 기준 주소와 크기를 레지스터 하나에 함께 담는 영리한 방법이다. 크기가 2n이고 기준 주소가 그 크기의 배수라면, 기준 주소의 하위 n비트는 모두 0이라 정보가 없다. 그 자리에 크기를 적는다. pmpaddr의 오른쪽 끝에서부터 연속된 1의 개수 k가 크기를 나타내고, 그 바로 위의 0이 경계 표시다.

pmpaddr 하위 비트 (A = NAPOT) 일치 구간 크기 k = 0 y y y y y y y y y y 0 8 바이트 k = 1 y y y y y y y y y 0 1 16 바이트 k = 2 y y y y y y y y 0 1 1 32 바이트 k = 3 y y y y y y y 0 1 1 1 64 바이트 k = 4 y y y y y y 0 1 1 1 1 128 바이트 크기 = 2^(k+3) 바이트 · pmpaddr = (기준 주소 ≫ 2) | (크기 ÷ 8 − 1)
그림 20-6. NAPOT 인코딩. 꼬리의 1(금색)이 하나 늘 때마다 구간이 두 배가 된다. 파란 y 비트는 기준 주소, 청록색 0은 “여기까지가 크기 표시”라는 경계다. 1이 0개면 8바이트이며, 4바이트 구간은 따로 NA4 모드(A=2)로 표현한다. pmpaddr의 모든 비트가 1이면 RV32에서 235바이트, 즉 34비트 물리 주소 공간 전체를 덮는다.
SIMULATOR

NAPOT 인코더 · 디코더

기준 주소 비트경계 0꼬리의 1 (크기)왼쪽이 최상위, 오른쪽 끝이 비트 0
XLEN
방향
pmpaddr—
꼬리의 1 개수 k—
시작 주소—
끝 주소 (포함)—
크기—
크기 슬라이더를 한 칸씩 움직이며 오른쪽 끝의 금색 1이 하나씩 늘어나는 모습을 보자. 기준 주소를 0x8000_1000처럼 크기의 배수가 아닌 값으로 바꾸면 무슨 일이 생기는지, 디코드 방향에서 0xFFFF_FFFF를 넣으면 몇 바이트가 되는지도 확인해 보자.

우선순위, 일치 규칙, 잠금

엔트리 여럿이 겹칠 수 있으므로 누가 이기는지 정해야 한다. 규칙은 단순하고 정적이다. 접근의 어느 한 바이트라도 일치하는 엔트리 중 번호가 가장 낮은 것이 결과를 정한다. 그런데 그 엔트리가 접근의 모든 바이트를 덮지 못하면, 권한 비트와 상관없이 접근은 실패한다. 영역 경계를 걸친 접근이 두 엔트리의 권한을 반씩 섞어 쓰는 일을 막기 위해서다.

규격 원문에서
“PMP entries are statically prioritized. The lowest-numbered PMP entry that matches any byte of a memory operation determines whether that operation succeeds or fails. The matching PMP entry must match all bytes of a memory operation, or the operation fails, irrespective of the L, R, W, and X bits.”

Machine-Level ISA, Priority and Matching Logic — 규격은 예로 0xC–0xF를 덮는 엔트리가 가장 높은 우선순위일 때 0x8–0xF 8바이트 접근은 실패한다고 든다.

접근: 주소 · 크기 · 종류 · 모드 엔트리 i = 0, 1, 2, … 순서로 한 바이트라도 일치? 모든 바이트가 이 엔트리 안? L = 0 이고 M 모드? 예예 아니오 다음 엔트리 (i + 1) 엔트리 소진 일치 없음 M: 허용 · S/U: 거부 (엔트리가 하나라도 있으면) 아니오 거부 (L·R·W·X 무관) 예 허용 아니오 R · W · X 비트로 결정
그림 20-7. PMP 판정 순서. 번호 순서로 엔트리를 훑다가 처음 걸린 엔트리에서 결정이 끝난다. 아무 엔트리에도 걸리지 않으면 M 모드는 통과하지만 S·U 모드는 실패한다. 그래서 엔트리를 모두 OFF로 두면 S·U 모드의 모든 접근이 실패한다.

L 비트: 잠금이자 “M 모드에도 적용”

L 비트는 두 가지 일을 한다. 첫째, 엔트리를 잠근다. 잠긴 엔트리의 pmpcfg와 pmpaddr에 대한 쓰기는 무시되며, 엔트리가 TOR이면 바닥으로 쓰이는 앞 엔트리의 pmpaddr도 잠긴다. 잠금은 하트가 리셋될 때까지 풀리지 않는다(A가 OFF여도 잠긴다). 둘째, 그 엔트리의 R·W·X를 M 모드에도 강제한다. L=0인 엔트리는 S·U 모드만 제한하고, M 모드 접근은 그 엔트리와 일치해도 무조건 성공한다.

규격의 표현을 빌리면 PMP는 기본적으로 아무 권한도 없는 S·U 모드에 권한을 주는 장치이고, 잠금을 통해 기본적으로 모든 권한을 가진 M 모드에서 권한을 빼앗는 장치다. M 모드 접근 판정에는 한 가지 예외가 더 있다. mstatus.MPRV=1이고 MPP가 S나 U이면 M 모드의 데이터 접근은 MPP 모드의 접근처럼 검사된다(17장). 또 페이지 테이블 워크 같은 암묵적 접근은 S 모드 접근으로 검사된다.

SIMULATOR

PMP 검사기 (RV32, 엔트리 8개)

#Apmpaddr (16진수)RWXL바이트 범위 [시작, 끝)
예시 설정 불러오기
크기
종류
모드
지도 범위
실행
결정한 엔트리—
결과—
예외 (mcause)—
“펌웨어 보호” 설정에서 접근 모드를 S와 M으로 바꿔 보자. 같은 엔트리 0과 일치하는데 결과가 왜 다른가? “부분 일치” 설정에서는 주소 0x8에 크기 8로 접근해 보자. L을 켠 엔트리는 편집할 수 없게 되는데, 이것을 푸는 유일한 방법은 “하트 리셋”이다(리셋은 A와 L 필드를 0으로 만든다).

펌웨어가 PMP를 설정하는 모습

전형적인 부팅 펌웨어는 S 모드 운영체제로 넘어가기 직전에 PMP를 설정한다. 목표는 두 가지다. 자기 자신(코드·데이터·스택)은 S·U 모드에서 보이지 않게 하고, 나머지 물리 메모리는 운영체제가 쓰게 열어 주는 것이다. 엔트리 0이 우선순위가 가장 높다는 점을 이용해, 숨길 영역을 먼저 두고 “나머지 전부”를 마지막 엔트리로 둔다.

# 엔트리 0: 펌웨어 영역. NAPOT, 권한 없음, L=0 → S/U는 거부, M은 통과 li t0, 0x20003fff # (0x80000000 >> 2) | (0x20000/8 - 1) csrw pmpaddr0, t0 # 엔트리 1: 모든 비트가 1인 NAPOT → 물리 주소 공간 전체, RWX li t0, -1 csrw pmpaddr1, t0 # pmp1cfg = 0x1F (A=NAPOT, X, W, R), pmp0cfg = 0x18 (A=NAPOT) li t0, 0x1f18 csrw pmpcfg0, t0 # 페이지 기반 가상 메모리가 있으면 캐시된 변환·PMP 판정을 비운다 sfence.vma zero, zero

몇 가지를 짚어 두자.

잠금은 되돌릴 수 없다

부트 ROM이 L 비트를 켜서 엔트리를 잠그면 이후 펌웨어는 그 엔트리를 고칠 수 없다. M 모드에도 권한이 강제되므로, 잠근 영역에 실행 권한을 빠뜨리면 M 모드 자신도 그 코드를 실행하지 못한다. 잠금은 “리셋 전까지 아무도 이 규칙을 바꿀 수 없다”는 보증이므로, 펌웨어 갱신 경로까지 고려해 신중히 설계해야 한다.

TOR로는 표현할 수 없는 주소

TOR의 꼭대기는 pmpaddr × 4이므로 2XLEN+2번지는 꼭대기로 쓸 수 없다. 예컨대 34비트 물리 주소의 RV32에서 TOR로는 맨 꼭대기 워드를 덜 특권적인 모드에 열어 줄 수 없다. 규격의 비고는 이럴 때 NAPOT를 쓰거나 그 워드를 포기하라고 하면서, 페이지 단위로 메모리를 관리하면 16 GiB 가운데 0.00002%만 잃는다고 덧붙인다.

Smepmp: M 모드도 함부로 실행하지 못하게

기본 PMP에서 잠기지 않은 규칙은 M 모드에 아무 영향이 없다. 즉 M 모드는 S·U 모드의 메모리를 마음대로 읽고 실행할 수 있다. 공격자가 U 모드 메모리에 악성 코드를 심고 M 모드가 그것을 실행하도록 속인다면? 이것이 Smepmp(“enhanced PMP”, 흔히 ePMP) 1.0이 막으려는 위협이다. S 모드에는 이미 sstatus.SUM과 “U 페이지는 S 모드가 실행할 수 없다”는 규칙이 있지만, M 모드에는 그런 장치가 없었다. M과 U만 있는 칩에서는 운영체제 전체가 M 모드에서 돌기 때문에 이 빈틈이 더 크다.

mseccfg 63:34 PMM33:32 31:11 MLPE10 SSEED9 USEED8 7:3 RLB2 MMWP1 MML0 MML = 0 (기본 PMP) M 모드 S/U 모드 펌웨어 영역L=0 · RWX=000 OS·앱 영역L=0 · RWX=111 M이 U 메모리의 코드도 실행할 수 있다 MML = 1 (Smepmp) M 모드 S/U 모드 M 전용 규칙L=1 S/U 전용 규칙L=0 L 비트가 “누구의 영역인가”로 바뀐다
그림 20-8. mseccfg의 비트 배치(위)와 MML 전후의 차이(아래). MML=0에서는 L=0인 규칙을 M 모드가 무시하므로 S·U 메모리를 실행할 수 있다. MML=1이면 L=1은 “M 전용”, L=0은 “S/U 전용”이 되고, 상대 모드의 접근은 예외가 된다. 공유가 필요한 영역은 별도의 인코딩(아래 규칙표)으로 표현한다.
mseccfg 필드비트뜻
MML Machine Mode Lockdown0L 비트의 뜻을 “M 전용(L=1) / S·U 전용(L=0)”으로 바꾸고, RW=01과 LRWX=1111을 공유 영역 인코딩으로 쓴다. 한 번 켜면 PMP 리셋 전까지 끌 수 없다(sticky).
MMWP Machine-Mode Allowlist Policy1일치하는 규칙이 없을 때 M 모드의 기본 정책을 “무시(허용)”에서 “거부”로 바꾼다. 역시 sticky.
RLB Rule Locking Bypass21이면 잠긴 규칙도 지우거나 고칠 수 있다(부팅 중 임시 우회, 디버그용). RLB=0인 상태에서 L=1인 엔트리가 하나라도 있으면 RLB는 0으로 고정된다.
USEED · SSEED · MLPE · PMM8, 9, 10, 33:32Zkr의 seed CSR 접근 허용, Zicfilp의 M 모드 착지 패드, Smmpm 포인터 마스킹(다음 절). Smepmp와는 별개의 필드다.

MML=1일 때 규격은 규칙 하나하나가 M 모드와 S·U 모드에 무엇을 허용하는지를 16칸짜리 규칙표로 정리한다. 아래 탐색기에서 각 칸을 눌러 보자. MML을 0으로 바꾸면 같은 비트가 기본 PMP에서 무엇을 뜻하는지 비교할 수 있다.

EXPLORER

Smepmp 규칙표 탐색기

각 칸: pmpcfg의 L R W X요약 = M · S/U 권한
mseccfg.MML
mseccfg.MMWP
M 모드—
S/U 모드—
MML=1에서 L=0인 0111과 L=1인 1111을 비교해 보자. 또 M 모드가 실행할 수 있는 칸은 몇 개뿐인지 세어 보고, “일치하는 규칙 없음”에서 MMWP를 0과 1로 바꿔 M 모드의 기본 정책이 어떻게 달라지는지 확인하자.
실행 권한을 가진 M 영역은 함부로 만들 수 없다

MML=1이면 실행 권한이 있는 M 전용 규칙(1001, 1101)이나 잠긴 공유 코드 규칙(1010, 1011)을 새로 쓰는 일은 무시되고 pmpcfg가 바뀌지 않는다. RLB=1일 때만(예: 부팅 과정) 이 제한이 풀린다. 그렇지 않으면 공격자가 M 모드 코드 실행 권한을 슬쩍 추가할 수 있기 때문이다.

Smepmp가 있는지 어떻게 아나

MML·MMWP는 켜면 잠기고 RLB는 끄면 잠기므로, 써 보고 읽는 방식으로 존재를 확인하기 어렵다. 그래서 규격은 부트 ROM이 일찍 MMWP나 MML을 켜고 펌웨어로 넘어가면, 펌웨어가 mseccfg를 읽어 확인하는 방식을 기대한다고 적는다.

포인터 마스킹: 주소 윗비트를 태그로

RV64의 64비트 주소 가운데 실제로 메모리를 가리키는 데 쓰는 비트는 Sv48이면 48개, Sv57이면 57개뿐이다. 남는 윗비트에 태그를 넣을 수 있다면 HWASAN 같은 메모리 오류 검출기가 포인터마다 “이 포인터가 가리켜야 할 메모리의 색깔”을 적어 둘 수 있다. 문제는 태그가 붙은 주소로 load하면 주소가 유효하지 않아 예외가 난다는 점이다. 소프트웨어로 매번 태그를 떼어 내면 느리다. 포인터 마스킹(Pointer Masking) 1.0.0은 하드웨어가 명시적 메모리 접근의 주소 윗비트 PMLEN개를 무시하게 해 준다.

PMLEN = 16 (PMM = 11) 6348470 태그 (무시됨) 실제 주소 비트 (Sv48 가상 주소) 비트 47을 부호 확장해 채운다 PMLEN = 7 (PMM = 10) 6357560 태그 실제 주소 비트 (Sv57 가상 주소) 비트 56을 부호 확장 (물리 주소면 0으로)
그림 20-9. 지금 규격이 허용하는 두 PMLEN 값. PMLEN = XLEN − 48 = 16이면 Sv48을, PMLEN = XLEN − 57 = 7이면 Sv57까지 쓸 수 있다. 가상 주소는 위 PMLEN비트를 비트 XLEN−PMLEN−1의 부호 확장으로 바꾸고, 물리 주소(M 모드나 Bare)는 0으로 바꾼다. 물리 주소를 0으로 채우는 까닭은 PMP 같은 물리 주소 장치와의 상호작용 때문이다.
확장무엇을 제어하나설정 위치
SmmpmM 모드 자신의 포인터 마스킹mseccfg.PMM
Smnpm바로 아래 모드 (S 모드가 있으면 S/HS, 없으면 U)menvcfg.PMM
SsnpmU 모드 (H 확장이 있으면 VS·VU 포함)senvcfg.PMM 등
Sspm · Supm“S 모드 / U 모드에서 포인터 마스킹을 쓸 수 있다”는 실행 환경의 약속 (하드웨어 요구 없음, 프로파일용)실행 환경이 제공

PMM 필드는 모두 같은 2비트 WARL 인코딩을 쓴다: 00 끔(PMLEN=0), 01 예약, 10 PMLEN = XLEN−57 (RV64에서 7), 11 PMLEN = XLEN−48 (RV64에서 16). 한 모드의 설정은 바로 위 모드의 CSR에 있다. 포인터 마스킹은 RV64 전용이며 RV32에서 켜려는 쓰기는 반영되지 않는다.

ANIMATION

“무시” 변환 한 비트씩 보기

PMLEN
주소 종류
예시
기준 비트—
유효 주소—
변환된 주소—
“규격 예”는 규격 표에 실린 Sv57·PMLEN=7 예다. 결과가 0xFFFF_FFFF_1234_5678인지, 물리 주소로 바꾸면 0x01FF_FFFF_1234_5678인지 확인하자. 태그 0x2A 포인터에서 PMLEN을 16으로 올리면 비트 47이 0이라 위쪽이 모두 0으로 채워진다.
태그 검사는 이 확장의 몫이 아니다

포인터 마스킹이 하는 일은 태그를 무시하는 것까지다. 태그가 메모리 쪽 태그와 맞는지 확인하는 일은 소프트웨어(또는 앞으로 나올 확장)의 몫이다. 그래도 검사하지 않는 일반 접근에서 매번 태그를 떼어 내지 않아도 되므로 큰 이득이다. 또 리눅스처럼 최상위 비트로 커널·사용자 주소를 구별하던 코드는, 마스킹이 켜지면 비트 XLEN−PMLEN−1을 보아야 한다는 점에 주의해야 한다.

핵심 정리

  1. PMA는 주소 구간마다 하드웨어가 정한 성질(주 메모리/I/O, 접근 폭, 원자성, 순서, 캐시, 멱등성)이고, PMP는 M 모드가 정하는 모드별 접근 권한이다. 둘 다 물리 주소로 나란히 검사하며 위반은 접근 오류(1·5·7)가 된다.
  2. PMP 엔트리는 pmpNcfg(L·A·X·W·R) + pmpaddrN(물리 주소 ≫ 2)이다. 엔트리는 0·16·64개이고 CSR은 M 모드 전용이다. RV64는 짝수 pmpcfg만 쓴다.
  3. A = OFF·TOR·NA4·NAPOT. TOR은 pmpaddr[i-1] ≤ y < pmpaddr[i], NAPOT은 꼬리의 1 k개로 크기 2k+3을 적는다.
  4. 가장 낮은 번호의 일치 엔트리가 결정하고, 그 엔트리가 접근의 모든 바이트를 덮지 못하면 실패한다. 일치가 없으면 M은 통과, S·U는 실패한다.
  5. L 비트는 리셋 전까지 엔트리를 잠그고 권한을 M 모드에도 강제한다. PMP를 바꾼 뒤에는 sfence.vma x0, x0로 동기화한다.
  6. Smepmp의 MML은 L을 “M 전용/S·U 전용”으로 바꿔 M 모드가 S·U 메모리를 실행하지 못하게 하고, MMWP는 일치 없는 M 접근을 거부, RLB는 부팅 중 잠금 우회를 허용한다.
  7. 포인터 마스킹은 RV64에서 명시적 접근 주소의 위 PMLEN(7 또는 16)비트를 무시해 HWASAN 같은 태그 기법을 빠르게 한다.

확인 퀴즈

A=NAPOT인 엔트리의 pmpaddr 하위 비트가 …10111로 끝난다. 이 엔트리가 덮는 구간의 크기는?

꼬리의 1이 3개(k=3)이므로 크기는 23+3 = 64바이트다. 바로 위의 0이 경계 표시다.

엔트리 2가 TOR일 때 일치하는 바이트 주소 y의 범위는?

TOR은 앞 엔트리의 주소를 바닥, 자기 주소를 꼭대기로 쓴다. 앞 엔트리의 A 설정과는 무관하다. pmpaddr는 주소를 4로 나눈 값이므로 바이트 주소로는 × 4다.

PMP 엔트리 16개가 구현되어 있고 모두 A=OFF다. S 모드에서 load를 하면?

엔트리가 하나라도 구현되어 있으면, 일치하는 엔트리가 없는 S·U 모드 접근은 실패한다. load이므로 load 접근 오류다.

엔트리 0이 NA4로 0xC–0xF를 덮고 R·W가 켜져 있다. 이것이 가장 높은 우선순위로 일치할 때, 0x8에서 시작하는 8바이트 load는?

한 바이트라도 일치한 가장 낮은 번호의 엔트리가 결정하며, 그 엔트리가 모든 바이트를 덮지 못하면 L·R·W·X와 무관하게 실패한다. 규격에 실린 예 그대로다.

L=0, RWX=000인 엔트리와 일치하는 M 모드 store의 결과는? (Smepmp의 MML은 0)

L이 0이면 R·W·X는 S·U 모드에만 적용된다. M 모드 접근은 (MPRV로 다른 모드처럼 검사되는 경우가 아니면) 성공한다.

mseccfg.MML=1일 때 LRWX=0111 규칙이 덮는 영역에서 M 모드가 명령어를 가져오면?

MML=1에서 L=0 규칙은 S/U 모드에만 적용되고 M 모드에는 거부된다. 0111은 “S/U 전용 읽기/쓰기/실행 영역”이다.

RV64에서 menvcfg.PMM=11로 S 모드 포인터 마스킹을 켰다. Sv48 가상 주소의 변환에서 위쪽 16비트는 무엇으로 채워지나?

PMLEN=16이면 기준 비트는 XLEN−PMLEN−1 = 47이다. 가상 주소는 그 비트로 부호 확장하고, 물리 주소일 때만 0으로 채운다.

다음 중 모든 특권 모드에서 같은 값이며 대개 칩·보드 설계 때 고정되는 것은?

PMA는 하드웨어의 타고난 성질이라 실행 문맥에 따라 달라지지 않는다. PMP는 M 모드가 설정하며 모드별로 다르게 적용된다.

더 읽을거리