본문으로 이동
  • 현재 모든 문서는 작성 중입니다. 보시는 데 불편함이 있더라도 양해 부탁드립니다.

운영체제 기본/보호: 두 판 사이의 차이

~ldmsys
출처·문체 정리: 커널 원문은 git.kernel.org, em dash는 콜론으로 변경
총론 재작성: 보호 경계와 인자 검증의 필요성, 격리와 통신 비용
1번째 줄: 1번째 줄:
[[운영체제 기본|목차]] · 이전: [[운영체제 기본/추상화|추상화]] · 다음: [[운영체제 기본/프로세스와 스레드|프로세스와 스레드]]
[[운영체제 기본|목차]] · 이전: [[운영체제 기본/추상화|추상화]] · 다음: [[운영체제 기본/프로세스와 스레드|프로세스와 스레드]]


여러 프로그램이 한 컴퓨터에서 실행된다면, 한 프로그램의 실수가 다른 프로그램이나 커널까지 망가뜨리지 않도록 경계를 두어야 한다. Linux에서는 CPU의 권한 모드와 메모리 접근 권한 등을 이용하고, 커널이 자원 접근 요청을 검사한다.
== 약속을 지키지 않는 프로그램 ==
이미지 변환 프로그램이 잘못된 포인터에 결과를 쓴다고 하자. 모든 프로그램이 메모리를 자유롭게 건드릴 수 있다면 이 실수가 편집 중인 문서나 운영체제의 관리 정보까지 훼손할 수 있다. 악의적인 프로그램은 같은 자유를 의도적으로 악용할 수 있다. 프로그램에 올바른 사용법을 알려 주는 것만으로는 이런 접근을 막을 수 없다.


== 사용자 공간과 커널 공간 ==
'''보호'''는 프로그램이 접근할 수 있는 자원과 허용된 연산에 경계를 두고 이를 강제하는 일이다. 파일이나 주소 공간이라는 추상화를 제공하더라도 프로그램이 그 아래의 저장장치나 다른 주소 공간에 직접 접근할 수 있다면 경계는 무너진다. 따라서 자원을 관리하는 운영체제에는 요청을 거부할 수단과, 프로그램이 그 수단을 우회하지 못하게 하는 구조가 함께 필요하다.
일상적으로 Linux라고 부르는 시스템에는 커널뿐 아니라 셸, C 라이브러리, 각종 명령어와 서비스가 함께 들어 있다. 이 중 '''커널'''은 CPU 실행, 메모리 매핑, 장치 접근 등을 관리하는 핵심 부분이다. Bash나 <code>ls</code>는 커널 바깥의 사용자 공간에서 실행되는 프로그램이다.


