/* Copyright (c) 2000-2001 Microsoft Corporation Module Name: extattr.cpp Abstract: Get's additional file attributes beyond what you get with FindFirstFile/FindNextFile. Author: Stefan R. Steiner [ssteiner] 02-27-2000 Revision History: --*/ #include "stdafx.h" #include #include #include #include "direntrs.h" #include "extattr.h" #include "hardlink.h" #define READ_BUF_SIZE ( 1024 * 1024 ) #define FSD_SHARE_MODE ( FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ) #define FSD_MS_HSM_REPARSE_TAG 0xC0000004 static VOID eaGetSecurityInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, OUT SFileExtendedInfo *psExtendedInfo ); static VOID eaGetFileInformationByHandle( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, IN OUT SDirectoryEntry *psDirEntry, OUT SFileExtendedInfo *psExtendedInfo ); static VOID eaGetAlternateStreamInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, OUT SFileExtendedInfo *psExtendedInfo ); static VOID eaGetReparsePointInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, IN OUT ULONGLONG *pullBytesChecksummed, IN OUT SDirectoryEntry *psDirEntry, OUT SFileExtendedInfo *psExtendedInfo ); static BOOL eaChecksumRawEncryptedData( IN CDumpParameters *pcParams, IN const CBsString& cwsFileName, IN OUT SFileExtendedInfo *psExtendedInfo ); static BOOL eaChecksumStream( IN const CBsString& cwsStreamPath, IN OUT ULONGLONG *pullBytesChecksummed, IN OUT DWORD *pdwRunningCheckSum ); static DWORD eaChecksumBlock( IN DWORD dwRunningChecksum, IN LPBYTE pBuffer, IN DWORD dwBufSize ); static VOID eaConvertUserSidToString ( IN CDumpParameters *pcParams, IN PSID pSid, OUT CBsString *pcwsSid ); static VOID eaConvertGroupSidToString ( IN CDumpParameters *pcParams, IN PSID pSid, OUT CBsString *pcwsSid ); static VOID eaConvertSidToString ( IN CDumpParameters *pcParams, IN PSID pSid, OUT CBsString *pcwsSid ); static DWORD eaChecksumHSMReparsePoint( IN CDumpParameters *pcParams, IN PREPARSE_DATA_BUFFER pReparseData, IN DWORD dwTotalSize // Size of reparse point data ); static VOID eaGetObjectIdInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, IN OUT ULONGLONG *pullBytesChecksummed, IN OUT SDirectoryEntry *psDirEntry, IN OUT SFileExtendedInfo *psExtendedInfo ); /*++ Routine Description: Performs all of the checksums, and retrieves the security info for one file. Arguments: Return Value: --*/ VOID GetExtendedFileInfo( IN CDumpParameters *pcParams, IN CFsdVolumeState *pcFsdVolState, IN const CBsString& cwsDirPath, IN BOOL bSingleEntryOutput, IN OUT SDirectoryEntry *psDirEntry, OUT SFileExtendedInfo *psExtendedInfo ) { CBsString cwsFullPath( cwsDirPath ); // // If we are dumping an individual file's data, cwsDirPath has the complete // path to the file, otherwise glue the filename from the find data structure // to the path. // if ( !bSingleEntryOutput ) { cwsFullPath += psDirEntry->GetFileName(); } // // Get the information that retrieved from GetFileInformationByHandle // ::eaGetFileInformationByHandle( pcParams, cwsFullPath, psDirEntry, psExtendedInfo ); if ( psExtendedInfo->lNumberOfLinks > 1 && pcParams->m_eFsDumpType != eFsDumpFile ) { if ( pcFsdVolState->IsHardLinkInList( psExtendedInfo->ullFileIndex, cwsDirPath, psDirEntry->GetFileName(), &psDirEntry->m_sFindData, psExtendedInfo ) ) { // // Found the link in the list, return with the previous link's information, except // zero out the number of bytes checksummed so that total counts remain accurate. // psExtendedInfo->ullTotalBytesChecksummed = 0; psExtendedInfo->ullTotalBytesNamedDataStream = 0; return; } } // // Get the security information. // ::eaGetSecurityInfo( pcParams, cwsFullPath, psExtendedInfo ); eaGetObjectIdInfo( pcParams, cwsFullPath, &psExtendedInfo->ullTotalBytesChecksummed, psDirEntry, psExtendedInfo ); // // Get the reparse point information if necessary // if ( psDirEntry->m_sFindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) ::eaGetReparsePointInfo( pcParams, cwsFullPath, &psExtendedInfo->ullTotalBytesChecksummed, psDirEntry, psExtendedInfo ); // // Get the raw encryption data checksum if necessary // if ( !pcParams->m_bNoChecksums && psDirEntry->m_sFindData.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED && !( psDirEntry->m_sFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) ::eaChecksumRawEncryptedData( pcParams, cwsFullPath, psExtendedInfo ); // // Checksum the unnamed datastream if this is not a directory // if ( !pcParams->m_bNoChecksums && !( psDirEntry->m_sFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) { DWORD dwChecksum = 0; ULONGLONG ullFileSize = ( ( ULONGLONG )( psDirEntry->m_sFindData.nFileSizeHigh ) << 32 ) + psDirEntry->m_sFindData.nFileSizeLow; if ( ullFileSize == 0 ) { // // In this case the default value for checksum of -------- is correct. // } else if ( psDirEntry->m_sFindData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE && pcParams->m_bDontChecksumHighLatencyData ) { psExtendedInfo->cwsUnnamedStreamChecksum = L"HighLtcy"; } else if ( ::eaChecksumStream( cwsFullPath, &psExtendedInfo->ullTotalBytesChecksummed, &dwChecksum ) ) { psExtendedInfo->cwsUnnamedStreamChecksum.Format( pcParams->m_pwszULongHexFmt, dwChecksum ); } else { psExtendedInfo->cwsUnnamedStreamChecksum.Format( L"<%6d>", ::GetLastError() ); } } // // Get info on and checksum the named data streams // ::eaGetAlternateStreamInfo( pcParams, cwsFullPath, psExtendedInfo ); // // If this file is multiply linked, add it to the hard-link file list // if ( psExtendedInfo->lNumberOfLinks > 1 && pcParams->m_eFsDumpType != eFsDumpFile ) { pcFsdVolState->AddHardLinkToList( psExtendedInfo->ullFileIndex, cwsDirPath, psDirEntry->GetFileName(), &psDirEntry->m_sFindData, psExtendedInfo ); } } /*++ Routine Description: Gets the security information for a file Arguments: Return Value: --*/ static VOID eaGetSecurityInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, OUT SFileExtendedInfo *psExtendedInfo ) { // // Now get the security information // PACL psDacl = NULL, psSacl = NULL; PSID pOwnerSid = NULL, pGroupSid = NULL; DWORD dwRet; DWORD dwSaclErrorRetCode = ERROR_SUCCESS; PSECURITY_DESCRIPTOR pDesc = NULL; try { dwRet = ::GetNamedSecurityInfoW( ( LPWSTR )cwsFileName.c_str(), // strange API, should ask for const SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, &pOwnerSid, &pGroupSid, &psDacl, &psSacl, &pDesc ); // // If it didn't work, try again without the Sacl information // if ( dwRet != ERROR_SUCCESS ) { dwSaclErrorRetCode = dwRet; psSacl = NULL; dwRet = ::GetNamedSecurityInfoW( ( LPWSTR )cwsFileName.c_str(), // strange API, should ask for const SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, &pOwnerSid, &pGroupSid, &psDacl, NULL, &pDesc ); } #if 0 // // Test code to find security API problem // pDesc = ::LocalAlloc( LMEM_FIXED, 4096 ); DWORD dwLengthNeeded; dwRet = ERROR_SUCCESS; if ( !::GetFileSecurityW( cwsFileName, // DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, DACL_SECURITY_INFORMATION, // | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, pDesc, 4096, &dwLengthNeeded ) ) dwRet = ::GetLastError(); if ( dwRet == ERROR_SUCCESS ) wprintf( L"Got security descripter for '%s'\n", cwsFileName.c_str() ); else wprintf( L"Error getting descripter for '%s', dwRet: %d\n", cwsFileName.c_str(), dwRet ); #endif if ( dwRet == ERROR_SUCCESS ) { if ( pDesc && pcParams->m_bEnableSDCtrlWordDump ) { SECURITY_DESCRIPTOR_CONTROL sdc; DWORD dwDescRevision; if ( ::GetSecurityDescriptorControl( pDesc, &sdc, &dwDescRevision ) ) psExtendedInfo->wSecurityDescriptorControl = ( WORD )( sdc & ~SE_SELF_RELATIVE ); else psExtendedInfo->wSecurityDescriptorControl = -1; } else psExtendedInfo->wSecurityDescriptorControl = -1; if ( psDacl ) { psExtendedInfo->lNumDACEs = 0; psExtendedInfo->wDACLSize = 0; // // Checksum the DACL data if necessary. // n.b. We only take into account ACEs that are inherited. // if ( psDacl->AclSize > 0 ) { DWORD dwChecksum = 0; // // The first ACE is right after the ACL header // PACE_HEADER pAceHeader = ( PACE_HEADER )( psDacl + 1 ); for ( USHORT aceNum = 0; aceNum < psDacl->AceCount; ++aceNum ) { // // Skip if an inherited ACE // if ( !( pAceHeader->AceFlags & INHERITED_ACE ) ) { dwChecksum += ::eaChecksumBlock( dwChecksum, ( LPBYTE )pAceHeader, pAceHeader->AceSize ); ++psExtendedInfo->lNumDACEs; psExtendedInfo->wDACLSize += pAceHeader->AceSize; if ( pcParams->m_bPrintDebugInfo ) wprintf( L"\t%d: f: %04x, t: %04x, s: %u\n", aceNum, pAceHeader->AceFlags, pAceHeader->AceType, pAceHeader->AceSize ); } pAceHeader = ( PACE_HEADER )( ( ( LPBYTE )pAceHeader ) + pAceHeader->AceSize ); } if ( psExtendedInfo->wDACLSize > 0 ) { psExtendedInfo->cwsDACLChecksum.Format( pcParams->m_pwszULongHexFmt, dwChecksum ); psExtendedInfo->ullTotalBytesChecksummed += psExtendedInfo->wDACLSize; } } } else psExtendedInfo->lNumDACEs = 0; // probably FAT or CDROM fs if ( psSacl ) { psExtendedInfo->lNumSACEs = 0; psExtendedInfo->wSACLSize = 0; // // Checksum the SACL data if necessary // n.b. We only take into account ACEs that are inherited. // if ( psSacl->AclSize > 0 ) { DWORD dwChecksum = 0; // // The first ACE is right after the ACL header // PACE_HEADER pAceHeader = ( PACE_HEADER )( psSacl + 1 ); for ( USHORT aceNum = 0; aceNum < psSacl->AceCount; ++aceNum ) { // // Skip if an inherited ACE // if ( !( pAceHeader->AceFlags & INHERITED_ACE ) ) { dwChecksum += ::eaChecksumBlock( dwChecksum, ( LPBYTE )pAceHeader, pAceHeader->AceSize ); ++psExtendedInfo->lNumSACEs; psExtendedInfo->wSACLSize += pAceHeader->AceSize; if ( pcParams->m_bPrintDebugInfo ) wprintf( L"\ts%d: f: %04x, t: %04x, s: %u\n", aceNum, ( DWORD)( pAceHeader->AceFlags ), pAceHeader->AceType, (DWORD)( pAceHeader->AceSize ) ); } pAceHeader = ( PACE_HEADER )( ( ( LPBYTE )pAceHeader ) + pAceHeader->AceSize ); } if ( psExtendedInfo->wSACLSize > 0 ) { psExtendedInfo->cwsSACLChecksum.Format( pcParams->m_pwszULongHexFmt, dwChecksum ); psExtendedInfo->ullTotalBytesChecksummed += psExtendedInfo->wSACLSize; } } } else if ( dwSaclErrorRetCode != ERROR_SUCCESS ) { psExtendedInfo->lNumSACEs = -1; psExtendedInfo->wSACLSize = -1; psExtendedInfo->cwsSACLChecksum.Format( L"<%6d>", dwSaclErrorRetCode ); } else psExtendedInfo->lNumSACEs = 0; // none eaConvertUserSidToString( pcParams, pOwnerSid, &psExtendedInfo->cwsOwnerSid ); eaConvertGroupSidToString( pcParams, pGroupSid, &psExtendedInfo->cwsGroupSid ); ::LocalFree( pDesc ); } else { // // Error getting security information // psExtendedInfo->lNumDACEs = -1; psExtendedInfo->lNumSACEs = -1; psExtendedInfo->wDACLSize = -1; psExtendedInfo->wSACLSize = -1; psExtendedInfo->cwsDACLChecksum.Format( L"<%6d>", dwRet ); psExtendedInfo->cwsSACLChecksum.Format( L"<%6d>", dwRet ); psExtendedInfo->cwsOwnerSid.Format( L"<%6d>", dwRet ); psExtendedInfo->cwsGroupSid.Format( L"<%6d>", dwRet ); } } catch( ... ) { psExtendedInfo->lNumDACEs = -1; psExtendedInfo->lNumSACEs = -1; psExtendedInfo->wDACLSize = -1; psExtendedInfo->wSACLSize = -1; psExtendedInfo->cwsOwnerSid.Format( L"<%6d>", ::GetLastError() ); psExtendedInfo->cwsGroupSid.Format( L"<%6d>", ::GetLastError() ); } } static VOID eaGetFileInformationByHandle( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, IN OUT SDirectoryEntry *psDirEntry, OUT SFileExtendedInfo *psExtendedInfo ) { HANDLE hFile; // // Note that while we do have to open the file, not even read access is needed // hFile = ::CreateFileW( cwsFileName, 0, FSD_SHARE_MODE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { psExtendedInfo->lNumberOfLinks = -1; return; } // // Now get the additional attributes // BY_HANDLE_FILE_INFORMATION sFileInfo; if ( ::GetFileInformationByHandle( hFile, &sFileInfo ) ) { psExtendedInfo->lNumberOfLinks = ( LONG )sFileInfo.nNumberOfLinks; psExtendedInfo->ullFileIndex = ( ( ULONGLONG )sFileInfo.nFileIndexHigh << 32 ) + sFileInfo.nFileIndexLow; if ( psExtendedInfo->lNumberOfLinks > 1 || psDirEntry->m_sFindData.ftLastWriteTime.dwLowDateTime == 0 ) { // // Expect that the FindFirst/NextFile dir entry is stale or non-existant. Use information // from this call. // psDirEntry->m_sFindData.dwFileAttributes = sFileInfo.dwFileAttributes; psDirEntry->m_sFindData.ftCreationTime = sFileInfo.ftCreationTime; psDirEntry->m_sFindData.ftLastAccessTime = sFileInfo.ftLastAccessTime; psDirEntry->m_sFindData.ftLastWriteTime = sFileInfo.ftLastWriteTime; psDirEntry->m_sFindData.nFileSizeHigh = sFileInfo.nFileSizeHigh; psDirEntry->m_sFindData.nFileSizeLow = sFileInfo.nFileSizeLow; } } else psExtendedInfo->lNumberOfLinks = -1; ::CloseHandle( hFile ); } static VOID eaGetAlternateStreamInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, OUT SFileExtendedInfo *psExtendedInfo ) { NTSTATUS Status; HANDLE hFile; // // Note that while we do have to open the file, not even read access is needed // hFile = CreateFileW( cwsFileName, FILE_GENERIC_READ, // | ACCESS_SYSTEM_SECURITY, FSD_SHARE_MODE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { psExtendedInfo->lNumNamedDataStreams = -1; psExtendedInfo->lNumPropertyStreams = -1; psExtendedInfo->cwsNamedDataStreamChecksum.Format( L"<%6d>", ::GetLastError() ); return; } // // Loop until we read the file information // LPBYTE pBuffer = NULL; ULONG ulBuffSize = 1024; IO_STATUS_BLOCK iosb; static const WCHAR * const pwszDefaultStreamName = L"::$DATA"; static const ULONG ulDefaultStreamNameLength = 7; while ( TRUE ) { pBuffer = new BYTE[ ulBuffSize ]; if ( pBuffer == NULL ) throw E_OUTOFMEMORY; Status = ::NtQueryInformationFile( hFile, &iosb, pBuffer, ulBuffSize, FileStreamInformation ); // // If we succeeded in getting data, when have the data so party on and get out of // the loop // if ( NT_SUCCESS( Status ) && iosb.Information != 0 ) { break; } // // If the error isn't overflow, get out // if ( Status != STATUS_BUFFER_OVERFLOW && Status != STATUS_BUFFER_TOO_SMALL ) { // // NOTE: If status is successful, we didn't get any data but it's not // an error. Happens a lot with directories since they don't have a default // unnamed stream. // if ( !NT_SUCCESS( Status ) ) { // // Another kind of error // BUGBUG: if not NTFS, C000000D occurs. Should not try this on // a non-NTFS volume //psExtendedInfo->lNumNamedDataStreams = -1; //psExtendedInfo->dwNamedDataStreamChecksum = ::GetLastError(); //psExtendedInfo->bNamedDataStreamHadError = TRUE; } delete [] pBuffer; ::CloseHandle( hFile ); return; } // // Increase the size of the buffer // ulBuffSize <<= 1; // double it each try delete [] pBuffer; pBuffer = NULL; } // // If we are here, we have a valid FileStreamInformation buffer // ::CloseHandle( hFile ); PFILE_STREAM_INFORMATION pFSI; pFSI = ( PFILE_STREAM_INFORMATION ) pBuffer; BOOL bHadError = FALSE; DWORD dwChecksum = 0; // // Now loop through the named streams. // while ( TRUE ) { if ( pFSI->StreamNameLength != sizeof( WCHAR ) * ulDefaultStreamNameLength || wcsncmp( pFSI->StreamName, pwszDefaultStreamName, ulDefaultStreamNameLength ) != 0 ) { LPWSTR pwszDataStr; pwszDataStr = ::wcsstr( pFSI->StreamName, L":$DATA" ); if ( pwszDataStr != NULL ) { pwszDataStr[0] = L'\0'; // Strip off the :$DATA off of name ++psExtendedInfo->lNumNamedDataStreams; // wprintf( L" %8I64u '%-*.*s' : %d\n", pFSI->StreamSize, pFSI->StreamNameLength / 2, // pFSI->StreamNameLength / 2, pFSI->StreamName, pFSI->StreamNameLength ); psExtendedInfo->ullTotalBytesNamedDataStream += ( ULONGLONG )pFSI->StreamSize.QuadPart; if ( !pcParams->m_bNoChecksums && !bHadError ) { // // Put into the checksum the name of the stream // dwChecksum = ::eaChecksumBlock( dwChecksum, ( LPBYTE )pFSI->StreamName, ::wcslen( pFSI->StreamName ) * sizeof WCHAR ); // // Now checksum the data in the stream // if ( ::eaChecksumStream( cwsFileName + pFSI->StreamName, &psExtendedInfo->ullTotalBytesChecksummed, &dwChecksum ) ) { psExtendedInfo->cwsNamedDataStreamChecksum.Format( pcParams->m_pwszULongHexFmt, dwChecksum ); } else { psExtendedInfo->cwsNamedDataStreamChecksum.Format( L"<%6d>", ::GetLastError() ); bHadError = TRUE; } } } else { // // Not an named data stream, probably a property stream // BUGBUG: need to verify that this is a property stream // ++psExtendedInfo->lNumPropertyStreams; } } if ( pFSI->NextEntryOffset == 0 ) break; pFSI = ( PFILE_STREAM_INFORMATION )( pFSI->NextEntryOffset + ( PBYTE ) pFSI ); } if ( !bHadError && !pcParams->m_bNoChecksums && psExtendedInfo->lNumNamedDataStreams > 0 ) { psExtendedInfo->cwsNamedDataStreamChecksum.Format( pcParams->m_pwszULongHexFmt, dwChecksum ); } if ( pBuffer != NULL ) delete [] pBuffer; } static VOID eaGetReparsePointInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, IN OUT ULONGLONG *pullBytesChecksummed, IN OUT SDirectoryEntry *psDirEntry, IN OUT SFileExtendedInfo *psExtendedInfo ) { HANDLE hFile = INVALID_HANDLE_VALUE; BOOL bRet = TRUE; LPBYTE pReadBuffer = NULL; DWORD dwChecksum = 0; try { // // Now get the reparse point data buffer // pReadBuffer = ( LPBYTE )::VirtualAlloc( NULL, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE ); if ( pReadBuffer == NULL ) { bRet = FALSE; goto EXIT; } // // Open the file in order to read reparse point data // hFile = ::CreateFileW( cwsFileName, GENERIC_READ, FSD_SHARE_MODE, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { bRet = FALSE; goto EXIT; } // // Now get the reparse point data // DWORD dwBytesReturned; if ( !::DeviceIoControl( hFile, FSCTL_GET_REPARSE_POINT, NULL, // lpInBuffer; must be NULL 0, // nInBufferSize; must be zero ( LPVOID )pReadBuffer, // pointer to output buffer MAXIMUM_REPARSE_DATA_BUFFER_SIZE, // size of output buffer &dwBytesReturned, // receives number of bytes returned NULL // pointer to OVERLAPPED structure ) ) { bRet = FALSE; goto EXIT; } PREPARSE_DATA_BUFFER pReparseData; pReparseData = ( PREPARSE_DATA_BUFFER )pReadBuffer ; psExtendedInfo->ulReparsePointTag = pReparseData->ReparseTag; psExtendedInfo->wReparsePointDataSize = ( WORD )dwBytesReturned; if ( !pcParams->m_bNoSpecialReparsePointProcessing && psExtendedInfo->ulReparsePointTag == FSD_MS_HSM_REPARSE_TAG ) { // // To make sure that dumps don't get many miscompares we // need to tweak the attributes. Raid #153050 // if ( pcParams->m_bDontChecksumHighLatencyData ) { // // Need to always make this file look like it is offline. // In this case, we need to always enable the FILE_ATTRIBUTE_OFFLINE // flag. // psDirEntry->m_sFindData.dwFileAttributes |= FILE_ATTRIBUTE_OFFLINE; } else { // // Need to always make this file look like it is cached. // In this case, we need to always disable the FILE_ATTRIBUTE_OFFLINE // flag. // psDirEntry->m_sFindData.dwFileAttributes &= ~FILE_ATTRIBUTE_OFFLINE; } // // Call a special HSM checksum function which filters out certain // dynamic fields before checksumming the data. // dwChecksum = eaChecksumHSMReparsePoint( pcParams, pReparseData, dwBytesReturned ); } else { // // Now checksum all of the reparse point data // dwChecksum = ::eaChecksumBlock( 0, pReadBuffer, dwBytesReturned ); } psExtendedInfo->cwsReparsePointDataChecksum.Format( pcParams->m_pwszULongHexFmt, dwChecksum ); *pullBytesChecksummed += dwBytesReturned; } catch( ... ) { bRet = FALSE; } EXIT: if ( pReadBuffer != NULL ) ::VirtualFree( pReadBuffer, 0, MEM_RELEASE ); if ( hFile != INVALID_HANDLE_VALUE ) ::CloseHandle( hFile ); if ( bRet == FALSE ) psExtendedInfo->cwsReparsePointDataChecksum.Format( L"<%6d>", ::GetLastError() ); } static BOOL eaChecksumStream( IN const CBsString& cwsStreamPath, IN OUT ULONGLONG *pullBytesChecksummed, IN OUT DWORD *pdwRunningCheckSum ) { LPBYTE pReadBuffer; BOOL bRet = TRUE; pReadBuffer = ( LPBYTE )::VirtualAlloc( NULL, READ_BUF_SIZE, MEM_COMMIT, PAGE_READWRITE ); if ( pReadBuffer == NULL ) return FALSE; // // Open file with NO_BUFFERING. // HANDLE hFile = INVALID_HANDLE_VALUE; try { hFile = ::CreateFileW( cwsStreamPath, GENERIC_READ, FSD_SHARE_MODE, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_NO_RECALL | FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { bRet = FALSE; goto EXIT; } DWORD dwBytesRead; while ( ::ReadFile( hFile, pReadBuffer, READ_BUF_SIZE, &dwBytesRead, NULL ) ) { if ( dwBytesRead == 0 ) break; *pdwRunningCheckSum = ::eaChecksumBlock( *pdwRunningCheckSum, pReadBuffer, dwBytesRead ); *pullBytesChecksummed += dwBytesRead; } if ( ::GetLastError() != ERROR_SUCCESS ) bRet = FALSE; } catch( ... ) { bRet = FALSE; } EXIT: if ( hFile != INVALID_HANDLE_VALUE ) ::CloseHandle( hFile ); ::VirtualFree( pReadBuffer, 0, MEM_RELEASE ); return bRet; } // // This class maintains the encryption context // class CFsdEncryptionContext { public: CFsdEncryptionContext() : m_hDoneEvent( NULL ), m_dwChecksum( 0 ), m_ullBytesRead( 0 ) { m_hDoneEvent = ::CreateEventW( NULL, TRUE, FALSE, NULL ); if ( m_hDoneEvent == NULL ) throw ::GetLastError(); } ~CFsdEncryptionContext() { if ( m_hDoneEvent != NULL ) ::CloseHandle( m_hDoneEvent ); } DWORD WaitForDoneEvent() { DWORD dwRet; dwRet = ::WaitForSingleObject( m_hDoneEvent, INFINITE ); if ( dwRet == WAIT_OBJECT_0 ) return ERROR_SUCCESS; else if ( dwRet == WAIT_TIMEOUT ) return ERROR_TIMEOUT; return ::GetLastError(); } VOID FireDoneEvent() { ::SetEvent( m_hDoneEvent ); } DWORD GetChecksum() { return m_dwChecksum; } ULONGLONG GetBytesRead() { return m_ullBytesRead; } static DWORD WINAPI ExportCallback( IN PBYTE pbData, IN PVOID pvCallbackContext, IN ULONG ulLength ) { CFsdEncryptionContext *pcThis = static_cast< CFsdEncryptionContext * >( pvCallbackContext ); pcThis->m_dwChecksum = ::eaChecksumBlock( pcThis->m_dwChecksum, pbData, ulLength ); pcThis->m_ullBytesRead += ulLength; return ERROR_SUCCESS; } private: HANDLE m_hDoneEvent; DWORD m_dwChecksum; ULONGLONG m_ullBytesRead; }; static BOOL eaChecksumRawEncryptedData( IN CDumpParameters *pcParams, IN const CBsString& cwsFileName, IN OUT SFileExtendedInfo *psExtendedInfo ) { PVOID pvContext = NULL; DWORD dwRet = ERROR_SUCCESS; CFsdEncryptionContext cEncryptionContext; try { // // Open this puppy up // dwRet = ::OpenEncryptedFileRawW( cwsFileName, 0, &pvContext ); if ( dwRet == ERROR_SUCCESS ) { //wprintf( L"**** Opened encrypted file '%s'\n", cwsFileName.c_str() ); dwRet = ::ReadEncryptedFileRaw( CFsdEncryptionContext::ExportCallback, &cEncryptionContext, pvContext ); if ( dwRet == ERROR_SUCCESS ) { //wprintf( L"**** Called read on encrypted file, bytes read: %u, checksum: %u\n", // cEncryptionContext.GetBytesRead(), cEncryptionContext.GetChecksum() ); psExtendedInfo->cwsEncryptedRawDataChecksum.Format( pcParams->m_pwszULongHexFmt, cEncryptionContext.GetChecksum() ); psExtendedInfo->ullTotalBytesChecksummed += cEncryptionContext.GetBytesRead(); } } } catch( ... ) { dwRet = ERROR_EXCEPTION_IN_SERVICE; // ??? } if ( pvContext != NULL ) ::CloseEncryptedFileRaw( pvContext ); if ( dwRet != ERROR_SUCCESS ) psExtendedInfo->cwsEncryptedRawDataChecksum.Format( L"<%6d>", dwRet ); return dwRet == ERROR_SUCCESS; } /*++ Routine Description: Checksums a block of data. The block needs to be DWORD aligned for performance and correctness since this function assumes it can zero out up to 3 bytes beyond the end of the buffer. Also, only the last buffer in a series of buffers can have unaligned data at the end of the buffer. Arguments: dwRunningChecksum - The previous checksum from a previous call. Should be zero if this is the first block to checksum in a series of blocks. pBuffer - Pointer to the buffer to checksum. dwBufSize - This should always be a multiple of a DWORD, except for the last block in a series. Return Value: The checksum. --*/ static DWORD eaChecksumBlock( IN DWORD dwRunningChecksum, IN LPBYTE pBuffer, IN DWORD dwBufSize ) { // // Need to zero out any additional bytes not aligned. // DWORD dwBytesToZero; DWORD dwBufSizeInDWords; dwBytesToZero = dwBufSize % sizeof( DWORD ); dwBufSizeInDWords = ( dwBufSize + ( sizeof( DWORD ) - 1 ) ) / sizeof( DWORD ); // int div while ( dwBytesToZero-- ) pBuffer[ dwBufSize + dwBytesToZero ] = 0; LPDWORD pdwBuf = ( LPDWORD )pBuffer; // BUGBUG: Need better checksum for ( DWORD dwIdx = 0; dwIdx < dwBufSizeInDWords; ++dwIdx ) dwRunningChecksum += ( dwRunningChecksum << 1 ) | pdwBuf[ dwIdx ]; return dwRunningChecksum; } /*++ Routine Description: Converts a user SID to a string. Has a simple one element cache to speed up conversions. This is especially useful when the user wants the symbolic DOMAIN\ACCOUNT strings. Arguments: Return Value: NONE --*/ static VOID eaConvertUserSidToString ( IN CDumpParameters *pcParams, IN PSID pSid, OUT CBsString *pcwsSid ) { static CBsString cwsCachedSidString; static PSID pCachedSid = NULL; // // Is the cached SID the same as the passed in one. If so, // return the cached sid string. // if ( pCachedSid != NULL && ::EqualSid( pSid, pCachedSid ) ) { *pcwsSid = cwsCachedSidString; return; } // // Convert the SID into a string // ::eaConvertSidToString( pcParams, pSid, pcwsSid ); // // Now cache the sid // cwsCachedSidString = *pcwsSid; if ( pCachedSid != NULL ) free( pCachedSid ); size_t cSidLength = ( size_t )::GetLengthSid( pSid ); pCachedSid = ( PSID )malloc( cSidLength ); if ( pCachedSid == NULL ) // prefix #171666 { pcParams->ErrPrint( L"eaConvertUserSidToString - Can't allocate memory, out of memory" ); throw E_OUTOFMEMORY; } ::CopySid( ( DWORD )cSidLength, pCachedSid, pSid ); } /*++ Routine Description: Converts a group SID to a string. Has a simple one element cache to speed up conversions. This is especially useful when the user wants the symbolic DOMAIN\ACCOUNT strings. Arguments: Return Value: NONE --*/ static VOID eaConvertGroupSidToString ( IN CDumpParameters *pcParams, IN PSID pSid, OUT CBsString *pcwsSid ) { static CBsString cwsCachedSidString; static PSID pCachedSid = NULL; // // Is the cached SID the same as the passed in one. If so, // return the cached sid string. // if ( pCachedSid != NULL && ::EqualSid( pSid, pCachedSid ) ) { *pcwsSid = cwsCachedSidString; return; } // // Convert the SID into a string // ::eaConvertSidToString( pcParams, pSid, pcwsSid ); // // Now cache the sid // cwsCachedSidString = *pcwsSid; if ( pCachedSid != NULL ) free( pCachedSid ); size_t cSidLength = ( size_t )::GetLengthSid( pSid ); pCachedSid = ( PSID )malloc( cSidLength ); if ( pCachedSid == NULL ) // prefix #171665 { pcParams->ErrPrint( L"eaConvertGroupSidToString - Can't allocate memory, out of memory" ); throw E_OUTOFMEMORY; } ::CopySid( ( DWORD )cSidLength, pCachedSid, pSid ); } /*++ Routine Description: Converts a SID to a string. Arguments: Return Value: NONE --*/ static VOID eaConvertSidToString ( IN CDumpParameters *pcParams, IN PSID pSid, OUT CBsString *pcwsSid ) { if ( pcParams->m_bShowSymbolicSIDNames ) { CBsString cwsAccountName; CBsString cwsDomainName; SID_NAME_USE eSidNameUse; DWORD dwAccountNameSize = 1024; DWORD dwDomainNameSize = 1024; if ( ::LookupAccountSidW( NULL, pSid, cwsAccountName.GetBufferSetLength( dwAccountNameSize ), &dwAccountNameSize, cwsDomainName.GetBufferSetLength( dwDomainNameSize ), &dwDomainNameSize, &eSidNameUse ) ) { cwsAccountName.ReleaseBuffer(); cwsDomainName.ReleaseBuffer(); *pcwsSid = L"'"; *pcwsSid += cwsDomainName; *pcwsSid += L"\\"; *pcwsSid += cwsAccountName; *pcwsSid += L"'"; return; } } LPWSTR pwszSid; if ( ::ConvertSidToStringSid( pSid, &pwszSid ) ) { *pcwsSid = pwszSid; ::LocalFree( pwszSid ); } else { pcwsSid->Format( L"<%6d>", ::GetLastError() ); } } /////////////////////////////////////////////////////////////////////////// // // FROM: base\fs\hsm\inc\rpdata.h // ////////////////////////////////////////////////////////////////////////// #define RP_RESV_SIZE 52 // // Placeholder data - all versions unioned together // typedef struct _RP_PRIVATE_DATA { CHAR reserved[RP_RESV_SIZE]; // Must be 0 ULONG bitFlags; // bitflags indicating status of the segment LARGE_INTEGER migrationTime; // When migration occurred GUID hsmId; GUID bagId; LARGE_INTEGER fileStart; LARGE_INTEGER fileSize; LARGE_INTEGER dataStart; LARGE_INTEGER dataSize; LARGE_INTEGER fileVersionId; LARGE_INTEGER verificationData; ULONG verificationType; ULONG recallCount; LARGE_INTEGER recallTime; LARGE_INTEGER dataStreamStart; LARGE_INTEGER dataStreamSize; ULONG dataStream; ULONG dataStreamCRCType; LARGE_INTEGER dataStreamCRC; } RP_PRIVATE_DATA, *PRP_PRIVATE_DATA; typedef struct _RP_DATA { GUID vendorId; // Unique HSM vendor ID -- This is first to match REPARSE_GUID_DATA_BUFFER ULONG qualifier; // Used to checksum the data ULONG version; // Version of the structure ULONG globalBitFlags; // bitflags indicating status of the file ULONG numPrivateData; // number of private data entries GUID fileIdentifier; // Unique file ID RP_PRIVATE_DATA data; // Vendor specific data } RP_DATA, *PRP_DATA; // // This function specifically zero's out certain HSM reparse point // fields before computing the checksum. The fields which are // zero'ed out are dynamic values and can cause miscompares. // static DWORD eaChecksumHSMReparsePoint( IN CDumpParameters *pcParams, IN PREPARSE_DATA_BUFFER pReparseData, IN DWORD dwTotalSize // Size of reparse point data ) { if ( dwTotalSize >= 8 && pReparseData->ReparseDataLength >= sizeof RP_DATA ) { // // If structure is not at least as large as the HSM RP_DATA structure, // then it doesn't appear to be a valid HSM RP_DATA structure. // PRP_DATA pRpData = ( PRP_DATA ) pReparseData->GenericReparseBuffer.DataBuffer; // // Zero out the proper fields // pRpData->qualifier = 0; pRpData->globalBitFlags = 0; pRpData->data.bitFlags = 0; pRpData->data.recallCount = 0; pRpData->data.recallTime.LowPart = 0; pRpData->data.recallTime.HighPart = 0; } else { pcParams->ErrPrint( L"Warning, HSM reparse point not valid, size: %u\n", dwTotalSize ); } return ::eaChecksumBlock( 0, ( LPBYTE )pReparseData, dwTotalSize ); } static VOID eaGetObjectIdInfo( IN CDumpParameters *pcParams, IN const CBsString &cwsFileName, IN OUT ULONGLONG *pullBytesChecksummed, IN OUT SDirectoryEntry *psDirEntry, IN OUT SFileExtendedInfo *psExtendedInfo ) { HANDLE hFile = INVALID_HANDLE_VALUE; BOOL bRet = TRUE; DWORD dwChecksum = 0; try { // // Open the file in order to read the object id // hFile = ::CreateFileW( cwsFileName, GENERIC_READ, FSD_SHARE_MODE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { bRet = FALSE; goto EXIT; } FILE_OBJECTID_BUFFER sObjIdBuffer; DWORD dwBytesReturned; // // Now get the object id info // if ( !::DeviceIoControl( hFile, FSCTL_GET_OBJECT_ID, NULL, // lpInBuffer; must be NULL 0, // nInBufferSize; must be zero ( LPVOID )&sObjIdBuffer, // pointer to output buffer sizeof FILE_OBJECTID_BUFFER,// size of output buffer &dwBytesReturned, // receives number of bytes returned NULL // pointer to OVERLAPPED structure ) ) { bRet = FALSE; goto EXIT; } // // Load up the object id // LPWSTR pwszObjIdGuid; // Check for RPC_S_OK added for Prefix bug #192596 if ( ::UuidToStringW( (GUID *)sObjIdBuffer.ObjectId, ( unsigned short ** )&pwszObjIdGuid ) == RPC_S_OK ) { psExtendedInfo->cwsObjectId = pwszObjIdGuid; ::RpcStringFreeW( ( unsigned short ** )&pwszObjIdGuid ); } // // Now checksum all of the extended object id data if necessary // if ( pcParams->m_bEnableObjectIdExtendedDataChecksums ) { dwChecksum = ::eaChecksumBlock( 0, sObjIdBuffer.ExtendedInfo, sizeof( sObjIdBuffer.ExtendedInfo ) ); psExtendedInfo->cwsObjectIdExtendedDataChecksum.Format( pcParams->m_pwszULongHexFmt, dwChecksum ); *pullBytesChecksummed += sizeof( sObjIdBuffer.ExtendedInfo ); } } catch( ... ) { bRet = FALSE; } EXIT: if ( hFile != INVALID_HANDLE_VALUE ) ::CloseHandle( hFile ); // if ( bRet == FALSE ) // psExtendedInfo->cwsReparsePointDataChecksum.Format( L"<%6d>", ::GetLastError() ); }