[9] Don't log interaction from temporary gesture on storage access rejection
requestStorageAccess()'s rejection path silently logged user interaction — ITP's opener heuristic read that as consent and granted access without a prompt.
gesture 없이 호출된
requestStorageAccess()rejection path에서 사용자 상호작용이 암묵적으로 기록되는 경로를 수정합니다. 기록된 상호작용을 ITP opener 휴리스틱이 동의로 해석하면, 프롬프트 없이 cross-site 쿠키 접근이 허용됩니다. High 분류 이유입니다.
enableTemporaryTimeUserGesture()는 기본값인 ProcessInteractionStyle::Immediate로 UserGestureIndicator를 생성합니다. 이 과정에서 합성 사용자 상호작용이 side effect로 기록됩니다. 이 함수는 rejection path에서 호출자가 window.open()을 계속 사용할 수 있도록 하는 용도로 쓰였습니다. 다만 rejection path에서 조작된 상호작용이 생성되면서 requestStorageAccessUnderOpener()가 유발되는 문제로 이어졌습니다.
Source/WebCore/dom/DocumentStorageAccess.cpp
Patch Details
한 줄 변경으로 UserGestureType::ActivationTriggering과 ProcessInteractionStyle::Never를 명시적으로 전달합니다. gesture 기반 기능은 그대로 유지하면서 interaction 기록은 억제됩니다. 회귀 테스트는 cross-site iframe이 포함된 popup을 설치하며, gesture 없이 호출된 requestStorageAccess() rejection이 더 이상 storage access를 허용하지 않음을 검증합니다.
side effect가 없어야 할 용도로 사용된 side-effecting 헬퍼: 내부 제어 흐름 목적으로 생성된 임시 UserGestureIndicator가 사용자 상호작용 telemetry를 암묵적으로 방출하며, downstream privacy 휴리스틱이 이를 동의로 해석합니다.
Background
UserGestureIndicator는 코드 실행 구간을 "사용자 gesture 처리 중" 상태로 표시하는 RAII 헬퍼로, gesture 기반 API를 활성화하는 역할을 담당합니다. 생성자는 ProcessInteractionStyle 인자를 받는데, 이 값에 따라 동작이 달라집니다. Immediate를 전달하면 페이지 도메인에 사용자 상호작용 이벤트가 등록되는 side effect가 발생해 privacy telemetry에 기록됩니다. 반면 Never를 전달하면 gesture 상태만 활성화되며 telemetry 기록은 이루어지지 않습니다. document.requestStorageAccess()는 cross-site iframe이 first-party 쿠키 접근을 요청하는 API입니다. ITP(Resource Load Statistics)는 도메인별 상호작용을 추적하며, requestStorageAccessUnderOpener()는 해당 origin에 최근 상호작용 기록이 있고 popup에 opener가 존재하면 자동으로 storage access를 허용합니다.
Analysis
UserGestureIndicator는 논리적으로 분리 가능한 두 가지 역할을 수행합니다. gesture 범위를 표시하는 역할과 사용자 상호작용을 기록하는 역할입니다. 기본 생성자는 이 두 동작을 하나로 묶어두었습니다. 결과적으로 순수하게 내부 제어 흐름 목적으로만 헬퍼를 사용하던 호출자들이 의도치 않게 interaction 기록 동작까지 유발하게 된 셈입니다.
공격 시나리오의 형태는 다음과 같습니다. publisher.example에서 window.open()으로, 최근 상호작용 기록이 있거나 ITP에서 prevalent로 분류된 tracker.example을 popup으로 열면, 해당 popup은 tracker를 가리키는 cross-site iframe을 포함한 페이지를 로드합니다. iframe의 onload 핸들러가 사용자 gesture 없이 document.requestStorageAccess()를 호출하면 promise는 rejection되지만, rejection path에서 임시 gesture가 합성 상호작용을 기록하게 됩니다. 이후 opener 휴리스틱은 popup-with-opener 조건과 최근 상호작용 기록을 함께 확인하고, 프롬프트 없이 tracker origin에 대한 storage access를 허용하는 결과로 이어집니다.
이 vulnerability는 ITP가 보호하는 third-party storage access의 사용자 동의 경계를 약화시킵니다. Storage Access API의 설계 원칙은 cross-site 쿠키 접근에 명시적 프롬프트 또는 사전 상호작용이 반드시 필요하다는 것입니다. 그러나 이 버그를 통해 동의를 생성해서는 안 되는 API의 rejection path를 거쳐 해당 원칙을 우회할 수 있었습니다.
Audit directions
- telemetry side effect와 제어 흐름 목적이 결합된 RAII 헬퍼.
Source/WebCore와Source/WebKit전체에서UserGestureIndicator(생성 지점을 검색하고, "실제 사용자 gesture"와 "합성/보존 gesture"로 분류합니다. 합성 gesture로 분류된 각 지점에서ProcessInteractionStyle::Never가 전달되고 있는지 확인합니다. - Resource Load Statistics opener 휴리스틱.
requestStorageAccessUnderOpener와logUserInteraction을 추적해, 클릭이 아닌 source에서 이 함수들이 호출되는 경로를 찾습니다. 클릭 이외의 source는 모두 silent-grant primitive 후보가 됩니다. - API rejection으로 활성화 가능한 상태에 의존하는 privacy 휴리스틱.
ResourceLoadObserver,ResourceLoadStatistics,WebResourceLoadStatisticsStore의 IPC 진입점부터 점검합니다. - 인접한 Storage Access 헬퍼.
consumeTemporaryTimeUserGesture호출 지점 근처에서 동일한 처리가 필요한 다른 임시UserGestureIndicator생성을 찾습니다.