Что такое транзакция?
Транзакция это группа операций, которая удовлетворяет следующим свойствам:
* атомарность(atomic),
* согласованность (consistent),
* изолированность(isolated)
* долговечность(durable) - ACID.
Под атомарностью подразумевается то, что все операции в рамках транзакции либо выполнятся успешно, либо не выполнятся вообще и откатятся(rollback) в исходное состояние.
Под согласованностью понимается то, что данные будут согласованы после транзакций.
Пример: снятие и зачисление на двух счетах при банковском переводе должны совпадать.
Под изолированностью понимается то, что к примеру, процесс обновления системы не виден для человека, который ей пользуется в данный момент.
Под долговечностью понимается сохранность данных при каком-либо сбое. Применительно к windows это может означать внезапную остановку системы или отключение питания системы. Данные транзакции при этом будут сохранены.
Как принцип ACID реализуется в windows ?
ACID реализуется через механизм транзакций в windows(начиная с vista), поддерживается два типа транзакционных операций: реестр(registry) и файловые операции на NTFS томе.
Создание транзации => выполнение действий в рамках транзакции => подтверждение изменений ( commit ) или откат изменений ( rollback ) обеспечивает атомарность и согласованность действий в рамках транзакции.
Изолированность заложена в сам процесс транзаций, к примеру, созданный ключ реестра в рамках транзации не виден для обычных реестровых api, он виден только для транзакционных api работы с ключем.
Долговечность достигается за счет компонента, под названием Common Log File System (CLFS). Это высокопроизводительная, обще-целевая подсистема логирования, которая к тому же устойчива к сбоям системы.
Как транзакции используются в windows ?
Самый наглядный пример - это атомарное обновление набора файлов и ключей реестра, этим занимаются разные инсталляторы, windows installer в частности.
На примере работы обновления windows попробуем получить имена создаваемых ключей реестра и создаваемых файлов при коммите транзакции.
Для этого, нужно немного разобрать внутренности функции коммита, это будет сделано в другой статье. Пока же, достаточно упомянуть точку, где происходит непосредственно запись. Это делается в функции NTSTATUS CmpTransMgrCommitUoW( CM_KCB_UOW *unitOfWork, PLARGE_INTEGER currentTime ), внутри которой есть switch ( unitOfWork->ActionType ).
Нас интересует unitOfWork->ActionType == UoWAddThisKey, который приводит нас к функции NTSTATUS CmpCommitAddKeyUoW( CM_KCB_UOW *uow, PLARGE_INTEGER lastWriteTime ).
Из структуры CM_KCB_UOW можно получить имя создаваемого ключа:
typedef struct _CM_KCB_UOW
{
...
CM_KEY_CONTROL_BLOCK *KeyControlBlock;
...
} CM_KCB_UOW;
typedef struct _CM_KEY_CONTROL_BLOCK
{
...
PCM_NAME_CONTROL_BLOCK NameBlock;
...
} CM_KEY_CONTROL_BLOCK;
typedef struct _CM_NAME_CONTROL_BLOCK
{
...
union
{
CM_NAME_HASH NameHash;
struct
{
ULONG ConvKey;
struct _CM_KEY_HASH *NextHash;
USHORT NameLength;
WCHAR Name[1] ; // The actual string value
};
};
} CM_NAME_CONTROL_BLOCK;
Имея эту информацию, можно получить список создаваемых ключей при обновлении(ставим брекпойнт и запускаем любое обновление windows):
bp CmpCommitAddKeyUoW "da /c 64 poi(poi(esi+18)+4*7)+e;.echo '---CmpCommitAddKeyUoW---'; g"
Вывод:
a0874016 "PACKAGE_19_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.H@......NtFs....h._.`...."
'---CmpCommitAddKeyUoW---------'
9694cd4e "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_SV-SE_2877C192B6493CCD*"
'---CmpCommitAddKeyUoW---------'
a0a0ba6e "PACKAGE_20_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.........CMN.."
'---CmpCommitAddKeyUoW---------'
a0b5f28e "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_TR-TR_D1850BD9A5053EBE0"
'---CmpCommitAddKeyUoW---------'
a05c96fe "PACKAGE_21_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.0.\.....SeTd.."
'---CmpCommitAddKeyUoW---------'
a064d986 "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_ZH-CN_A2E229D7553D10DD0"
'---CmpCommitAddKeyUoW---------'
a0791426 "PACKAGE_22_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.X.y.....Ntfo"
'---CmpCommitAddKeyUoW---------'
a058a3a6 "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_ZH-HK_A18D22655618836D0.0.X..."
'---CmpCommitAddKeyUoW---------'
9e773b16 "PACKAGE_23_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.H;w.....FMfn..."
'---CmpCommitAddKeyUoW---------'
a0d26696 "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_ZH-TW_A6DE672D52ADED4D0.0.0"
'---CmpCommitAddKeyUoW---------'
9eef0f46 "PACKAGE_24_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.x.......FSim."
'---CmpCommitAddKeyUoW---------'
96e7f5e6 "X86_MICROSOFT-WINDOWS-SERVICINGSTACK-MSG_31BF3856AD364E35_0.0.0.0_NONE_62795FA07331A3BC442....IoNm"
'---CmpCommitAddKeyUoW---------'
Примерно также можно получить и значения:
a0c19858 "PendingXmlIdentifier"
'---CmpCommitSetValueKeyUoW----'
a0c198d0 "AdvancedInstallersNeedResolving"
'---CmpCommitSetValueKeyUoW----'
a20f12b8 "InstallClient"
'---CmpCommitSetValueKeyUoW----'
a20f1310 "InstallName"
'---CmpCommitSetValueKeyUoW----'
a20f1358 "InstallLocation"
'---CmpCommitSetValueKeyUoW----'
a20f1430 "CurrentState"
'---CmpCommitSetValueKeyUoW----'
a20f1470 "Visibility"
'---CmpCommitSetValueKeyUoW----'
Для файлов интересна функция:
Ntfs!TxfGetTransactionFromFileObject:
8519e67e 8bff mov edi,edi
8519e680 55 push ebp
8519e681 8bec mov ebp,esp
8519e683 56 push esi
8519e684 ff7508 push dword ptr [ebp+8]
8519e687 33f6 xor esi,esi
8519e689 e8fd3ff7ff call Ntfs!IoGetTransactionParameterBlock
8519e68e 85c0 test eax,eax
Прототип не известен, но сразу на входе есть вызов IoGetTransactionParameterBlock( PFILE_OBJECT FileObject ), которая есть в msdn.
В аргументах идет нужный нам FILE_OBJECT.
Ставим брекпойнт, чтобы получить список создаваемых и открываемых файлов при коммите транзакции:
bp 8519e689 "!object poi(esp);.echo '---File---'; g"
Получаем:
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_microsoft.web.management_31bf3856ad364e35_6.0.6000.16386_none_c30fe7d58014a975\ {HarddiskVolume1}
'---File---'
Object: 83301c88 Type: (82b93a80) File
ObjectHeader: 83301c70 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \Windows\winsxs {HarddiskVolume1}
'---File---'
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_microsoft_vsavb_b03f5f7f11d50a3a_6.0.6000.16386_none_6728c2d6cd97e7f4\ {HarddiskVolume1}
'---File---'
Object: 83301c88 Type: (82b93a80) File
ObjectHeader: 83301c70 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \Windows\winsxs {HarddiskVolume1}
'---File---'
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_miguicontrols.resources_31bf3856ad364e35_6.0.6000.16386_ru-ru_0c31b4feb872e0ff\ {HarddiskVolume1}
'---File---'
Object: 83301c88 Type: (82b93a80) File
ObjectHeader: 83301c70 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \Windows\winsxs {HarddiskVolume1}
'---File---'
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_miguicontrols_31bf3856ad364e35_6.0.6000.16386_none_ac1216923fb00239\ {HarddiskVolume1}
Поддержка транзакций в ядре windows
Для поддержки транзакций в ядре существуют четыре объекта ядра:
Transaction ( KTRANSACTION )
Transaction Manager ( KTM )
Resource Manager ( KRESOURCEMANAGER )
Enlistment ( KENLISTMENT )
Жизненный цикл этих объектов, как собственно и любых других в windows состоит из инициализации, создания, работы с объектами и их удаления.
Инициализация
Инициализация(заполнение OpenProcedure/CloseProcedure/DeleteProcedure и остальных данных ) и создание(ObCreateObjectTypeEx) этих объектов происходит на этапе инициализации системы:
Phase1Initialization => Phase1InitializationDiscard => TmInitSystem =>
=> TmpTransactionManagerInitialization / TmpTransactionInitialization / TmpResourceManagerInitialization / TmpEnlistmentInitialization => инициализация и создания соответствующих объектов.
Пример инициализации KTRANSACTION:
BOOLEAN TmpTransactionInitialization()
{
OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
UNICODE_STRING DestinationString;
NTSTATUS status;
RtlInitUnicodeString( &DestinationString, L"TmTx" );
TmpTransactionTypeName.Buffer = NULL;
status = RtlDuplicateUnicodeString( 0, &DestinationString, &TmpTransactionTypeName );
if ( !NT_SUCCESS(status) )
return FALSE;
RtlZeroMemory( &ObjectTypeInitializer, sizeof(OBJECT_TYPE_INITIALIZER) );
ObjectTypeInitializer.Length = sizeof(OBJECT_TYPE_INITIALIZER);
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObjectTypeInitializer.GenericMapping.GenericRead = TmpTransactionMapping[0];
ObjectTypeInitializer.GenericMapping.GenericWrite = TmpTransactionMapping[1];
ObjectTypeInitializer.GenericMapping.GenericExecute = TmpTransactionMapping[2];
ObjectTypeInitializer.GenericMapping.GenericAll = TmpTransactionMapping[3];
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(KTRANSACTION);
ObjectTypeInitializer.ValidAccessMask = TRANSACTION_ALL_ACCESS | TRANSACTION_RIGHT_RESERVED1;
ObjectTypeInitializer.CloseProcedure = TmpCloseTransaction;
ObjectTypeInitializer.DeleteProcedure = TmpDeleteTransaction;
status = ObCreateObjectTypeEx( &TmpTransactionTypeName, &ObjectTypeInitializer, 0, &TmTransactionObjectType );
if ( !NT_SUCCESS(status) )
return FALSE;
return TRUE;
}
Полностью функция инициализации выглядит так:
BOOLEAN TmInitSystem()
{
if ( !TmpTransactionManagerInitialization() )
return FALSE;
if ( !TmpTransactionInitialization() )
return FALSE;
if ( !TmpResourceManagerInitialization() )
return FALSE;
if ( !TmpEnlistmentInitialization() )
return FALSE;
ExInitializePagedLookasideList(&TmpLogWriteLookasideList, 0, 0, 0, 0x214, 'lLmT', 0);
KeInitializeMutex(&TmpAllProtocolsListMutex, 0);
InitializeListHead(&TmpAllProtocolsList);
TmpAllProtocolsListCount = 0;
KeInitializeMutex(&TmpAllPropReqsListMutex, 0);
InitializeListHead(&TmpAllPropReqsList);
KeInitializeMutex(&TmpAllCRMListMutex, 0);
InitializeListHead(&TmpAllCRMList);
TmpAllCRMListCount = 0;
TmpNamespaceInitialize( FIELD_OFFSET( KTM, NamespaceLink ), &TmpTmNamespace, FIELD_OFFSET( KTM, TmIdentity ) );
TmpNamespaceInitialize( FIELD_OFFSET( KTRANSACTION, GlobalNamespaceLink ), &TmpTransactionsNamespace, FIELD_OFFSET( KTRANSACTION, UOW ) );
KeInitializeEvent( &TmpTransactionFreezeCompleteEvent, NotificationEvent, FALSE );
KeInitializeEvent( &TmpTransactionThawEvent, NotificationEvent, FALSE );
KeInitializeMutex( &TmpFreezeMutex, 0 );
KeInitializeEvent( &TmpTransactionFreezeCancelEvent, NotificationEvent, FALSE );
KeInitializeTimer( &TmpTransactionThawTimer );
KeInitializeDpc( &TmpTransactionThawDpc, TmpTransactionThawDpcRoutine, NULL );
if ( !TmpInitializeKtmRmSecurityDescriptor() )
return FALSE;
return TRUE;
}
Где хранятся транзакции? Логично было бы предположить, что как и процессы/потоки они хранятся в связанных списках, но нет.
Транзакции хранятся в KTMOBJECT_NAMESPACE. В основе этой структуры лежит AVL дерево. Функции для работы с данной структурой:
TmpNamespaceLock
TmpNamespaceUnlock
TmpNamespaceInitialize
TmpNamespaceEnumerate
TmpNamespaceEnumerateObject
TmpNamespaceForEach
TmpNamespaceLookup
TmpNamespaceReplace
TmpNamespaceInsert
TmpNamespaceRemove
TmpNamespaceRename
TmpNamespaceCompareGuids
TmpNamespaceAllocateEntry
TmpNamespaceFreeEntry
В TmInitSystem вызывается инициализация глобальных переменных TmpTmNamespace / TmpTransactionsNamespace для хранения менеджеров транзакций и самих транзакций. Кроме того, в KRESOURCEMANAGER / KTM есть локальные namespace'ы. Подробнее про namespace'ы я расскажу в следующей статье.
Создание объектов
Создание объектов как и любых других в windows реализуется через NtCreate* функции:
NtCreateTransaction
NtCreateEnlistment
NtCreateResourceManager
NtCreateTransactionManager
Общая реализация также стандартна:
1) try + ProbeForWrite/ProbeForRead
2) проверка аргументов (флаги, длина юникодных строк и так далее)
3) создание объекта через ObCreateObject
4) инициализация созданного объекта (TmInitializeResourceManager/TmpInitializeEnlistment/TmInitializeTransaction/TmInitializeTransactionManager)
Работа с объектами
Для работы с объектами транзакций / ресурс менеджеров и прочих объектов существует куча сервисных функций:
NtCommitTransaction
NtRollbackTransaction
NtCommitEnlistment
NtRollbackEnlistment
NtRecoverEnlistment
NtSetInformationEnlistment
NtQueryInformationEnlistment
NtSetInformationTransaction
NtCreateKeyTransacted
...
И так далее, подробнее про функции и механизм commit'a / rollback'a будет рассказано в отдельной статье.
Удаление объектов
При удалении вышеупомянутых 4х объектов происходит все тоже самое, что и должно происходить с объектами. При закрытии хендла на объект вызывается
CloseProcedure. Когда счетчик ссылок на объект становится равен нулю - вызывается
DeleteProcedure. Установка этих функций для каждого объекта происходит на этапе инициализации системы в функции
TmInitSystem ( см. выше, раздел
Инициализация ).
Рассмотрим на примере объекта транзакции. Скажем, в доке
https://docs.microsoft.com/en-us/windows/desktop/ktm/transactions упоминается следующее поведение для транзакций:
"A transaction is an object that defines a logical unit of work. The transaction is alive as long as there is a handle referencing the transaction and it is considered active if the transaction has not yet been committed or rolled back. If a transaction is created and all handles to it have been closed before a commit or rollback occurs, the transaction will be rolled back."
Заглянем в код,
CloseProcedure'ой для транзакции является функция
TmpCloseTransaction.
VOID
TmpCloseTransaction( IN PEPROCESS Process OPTIONAL, IN PVOID Object, IN ULONG GrantedAccess, IN ULONG_PTR ProcessHandleCount, IN ULONG_PTR SystemHandleCount)
{
if ( SystemHandleCount == 1 || ProcessHandleCount == 1 )
{
TmRollbackTransaction( (KTRANSACTION *)Object, 0 );
}
}
Msdn не обманул.
Для
DeleteProcedure'ы транзакции код выглядит как-то так:
VOID
TmpDeleteTransaction( PVOID object )
{
KTM *tm = NULL;
KTRANSACTION *transaction = (KTRANSACTION*)object;
SetFlag( transaction->Flags,
KTRANSACTION_FLAG_DELETED );
if ( transaction->State ==
KTransactionUninitialized )
return;
if ( transaction->TmNamespaceLink.Links.Parent )
{
tm = transaction->Tm;
if ( tm )
{
TmpNamespaceRemove( (PVOID)transaction, &tm->Transactions, (PVOID)transaction->Tm );
transaction->Tm = NULL;
}
}
if ( transaction->GlobalNamespaceLink.Links.Parent )
TmpNamespaceRemove( (PVOID)transaction, &TmpTransactionsNamespace, NULL );
if ( transaction->Description.Buffer )
RtlFreeUnicodeString( &transaction->Description );
if ( transaction->TreeTx != transaction )
{
ObfDereferenceObject( transaction->TreeTx );
transaction->TreeTx = NULL;
}
}