← All reports

Swift IPC receivers get a throwing MESSAGE_CHECK

Component: WebKit IPC / UIProcess | e7b60ae

WebKit의 multi-process sandboxing 모델은 IPC 메시지 validation에 크게 의존합니다. 정확히는 sandbox 안의 WebContent process와 권한을 가진 UI / GPU / Network process 사이 경계에서 수행되는 검증입니다. C++ 쪽에서는 MESSAGE_CHECK 매크로 계열이 이 역할을 담당합니다. 메시지 필드가 원래 위반될 수 없어야 하는 invariant를 깨뜨리면, 로그를 기록하고 경우에 따라 crash를 유발하며, 어떤 경우에도 그 지점에서 곧바로 함수를 빠져나옵니다. 덕분에 장악된 renderer가 검증되지 않은 데이터를 권한 영역 깊숙이 밀어 넣지 못합니다. 다만 WebBackForwardList 같은 IPC receiver를 Swift로 옮기는 과정에서는 이에 대응하는 장치가 없습니다. Swift에는 early return 제어 흐름을 포함하는 매크로가 존재하지 않기 때문입니다. 그래서 이전 코드는 같은 패턴을 직접 손으로 구현했습니다. boolean 검사를 수행한 뒤 각 호출 지점마다 return을 수동으로 적어 주는 방식이었습니다.

이번 commit은 Platform/IPC/MessageCheck.swift를 추가했습니다. throwing function을 토대로 Swift다운 형태로 다시 만든 대체물이며, messageCheck, dispatchMessage, markMessageInvalid 세 가지로 구성됩니다. WebBackForwardList.swift의 UIProcess IPC handler들은 try messageCheck { } 형태로 변환되었습니다. 실제로 connection을 오염시키는 단계, 즉 markCurrentlyDispatchedMessageAsInvalid 호출은 dispatchMessage 내부의 catch 블록으로 미뤄졌습니다. 그 지점에서 IPC::Connection에 접근할 수 있기 때문입니다. 한편 Connection::takeErrorString과 setErrorString은 ASCIILiteral 대신 WTF::String을 다루도록 확장되었습니다. Swift 쪽에서 생성된 에러 문자열을 전달하기 위해서입니다. GPU process와 Network process의 takeInvalidMessageStringForTesting 호출 지점이 함께 바뀐 이유도 여기에 있습니다. 두 process를 새로운 Swift 패턴으로 전환한 것이 아니라, 타입 변경에 따라온 기계적인 수정입니다.

기존 Swift 패턴에서는 검사가 실패한 뒤 early return을 명시적으로 적어 주는 일을 개발자가 직접 기억해야 했습니다. 반면 새로운 throw/try 방식에서는 그 early exit가 컴파일러 차원에서 강제됩니다. 덕분에 Swift IPC receiver에서 "return을 빠뜨리는" 유형의 버그가 제거됩니다. 그리고 이번 commit은 message check shim을 자동 생성하는 방향으로 이어지는 여러 변경 중 첫 번째임을 스스로 밝히고 있습니다.

Narrow: 기존의 수동 return 기반 검사와 throw 기반으로 변환된 결과를 나란히 비교하는 작업이 우선입니다. 변환 과정에서 검사 하나가 누락되거나 순서가 뒤바뀌면, 이 메커니즘이 막으려던 바로 그 부류의 버그가 조용히 되살아나기 때문입니다. 판별 기준은 단순합니다. 변환된 handler의 try messageCheck 호출 수가 변환 전의 if messageCheck(...) { return } 블록 수보다 적다면 의심해 볼 지점입니다. 함께 살펴볼 대상은 dispatchMessage의 onInvalidMessage completion handler입니다. reply를 기대하는 모든 메시지 타입에 대해 실제로 reply가 전달되는지 점검해야 합니다. 검사가 실패했을 때 reply가 빠지면 synchronous IPC 호출이 그대로 멈춰 버리기 때문입니다. Wider: dispatchMessage 바깥에서 catch를 직접 작성한 지점이 하나 있습니다(backForwardGoToItemShared). 이 코드가 올바른 connection에 대해 markCurrentlyDispatchedMessageAsInvalid를 호출하는지 확인이 필요합니다. 검색해야 할 일반적인 형태는, 공용 dispatch wrapper를 우회한 채 InvalidMessage를 별도로 catch하는 코드입니다. connection을 오염 상태로 표시하는 단계를 빠뜨리기 쉬운 자리가 바로 이런 곳입니다. Widest: 앞으로 더 많은 receiver가 Swift로 옮겨 간다는 점을 고려하면, 언어 경계를 넘어 security 매크로를 다시 구현할 때마다 같은 질문을 던질 필요가 있습니다. "기존 형태가 막아 주던 모든 경로에서 새로운 형태도 동일하게 fail closed 되는가?" C의 error code 검사를 Rust의 ? 기반으로 옮기는 경우에도, 매크로를 exception으로 바꾸는 모든 변환에도 똑같이 적용되는 질문입니다.