거북이의 쉼터

(2022.02.03) User Memory Access 구현 본문

코딩 삽질/KAIST PINTOS (CS330)

(2022.02.03) User Memory Access 구현

onlim 2022. 2. 3. 15:13

1. 서론 및 필요 내용 설명

지난 포스팅에 어느 정도 설명을 하긴 했지만 syscall을 할 때 유저가 넣어주는 주소값은 검증이 필요하다. 유저가 악의적으로 잘못된 주소나 커널 주소를 syscall에 넣어 의도치 않은 동작을 끌어낼 수 있기 때문이다. 따라서 syscall을 실행함에 앞서 유저가 넣어준 메모리 주소값에 대한 검증을 오늘 구현하려고 한다.

 


2. 구현해야 하는 것

  • 주어진 uaddr이 유효한지 검사하는 함수
  • uaddr과 범위 n이 주어졌을 때 해당 범위가 모두 유효한 주소값인지 검사하는 함수

 


3. 구현 과정

우선 주어진 uaddr이 유효한지 검사하는 함수를 구현하자. 매핑이 안되어 있는 주소는 threads/mmu.c에 있는 pml4_get_page를 통해 검증할 수 있다.

/* Looks up the physical address that corresponds to user virtual
 * address UADDR in pml4.  Returns the kernel virtual address
 * corresponding to that physical address, or a null pointer if
 * UADDR is unmapped. */
void *
pml4_get_page (uint64_t *pml4, const void *uaddr)

pml4에 대한 자세한 설명은 프로젝트 3을 하면서 다시 볼 것이지만 대략적으로 설명하자면 pml4는 가상 주소를 실제 물리 주소로 변환해주는 역할을 한다. 해당 변환은 각 실행 process 마다 분리되어 있어 다른 process가 현재 process의 메모리에 접근하지 못하도록 하는 기능도 포함하며, 상대적으로 적은 물리주소공간을 활용해 유저가 넓은 가상주소공간을 활용하고 있다는 가상화 기능 (Virtualization)도 제공한다. 아무튼 이러한 pml4를 참조하면 현재 사용하려고 하는 가상 주소가 실제 물리 주소와 매핑이 되어있는지 여부를 판단할 수 있다. 프로젝트 2까지는 매핑되지 않은 주소로의 접근은 모두 비정상적인 접근이므로, 전부 차단하면 된다. pml4의 정보는 thread_current()의 pml4 멤버로 참조할 수 있으며, uaddr은 유저 프로세스가 참조하려는 주소값이다.

 

다음으로, kernel에 접근하려는 시도는 include/threads/vaddr.h에 있는 is_user_vaddr()과 is_kernel_vaddr() 매크로를 활용하여 검증한다. 이름 그대로 KERN_BASE 이상인지 아닌지를 검사하는 단순한 매크로이다.

/* Returns true if VADDR is a user virtual address. */
#define is_user_vaddr(vaddr) (!is_kernel_vaddr((vaddr)))

/* Returns true if VADDR is a kernel virtual address. */
#define is_kernel_vaddr(vaddr) ((uint64_t)(vaddr) >= KERN_BASE)

참고로 pml4_get_page() 내부에서 인자로 주어지는 uaddr에 대해 is_user_vaddr()인지 ASSERT를 하고 있기에 순서는 다음과 같이 맞춰서 코딩한다.

void
is_valid_uaddr(void *uaddr)
{
	if (is_kernel_vaddr(uaddr) || pml4_get_page(thread_current()->pml4, uaddr) == NULL)
		exit(-1);
}

exit(-1)을 해주는 이유는 test case를 보면 비정상적 접근에 대해 일괄적으로 

/* This should terminate the process with a -1 exit code. */

라고 명시하고 있기 때문이다. 이제 범위 유효 검사 함수를 작성하자.

 

가장 간단하게 하려면 for문을 돌면서 범위 내에 해당하는 모든 주소값에 대해 is_valid_uaddr() 검사를 실행하면 된다. 나중에 효율성 문제가 발견이 되면 그때 수정하기로 하고 일단은 가장 간단한 형태로 구현해놓자.

void
is_valid_uaddr_region(void *uaddr, unsigned range)
{
	unsigned i;
	for (i = 0; i < range; i++)
		is_valid_uaddr(uaddr + i);
}

 


4. 디버깅

오늘 구현한 사항은 실행 및 디버깅 방법이 아직 없다. 왜냐하면 syscall_handler를 통해 syscall 호출 과정을 구현해야 비로소 유저 프로그램의 동작이 가능하기 때문이다. 따라서 해당 내용은 syscall_handler를 통해 각 syscall을 구현할 때 디버깅하도록 하겠다. 

 


5. 후기

아직까진 괜찮다.. 다음에 다가올 노가다가 심히 염려될 뿐...... 히히히히

Comments