windows-nt/Source/XPSP1/NT/sdktools/fsdump/lib/extattr.cpp

1444 lines
43 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*
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 <ntioapi.h>
#include <aclapi.h>
#include <sddl.h>
#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() );
}