- Hubris는 격리된 태스크들이 IPC로 통신하는 OS이며, 13번째 시스템 호출인
REPLY_FAULT로 서버가 잘못된 클라이언트 요청을 오류값 대신 fault로 종료할 수 있게 함 - 클라이언트 입장에서는 IPC가 함수 호출처럼 보이지만, 태스크가 별도 컴파일되기 때문에 잘못된 작업 코드, 해석 불가능한 바이트, 부적절한 loaned memory를 컴파일러가 모두 막지는 못함
- 정상적인 Hubris 프로그램은 빌드 설정과 생성된 Rust 코드 덕분에 이런 오류를 거의 만나지 않으므로, 모든 호출에
Result<T, IpcError>와unwrap()을 강제하면 코드 크기와 런타임 비용이 늘어남 - 커널은 시스템 호출 전제조건을 어긴 태스크를 오류 코드 없이 즉시 죽이며,
REPLY_FAULT는 같은 fail-fast 정책을 서버 응답까지 확장함 - 이 설계는 잘못된 API 사용을 빠르게 드러내지만, 무작위 IPC와 시스템 호출을 보내는 fuzz test나 chaos 태스크는 거의 즉시 재시작되어 테스트가 어려워짐
Hubris IPC와 REPLY_FAULT의 위치
- Hubris는 작은 애플리케이션 독립 커널을 두고, 드라이버·애플리케이션 로직·네트워크 스택 같은 대부분의 코드를 분리 컴파일된 격리 태스크에 둠
- 태스크 간 통신은 커널이 구현한 IPC 시스템 호출로 이뤄짐
RECV: 가장 높은 우선순위의 수신 메시지를 가져오거나 메시지가 올 때까지 블록됨SEND: 호출자를 멈추고 메시지와 제어권을 수신 태스크에 넘긴 뒤, 응답을 받을 때까지 대기함REPLY: 이전에SEND한 태스크에 응답을 전달해 다시 실행할 수 있게 함
- Hubris의 클라이언트와 서버는 고정된 신분이 아니라 태스크가 수행하는 역할임
SEND를 쓰는 태스크는 클라이언트 역할RECV와REPLY를 쓰는 태스크는 서버 역할- 한 태스크가 어떤 태스크에는 서버이면서 다른 태스크에는 클라이언트일 수 있음
태스크 경계에서 컴파일러가 놓치는 오류
- 일반적인 함수 호출에서는 컴파일러와 링커가 타입과 호출 대상을 상당 부분 보장함
- Rust 함수가
String인자를 받으면 호출자가bool을 넘기는 일은 컴파일러가 막음 pet_cat을 호출하려다fire_missiles를 호출하는 식의 대상 혼동도 보통 일어나지 않음
- Rust 함수가
- Hubris IPC는 태스크 경계를 넘고 각 태스크가 별도 프로그램으로 컴파일되므로, 컴파일러가 전체 IPC 관계를 직접 검증하지 못함
- IPC 서버가 마주칠 수 있는 오류는 크게 세 가지임
- 인터페이스에 맞지 않는 작업 코드, 예를 들어 두 작업만 있는 인터페이스에 “operation number 48”이 들어오는 경우
- 기대한 메시지 타입이 아니라 해석 불가능한 바이트 묶음이 오거나, 메시지가 너무 짧거나 긴 경우
- 필요한 loaned memory가 없거나, 쓰기 가능 메모리가 필요한데 읽기 전용으로 오는 경우
정상 프로그램에 오류 처리를 강제하지 않는 이유
- 정상적인 Hubris 프로그램에서는 이런 IPC 오류가 발생하지 않도록 구성됨
- 태스크 연결은 빌드 시스템 설정으로 구성되어 서로를 혼동하기 어려움
- 클라이언트는 생성된 Rust 코드로 IPC를 구성하고 전송함
- 서버도 별도의 생성 Rust 코드로 결과를 처리함
- 모든 IPC 작업이
Result<T, IpcError>를 반환하게 만들면, 정상 프로그램은 실제로 만날 수 없는 오류에 대해unwrap()을 넣어야 함unwrap()은 코드 크기 측면에서 부담이 큼- 런타임에도 일어나지 않을 오류를 검사하는 비용이 생김
- 생성 코드 안에
unwrap()이나panic!을 넣으면 panic 위치를 중앙화해 코드 크기 영향은 줄일 수 있지만, 런타임 비용은 그대로 남음 - 보편 오류 코드를 지원하려면 모든 작업이 같은 오류 인코딩 규칙을 따라야 함
- 모든 작업은 오류를 반환할 수 있어야 함
- 모든 작업은 해당 오류를 같은 방식으로 인코딩해야 함
- 실패할 수 없는 작업도 실패 가능한 형태로 표현해야 함
- Hubris 기반 펌웨어에서는 실제로 실패할 수 없는 작업이 계속 발견됐으며, GPIO 핀 설정이 그 예임
Hubris 커널의 공격적인 fault 정책
- 많은 운영체제는 시스템 호출 전제조건을 어겨도 오류 코드를 반환하거나 예외·시그널 처리 기회를 줌
- Unix에서 열지 않은 파일 디스크립터를
close하면 오류 코드가 반환됨 open에 경로명 대신 null pointer를 넘겨도 오류 코드가 반환됨
- Unix에서 열지 않은 파일 디스크립터를
- Hubris는 시스템 호출 전제조건을 깨면 해당 태스크를 즉시 파괴함
- 태스크는 더 이상 명령을 실행할 수 없음
- 태스크 자체에는 복구하거나 재개할 기회가 없음
- 애플리케이션의 supervisor 태스크는 fault를 통지받고, 보통 태스크를 지운 뒤 재시작함
- 커널이 만드는 fault는 synthetic fault임
- null pointer 역참조나 0으로 나누기처럼 CPU가 만드는 하드웨어 fault와 유사함
- 하드웨어 fault는 프로세서 아키텍처 규칙 위반에서 나오고, synthetic fault는 커널 규칙 위반에서 나옴
- 예를 들어
SEND호출에서 수신 태스크 인덱스가 애플리케이션 범위를 벗어나거나, 메시지 포인터가 접근 권한 없는 메모리를 가리키면 synthetic fault가 발생함 - Hubris는 복구 가능하거나 재개 가능한 fault를 허용하지 않음
- 하드웨어 fault든 synthetic fault든 fault를 받은 태스크는 죽은 상태가 됨
- 이 선택은 미묘한 실패 모드를 피하고 시스템 추론을 단순화하기 위한 것임
서버가 클라이언트를 fault로 응답하는 방식
REPLY_FAULT는 서버가 클라이언트에 정상 응답 대신 fault를 전달하는 시스템 호출임- 일반적인
REPLY흐름은 다음과 같음- 클라이언트가
SEND를 쓰면 커널은 클라이언트 태스크를 수신 태스크에 대한 “waiting to send” 상태로 표시함 - 수신 태스크가
RECV를 쓰면 해당 클라이언트는 “waiting for reply” 상태가 됨 - 서버가
REPLY를 호출하면 클라이언트는 runnable 상태로 돌아감
- 클라이언트가
REPLY_FAULT는REPLY와 비슷하지만, 메시지를 전달하고 실행 가능 상태로 만드는 대신 fault를 전달해 태스크를 죽은 상태로 만듦- 서버가 임의의 태스크를 죽일 수는 없음
REPLY_FAULT는 해당 서버가RECV했고 아직REPLY하지 않은 태스크에만 사용할 수 있음- 특정 서버의 응답을 기다리는 클라이언트에 대해서만 동작함
- Hubris는
REPLY_FAULT를 다음 오류 처리에 사용함- 잘못된 작업 코드
- 손상·잘림·무의미한 메시지
- 클라이언트가 올바른 종류의 loaned memory를 보내지 않은 경우
애플리케이션 오류와 fail-fast 경험
REPLY_FAULT는 IPC 형식 오류뿐 아니라 애플리케이션별 오류에도 사용할 수 있음- Hubris IP 스택은 IP 포트를 태스크에 정적으로 할당함
- 어떤 태스크가 다른 태스크의 IP 포트를 건드리려 하면 IP 스택이 해당 태스크에 fault를 줌
- 이 방식은 실제로 발생하지 않아야 하는 “이론적” 오류 처리를 줄이고, 잘못된 사용을 개발 중에 빠르게 드러냄
REPLY_FAULT는 Rust 함수 호출 전제조건 위반 시 일반적으로panic!이 발생하는 모델과 유사하게, 서버가 클라이언트 프로세스에 대해 교차 프로세스panic!을 일으키는 수단이 됨- 클라이언트는 이를 위한 코드를 포함하거나 협조할 필요가 없음
보안 성향과 테스트상의 제약
- Eliza Weissman은 Hubris를 “악성 프로그램에 공격적으로 적대적”이라고 표현함
- 악용 시도는 종종 API 오류나 오용으로 먼저 나타나므로, 잘못 행동한 컴포넌트의 상태를 지우는 시스템은 악용하기 더 어려울 수 있음
- 이 가설은 아직 테스트되지 않았음
- Hubris exploit 시도에 관심이 있으면 연락해 달라는 요청이 포함됨
- 관찰된 단점은 시스템이 fuzz test하기 매우 어렵다는 점임
- 무작위 IPC와 시스템 호출을 생성하는 작은 chaos 태스크가 구현됐지만, 거의 무엇을 하든 즉시 재설정됨
- 유용하게 동작하려면 시작할 때마다 관찰 가능하게 달라지는 시스템 uptime counter에 결정을 의존해야 함
REPLY_FAULT는 서버가 클라이언트를 무작위로 죽여 chaos를 강제하는 방법도 제공하지만, 이 선택지는 아직 완전히 평가되지 않았음- 일반적인 Hubris 태스크는 의도적으로 잘못된 IPC 메시지를 동적으로 생성하지 않으므로, 보통
REPLY_FAULT의 존재를 의식하지 않고 실행될 수 있음