Git book User memory 번역
User memory access
시스템 콜의 일부로서, 커널은 종종 사용자 프로그램이 제공한 포인터를 통해 메모리에 접근해야 합니다. 커널은 이를 매우 신중하게 처리해야 합니다. 사용자가 null 포인터, 매핑되지 않은 가상 메모리를 가리키는 포인터, 또는 커널 가상 주소 공간(KERN_BASE 이상)을 가리키는 포인터를 전달할 수 있기 때문입니다. 이러한 모든 유형의 잘못된 포인터는 문제를 일으킨 프로세스를 종료하고 해당 리소스를 해제하여, 커널이나 다른 실행 중인 프로세스에 피해를 주지 않고 거부되어야 합니다.
유저모드 프로그램이 유저 메모리에서 접근하지못하는 주소로 커널을 통해 접근하려고할 수가 있다. 커널은 자기 자신을 보호하기 위한 수단으로 시스템 콜을 활용한다 그런데 유저가 잘못된 주소로 시스템 콜을 실행시키려하면 커널을 이것을 막아야한다. 막기 위한 수단으로는 두가지 방법이 존재한다.
- 사용자가 제공한 포인터의 유효성을 검증한 다음 역참조하는 방법 (check_address)
- 사용자 포인터가 KERN_BASE 아래를 가리키는지만 확인한 후 역참조하는 것 (page_fault)
check_address
사용자 제공 포인터의 유효성 검사 후 역참조: 사용자가 제공한 포인터의 유효성을 검증하는 방법은 간단하다.
- null 포인터
- 매핑되지 않은 가상 메모리를 가리키는 포인터
- 커널 가상 주소 공간(KERN_BASE 이상)을 가리키는 포인터
이 세가지 경우만 고려하면 된다. 이경우 포인터를 역참조하기 전에 유효한 포인터인지 검사한다. thread/mmu.c와 include/threads/vaddr.h에서 제공하는 함수를 사용하면 된다. 하지만 이 방법은 구현이 간단하지만 성능 오버헤드가 있을 수 있다.
구현
/*User memory access는 이후 시스템 콜 구현할 때 메모리에 접근할 텐데,
이때 접근하는 메모리 주소가 유저 영역인지 커널 영역인지를 체크*/
void check_address (void *addr){
struct thread *t = thread_current();
/* 포인터가 가리키는 주소가 유저영역의 주소인지 확인
* 포인터가 가리키는 주소가 유저 영역 내에 있지만
* 페이지로 할당하지 않은 영역일수도 잇으니 체크 */
if (!is_user_vaddr(addr) || addr == NULL || pml4_get_page(t->pml4, addr) == NULL){
exit(-1); // 잘못된 접근일 경우 프로세스 종료
}
}
Page_fault
두번째 방법은 사용자 포인터가 KERN_BASE 아래를 가리키는지만 확인한 후 역참조하는 것이다. 이렇게 되면 주소가 포인터가 커널 메모리 영역을 가리키지 않는지만 확인한다. 그 이외에는 잘못된 포인터로 인한 페이지 폴트를 처리하기 위해 page_fault()를 발생시켜 page_fault 함수에서 에러를 핸들링 하는것이다. 이 방법은 MMU를 활용해 매우 빠르게 수행이된다. 대부분의 운영체제에서 채택하고 있는 방법이다.
page_fault는 어떤 오류에 의해 발생한것인지 오류를 반환하지 않아 약간의 어셈블리 코드를 이용해 이를 사용할 수 있다.
/* Reads a byte at user virtual address UADDR.
* UADDR must be below KERN_BASE.
* Returns the byte value if successful, -1 if a segfault
* occurred. */
static int64_t
get_user (const uint8_t *uaddr) {
int64_t result;
__asm __volatile (
"movabsq $done_get, %0\n"
"movzbq %1, %0\n"
"done_get:\n"
: "=&a" (result) : "m" (*uaddr));
return result;
}
/* Writes BYTE to user address UDST.
* UDST must be below KERN_BASE.
* Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte) {
int64_t error_code;
__asm __volatile (
"movabsq $done_put, %0\n"
"movb %b2, %1\n"
"done_put:\n"
: "=&a" (error_code), "=m" (*udst) : "q" (byte));
return error_code != -1;
}
구현
static void
page_fault (struct intr_frame *f) {
bool not_present; /* True: not-present page, false: writing r/o page. */
bool write; /* True: access was write, false: access was read. */
bool user; /* True: access by user, false: access by kernel. */
void *fault_addr; /* Fault address. */
/* Obtain faulting address, the virtual address that was
accessed to cause the fault. It may point to code or to
data. It is not necessarily the address of the instruction
that caused the fault (that's f->rip). */
fault_addr = (void *) rcr2();
/* Turn interrupts back on (they were only off so that we could
be assured of reading CR2 before it changed). */
intr_enable ();
/* Determine cause. */
not_present = (f->error_code & PF_P) == 0;
write = (f->error_code & PF_W) != 0;
user = (f->error_code & PF_U) != 0;
// printf("not_present:%d write:%d user:%d\n", not_present, write, user);
// 사용자 모드에서 발생한 페이지 폴트인 경우
if (user) {
// 잘못된 사용자 포인터로 인한 폴트 처리
if (!is_user_vaddr(fault_addr) || fault_addr == NULL) {
exit(-1); // 프로세스 종료
}
}
// 커널 모드에서 발생한 페이지 폴트인 경우
else {
// get_user() 또는 put_user() 함수 내에서 발생한 폴트인 경우
if (f->rip == (uint64_t)get_user || f->rip == (uint64_t)put_user) {
f->R.rax = -1; // 오류 코드 설정
f->rip = f->rsp; // 함수 리턴 주소 설정
return;
}
}
...
}
사용방법
case SYS_WRITE:
{
int fd = f->R.rdi;
const void *buffer = f->R.rsi;
unsigned size = f->R.rdx;
// 버퍼 주소 유효성 검사
if (!is_user_vaddr(buffer)) {
exit(-1);
}
// 버퍼에서 데이터 읽어오기
char *kernel_buffer = palloc_get_page(0);
if (kernel_buffer == NULL) {
exit(-1);
}
for (unsigned i = 0; i < size; i++) {
int byte = get_user(buffer + i);
if (byte == -1) {
palloc_free_page(kernel_buffer);
exit(-1);
}
kernel_buffer[i] = (char)byte;
}
// 시스템 콜 처리
f->R.rax = write(fd, kernel_buffer, size);
// 커널 버퍼 해제
palloc_free_page(kernel_buffer);
}
break;
테스트 통과
pass tests/userprog/create-bad-ptr
pass tests/userprog/open-bad-ptr
pass tests/userprog/read-bad-ptr
pass tests/userprog/write-bad-ptr
pass tests/userprog/bad-read
pass tests/userprog/bad-write
pass tests/userprog/bad-read2
pass tests/userprog/bad-write2
pass tests/userprog/bad-jump
pass tests/userprog/bad-jump2
참고
https://casys-kaist.github.io/pintos-kaist/project2/introduction.html
GitHub - hyunS00/pintos-kaist: 크래프톤 정글 5기 Pint OS
크래프톤 정글 5기 Pint OS. Contribute to hyunS00/pintos-kaist development by creating an account on GitHub.
github.com
'운영체제' 카테고리의 다른 글
[PintOS] Project3-0 가상메모리는 DRAM의 추상화다 (0) | 2024.06.02 |
---|---|
[PintOS] Project2-3 System calls: File System Call (1) | 2024.05.26 |
[PintOS] Project2-1 Passing the arguments and creating a thread (2) | 2024.05.22 |
[PintOS] Project2-0 Background (2) | 2024.05.22 |
[PintOS] Project1-2 Alarm Clock (1) | 2024.05.20 |