← All issues

[7] GameControllerGamepad UAF from raw this capture in framework-retained blocks

Severity: Medium | Component: WebCore Gamepad | 101c855

컨트롤러 연결 해제 시 UAF crash가 재현 가능한 수준으로 확인된 점에서 취약점의 존재는 분명합니다. 다만 exploit을 위해서는 물리적 또는 가상 HID 장치에 대한 접근이 필요하며, 웹 콘텐츠만으로는 유발할 수 없습니다. race window도 좁아 UI process context임에도 불구하고 안정적인 exploit은 어렵습니다. 이런 이유로 Medium으로 평가되었습니다.

Objective-C callback block에서 raw this 캡처를 WeakPtr과 null 검사 방식으로 교체했습니다. 또한 객체가 소멸될 때 모든 GameController framework handler를 해제하도록 teardownElements()를 호출하는 destructor가 추가되었습니다.

Source/WebCore/platform/gamepad/cocoa/GameControllerGamepad.mm

void GameControllerGamepad::setupElements()
{
RetainPtr<GCPhysicalInputProfile> profile = m_gcController.get().physicalInputProfile;
+ WeakPtr weakThis { *this };
 
if ([profile respondsToSelector:@selector(setThumbstickUserIntentHandler:)]) {
[profile setThumbstickUserIntentHandler:^(__kindof GCPhysicalInputProfile*, GCControllerElement*) {
- m_lastUpdateTime = MonotonicTime::now();
- GameControllerGamepadProvider::singleton().gamepadHadInput(*this, true);
+ if (!weakThis)
+ return;
+ weakThis->m_lastUpdateTime = MonotonicTime::now();
+ weakThis->m_lastUpdateTime = MonotonicTime::now();
+ GameControllerGamepadProvider::singleton().gamepadHadInput(*weakThis, true);
}];
}
+GameControllerGamepad::~GameControllerGamepad()
+{
+ teardownElements();
+}
+void GameControllerGamepad::teardownElements()
+{
+ auto profile = RetainPtr { m_gcController.get().physicalInputProfile };
+ if (!profile)
+ return;
+
+ if ([profile respondsToSelector:@selector(setThumbstickUserIntentHandler:)])
+ [profile setThumbstickUserIntentHandler:nil];
+
+ for (GCControllerButtonInput *button in [profile allButtons])
+ button.valueChangedHandler = nil;
+
+ profile.get().dpads[GCInputLeftThumbstick].xAxis.valueChangedHandler = nil;
+ profile.get().dpads[GCInputLeftThumbstick].yAxis.valueChangedHandler = nil;
+ profile.get().dpads[GCInputRightThumbstick].xAxis.valueChangedHandler = nil;
+ profile.get().dpads[GCInputRightThumbstick].yAxis.valueChangedHandler = nil;
+}

Tools/TestWebKitAPI/Tests/mac/HIDGamepads.mm

+TEST(Gamepad, DisconnectDuringInput)
+{
+ // Tests that handler blocks don't crash when invoked after GameControllerGamepad destruction.
+ ...
+ for (int i = 0; i < 100; ++i) {
+ gamepad->setAxisValue(0, (float)i / 100.0);
+ gamepad->setAxisValue(1, (float)(100 - i) / 100.0);
+ gamepad->publishReport();
+ }
+
+ // This destroys VirtualGamepad, which triggers controllerDidDisconnect,
+ // which destroys GameControllerGamepad.
+ gamepad = nullptr;
+
+ // Wait for disconnect message.
+ Util::run(&didReceiveMessage);
+}

패치는 세 가지 변경으로 구성됩니다.

첫째, setupElements()WeakPtr weakThis { *this } 캡처가 추가되었습니다. 모든 callback block은 멤버 변수에 접근하기 전에 if (!weakThis) return; 검사를 수행하도록 변경되었습니다.

둘째, 새로운 ~GameControllerGamepad() destructor가 추가되어 teardownElements()를 호출합니다.

셋째, 새로운 teardownElements() 메서드가 추가되었습니다. 이 메서드는 GCPhysicalInputProfile에 등록된 모든 handler block을 nil로 설정하여, 객체 소멸 이후 framework가 callback을 호출하지 못하도록 차단합니다.

이 수정은 belt-and-suspenders 방식을 채택했습니다. WeakPtr null 검사(심층 방어)와 handler 해제(주요 수정)를 함께 적용했습니다. 다만 한 가지 주목할 점이 있습니다. axis handler block은 멤버 접근에 weakThis를 사용하면서도 gamepadHadInput()에는 *weakThis 대신 여전히 *this를 전달하고 있어, 미처 수정되지 않은 부분으로 보입니다. 단, teardownElements()가 소멸 시점에 handler를 초기화하므로 이 문제는 완화됩니다.

Gamepad API는 물리적 게임 컨트롤러를 웹 콘텐츠에 노출하는 기능입니다. Apple 플랫폼에서 WebKit은 GCController, GCPhysicalInputProfile 등 GameController framework를 통해 입력 이벤트를 수신합니다. 입력 handler는 버튼, 축, 썸스틱 등 컨트롤러 요소에 Objective-C block 형태로 등록됩니다. 이 block들은 framework가 retain하며, 하드웨어에서 새로운 입력이 발생할 때 비동기적으로 호출됩니다. 따라서 이 block의 생명주기는 이를 등록한 C++ 객체가 아닌 Objective-C runtime에 의해 관리됩니다.

WeakPtr는 참조 대상이 소멸될 때 자동으로 무효화되는 weak reference로, retain된 callback에서 객체의 유효성을 안전하게 확인할 수 있게 해줍니다. 명시적인 정리 작업이 없으면 framework 객체가 캡처한 Objective-C block은 무기한 유지되며, 캡처된 참조도 함께 살아남게 됩니다.

C++ 객체보다 오래 유지되는 framework-retained callback block의 raw this 캡처로 인한 use-after-free.

패치 이전에는 GameControllerGamepad::setupElements()가 raw this 포인터를 캡처하는 Objective-C block callback을 GameController framework에 등록했습니다. 이 block들은 GCController의 input profile이 retain하며, 물리적 컨트롤러에서 입력이 발생할 때마다 비동기적으로 호출될 수 있었습니다.

GameControllerGamepad 객체가 소멸될 때(예: 컨트롤러 연결 해제 시) 이 handler를 해제하는 destructor가 존재하지 않았습니다. 그 결과 GameController framework는 계속해서 block을 보유했습니다. 이 상태에서 대기 중이거나 새로 유입된 입력 이벤트가 callback을 호출하면, 이미 해제된 this 포인터를 역참조하게 되었습니다. 이 과정에서 m_axisValues, m_buttonValues, m_lastUpdateTime에 쓰기가 발생하고, gamepadHadInput()에 dangling reference가 전달되었습니다.

🔒

Explores the heap exploitation potential of the dangling-pointer writes and the process-level implications of where this code executes

더 확인하려면 구독해 주세요

🔒

Multiple audit patterns identified for framework callback lifetime issues across WebKit's platform integration layers, with concrete search targets

더 확인하려면 구독해 주세요