DEPEX 담당의 가설을 검증해 보라는 교수님의 말씀에 따라, 자료들을 찾아보며 가능성을 파악해 봤다. DEPEX 담당이 선택한 2가지 취약점 Missing Protocol Vulnerability 와 Cycle Vulnerability 중 Cycle Vulnerability를 먼저 시도했다.
내가 이해하기 위한 디스패처의 의존성 평가 및 상태 전이 방식
DXE 코어가 시스템 메모리와 FV을 파싱하여 내부의 드라이버들을 찾아내면, EFI_CORE_DRIVER_ENTRY 구조체 형태로 식별되어 mDiscoveredList라는 전역 연결 리스트에 추가된다. 이때 드라이버들은 아직 실행 조건이 충족되지 않았기 때문에 모두 Dependent 플래그가 설정된 대기 상태로 머무르게 된다.
이후 디스패처는 각 드라이버의 실행 가능 여부를 판단하기 위해 CoreIsSchedulable() 함수를 통해 DEPEX 평가를 시작한다. CoreIsSchedulable() 함수는 전역 부울 스택인 mDepexEvaluationStack을 활용해 POSTFIX 방식으로 DEPEX 명령어들을 차례대로 읽어 들이며 평가한다.
만약 서명 검증을 담당하는 특정 보안 드라이버가 특정 프로토콜을 요구한다면, 해당 드라이버의 DEPEX에는 EFI_DEP_PUSH <특정 프로토콜의 GUID> 명령어가 포함되어 있을 것이다. 하지만 악의적인 펌웨어 변조 등의 이유로 특정 프로토콜을 생성하는 선행 드라이버가 손상되거나 삭제되었다면, 디스패처가 CoreLocateProtocol()을 호출하여 핸들 데이터베이스를 검색할 때 해당 GUID를 찾지 못하는 문제가 발생한다.
프로토콜 검색이 실패하면 의존성 평가 스택에는 논리적 거짓을 의미하는 FALSE가 push 되고, 해당 DEPEX 논리 연산의 최종 결과 또한 FALSE로 판정된다. 결과적으로 보안 드라이버의 Dependent 상태는 해제되지 못하고, 실행 대기열인 mScheduledQueue로 승격되지 못한 채 초기 리스트인 mDiscoveredList에 무기한 잔류하게 됨
mScheduledQueue로 승격될 수 있는 드라이버가 더 이상 존재하지 않아 남은 모든 드라이버의 DEPEX가 FALSE로 평가되는 순간 CoreDispatcher()는 실행 루프를 종료한다. 이때 DXE 디스패처는 mDiscoveredList에 실행되지 못한 드라이버들이 남아있음에도 불구하고, 어떠한 예외처리나 시스템 정지를 발생시키지 않은 채 EFI_SUCCESS나 EFI_NOT_FOUND를 반환하며 조용히 종료된다.
디스패처가 이렇게 종료되고 나면, 제어 흐름은 DXE 코어의 진입점으로 돌아온다. 이후 부트 정책을 결정하는 BDS 단계로 넘어가기 직전에 DXE 코어는 CoreAllEfiServicesAvailable() 함수를 호출한다. 하지만 이 함수는 단지 시스템 구동에 필수적인 아키텍처 프로토콜들이 모두 설치되었는지만을 검증할 뿐이므로, 앞서 누락된 특정 보안 드라이버의 실행 여부는 확인하지 않은 채 제어권을 BDS로 넘기게 된다.
참고
디스패처가 예외 없이 종료되는 과정은 Dispatcher.c 의 CoreDispatcher() 함수 참고
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Core/Dxe/Dispatcher/Dispatcher.c
- while (!IsListEmpty (&mScheduledQueue)) 루프를 돌며 큐를 확인하고, 더 이상 처리할 드라이버가 없을 때 예외 처리 없이 반환
의존성 평가 실패하는 과정은 Dependency.c 의 CoreIsSchedulable() 함수 참고
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Core/Dxe/Dispatcher/Dependency.c
- EFI_DEP_PUSH 명령어를 만나 CoreLocateProtocol()로 프로토콜을 찾고, 해당 프로토콜이 없을 때 PushBool (FALSE)를 호출하여 전역 스택에 FALSE를 넣음
디스패처 종료 직후 필수 프로토콜만을 검증하고 BDS로 넘어가는 흐름은 DxeMain.c의 DxeMain() 함수 참고
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Core/Dxe/DxeMain/DxeMain.c
- CoreAllEfiServicesAvailable()을 호출하여 필수 아키텍처 프로토콜만 확인한 뒤 다음 단계로 핸드오프
드라이버의 의존성 방식 2가지
1. 프로토콜 기반 의존성
드라이버가 실행되기 위해 특정 프로토콜이 시스템에 설치되어 있어야 한다고 명시하는 것으로, DEPEX 섹션에 연산 코드를 작성한다. EFI_DEP_PUSH는 특정 프로토콜의 GUID를 평가 스택에 쌓으라는 명령어이다.
동작 방식
- 디스패처가 드라이버의 DEPEX를 읽는다
- PUSH <Protocol_GUID>를 만나면, "이 GUID가 지금 시스템에 등록되어 있나?"를 확인
- 있으면 TRUE, 없으면 FALSE를 스택에 넣고 AND, OR 같은 논리로 최종 결과를 낸다.
2. 스케줄링 기반 의존성
프로토콜과는 상관없이, 특정 드라이버 파일(GUID)과의 상대적인 실행 순서를 강제하고 싶을 때 사용한다.EFI_DEP_BEFORE 또는 EFI_DEP_AFTER 연산자를 사용하며, 뒤에 대상 드라이버의 파일 GUID가 온다.
동작 방식
- BEFORE: "A BEFORE B"라면, 디스패처는 B를 스케줄링 큐에 넣으려고 할 때, A부터 먼저 큐에 넣고 실행시켜야 한다며 A를 먼저 처리
- AFTER: "A AFTER B"라면, B가 먼저 실행된 것이 확인될 때까지 A는 큐에 들어가지 못하고 대기
기존 가설