프로그램이 정수 두 개를 더할 때마다 커널을 호출하는 것은 아니다. 일반적인 계산과 접근이 허용된 메모리의 읽기·쓰기는 사용자 공간에서 진행된다. 파일을 열거나 새 프로세스를 만드는 등 커널의 도움이 필요한 작업에는 '''시스템 호출'''을 사용한다. C에서는 보통 라이브러리 함수를 통해 이 인터페이스를 사용한다. 따라서 C 함수 호출 하나와 시스템 호출 하나가 반드시 일대일로 대응하지는 않는다. [https://man7.org/linux/man-pages/man2/syscalls.2.html syscalls(2)]
보호 규칙 자체도 보호해야 한다. 프로그램이 자신의 접근 권한을 기록한 메모리를 마음대로 수정할 수 있다면 권한 검사는 의미가 없어진다. 규칙을 집행하는 코드와 상태를 보호 대상과 분리해야 한다. [https://web.mit.edu/saltzer/www/publications/protection/Basic.html Saltzer·Schroeder, Basic Principles of Information Protection]


== 실행 권한, 주소, 자원의 경계 ==
하드웨어의 권한 모드를 사용하는 운영체제에서는 일반 프로그램을 제한된 권한으로 실행하고, 메모리 매핑이나 장치 제어처럼 시스템 전체에 영향을 주는 동작은 커널이 담당한다. 사용자 모드에서도 산술 연산과 허용된 메모리 접근은 CPU가 직접 실행한다. 모든 명령을 커널이 대신 실행하면 보호를 위해 지나치게 큰 비용을 치르게 된다.


주소 공간은 접근 가능한 메모리의 범위를 나눈다. MMU를 사용하는 시스템에서는 운영체제가 주소 변환과 접근 권한을 설정하고, 하드웨어가 메모리 접근 때 이를 검사한다. 프로그램이 다른 주소를 계산했다고 해서 해당 메모리에 접근할 권한까지 생기지는 않는다. 잘못된 접근은 예외를 일으킬 수 있고, 운영체제는 이를 처리하거나 해당 프로그램의 실행을 중단한다.


사용자 모드의 프로그램은 임의의 커널 메모리를 읽고 쓰거나, 제한된 CPU 명령을 마음대로 실행할 수 없다. 그렇다고 아무 일도 못 하는 것은 아니다. 일반적인 계산은 CPU에서 직접 실행하며, 커널의 권한이 필요한 작업만 정해진 경로로 요청한다.
자원별 권한은 별도로 판단한다. 사용자 모드라는 이유만으로 모든 파일 접근이 금지되는 것은 아니며, 어떤 파일을 읽을 수 있다고 해서 수정도 허용되는 것은 아니다. CPU 권한 모드는 어떤 종류의 동작을 직접 실행할 수 있는지, 주소 공간은 어떤 메모리에 접근할 수 있는지, 자원 권한은 어떤 대상에 어떤 연산을 허용할지를 나눈다. [https://mit-pdos.github.io/xv6-riscv-book/first.html xv6: Operating system organization, §2.2]


== 시스템 호출은 권한을 넘겨주는 통로가 아니다 ==
== 경계를 넘는 요청도 검증해야 한다 ==
파일을 열 때 프로그램은 경로와 열기 방식을 전달한다. 커널은 경로를 해석하고 접근 조건을 검사한 다음 성공하면 파일 디스크립터를 반환한다. 실패하면 프로그램은 오류를 처리해야 한다. 호출한 프로그램이 잠시 커널의 모든 권한을 얻어 원하는 일을 하는 구조가 아니다.
격리된 프로그램도 파일을 읽고 다른 프로그램과 통신해야 한다. '''시스템 호출'''은 프로그램이 정해진 진입점을 통해 커널에 작업을 요청하는 경로다. 하드웨어가 제어를 커널로 넘기면 커널 코드가 요청을 처리하고 결과를 돌려준다. 호출자는 요청할 작업과 인자를 고를 수 있지만 커널에서 실행할 임의의 명령을 지정할 수는 없다.


C에서 <code>open()</code>이 -1을 반환하면 <code>errno</code>를 확인한다. 예를 들어 <code>ENOENT</code>는 경로의 구성 요소가 없거나 끊어진 심볼릭 링크 등으로 대상을 찾지 못한 경우에 나올 수 있고, <code>EACCES</code>는 필요한 접근 권한이 없는 경우에 나올 수 있다. “열기에 실패했다”는 결과만 보고 파일이 없다고 단정하면 원인을 잘못 찾게 된다. [https://man7.org/linux/man-pages/man2/open.2.html open(2)]
예를 들어 데이터를 읽어 달라는 요청에는 대상, 결과를 저장할 주소, 최대 길이가 들어간다. 커널은 그 대상을 읽을 권한이 있는지와 결과를 돌려줄 메모리 범위가 유효한지 확인해야 한다. 주소와 길이를 합산할 때 정수 범위를 넘는 경우도 고려해야 한다. 호출자가 넘긴 숫자나 포인터를 신뢰하면 커널이 자신의 권한으로 금지된 접근을 대신 수행할 수 있다. [https://mit-pdos.github.io/xv6-riscv-book/trap.html xv6: Traps and system calls, §4.4]


같은 이유로 읽기 전용으로 연 fd에 쓰기를 요청하면 거부된다. 메모리에도 비슷한 제한이 있다. 매핑되지 않은 주소나 허용되지 않은 쓰기 접근은 정상적인 메모리 사용으로 처리되지 않는다. 사용자와 그룹에 따른 파일 권한 검사, CPU 권한 모드, 페이지 접근 권한은 서로 관련되지만 동일한 장치는 아니다.
검사 시점과 사용 시점 사이의 변화도 문제다. 다른 스레드가 인자가 담긴 메모리를 바꿀 수 있다면, 검사한 내용과 실제 처리한 내용이 달라질 수 있다. 필요한 값을 커널의 관리 영역에 복사하거나, 대상의 수명을 유지하고 동기화하는 등 처리 과정 내내 가정이 유지되도록 만들어야 한다.


== 커널 진입과 문맥 교환 ==
== 격리와 협력의 비용 ==
시스템 호출로 커널 코드를 실행하는 것과 다른 스레드에 CPU를 넘기는 것은 구별해야 한다. 요청을 바로 처리할 수 있다면 같은 스레드가 커널 작업을 마치고 사용자 공간으로 돌아갈 수 있다. 반면 데이터가 도착할 때까지 기다려야 하는 호출이라면 해당 스레드가 잠들고 다른 스레드가 실행될 수 있다.
경계를 넘을 때는 실행 상태를 보존하고 복원하며 인자와 권한을 확인하는 비용이 든다. 데이터를 복사해야 할 수도 있다. 다만 커널에 진입했다고 반드시 다른 스레드로 문맥 교환하는 것은 아니다. 요청을 바로 끝내면 같은 스레드가 돌아오고, 기다려야 한다면 다른 스레드에 CPU를 넘길 수 있다.


이 차이는 시스템 호출 비용을 이야기할 때도 중요하다. 호출 횟수만으로 문맥 교환 횟수를 추정할 수 없으며, 호출에 걸린 시간에는 커널 처리뿐 아니라 대기 시간이 포함될 수도 있다.
이미지 처리 기능을 별도 프로세스에 두면 그 기능의 잘못된 메모리 쓰기가 편집기의 메모리를 직접 훼손하는 일을 막을 수 있다. 대신 큰 이미지와 결과를 주고받는 통신 비용이 생긴다. 공유 메모리로 복사를 줄이면 같은 영역에 대한 동기화와 변경 권한을 관리해야 한다. 경계를 나누는 단위와 데이터를 교환하는 방식은 함께 설계해야 한다.
 
'''최소 권한''' 원칙은 각 구성 요소에 필요한 자원과 연산만 허용하여 오류나 침해의 피해 범위를 줄인다. 그러나 허용된 출력 파일에 잘못된 결과를 쓰는 것까지 막아 주지는 않는다. 보호는 프로그램의 정확성, 공유 데이터의 동시성, 장애 후 복구를 모두 보장하는 말이 아니다. 어떤 실패를 경계 안에 가두고 어떤 협력을 허용할지 정하는 설계다.


== 참고 문헌 ==
== 참고 문헌 ==
* [https://man7.org/linux/man-pages/man2/syscalls.2.html syscalls(2)]: 커널 인터페이스와 라이브러리 래퍼.
* [https://web.mit.edu/saltzer/www/publications/protection/Basic.html Jerome H. Saltzer·Michael D. Schroeder, The Protection of Information in Computer Systems (1975), §I]: 최소 권한, 접근 검사와 보호 원칙.
* [https://man7.org/linux/man-pages/man2/open.2.html open(2)]: 파일 열기의 권한 검사와 오류.
* [https://mit-pdos.github.io/xv6-riscv-book/first.html xv6: Operating system organization]: 권한 모드와 격리의 구성.
* [https://man7.org/linux/man-pages/man2/mprotect.2.html mprotect(2)]: 메모리 영역의 접근 보호.
* [https://mit-pdos.github.io/xv6-riscv-book/trap.html xv6: Traps and system calls]: 제어 이전과 사용자 인자의 검증.
 
----
[[운영체제 기본|목차]] · 이전: [[운영체제 기본/추상화|추상화]] · 다음: [[운영체제 기본/프로세스와 스레드|프로세스와 스레드]]

2026년 10월 31일 (토) 06:59 판

목차 · 이전: 추상화 · 다음: 프로세스와 스레드

약속을 지키지 않는 프로그램

이미지 변환 프로그램이 잘못된 포인터에 결과를 쓴다고 하자. 모든 프로그램이 메모리를 자유롭게 건드릴 수 있다면 이 실수가 편집 중인 문서나 운영체제의 관리 정보까지 훼손할 수 있다. 악의적인 프로그램은 같은 자유를 의도적으로 악용할 수 있다. 프로그램에 올바른 사용법을 알려 주는 것만으로는 이런 접근을 막을 수 없다.

보호는 프로그램이 접근할 수 있는 자원과 허용된 연산에 경계를 두고 이를 강제하는 일이다. 파일이나 주소 공간이라는 추상화를 제공하더라도 프로그램이 그 아래의 저장장치나 다른 주소 공간에 직접 접근할 수 있다면 경계는 무너진다. 따라서 자원을 관리하는 운영체제에는 요청을 거부할 수단과, 프로그램이 그 수단을 우회하지 못하게 하는 구조가 함께 필요하다.

보호 규칙 자체도 보호해야 한다. 프로그램이 자신의 접근 권한을 기록한 메모리를 마음대로 수정할 수 있다면 권한 검사는 의미가 없어진다. 규칙을 집행하는 코드와 상태를 보호 대상과 분리해야 한다. Saltzer·Schroeder, Basic Principles of Information Protection

실행 권한, 주소, 자원의 경계

하드웨어의 권한 모드를 사용하는 운영체제에서는 일반 프로그램을 제한된 권한으로 실행하고, 메모리 매핑이나 장치 제어처럼 시스템 전체에 영향을 주는 동작은 커널이 담당한다. 사용자 모드에서도 산술 연산과 허용된 메모리 접근은 CPU가 직접 실행한다. 모든 명령을 커널이 대신 실행하면 보호를 위해 지나치게 큰 비용을 치르게 된다.

주소 공간은 접근 가능한 메모리의 범위를 나눈다. MMU를 사용하는 시스템에서는 운영체제가 주소 변환과 접근 권한을 설정하고, 하드웨어가 메모리 접근 때 이를 검사한다. 프로그램이 다른 주소를 계산했다고 해서 해당 메모리에 접근할 권한까지 생기지는 않는다. 잘못된 접근은 예외를 일으킬 수 있고, 운영체제는 이를 처리하거나 해당 프로그램의 실행을 중단한다.

자원별 권한은 별도로 판단한다. 사용자 모드라는 이유만으로 모든 파일 접근이 금지되는 것은 아니며, 어떤 파일을 읽을 수 있다고 해서 수정도 허용되는 것은 아니다. CPU 권한 모드는 어떤 종류의 동작을 직접 실행할 수 있는지, 주소 공간은 어떤 메모리에 접근할 수 있는지, 자원 권한은 어떤 대상에 어떤 연산을 허용할지를 나눈다. xv6: Operating system organization, §2.2

경계를 넘는 요청도 검증해야 한다

격리된 프로그램도 파일을 읽고 다른 프로그램과 통신해야 한다. 시스템 호출은 프로그램이 정해진 진입점을 통해 커널에 작업을 요청하는 경로다. 하드웨어가 제어를 커널로 넘기면 커널 코드가 요청을 처리하고 결과를 돌려준다. 호출자는 요청할 작업과 인자를 고를 수 있지만 커널에서 실행할 임의의 명령을 지정할 수는 없다.

예를 들어 데이터를 읽어 달라는 요청에는 대상, 결과를 저장할 주소, 최대 길이가 들어간다. 커널은 그 대상을 읽을 권한이 있는지와 결과를 돌려줄 메모리 범위가 유효한지 확인해야 한다. 주소와 길이를 합산할 때 정수 범위를 넘는 경우도 고려해야 한다. 호출자가 넘긴 숫자나 포인터를 신뢰하면 커널이 자신의 권한으로 금지된 접근을 대신 수행할 수 있다. xv6: Traps and system calls, §4.4

검사 시점과 사용 시점 사이의 변화도 문제다. 다른 스레드가 인자가 담긴 메모리를 바꿀 수 있다면, 검사한 내용과 실제 처리한 내용이 달라질 수 있다. 필요한 값을 커널의 관리 영역에 복사하거나, 대상의 수명을 유지하고 동기화하는 등 처리 과정 내내 가정이 유지되도록 만들어야 한다.

격리와 협력의 비용

경계를 넘을 때는 실행 상태를 보존하고 복원하며 인자와 권한을 확인하는 비용이 든다. 데이터를 복사해야 할 수도 있다. 다만 커널에 진입했다고 반드시 다른 스레드로 문맥 교환하는 것은 아니다. 요청을 바로 끝내면 같은 스레드가 돌아오고, 기다려야 한다면 다른 스레드에 CPU를 넘길 수 있다.

이미지 처리 기능을 별도 프로세스에 두면 그 기능의 잘못된 메모리 쓰기가 편집기의 메모리를 직접 훼손하는 일을 막을 수 있다. 대신 큰 이미지와 결과를 주고받는 통신 비용이 생긴다. 공유 메모리로 복사를 줄이면 같은 영역에 대한 동기화와 변경 권한을 관리해야 한다. 경계를 나누는 단위와 데이터를 교환하는 방식은 함께 설계해야 한다.

최소 권한 원칙은 각 구성 요소에 필요한 자원과 연산만 허용하여 오류나 침해의 피해 범위를 줄인다. 그러나 허용된 출력 파일에 잘못된 결과를 쓰는 것까지 막아 주지는 않는다. 보호는 프로그램의 정확성, 공유 데이터의 동시성, 장애 후 복구를 모두 보장하는 말이 아니다. 어떤 실패를 경계 안에 가두고 어떤 협력을 허용할지 정하는 설계다.

참고 문헌


목차 · 이전: 추상화 · 다음: 프로세스와 스레드