среда, 15 мая 2019 г.

Транзакции в windows. Часть 1.

Что такое транзакция?


Транзакция это группа операций, которая удовлетворяет следующим свойствам:

* атомарность(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;
    }  
}

Комментариев нет:

Отправить комментарий