[프로젝트]UEFI DXE 바이너리 취약점 분석기 9주차 - 취약점 선정 및 Ghidra Script 수정
저번주에 언급했던 부분 중 취약점 개수를 줄이는 방향으로 가는 것이 좋을 것 같다는 의견을 주셔서 2개로 줄여보았다. [선정된 취약점]Missing Protocol Vulnerability취약점 정의핵심 원인 : DXE 드라이
manguu.tistory.com
가설 검증
우선 이론적으로 가능한 사실인지 알아보는데 중점을 두었다.
최대한 코드를 기반으로 정리했으나, 혹여 잘못된 내용이 있다면 수정 예정..
1. 프로토콜 기반 순환 의존성으로 인한 시스템 크래시 및 deadlock
-> 불가능
DXE 디스패처는 특정 조건이 충족될 때까지 스레드를 대기시키는 차단형 함수를 사용하지 않고 무차단 폴링 기반으로 동작한다. 따라서 프로토콜 기반 평가를 수행할 때 요구되는 프로토콜이 시스템에 존재하지 않으면 디스패처는 즉시 의존성 평가 스택에 거짓 값을 push하고 해당 드라이버의 상태를 유보한 채 다음 드라이버의 평가로 넘어감
이 과정을 지속하다가 스케줄링 큐에 새롭게 편입되는 드라이버가 더 이상 없으면 메인 디스패치 루프를 자연스럽게 종료한다. 이후 시스템은 필수 아키텍처 프로토콜 13개의 설치 여부만을 일괄 검증하거, 순환 구조를 가진 문제의 드라이버들만 실행에서 배제한 채 정상적인 운영체제 부팅 단계인 BDS 페이즈로 부팅을 계속 진행
Dependency.c https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Core/Dxe/Dispatcher/Dependency.c
- 글 상단의 '내가 이해하기 위한 디스패처의 의존성 평가 및 상태 전이 방식'의 '참고'의 '의존성 평가 실패하는 과정' 참고
DxeMain.c
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Core/Dxe/DxeMain/DxeMain.c
- 글 상단의 '내가 이해하기 위한 디스패처의 의존성 평가 및 상태 전이 방식'의 '참고'의 ' 디스패처 종료 직후 필수 프로토콜만을 검증하고 BDS로 넘어가는 흐름 ' 참고
2. 로드 순서 조작을 통한 레이스 컨디션 및 권한 상승 공격
-> 아키텍처 상으로는 불가능해보이지만..
EDK2의 DXE 디스패처 엔진은 FV를 파싱하여 발견된 모듈을 연결 리스트에 탐색된 순서대로 결정론적으로 추가한다. 디스패처 내부에는 의존성 평가 과정 중 교착 상태나 지연이 발생했다고 하여 특정 드라이버의 스케줄링 순서를 임의로 앞당기거나 뒤섞는 비결정적인 휴리스틱 방어 로직이 전혀 구현되어 있지 않다
또한 플랫폼의 핵심적인 보안 드라이버들은 플랫폼 제조사가 지정한 Apriori 파일에 명시되어 어떠한 의존성 조건보다 무조건적으로 우선하여 스케줄링 큐의 최상단에 삽입되도록 구조화되어 있다. 따라서 공격자가 프로토콜 의존성 구문을 조작하더라도 보안 모듈의 실행 타이밍을 늦추고 악성 드라이버를 먼저 실행시키는 실행 순서 역전 공격은 아키텍처 설계상으로는 불가능에 가깝지만...
그러나 공격자가 펌웨어 업데이트 과정이나 물리적 접근을 통해 FV 자체를 수정할 권한을 획득한다면 공격자는 복잡한 로직을 우회할 필요 없이 Apriori 파일 내부에 자신의 악성 드라이버 GUID를 직접 삽입할 수 있으며, 이 경우 디스패처 엔진은 설계된 대로 악성 드라이버를 가장 먼저, 그리고 가장 안정적으로 실행해 주는 역설적인 상황이 발생
또한 모든 보안 모듈이 Apriori에 포함되는 것은 아니므로, 목록에서 누락된 보안 드라이버를 순환 참조의 늪에 빠뜨려 무한 대기 상태로 고립시킨 뒤 의존성이 없는 악성 드라이버를 먼저 실행시키는 지연 기반의 순서 역전 가능성도 완전히 배제할 수 없다.
DXE 디스패처 스케줄링 메커니즘
Dispatcher.c
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Core/Dxe/Dispatcher/Dispatcher.c
edk2/MdeModulePkg/Core/Dxe/Dispatcher/Dispatcher.c at master · tianocore/edk2
EDK II. Contribute to tianocore/edk2 development by creating an account on GitHub.
github.com
1. FV 파싱 및 모듈 탐색 (발견 대기열에 추가)
시스템 부팅 초기나 새로운 펌웨어 볼륨(FV)이 마운트 되면 가장 먼저 CoreFwVolEventProtocolNotify 함수가 호출되고, 이 함수는 펌웨어 볼륨 내부를 스캔하여 발견된 드라이버와 파일들을 차례대로 탐색한다. 파일의 타입에 맞게 처리를 진행한 뒤, CoreAddToDriverList를 호출하여 발견된 일반 모듈들을 mDiscoveredList(발견 대기열)에 순차적으로 등록한다.
VOID
EFIAPI
CoreFwVolEventProtocolNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
EFI_STATUS GetNextFileStatus;
EFI_FIRMWARE_VOLUME2_PROTOCOL *Fv;
EFI_DEVICE_PATH_PROTOCOL *FvDevicePath;
EFI_HANDLE FvHandle;
UINTN BufferSize;
EFI_GUID NameGuid;
UINTN Key;
EFI_FV_FILETYPE Type;
EFI_FV_FILE_ATTRIBUTES Attributes;
UINTN Size;
EFI_CORE_DRIVER_ENTRY *DriverEntry;
EFI_GUID *AprioriFile;
UINTN AprioriEntryCount;
UINTN Index;
LIST_ENTRY *Link;
UINT32 AuthenticationStatus;
UINTN SizeOfBuffer;
VOID *DepexBuffer;
KNOWN_HANDLE *KnownHandle;
FvHandle = NULL;
while (TRUE) {
BufferSize = sizeof (EFI_HANDLE);
Status = CoreLocateHandle (
ByRegisterNotify,
NULL,
mFwVolEventRegistration,
&BufferSize,
&FvHandle
);
if (EFI_ERROR (Status)) {
//
// If no more notification events exit
//
return;
}
if (FvHasBeenProcessed (FvHandle)) {
//
// This Fv has already been processed so lets skip it!
//
continue;
}
//
// Since we are about to process this Fv mark it as processed.
//
KnownHandle = FvIsBeingProcessed (FvHandle);
if (KnownHandle == NULL) {
//
// The FV with the same FV name guid has already been processed.
// So lets skip it!
//
continue;
}
Status = CoreHandleProtocol (FvHandle, &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&Fv);
if (EFI_ERROR (Status) || (Fv == NULL)) {
//
// FvHandle must have Firmware Volume2 protocol thus we should never get here.
//
ASSERT (FALSE);
continue;
}
Status = CoreHandleProtocol (FvHandle, &gEfiDevicePathProtocolGuid, (VOID **)&FvDevicePath);
if (EFI_ERROR (Status)) {
//
// The Firmware volume doesn't have device path, can't be dispatched.
//
continue;
}
//
// Discover Drivers in FV and add them to the Discovered Driver List.
// Process EFI_FV_FILETYPE_DRIVER type and then EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER
// EFI_FV_FILETYPE_DXE_CORE is processed to produce a Loaded Image protocol for the core
// EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE is processed to create a Fvb
//
for (Index = 0; Index < sizeof (mDxeFileTypes) / sizeof (EFI_FV_FILETYPE); Index++) {
//
// Initialize the search key
//
Key = 0;
do {
Type = mDxeFileTypes[Index];
GetNextFileStatus = Fv->GetNextFile (
Fv,
&Key,
&Type,
&NameGuid,
&Attributes,
&Size
);
if (!EFI_ERROR (GetNextFileStatus)) {
if (Type == EFI_FV_FILETYPE_DXE_CORE) {
//
// If this is the DXE core fill in it's DevicePath & DeviceHandle
//
if (gDxeCoreLoadedImage->FilePath == NULL) {
if (CompareGuid (&NameGuid, gDxeCoreFileName)) {
//
// Maybe One specail Fv cantains only one DXE_CORE module, so its device path must
// be initialized completely.
//
EfiInitializeFwVolDevicepathNode (&mFvDevicePath.File, &NameGuid);
SetDevicePathEndNode (&mFvDevicePath.End);
gDxeCoreLoadedImage->FilePath = DuplicateDevicePath (
(EFI_DEVICE_PATH_PROTOCOL *)&mFvDevicePath
);
gDxeCoreLoadedImage->DeviceHandle = FvHandle;
}
}
} else if (Type == EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE) {
//
// Check if this EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE file has already
// been extracted.
//
if (FvFoundInHobFv2 (&KnownHandle->FvNameGuid, &NameGuid)) {
continue;
}
//
// Check if this EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE file has SMM depex section.
//
DepexBuffer = NULL;
SizeOfBuffer = 0;
Status = Fv->ReadSection (
Fv,
&NameGuid,
EFI_SECTION_SMM_DEPEX,
0,
&DepexBuffer,
&SizeOfBuffer,
&AuthenticationStatus
);
if (!EFI_ERROR (Status)) {
//
// If SMM depex section is found, this FV image is invalid to be supported.
// ASSERT FALSE to report this FV image.
//
FreePool (DepexBuffer);
ASSERT (FALSE);
}
//
// Check if this EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE file has DXE depex section.
//
DepexBuffer = NULL;
SizeOfBuffer = 0;
Status = Fv->ReadSection (
Fv,
&NameGuid,
EFI_SECTION_DXE_DEPEX,
0,
&DepexBuffer,
&SizeOfBuffer,
&AuthenticationStatus
);
if (EFI_ERROR (Status)) {
//
// If no depex section, produce a firmware volume block protocol for it so it gets dispatched from.
//
CoreProcessFvImageFile (Fv, FvHandle, &NameGuid);
} else {
//
// If depex section is found, this FV image will be dispatched until its depex is evaluated to TRUE.
//
FreePool (DepexBuffer);
CoreAddToDriverList (Fv, FvHandle, &NameGuid, Type);
}
} else {
//
// Transition driver from Undiscovered to Discovered state
//
CoreAddToDriverList (Fv, FvHandle, &NameGuid, Type);
}
}
} while (!EFI_ERROR (GetNextFileStatus));
}
2. Apriori 파일 확인 및 스케줄링 큐 강제 삽입
모듈 탐색 루프가 완전히 종료된 직후, 동일한 함수인 CoreFwVolEventProtocolNotify의 하단 로직이 이어서 실행된다. 펌웨어 볼륨에서 Apriori 파일을 읽어 들인 다음, 해당 목록에 있는 보안 모듈들을 방금 구성한 mDiscoveredList에서 찾아낸다. 일치하는 모듈을 찾으면 의존성 조건(Dependent = FALSE)을 완전히 해제하고, 일반적인 평가 절차를 무시한 채 곧바로 mScheduledQueue(실행 대기열)의 꼬리에 삽입하여 최우선 실행 권한을 부여한다.
//
// Read the array of GUIDs from the Apriori file if it is present in the firmware volume
//
AprioriFile = NULL;
Status = Fv->ReadSection (
Fv,
&gAprioriGuid,
EFI_SECTION_RAW,
0,
(VOID **)&AprioriFile,
&SizeOfBuffer,
&AuthenticationStatus
);
if (!EFI_ERROR (Status)) {
AprioriEntryCount = SizeOfBuffer / sizeof (EFI_GUID);
} else {
AprioriEntryCount = 0;
}
//
// Put drivers on Apriori List on the Scheduled queue. The Discovered List includes
// drivers not in the current FV and these must be skipped since the a priori list
// is only valid for the FV that it resided in.
//
for (Index = 0; Index < AprioriEntryCount; Index++) {
for (Link = mDiscoveredList.ForwardLink; Link != &mDiscoveredList; Link = Link->ForwardLink) {
DriverEntry = CR (Link, EFI_CORE_DRIVER_ENTRY, Link, EFI_CORE_DRIVER_ENTRY_SIGNATURE);
if (CompareGuid (&DriverEntry->FileName, &AprioriFile[Index]) &&
(FvHandle == DriverEntry->FvHandle))
{
CoreAcquireDispatcherLock ();
DriverEntry->Dependent = FALSE;
DriverEntry->Scheduled = TRUE;
InsertTailList (&mScheduledQueue, &DriverEntry->ScheduledLink);
CoreReleaseDispatcherLock ();
DEBUG ((DEBUG_DISPATCH, "Evaluate DXE DEPEX for FFS(%g)\n", &DriverEntry->FileName));
DEBUG ((DEBUG_DISPATCH, " RESULT = TRUE (Apriori)\n"));
break;
}
}
}
//
// Free data allocated by Fv->ReadSection ()
//
CoreFreePool (AprioriFile);
}
}
3. 디스패처 가동 및 스케줄링 큐 비우기 (Apriori 최우선 실행)
본격적인 디스패치 루틴을 처리하기 위해 CoreDispatcher 메인 엔진이 가동된다. 이 함수는 실행되자마자 mScheduledQueue가 비어있는지부터 점검한다. 2단계에서 미리 새치기하여 꽂아둔 Apriori 모듈들이 이곳에 대기하고 있으므로, 어떠한 프로토콜 의존성 평가 로직도 거치지 않고 가장 먼저 CoreLoadImage와 CoreStartImage를 통해 실행된다.
EFI_STATUS
EFIAPI
CoreDispatcher (
VOID
)
{
EFI_STATUS Status;
EFI_STATUS ReturnStatus;
LIST_ENTRY *Link;
EFI_CORE_DRIVER_ENTRY *DriverEntry;
BOOLEAN ReadyToRun;
EFI_EVENT DxeDispatchEvent;
PERF_FUNCTION_BEGIN ();
if (gDispatcherRunning) {
//
// If the dispatcher is running don't let it be restarted.
//
PERF_FUNCTION_END ();
return EFI_ALREADY_STARTED;
}
gDispatcherRunning = TRUE;
Status = CoreCreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
EfiEventEmptyFunction,
NULL,
&gEfiEventDxeDispatchGuid,
&DxeDispatchEvent
);
if (EFI_ERROR (Status)) {
PERF_FUNCTION_END ();
return Status;
}
ReturnStatus = EFI_NOT_FOUND;
do {
//
// Drain the Scheduled Queue
//
while (!IsListEmpty (&mScheduledQueue)) {
DriverEntry = CR (
mScheduledQueue.ForwardLink,
EFI_CORE_DRIVER_ENTRY,
ScheduledLink,
EFI_CORE_DRIVER_ENTRY_SIGNATURE
);
//
// Load the DXE Driver image into memory. If the Driver was transitioned from
// Untrused to Scheduled it would have already been loaded so we may need to
// skip the LoadImage
//
if ((DriverEntry->ImageHandle == NULL) && !DriverEntry->IsFvImage) {
DEBUG ((DEBUG_INFO, "Loading driver %g\n", &DriverEntry->FileName));
Status = CoreLoadImage (
FALSE,
gDxeCoreImageHandle,
DriverEntry->FvFileDevicePath,
NULL,
0,
&DriverEntry->ImageHandle
);
//
// Update the driver state to reflect that it's been loaded
//
if (EFI_ERROR (Status)) {
CoreAcquireDispatcherLock ();
if (Status == EFI_SECURITY_VIOLATION) {
//
// Take driver from Scheduled to Untrused state
//
DriverEntry->Untrusted = TRUE;
} else {
//
// The DXE Driver could not be loaded, and do not attempt to load or start it again.
// Take driver from Scheduled to Initialized.
//
// This case include the Never Trusted state if EFI_ACCESS_DENIED is returned
//
DriverEntry->Initialized = TRUE;
}
DriverEntry->Scheduled = FALSE;
RemoveEntryList (&DriverEntry->ScheduledLink);
CoreReleaseDispatcherLock ();
//
// If it's an error don't try the StartImage
//
continue;
}
}
CoreAcquireDispatcherLock ();
DriverEntry->Scheduled = FALSE;
DriverEntry->Initialized = TRUE;
RemoveEntryList (&DriverEntry->ScheduledLink);
CoreReleaseDispatcherLock ();
if (DriverEntry->IsFvImage) {
//
// Produce a firmware volume block protocol for FvImage so it gets dispatched from.
//
Status = CoreProcessFvImageFile (DriverEntry->Fv, DriverEntry->FvHandle, &DriverEntry->FileName);
} else {
REPORT_STATUS_CODE_WITH_EXTENDED_DATA (
EFI_PROGRESS_CODE,
(EFI_SOFTWARE_DXE_CORE | EFI_SW_PC_INIT_BEGIN),
&DriverEntry->ImageHandle,
sizeof (DriverEntry->ImageHandle)
);
ASSERT (DriverEntry->ImageHandle != NULL);
Status = CoreStartImage (DriverEntry->ImageHandle, NULL, NULL);
REPORT_STATUS_CODE_WITH_EXTENDED_DATA (
EFI_PROGRESS_CODE,
(EFI_SOFTWARE_DXE_CORE | EFI_SW_PC_INIT_END),
&DriverEntry->ImageHandle,
sizeof (DriverEntry->ImageHandle)
);
}
ReturnStatus = EFI_SUCCESS;
}
4. 일반 모듈의 의존성 평가 및 큐 편입
미리 삽입되어 있던 Apriori 모듈들이 모두 처리되어 큐가 비워지면, CoreDispatcher는 일반 모듈들이 모여있는 mDiscoveredList를 순회하기 시작한다. 이때 각각의 모듈에 대해 CoreIsSchedulable 함수를 호출하여 현재 시스템에 필요한 프로토콜이 준비되었는지 DEPEX를 평가한다. 평가를 통과한 모듈은 mScheduledQueue에 추가되고, 플래그(ReadyToRun = TRUE;)가 변경되어 루프의 최상단(3단계)으로 다시 돌아가 실제 로드와 실행을 거치게 된다.
//
// Now DXE Dispatcher finished one round of dispatch, signal an event group
// so that SMM Dispatcher get chance to dispatch SMM Drivers which depend
// on UEFI protocols
//
if (!EFI_ERROR (ReturnStatus)) {
CoreSignalEvent (DxeDispatchEvent);
}
//
// Search DriverList for items to place on Scheduled Queue
//
ReadyToRun = FALSE;
for (Link = mDiscoveredList.ForwardLink; Link != &mDiscoveredList; Link = Link->ForwardLink) {
DriverEntry = CR (Link, EFI_CORE_DRIVER_ENTRY, Link, EFI_CORE_DRIVER_ENTRY_SIGNATURE);
if (DriverEntry->DepexProtocolError) {
//
// If Section Extraction Protocol did not let the Depex be read before retry the read
//
Status = CoreGetDepexSectionAndPreProccess (DriverEntry);
}
if (DriverEntry->Dependent) {
if (CoreIsSchedulable (DriverEntry)) {
CoreInsertOnScheduledQueueWhileProcessingBeforeAndAfter (DriverEntry);
ReadyToRun = TRUE;
}
} else {
if (DriverEntry->Unrequested) {
DEBUG ((DEBUG_DISPATCH, "Evaluate DXE DEPEX for FFS(%g)\n", &DriverEntry->FileName));
DEBUG ((DEBUG_DISPATCH, " SOR = Not Requested\n"));
DEBUG ((DEBUG_DISPATCH, " RESULT = FALSE\n"));
}
}
}
} while (ReadyToRun);
//
// Close DXE dispatch Event
//
CoreCloseEvent (DxeDispatchEvent);
gDispatcherRunning = FALSE;
PERF_FUNCTION_END ();
return ReturnStatus;
}
UEFI PI Specification 중 발췌
https://uefi.org/specs/PI/1.8/V2_DXE_Dispatcher.html#dxe-dispatcher
10.3. The A Priori File
"The a priori file contains the list of DXE drivers from that firmware volume that should be loaded and executed before any other DXE drivers are discovered."
"If any of those DXE drivers have an associated dependency expression, then those dependency expressions are ignored. The a priori file provides a deterministic execution order of DXE drivers."
3. 스케줄링 기반(BEFORE/AFTER) 순환 의존성을 이용한 영구적 서비스 거부
-> 가능
실행의 전후 관계를 명시적으로 강제하는 스케줄링 기반 의존성 연산자는 일반적인 스택 평가 루틴을 우회하여 디스패치 과정에서 전용 재귀 함수를 통해 처리된다. 이 함수는 큐에 대기 중인 드라이버들의 논리적 선후 관계를 충족하기 위해 그래프의 역방향 탐색과 유사한 깊이 우선 탐색 형태의 재귀 호출을 실행한다. 하지만 이 재귀 아키텍처 내부에는 방문 중인 노드를 표시하는 상태 플래그나 재귀 깊이의 한계를 계산하는 순환 탐지 방어 기제가 마련되어 있지 않으며, 대상 드라이버가 종속 상태에 머물러 있는지만을 확인하여 재귀 진입을 허용한다
따라서 공격자가 악의적으로 변조된 펌웨어 업데이트를 통해 서로가 서로의 실행을 선행 요구하는 미세한 순환 고리를 주입하면, 함수는 큐 삽입 및 상태 갱신을 완료하지 못한 채 끊임없이 자기 자신을 무한 재귀 호출하게 된다
부팅 초기 단계에서 시스템의 협소한 메모리 콜 스택을 고갈시키고 하드 폴트를 유발하여, 소프트웨어적 복구가 불가능한 영구적인 시스템 마비 상태를 초래할 수 있음
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Core/Dxe/Dispatcher/Dispatcher.c
CoreInsertOnScheduledQueueWhileProcessingBeforeAndAfter()
VOID
CoreInsertOnScheduledQueueWhileProcessingBeforeAndAfter (
IN EFI_CORE_DRIVER_ENTRY *InsertedDriverEntry
)
{
LIST_ENTRY *Link;
EFI_CORE_DRIVER_ENTRY *DriverEntry;
//
// Process Before Dependency
//
for (Link = mDiscoveredList.ForwardLink; Link != &mDiscoveredList; Link = Link->ForwardLink) {
DriverEntry = CR (Link, EFI_CORE_DRIVER_ENTRY, Link, EFI_CORE_DRIVER_ENTRY_SIGNATURE);
if (DriverEntry->Before && DriverEntry->Dependent && (DriverEntry != InsertedDriverEntry)) {
DEBUG ((DEBUG_DISPATCH, "Evaluate DXE DEPEX for FFS(%g)\n", &DriverEntry->FileName));
DEBUG ((DEBUG_DISPATCH, " BEFORE FFS(%g) = ", &DriverEntry->BeforeAfterGuid));
if (CompareGuid (&InsertedDriverEntry->FileName, &DriverEntry->BeforeAfterGuid)) {
//
// Recursively process BEFORE
//
DEBUG ((DEBUG_DISPATCH, "TRUE\n END\n RESULT = TRUE\n"));
CoreInsertOnScheduledQueueWhileProcessingBeforeAndAfter (DriverEntry);
} else {
DEBUG ((DEBUG_DISPATCH, "FALSE\n END\n RESULT = FALSE\n"));
}
}
}
//
// Convert driver from Dependent to Scheduled state
//
CoreAcquireDispatcherLock ();
InsertedDriverEntry->Dependent = FALSE;
InsertedDriverEntry->Scheduled = TRUE;
InsertTailList (&mScheduledQueue, &InsertedDriverEntry->ScheduledLink);
CoreReleaseDispatcherLock ();
//
// Process After Dependency
//
for (Link = mDiscoveredList.ForwardLink; Link != &mDiscoveredList; Link = Link->ForwardLink) {
DriverEntry = CR (Link, EFI_CORE_DRIVER_ENTRY, Link, EFI_CORE_DRIVER_ENTRY_SIGNATURE);
if (DriverEntry->After && DriverEntry->Dependent && (DriverEntry != InsertedDriverEntry)) {
DEBUG ((DEBUG_DISPATCH, "Evaluate DXE DEPEX for FFS(%g)\n", &DriverEntry->FileName));
DEBUG ((DEBUG_DISPATCH, " AFTER FFS(%g) = ", &DriverEntry->BeforeAfterGuid));
if (CompareGuid (&InsertedDriverEntry->FileName, &DriverEntry->BeforeAfterGuid)) {
//
// Recursively process AFTER
//
DEBUG ((DEBUG_DISPATCH, "TRUE\n END\n RESULT = TRUE\n"));
CoreInsertOnScheduledQueueWhileProcessingBeforeAndAfter (DriverEntry);
} else {
DEBUG ((DEBUG_DISPATCH, "FALSE\n END\n RESULT = FALSE\n"));
}
}
}
}
InsertedDriverEntry->Dependent = FALSE;를 수행하는 시점이 첫 번째 BEFORE 루프 뒤에 있다.
만약 드라이버 A가 드라이버 B의 BEFORE이고, 드라이버 B도 드라이버 A의 BEFORE인 순환 구조라면
- A로 함수 진입 → BEFORE 루프에서 B 발견
- B로 재귀 호출 → B의 BEFORE 루프에서 다시 A 발견
- 이때 A는 아직 Dependent = FALSE 처리가 되기 전이므로, 다시 A로 재귀 호출
- 함수 호출 스택이 무한히 쌓이며 stack overflow 발생
'CS > Graduation project' 카테고리의 다른 글
| [UEFI] Oob 탐지 스크립트 검증 데이터 생성 -1 (0) | 2026.03.29 |
|---|---|
| [UEFI] SMI 발생 원리 및 처리 방법 (0) | 2026.03.10 |
| Taint Analysis 개념 정리 (0) | 2026.03.01 |
| [UEFI] Ghidra에서 edk2 함수 P-code로 확인해 보기 (0) | 2026.02.26 |
| [UEFI] gRT(Runtime Services)란? (0) | 2026.02.24 |
