summaryrefslogtreecommitdiff
path: root/src/vm/securityconfig.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/securityconfig.cpp')
-rw-r--r--src/vm/securityconfig.cpp2181
1 files changed, 2181 insertions, 0 deletions
diff --git a/src/vm/securityconfig.cpp b/src/vm/securityconfig.cpp
new file mode 100644
index 0000000000..9c7970db8c
--- /dev/null
+++ b/src/vm/securityconfig.cpp
@@ -0,0 +1,2181 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//
+// File: SecurityConfig.cpp
+//
+
+//
+// Native implementation for security config access and manipulation
+//
+
+
+// #SecurityConfigFormat
+//
+// The security config system resides outside of the rest
+// of the config system since our needs are different. The
+// unmanaged portion of the security config system is only
+// concerned with data file/cache file pairs, not what they
+// are used for. It performs all the duties of reading data
+// from the disk, saving data back to the disk, and maintaining
+// the policy and quick cache data structures.
+//
+// FILE FORMAT
+//
+// The data file is a purely opaque blob for the unmanaged
+// code; however, the cache file is constructed and maintained
+// completely in the unmanaged code. It's format is as follows:
+//
+// CacheHeader
+// |
+// +-- dummyFileTime (FILETIME, 8 bytes) = this exists to make sure we don't read old format cache files. Must be set to {1, 0}.
+// |
+// +-- version (DWORD) = The version of this config file.
+// |
+// +-- configFileTime (FILETIME, 8 bytes) = The file time of the config file associated with this cache file.
+// |
+// +-- isSecurityOn (DWORD, 4 bytes) = This is currently not used.
+// |
+// +-- quickCache (DWORD, 4 bytes) = Used as a bitfield to maintain the information for the QuickCache. See the QuickCache section for more details.
+// |
+// +-- registryExtensionsInfo (struct RegistryExtensionsInfo) = Indicates whether this cache file was generated in the presence of registry extensions.
+// |
+// +-- numEntries (DWORD, 4 bytes) = The number of policy cache entries in the latter portion of this cache file.
+// |
+// +-- sizeConfig (DWORD, 4 bytes) = The size of the config information stored in the latter portion of this cache file.
+//
+// Config Data (if any)
+// The cache file can include an entire copy of this
+// information in the adjoining config file. This is
+// necessary since the cache often allows us to make
+// policy decisions without having parsed the data in
+// the config file. In order to guarantee that the config
+// data used by this process is not altered in the
+// meantime, we need to store the data in a readonly
+// location. Due to the design of the caching system
+// the cache file is locked when it is opened and therefore
+// is the perfect place to store this information. The
+// other alternative is to hold it in memory, but since
+// this can amount to many kilobytes of data we decided
+// on this design.
+//
+// List of CacheEntries
+// |
+// +-- CacheEntry
+// | |
+// | +-- numItemsInKey (DWORD, 4 bytes) = The number of evidence objects serialized in the key blob
+// | |
+// | +-- keySize (DWORD, 4 bytes) = The number of bytes in the key blob.
+// | |
+// | +-- dataSize (DWORD, 4 bytes) = The number of bytes in the data blob.
+// | |
+// | +-- keyBlob (raw) = A raw blob representing the serialized evidence.
+// | |
+// | +-- dataBlob (raw) = A raw blob representing an XML serialized PolicyStatement
+// |
+// +-- ...
+
+#include "common.h"
+
+#ifdef FEATURE_CAS_POLICY
+
+#include "securityconfig.h"
+
+// Header version of the cache file.
+#define CONFIG_VERSION 2
+// This controls the maximum size of the cache file.
+#define MAX_CACHEFILE_SIZE (1 << 20)
+
+#define SIZE_OF_ENTRY( X ) sizeof( CacheEntryHeader ) + X->header.keySize + X->header.dataSize
+#define MAX_NUM_LENGTH 16
+
+WCHAR* SecurityConfig::wcscatDWORD( __out_ecount(cchdst) __out_z WCHAR* dst, size_t cchdst, DWORD num )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ _ASSERTE( SecurityConfig::dataLock_.OwnedByCurrentThread() );
+
+ static WCHAR buffer[MAX_NUM_LENGTH];
+
+ buffer[MAX_NUM_LENGTH-1] = W('\0');
+
+ size_t index = MAX_NUM_LENGTH-2;
+
+ if (num == 0)
+ {
+ buffer[index--] = W('0');
+ }
+ else
+ {
+ while (num != 0)
+ {
+ buffer[index--] = (WCHAR)(W('0') + (num % 10));
+ num = num / 10;
+ }
+ }
+
+ wcscat_s( dst, cchdst, buffer + index + 1 );
+
+ return dst;
+}
+
+inline WCHAR * Wszdup(const WCHAR * str)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ size_t len = wcslen(str) + 1;
+ WCHAR * ret = new WCHAR[len];
+ wcscpy_s(ret, len, str);
+ return ret;
+}
+
+struct CacheHeader
+{
+ FILETIME dummyFileTime;
+ DWORD version;
+ FILETIME configFileTime;
+ DWORD isSecurityOn, quickCache;
+ SecurityConfig::RegistryExtensionsInfo registryExtensionsInfo;
+ DWORD numEntries, sizeConfig;
+
+ CacheHeader() : isSecurityOn( (DWORD) -1 ), quickCache( 0 ), numEntries( 0 ), sizeConfig( 0 )
+ {
+ WRAPPER_NO_CONTRACT;
+ memset( &this->configFileTime, 0, sizeof( configFileTime ) );
+ dummyFileTime.dwLowDateTime = 1;
+ dummyFileTime.dwHighDateTime = 0;
+ version = CONFIG_VERSION;
+ memset(&registryExtensionsInfo, 0, sizeof(registryExtensionsInfo));
+ _ASSERTE( IsValid() && "CacheHeader constructor should make it valid" );
+ };
+
+ bool IsValid()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return dummyFileTime.dwLowDateTime == 1 &&
+ dummyFileTime.dwHighDateTime == 0 &&
+ version == CONFIG_VERSION;
+ }
+};
+
+struct CacheEntryHeader
+{
+ DWORD numItemsInKey;
+ DWORD keySize;
+ DWORD dataSize;
+};
+
+struct CacheEntry
+{
+ CacheEntryHeader header;
+ BYTE* key;
+ BYTE* data;
+ DWORD cachePosition;
+ BOOL used;
+
+ CacheEntry() : key( NULL ), data( NULL ), used( FALSE )
+ {
+ LIMITED_METHOD_CONTRACT;
+ };
+
+ ~CacheEntry( void )
+ {
+ WRAPPER_NO_CONTRACT;
+ delete [] key;
+ delete [] data;
+ }
+};
+
+struct Data
+{
+ enum State
+ {
+ None = 0x0,
+ UsingCacheFile = 0x1,
+ CopyCacheFile = 0x2,
+ CacheUpdated = 0x4,
+ UsingConfigFile = 0x10,
+ CacheExhausted = 0x20,
+ NewConfigFile = 0x40
+ };
+
+ INT32 id;
+ WCHAR* configFileName;
+ WCHAR* cacheFileName;
+ WCHAR* cacheFileNameTemp;
+
+ LPBYTE configData;
+ DWORD configDataSize;
+ FILETIME configFileTime;
+ FILETIME cacheFileTime;
+ CacheHeader header;
+ ArrayList* oldCacheEntries;
+ ArrayList* newCacheEntries;
+ State state;
+ DWORD cacheCurrentPosition;
+ HANDLE cache;
+ PBYTE configBuffer;
+ DWORD sizeConfig;
+ SecurityConfig::ConfigRetval initRetval;
+ DWORD newEntriesSize;
+
+ Data( INT32 id )
+ : id( id ),
+ configFileName( NULL ),
+ cacheFileName( NULL ),
+ configData( NULL ),
+ oldCacheEntries( new ArrayList ),
+ newCacheEntries( new ArrayList ),
+ state( Data::None ),
+ cache( INVALID_HANDLE_VALUE ),
+ configBuffer( NULL ),
+ newEntriesSize( 0 )
+ {
+ LIMITED_METHOD_CONTRACT;
+ }
+
+ Data( INT32 id, STRINGREF* configFile )
+ : id( id ),
+ cacheFileName( NULL ),
+ configData( NULL ),
+ oldCacheEntries( new ArrayList ),
+ newCacheEntries( new ArrayList ),
+ state( Data::None ),
+ cache( INVALID_HANDLE_VALUE ),
+ configBuffer( NULL ),
+ newEntriesSize( 0 )
+ {
+ CONTRACTL {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(*configFile != NULL);
+ } CONTRACTL_END;
+
+ configFileName = Wszdup( (*configFile)->GetBuffer() );
+ cacheFileName = NULL;
+ cacheFileNameTemp = NULL;
+ }
+
+ Data( INT32 id, STRINGREF* configFile, STRINGREF* cacheFile )
+ : id( id ),
+ configData( NULL ),
+ oldCacheEntries( new ArrayList ),
+ newCacheEntries( new ArrayList ),
+ state( Data::None ),
+ cache( INVALID_HANDLE_VALUE ),
+ configBuffer( NULL ),
+ newEntriesSize( 0 )
+ {
+ CONTRACTL {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(*configFile != NULL);
+ } CONTRACTL_END;
+
+ configFileName = Wszdup( (*configFile)->GetBuffer() );
+
+ if (cacheFile != NULL)
+ {
+ // Since temp cache files can stick around even after the process that
+ // created them, we want to make sure they are fairly unique (if they
+ // aren't, we'll just fail to save cache information, which is not good
+ // but it won't cause anyone to crash or anything). The unique name
+ // algorithm used here is to append the process id and tick count to
+ // the name of the cache file.
+
+ cacheFileName = Wszdup( (*cacheFile)->GetBuffer() );
+ size_t len = wcslen( cacheFileName ) + 1 + 2 * MAX_NUM_LENGTH;
+ cacheFileNameTemp = new WCHAR[len];
+ wcscpy_s( cacheFileNameTemp, len, cacheFileName );
+ wcscat_s( cacheFileNameTemp, len, W(".") );
+ SecurityConfig::wcscatDWORD( cacheFileNameTemp, len, GetCurrentProcessId() );
+ wcscat_s( cacheFileNameTemp, len, W(".") );
+ SecurityConfig::wcscatDWORD( cacheFileNameTemp, len, GetTickCount() );
+ }
+ else
+ {
+ cacheFileName = NULL;
+ cacheFileNameTemp = NULL;
+ }
+ }
+
+ Data( INT32 id, const WCHAR* configFile, const WCHAR* cacheFile )
+ : id( id ),
+ configData( NULL ),
+ oldCacheEntries( new ArrayList ),
+ newCacheEntries( new ArrayList ),
+ state( Data::None ),
+ cache( INVALID_HANDLE_VALUE ),
+ configBuffer( NULL ),
+ newEntriesSize( 0 )
+
+ {
+ CONTRACTL {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(*configFile != NULL);
+ } CONTRACTL_END;
+
+ configFileName = Wszdup( configFile );
+
+ if (cacheFile != NULL)
+ {
+ cacheFileName = Wszdup( cacheFile );
+ size_t len = wcslen( cacheFileName ) + 1 + 2 * MAX_NUM_LENGTH;
+ cacheFileNameTemp = new WCHAR[len];
+ wcscpy_s( cacheFileNameTemp, len, cacheFileName );
+ wcscat_s( cacheFileNameTemp, len, W(".") );
+ SecurityConfig::wcscatDWORD( cacheFileNameTemp, len, GetCurrentProcessId() );
+ wcscat_s( cacheFileNameTemp, len, W(".") );
+ SecurityConfig::wcscatDWORD( cacheFileNameTemp, len, GetTickCount() );
+ }
+ else
+ {
+ cacheFileName = NULL;
+ cacheFileNameTemp = NULL;
+ }
+ }
+
+ void Reset( void )
+ {
+ CONTRACTL {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ } CONTRACTL_END;
+
+ delete [] configBuffer;
+ configBuffer = NULL;
+
+ if (cache != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle( cache );
+ cache = INVALID_HANDLE_VALUE;
+ }
+
+ if (cacheFileNameTemp != NULL)
+ {
+ // Note: we don't check a return value here as the worst thing that
+ // happens is we leave a spurious cache file.
+
+ WszDeleteFile( cacheFileNameTemp );
+ }
+
+ if (configData != NULL)
+ delete [] configData;
+ configData = NULL;
+
+ DeleteAllEntries();
+ header = CacheHeader();
+
+ oldCacheEntries = new ArrayList();
+ newCacheEntries = new ArrayList();
+
+ }
+
+ void Cleanup( void )
+ {
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ } CONTRACTL_END;
+
+ if (cache != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle( cache );
+ cache = INVALID_HANDLE_VALUE;
+ }
+
+ if (cacheFileNameTemp != NULL)
+ {
+ // Note: we don't check a return value here as the worst thing that
+ // happens is we leave a spurious cache file.
+
+ WszDeleteFile( cacheFileNameTemp );
+ }
+ }
+
+ ~Data( void )
+ {
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ } CONTRACTL_END;
+
+ Cleanup();
+ delete [] configBuffer;
+
+ delete [] configFileName;
+ delete [] cacheFileName;
+ delete [] cacheFileNameTemp;
+
+ if (configData != NULL)
+ delete [] configData;
+ DeleteAllEntries();
+ }
+
+ void DeleteAllEntries( void );
+};
+
+void Data::DeleteAllEntries( void )
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ } CONTRACTL_END;
+
+ ArrayList::Iterator iter;
+
+ if (oldCacheEntries != NULL)
+ {
+ iter = oldCacheEntries->Iterate();
+
+ while (iter.Next())
+ {
+ delete (CacheEntry*) iter.GetElement();
+ }
+
+ delete oldCacheEntries;
+ oldCacheEntries = NULL;
+ }
+
+ if (newCacheEntries != NULL)
+ {
+ iter = newCacheEntries->Iterate();
+
+ while (iter.Next())
+ {
+ delete (CacheEntry*) iter.GetElement();
+ }
+
+ delete newCacheEntries;
+ newCacheEntries = NULL;
+ }
+}
+
+void* SecurityConfig::GetData( INT32 id )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ ArrayList::Iterator iter = entries_.Iterate();
+
+ while (iter.Next())
+ {
+ Data* data = (Data*)iter.GetElement();
+
+ if (data->id == id)
+ {
+ return data;
+ }
+ }
+
+ return NULL;
+}
+
+static BOOL CacheOutOfDate( FILETIME* configFileTime, __in_z WCHAR* configFileName, __in_z_opt WCHAR* cacheFileName )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END
+
+ BOOL retval = TRUE;
+ BOOL deleteFile = FALSE;
+
+ HandleHolder config(WszCreateFile( configFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ));
+
+ if (config.GetValue() == INVALID_HANDLE_VALUE)
+ {
+ goto CLEANUP;
+ }
+
+ // Get the last write time for both files.
+
+ FILETIME newConfigTime;
+
+ if (!GetFileTime( config.GetValue(), NULL, NULL, &newConfigTime ))
+ {
+ goto CLEANUP;
+ }
+
+ if (CompareFileTime( configFileTime, &newConfigTime ) != 0)
+ {
+ // Cache is dated. Delete the cache.
+ deleteFile = TRUE;
+ goto CLEANUP;
+ }
+
+ retval = FALSE;
+
+CLEANUP:
+ // Note: deleting this file is a perf optimization so that
+ // we don't have to do this file time comparison next time.
+ // Therefore, if it fails for some reason we just loss a
+ // little perf.
+
+ if (deleteFile && cacheFileName != NULL)
+ WszDeleteFile( cacheFileName );
+
+ return retval;
+}
+
+static BOOL CacheOutOfDate( FILETIME* cacheFileTime, HANDLE cache, __in_z_opt WCHAR* cacheFileName )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END
+
+ BOOL retval = TRUE;
+
+ // Get the last write time for both files.
+
+ FILETIME newCacheTime;
+
+ if (!GetFileTime( cache, NULL, NULL, &newCacheTime ))
+ {
+ goto CLEANUP;
+ }
+
+ if (CompareFileTime( cacheFileTime, &newCacheTime ) != 0)
+ {
+ // Cache is dated. Delete the cache.
+ // Note: deleting this file is a perf optimization so that
+ // we don't have to do this file time comparison next time.
+ // Therefore, if it fails for some reason we just loss a
+ // little perf.
+
+ if (cacheFileName != NULL)
+ {
+ CloseHandle( cache );
+ WszDeleteFile( cacheFileName );
+ }
+ goto CLEANUP;
+ }
+
+ retval = FALSE;
+
+CLEANUP:
+ return retval;
+}
+
+static BOOL CacheOutOfDate( FILETIME* configTime, FILETIME* cachedConfigTime )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END
+
+ DWORD result = CompareFileTime( configTime, cachedConfigTime );
+
+ return result != 0;
+}
+
+static DWORD GetShareFlags()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return FILE_SHARE_READ | FILE_SHARE_DELETE;
+}
+
+static DWORD WriteFileData( HANDLE file, LPCBYTE data, DWORD size )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ DWORD totalBytesWritten = 0;
+ DWORD bytesWritten;
+
+ do
+ {
+ if (WriteFile( file, data, size - totalBytesWritten, &bytesWritten, NULL ) == 0)
+ {
+ return E_FAIL;
+ }
+ if (bytesWritten == 0)
+ {
+ return E_FAIL;
+ }
+ totalBytesWritten += bytesWritten;
+ } while (totalBytesWritten < size);
+
+ return S_OK;
+}
+
+// the data argument to this function can be a pointer to GC heap.
+// We do ensure cooperative mode before we call this function using a pointer to GC heap,
+// so we can't change GC mode inside this function.
+// Greg will look into the ways to pin the object.
+
+static DWORD ReadFileData( HANDLE file, PBYTE data, DWORD size )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ DWORD totalBytesRead = 0;
+ DWORD bytesRead;
+ do
+ {
+ if (ReadFile( file, data, size - totalBytesRead, &bytesRead, NULL ) == 0)
+ {
+ return E_FAIL;
+ }
+
+ if (bytesRead == 0)
+ {
+ return E_FAIL;
+ }
+
+ totalBytesRead += bytesRead;
+
+ } while (totalBytesRead < size);
+
+ return S_OK;
+}
+
+SecurityConfig::ConfigRetval SecurityConfig::InitData( INT32 id, const WCHAR* configFileName, const WCHAR* cacheFileName )
+{
+ STANDARD_VM_CONTRACT;
+
+ Data* data = (Data*)GetData( id );
+ if (data != NULL)
+ {
+ return data->initRetval;
+ }
+
+ if (configFileName == NULL || wcslen( configFileName ) == 0)
+ {
+ return NoFile;
+ }
+
+ {
+ CrstHolder ch( &dataLock_ );
+ data = new (nothrow) Data( id, configFileName, cacheFileName );
+ }
+
+ if (data == NULL)
+ {
+ return NoFile;
+ }
+
+ return InitData( data, TRUE );
+}
+
+
+SecurityConfig::ConfigRetval SecurityConfig::InitData( void* configDataParam, BOOL addToList )
+{
+ STANDARD_VM_CONTRACT;
+
+ _ASSERTE( configDataParam != NULL );
+
+ Data* data = (Data*) configDataParam;
+ DWORD cacheSize;
+ DWORD configSize;
+ ConfigRetval retval = NoFile;
+ DWORD shareFlags;
+
+ shareFlags = GetShareFlags();
+
+ // Crack open the config file.
+
+ HandleHolder config(WszCreateFile( data->configFileName, GENERIC_READ, shareFlags, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ));
+ if (config == INVALID_HANDLE_VALUE || !GetFileTime( config, NULL, NULL, &data->configFileTime ))
+ {
+ memset( &data->configFileTime, 0, sizeof( data->configFileTime ) );
+ }
+ else
+ {
+ data->state = (Data::State)(Data::UsingConfigFile | data->state);
+ }
+
+ // If we want a cache file, try to open that up.
+ // Note: we do not use a holder for data->cache because the new holder for data will
+ // delete the entire data structure which includes closing this handle as necessary.
+
+ if (data->cacheFileName != NULL)
+ data->cache = WszCreateFile( data->cacheFileName, GENERIC_READ, shareFlags, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
+
+ if (data->cache == INVALID_HANDLE_VALUE)
+ {
+ goto READ_DATA;
+ }
+
+ // Validate that the cache file is in a good form by checking
+ // that it is at least big enough to contain a header.
+
+ cacheSize = SafeGetFileSize( data->cache, NULL );
+
+ if (cacheSize == 0xFFFFFFFF)
+ {
+ goto READ_DATA;
+ }
+
+ if (cacheSize < sizeof( CacheHeader ))
+ {
+ goto READ_DATA;
+ }
+
+ // Finally read the data from the file into the buffer.
+
+ if (ReadFileData( data->cache, (BYTE*)&data->header, sizeof( CacheHeader ) ) != S_OK)
+ {
+ goto READ_DATA;
+ }
+
+ if (!data->header.IsValid())
+ {
+ goto READ_DATA;
+ }
+
+ // Check to make sure the cache file and the config file
+ // match up by comparing the actual file time of the config
+ // file and the config file time stored in the cache file.
+
+ if (CacheOutOfDate( &data->configFileTime, &data->header.configFileTime ))
+ {
+ goto READ_DATA;
+ }
+
+ if (!GetFileTime( data->cache, NULL, NULL, &data->cacheFileTime ))
+ {
+ goto READ_DATA;
+ }
+
+ // Set the file pointer to after both the header and config data (if any) so
+ // that we are ready to read cache entries.
+
+ if (SetFilePointer( data->cache, sizeof( CacheHeader ) + data->header.sizeConfig, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ {
+ goto READ_DATA;
+ }
+
+ data->cacheCurrentPosition = sizeof( CacheHeader ) + data->header.sizeConfig;
+ data->state = (Data::State)(Data::UsingCacheFile | Data::CopyCacheFile | data->state);
+
+ retval = (ConfigRetval)(retval | CacheFile);
+
+READ_DATA:
+ // If we are not using the cache file but we successfully opened it, we need
+ // to close it now. In addition, we need to reset the cache information
+ // stored in the Data object to make sure there is no spill over.
+
+ if (data->cache != INVALID_HANDLE_VALUE && (data->state & Data::UsingCacheFile) == 0)
+ {
+ CloseHandle( data->cache );
+ data->header = CacheHeader();
+ data->cache = INVALID_HANDLE_VALUE;
+ }
+
+ if (config != INVALID_HANDLE_VALUE)
+ {
+ configSize = SafeGetFileSize( config, NULL );
+
+ if (configSize == 0xFFFFFFFF)
+ {
+ goto ADD_DATA;
+ }
+
+ // Be paranoid and only use the cache file version if we find that it has the correct sized
+ // blob in it.
+
+ if ((data->state & Data::UsingCacheFile) != 0 && configSize == data->header.sizeConfig)
+ {
+ goto ADD_DATA;
+ }
+ else
+ {
+ if (data->cache != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle( data->cache );
+ data->header = CacheHeader();
+ data->cache = INVALID_HANDLE_VALUE;
+ data->state = (Data::State)(data->state & ~(Data::UsingCacheFile));
+ }
+
+ data->configData = new BYTE[configSize];
+ if (ReadFileData( config, data->configData, configSize ) != S_OK)
+ {
+ goto ADD_DATA;
+ }
+ data->configDataSize = configSize;
+ }
+ retval = (ConfigRetval)(retval | ConfigFile);
+ }
+
+ADD_DATA:
+ {
+ CrstHolder ch(&dataLock_);
+
+ if (addToList)
+ {
+ IfFailThrow(entries_.Append(data));
+ }
+ }
+
+ _ASSERTE( data );
+ data->initRetval = retval;
+
+ return retval;
+
+};
+
+static CacheEntry* LoadNextEntry( HANDLE cache, Data* data )
+{
+ STANDARD_VM_CONTRACT;
+
+ if ((data->state & Data::CacheExhausted) != 0)
+ return NULL;
+
+ NewHolder<CacheEntry> entry(new CacheEntry());
+
+ if (SetFilePointer( cache, data->cacheCurrentPosition, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ {
+ return NULL;
+ }
+
+ if (ReadFileData( cache, (BYTE*)&entry.GetValue()->header, sizeof( CacheEntryHeader ) ) != S_OK)
+ {
+ return NULL;
+ }
+
+ entry.GetValue()->cachePosition = data->cacheCurrentPosition + sizeof( entry.GetValue()->header );
+
+ data->cacheCurrentPosition += sizeof( entry.GetValue()->header ) + entry.GetValue()->header.keySize + entry.GetValue()->header.dataSize;
+
+ if (SetFilePointer( cache, entry.GetValue()->header.keySize + entry->header.dataSize, NULL, FILE_CURRENT ) == INVALID_SET_FILE_POINTER)
+ {
+ return NULL;
+ }
+
+ // We append a partially populated entry. CompareEntry is robust enough to handle this.
+ IfFailThrow(data->oldCacheEntries->Append( entry ));
+
+ return entry.Extract();
+}
+
+static BOOL WriteEntry( HANDLE cache, CacheEntry* entry, HANDLE oldCache = NULL )
+{
+ STANDARD_VM_CONTRACT;
+
+ if (WriteFileData( cache, (BYTE*)&entry->header, sizeof( CacheEntryHeader ) ) != S_OK)
+ {
+ return FALSE;
+ }
+
+ if (entry->key == NULL)
+ {
+ _ASSERTE (oldCache != NULL);
+
+ // We were lazy in reading the entry. Read the key now.
+ entry->key = new BYTE[entry->header.keySize];
+
+ _ASSERTE (cache != INVALID_HANDLE_VALUE);
+
+ if (SetFilePointer( oldCache, entry->cachePosition, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ return NULL;
+
+ if (ReadFileData( oldCache, entry->key, entry->header.keySize ) != S_OK)
+ {
+ return NULL;
+ }
+
+ entry->cachePosition += entry->header.keySize;
+ }
+
+ _ASSERTE( entry->key != NULL );
+
+ if (entry->data == NULL)
+ {
+ _ASSERTE (oldCache != NULL);
+
+ // We were lazy in reading the entry. Read the data also.
+ entry->data = new BYTE[entry->header.dataSize];
+
+ if (SetFilePointer( oldCache, entry->cachePosition, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ return NULL;
+
+ if (ReadFileData( oldCache, entry->data, entry->header.dataSize ) != S_OK)
+ return NULL;
+
+ entry->cachePosition += entry->header.dataSize;
+ }
+
+ _ASSERT( entry->data != NULL );
+
+ if (WriteFileData( cache, entry->key, entry->header.keySize ) != S_OK)
+ {
+ return FALSE;
+ }
+
+ if (WriteFileData( cache, entry->data, entry->header.dataSize ) != S_OK)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL SecurityConfig::SaveCacheData( INT32 id )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ GCX_PREEMP();
+
+ // Note: this function should only be called at EEShutdown time.
+ // This is because we need to close the current cache file in
+ // order to delete it. If it ever became necessary to do
+ // cache saves while a process we still executing managed code
+ // it should be possible to create a locking scheme for usage
+ // of the cache handle with very little reordering of the below
+ // (as it should always be possible for us to have a live copy of
+ // the file and yet still be making the swap).
+
+ HandleHolder cache;
+ HandleHolder config;
+ CacheHeader header;
+ BOOL retval = FALSE;
+ BOOL fWriteSucceeded = FALSE;
+ DWORD numEntriesWritten = 0;
+ DWORD amountWritten = 0;
+ DWORD sizeConfig = 0;
+ NewHolder<BYTE> configBuffer;
+ BOOL useConfigData = FALSE;
+
+ Data* data = (Data*)GetData( id );
+
+ // If there is not data by the id or there is no
+ // cache file name associated with the data, then fail.
+
+ if (data == NULL || data->cacheFileName == NULL)
+ return FALSE;
+
+ // If we haven't added anything new to the cache
+ // then just return success.
+
+ if ((data->state & Data::CacheUpdated) == 0)
+ return TRUE;
+
+ // If the config file has changed since the process started
+ // then our cache data is no longer valid. We'll just
+ // return success in this case.
+
+ if ((data->state & Data::UsingConfigFile) != 0 && CacheOutOfDate( &data->configFileTime, data->configFileName, NULL ))
+ return TRUE;
+
+ DWORD fileNameLength = (DWORD)wcslen( data->cacheFileName );
+
+ NewArrayHolder<WCHAR> newFileName(new WCHAR[fileNameLength + 5]);
+
+ swprintf_s( newFileName.GetValue(), fileNameLength + 5, W("%s%s"), data->cacheFileName, W(".new") );
+
+ cache.Assign( WszCreateFile( newFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ) );
+
+ for (DWORD RetryCount = 0; RetryCount < 5; RetryCount++)
+ {
+ if (cache != INVALID_HANDLE_VALUE)
+ {
+ break;
+ }
+ else
+ {
+ DWORD error = GetLastError();
+
+ if (error == ERROR_PATH_NOT_FOUND)
+ {
+ // The directory does not exist, iterate through and try to create it.
+
+ WCHAR* currentChar = newFileName;
+
+ // Skip the first backslash
+
+ while (*currentChar != W('\0'))
+ {
+ if (*currentChar == W('\\') || *currentChar == W('/'))
+ {
+ currentChar++;
+ break;
+ }
+ currentChar++;
+ }
+
+ // Iterate through trying to create each subdirectory.
+
+ while (*currentChar != W('\0'))
+ {
+ if (*currentChar == W('\\') || *currentChar == W('/'))
+ {
+ *currentChar = W('\0');
+
+ if (!WszCreateDirectory( newFileName, NULL ))
+ {
+ error = GetLastError();
+
+ if (error != ERROR_ACCESS_DENIED && error != ERROR_INVALID_NAME && error != ERROR_ALREADY_EXISTS)
+ {
+ goto CLEANUP;
+ }
+ }
+
+ *currentChar = W('\\');
+ }
+ currentChar++;
+ }
+
+ // Try the file creation again
+ continue;
+ }
+ }
+
+ // CreateFile failed. Sleep a little and retry, in case a
+ // virus scanner caused the creation to fail.
+ ClrSleepEx(10, FALSE);
+ }
+
+ if (cache.GetValue() == INVALID_HANDLE_VALUE)
+ goto CLEANUP;
+
+ // This code seems complicated only because of the
+ // number of cases that we are trying to handle. All we
+ // are trying to do is determine the amount of space to
+ // leave for the config information.
+
+ // If we saved out a new config file during this run, use
+ // the config size stored in the Data object itself.
+
+ if (data->configData != NULL)
+ {
+ useConfigData = TRUE;
+ }
+
+ if ((data->state & Data::NewConfigFile) != 0)
+ {
+ sizeConfig = data->sizeConfig;
+ }
+
+ // If we have a cache file, then use the size stored in the
+ // cache header.
+
+ else if ((data->state & Data::UsingCacheFile) != 0)
+ {
+ sizeConfig = data->header.sizeConfig;
+ }
+
+ // If we read in the config data, use the size of the
+ // managed byte array that it is stored in.
+
+ else if (useConfigData)
+ {
+ sizeConfig = data->configDataSize;
+ }
+
+ // Otherwise, check the config file itself to get the size.
+
+ else
+ {
+ DWORD shareFlags;
+
+ shareFlags = GetShareFlags();
+
+ config.Assign( WszCreateFile( data->configFileName, GENERIC_READ, shareFlags, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ) );
+
+ if (config == INVALID_HANDLE_VALUE)
+ {
+ sizeConfig = 0;
+ }
+ else
+ {
+ sizeConfig = SafeGetFileSize( config, NULL );
+
+ if (sizeConfig == 0xFFFFFFFF)
+ {
+ sizeConfig = 0;
+ }
+ }
+ }
+
+ // First write the entries.
+
+ if (SetFilePointer( cache, sizeof( CacheHeader ) + sizeConfig, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ {
+ goto CLEANUP;
+ }
+
+ // We're going to write out the cache entries in a modified
+ // least recently used order, throwing out any that end up
+ // taking us past our hardcoded max file size.
+
+ {
+ // First, write the entries from the cache file that were used.
+ // We do this because presumably these are system assemblies
+ // and other assemblies used by a number of applications.
+
+ ArrayList::Iterator iter;
+
+ if ((data->state & Data::UsingCacheFile) != 0)
+ {
+ iter = data->oldCacheEntries->Iterate();
+
+ while (iter.Next() && amountWritten < MAX_CACHEFILE_SIZE)
+ {
+ CacheEntry* currentEntry = (CacheEntry*)iter.GetElement();
+
+ if (currentEntry->used)
+ {
+ if(!WriteEntry( cache, currentEntry, data->cache ))
+ {
+ goto CLEANUP;
+ }
+
+ amountWritten += SIZE_OF_ENTRY( currentEntry );
+ numEntriesWritten++;
+ }
+ }
+ }
+
+ // Second, write any new cache entries to the file. These are
+ // more likely to be assemblies specific to this app.
+
+ iter = data->newCacheEntries->Iterate();
+
+ while (iter.Next() && amountWritten < MAX_CACHEFILE_SIZE)
+ {
+ CacheEntry* currentEntry = (CacheEntry*)iter.GetElement();
+
+ if (!WriteEntry( cache, currentEntry ))
+ {
+ goto CLEANUP;
+ }
+
+ amountWritten += SIZE_OF_ENTRY( currentEntry );
+ numEntriesWritten++;
+ }
+
+ // Third, if we are using the cache file, write the old entries
+ // that were not used this time around.
+
+ if ((data->state & Data::UsingCacheFile) != 0)
+ {
+ // First, write the ones that we already have partially loaded
+
+ iter = data->oldCacheEntries->Iterate();
+
+ while (iter.Next() && amountWritten < MAX_CACHEFILE_SIZE)
+ {
+ CacheEntry* currentEntry = (CacheEntry*)iter.GetElement();
+
+ if (!currentEntry->used)
+ {
+ if(!WriteEntry( cache, currentEntry, data->cache ))
+ {
+ goto CLEANUP;
+ }
+
+ amountWritten += SIZE_OF_ENTRY( currentEntry );
+ numEntriesWritten++;
+ }
+ }
+
+ while (amountWritten < MAX_CACHEFILE_SIZE)
+ {
+ CacheEntry* entry = LoadNextEntry( data->cache, data );
+
+ if (entry == NULL)
+ break;
+
+ if (!WriteEntry( cache, entry, data->cache ))
+ {
+ goto CLEANUP;
+ }
+
+ amountWritten += SIZE_OF_ENTRY( entry );
+ numEntriesWritten++;
+ }
+ }
+
+ fWriteSucceeded = TRUE;
+ }
+
+
+ if (!fWriteSucceeded)
+ {
+ CloseHandle( cache.GetValue() );
+ cache.SuppressRelease();
+ WszDeleteFile( newFileName );
+ goto CLEANUP;
+ }
+
+ // End with writing the header.
+
+ header.configFileTime = data->configFileTime;
+ header.isSecurityOn = 1;
+ header.numEntries = numEntriesWritten;
+ header.quickCache = data->header.quickCache;
+ header.sizeConfig = sizeConfig;
+
+ if (SetFilePointer( cache, 0, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ {
+ // Couldn't move to the beginning of the file
+ goto CLEANUP;
+ }
+
+ if (WriteFileData( cache, (PBYTE)&header, sizeof( header ) ) != S_OK)
+ {
+ // Couldn't write header info.
+ goto CLEANUP;
+ }
+
+ if (sizeConfig != 0)
+ {
+ if ((data->state & Data::NewConfigFile) != 0)
+ {
+ if (WriteFileData( cache, data->configBuffer, sizeConfig ) != S_OK)
+ {
+ goto CLEANUP;
+ }
+ }
+ else
+ {
+ if (data->configData != NULL)
+ {
+ if (WriteFileData( cache, data->configData, sizeConfig ) != S_OK)
+ {
+ goto CLEANUP;
+ }
+ }
+ else if ((data->state & Data::UsingCacheFile) != 0)
+ {
+ configBuffer.Assign( new BYTE[sizeConfig] );
+
+ if (SetFilePointer( data->cache, sizeof( CacheHeader ), NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ {
+ goto CLEANUP;
+ }
+
+ if (ReadFileData( data->cache, configBuffer.GetValue(), sizeConfig ) != S_OK)
+ {
+ goto CLEANUP;
+ }
+
+ if (WriteFileData( cache, configBuffer.GetValue(), sizeConfig ) != S_OK)
+ {
+ goto CLEANUP;
+ }
+ }
+ else
+ {
+ configBuffer.Assign( new BYTE[sizeConfig] );
+
+ if (SetFilePointer( config, 0, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ {
+ goto CLEANUP;
+ }
+
+ if (ReadFileData( config, configBuffer.GetValue(), sizeConfig ) != S_OK)
+ {
+ goto CLEANUP;
+ }
+
+ if (WriteFileData( cache, configBuffer.GetValue(), sizeConfig ) != S_OK)
+ {
+ goto CLEANUP;
+ }
+ }
+ }
+ }
+
+ // Flush the file buffers to make sure
+ // we get full write through.
+
+ FlushFileBuffers( cache.GetValue() );
+
+ CloseHandle( cache );
+ cache.SuppressRelease();
+ CloseHandle( data->cache );
+ data->cache = INVALID_HANDLE_VALUE;
+
+ // Move the existing file out of the way
+ // Note: use MoveFile because we know it will never cross
+ // device boundaries.
+
+ // Note: the delete file can fail, but we can't really do anything
+ // if it does so just ignore any failures.
+ WszDeleteFile( data->cacheFileNameTemp );
+
+ // Try to move the existing cache file out of the way. However, if we can't
+ // then try to delete it. If it can't be deleted then just bail out.
+ if (!WszMoveFile( data->cacheFileName, data->cacheFileNameTemp ) &&
+ (!Assembly::FileNotFound(HRESULT_FROM_WIN32(GetLastError()))) &&
+ !WszDeleteFile( data->cacheFileName ))
+ {
+ if (!Assembly::FileNotFound(HRESULT_FROM_WIN32(GetLastError())))
+ goto CLEANUP;
+ }
+
+ // Move the new file into position
+
+ if (!WszMoveFile( newFileName, data->cacheFileName ))
+ {
+ goto CLEANUP;
+ }
+
+ retval = TRUE;
+
+CLEANUP:
+ if (retval)
+ cache.SuppressRelease();
+
+ return retval;
+
+}
+
+void QCALLTYPE SecurityConfig::ResetCacheData(INT32 id)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ Data* data = (Data*)GetData( id );
+
+ if (data != NULL)
+ {
+ CrstHolder ch(&dataLock_);
+
+ data->DeleteAllEntries();
+
+ data->oldCacheEntries = new ArrayList;
+ data->newCacheEntries = new ArrayList;
+
+ data->header = CacheHeader();
+ data->state = (Data::State)(~(Data::CopyCacheFile | Data::UsingCacheFile) & data->state);
+
+ HandleHolder config(WszCreateFile( data->configFileName, GENERIC_READ, GetShareFlags(), NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ));
+
+ if (config.GetValue() != INVALID_HANDLE_VALUE)
+ {
+ VERIFY(GetFileTime( config, NULL, NULL, &data->configFileTime ));
+ VERIFY(GetFileTime( config, NULL, NULL, &data->header.configFileTime ));
+ }
+}
+
+ END_QCALL;
+}
+
+HRESULT QCALLTYPE SecurityConfig::SaveDataByte(LPCWSTR wszConfigPath, LPCBYTE pbData, DWORD cbData)
+{
+ QCALL_CONTRACT;
+
+ HRESULT retval = E_FAIL;
+
+ BEGIN_QCALL;
+
+ HandleHolder newFile(INVALID_HANDLE_VALUE);
+
+ int RetryCount;
+ DWORD error = 0;
+ DWORD fileNameLength = (DWORD) wcslen(wszConfigPath);
+
+ NewArrayHolder<WCHAR> newFileName(new WCHAR[fileNameLength + 5]);
+ NewArrayHolder<WCHAR> oldFileName(new WCHAR[fileNameLength + 5]);
+
+ swprintf_s( newFileName.GetValue(), fileNameLength + 5, W("%s%s"), wszConfigPath, W(".new") );
+ swprintf_s( oldFileName.GetValue(), fileNameLength + 5, W("%s%s"), wszConfigPath, W(".old") );
+
+ // Create the new file.
+ for (RetryCount = 0; RetryCount < 5; RetryCount++) {
+ newFile.Assign( WszCreateFile( newFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ) );
+
+ if (newFile != INVALID_HANDLE_VALUE)
+ break;
+ else
+ {
+ error = GetLastError();
+
+ if (error == ERROR_PATH_NOT_FOUND)
+ {
+ // The directory does not exist, iterate through and try to create it.
+
+ WCHAR* currentChar = newFileName;
+
+ // Skip the first backslash
+
+ while (*currentChar != W('\0'))
+ {
+ if (*currentChar == W('\\') || *currentChar == W('/'))
+ {
+ currentChar++;
+ break;
+ }
+ currentChar++;
+ }
+
+ // Iterate through trying to create each subdirectory.
+
+ while (*currentChar != W('\0'))
+ {
+ if (*currentChar == W('\\') || *currentChar == W('/'))
+ {
+ *currentChar = W('\0');
+
+ if (!WszCreateDirectory( newFileName, NULL ))
+ {
+ error = GetLastError();
+
+ if (error != ERROR_ACCESS_DENIED && error != ERROR_ALREADY_EXISTS)
+ {
+ goto CLEANUP;
+ }
+ }
+
+ *currentChar = W('\\');
+ }
+ currentChar++;
+ }
+
+ // Try the file creation again
+ continue;
+ }
+ }
+
+ // CreateFile failed. Sleep a little and retry, in case a
+ // virus scanner caused the creation to fail.
+ ClrSleepEx(10, FALSE);
+ }
+
+ if (newFile == INVALID_HANDLE_VALUE) {
+ goto CLEANUP;
+ }
+
+ // Write the data into it.
+ if ((retval = WriteFileData(newFile.GetValue(), pbData, cbData)) != S_OK)
+ {
+ // Write failed, destroy the file and bail.
+ // Note: if the delete fails, we always do a CREATE_NEW
+ // for this file so that should take care of it. If not
+ // we'll fail to write out future cache files.
+ CloseHandle( newFile.GetValue() );
+ newFile.SuppressRelease();
+ WszDeleteFile( newFileName );
+ goto CLEANUP;
+ }
+
+ if (!FlushFileBuffers(newFile.GetValue()))
+ {
+ error = GetLastError();
+ goto CLEANUP;
+ }
+
+ CloseHandle( newFile.GetValue() );
+ newFile.SuppressRelease();
+
+ // Move the existing file out of the way
+ if (!WszMoveFileEx( wszConfigPath, oldFileName, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED ))
+ {
+ // If move fails for a reason other than not being able to find the file, bail out.
+ // Also, if the old file didn't exist, we have no need to delete it.
+ HRESULT hrMove = HRESULT_FROM_WIN32(GetLastError());
+ if (!Assembly::FileNotFound(hrMove))
+ {
+ retval = hrMove;
+ WszDeleteFile(wszConfigPath);
+ goto CLEANUP;
+ }
+ }
+
+ // Move the new file into position
+
+ if (!WszMoveFileEx( newFileName, wszConfigPath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED ))
+ {
+ error = GetLastError();
+ goto CLEANUP;
+ }
+
+ retval = S_OK;
+
+CLEANUP:
+ if (retval == E_FAIL && error != 0)
+ retval = HRESULT_FROM_WIN32(error);
+
+ END_QCALL;
+
+ return retval;
+}
+
+BOOL QCALLTYPE SecurityConfig::RecoverData(INT32 id)
+ {
+ QCALL_CONTRACT;
+
+ BOOL retval = FALSE;
+
+ BEGIN_QCALL;
+
+ Data* data = (Data*)GetData( id );
+
+ if (data == NULL)
+ goto CLEANUP;
+
+ {
+ DWORD fileNameLength = (DWORD)wcslen( data->configFileName );
+
+ NewArrayHolder<WCHAR> tempFileName(new WCHAR[fileNameLength + 10]);
+ NewArrayHolder<WCHAR> oldFileName(new WCHAR[fileNameLength + 5]);
+
+ swprintf_s( tempFileName.GetValue(), fileNameLength + 10, W("%s%s"), data->configFileName, W(".old.temp") );
+ swprintf_s( oldFileName.GetValue(), fileNameLength + 5, W("%s%s"), data->configFileName, W(".old") );
+
+ HandleHolder oldFile(WszCreateFile( oldFileName, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ));
+
+ if (oldFile.GetValue() == INVALID_HANDLE_VALUE)
+ {
+ goto CLEANUP;
+ }
+
+ CloseHandle( oldFile );
+ oldFile.SuppressRelease();
+
+ if (!WszMoveFile( data->configFileName, tempFileName ))
+ {
+ goto CLEANUP;
+ }
+
+ if (!WszMoveFile( oldFileName, data->configFileName ))
+ {
+ goto CLEANUP;
+ }
+
+ if (!WszMoveFile( tempFileName, oldFileName ))
+ {
+ goto CLEANUP;
+ }
+ }
+
+ // We need to do some work to reset the unmanaged data object
+ // so that the managed side of things behaves like you'd expect.
+ // This basically means cleaning up the open resources and
+ // doing the work to init on a different set of files.
+
+ data->Reset();
+ InitData( data, FALSE );
+
+ retval = TRUE;
+
+CLEANUP:
+ END_QCALL;
+
+ return retval;
+}
+
+BOOL SecurityConfig::GetQuickCacheEntry( INT32 id, QuickCacheEntryType type )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ //
+ // If there is no config file for this level, then we'll assume the default
+ // security policy is in effect. This could happen for example if there is
+ // user profile loaded or if the config file is not present.
+ //
+
+ Data* data = (Data*)GetData( id );
+ if (data == NULL || ((data->state & Data::UsingConfigFile) == 0))
+ return (type == FullTrustZoneMyComputer); // MyComputer gets FT by default.
+
+ if ((data->state & Data::UsingCacheFile) == 0)
+ return FALSE;
+
+ return (data->header.quickCache & type);
+}
+
+void QCALLTYPE SecurityConfig::SetQuickCache(INT32 id, QuickCacheEntryType type)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ Data* data = (Data*)GetData( id );
+
+ if (data != NULL && (DWORD) type != data->header.quickCache)
+ {
+ CrstHolder ch(&dataLock_);
+
+ data->state = (Data::State)(Data::CacheUpdated | data->state);
+ data->header.quickCache = type;
+ }
+
+ END_QCALL;
+}
+
+static HANDLE OpenCacheFile( Data* data )
+{
+ STANDARD_VM_CONTRACT;
+
+ CrstHolder ch(&SecurityConfig::dataLock_);
+
+ if (data->cache != INVALID_HANDLE_VALUE)
+ return data->cache;
+
+ _ASSERTE( FALSE && "This case should never happen" );
+
+ data->cache = WszCreateFile( data->cacheFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
+ if (data->cache == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ // Check whether the cache has changed since we first looked at it.
+ // If it has but the config file hasn't, then we need to start fresh.
+ // However, if the config file has changed then we have to ignore it.
+
+ if (CacheOutOfDate( &data->cacheFileTime, data->cache, NULL ))
+ {
+ if (CacheOutOfDate( &data->configFileTime, data->configFileName, NULL ))
+ return NULL;
+
+ if (ReadFileData( data->cache, (BYTE*)&data->header, sizeof( CacheHeader ) ) != S_OK)
+ return NULL;
+
+ data->cacheCurrentPosition = sizeof( CacheHeader );
+
+ if (data->oldCacheEntries != NULL)
+ {
+ ArrayList::Iterator iter = data->oldCacheEntries->Iterate();
+ while (iter.Next())
+ {
+ delete (CacheEntry*)iter.GetElement();
+ }
+ delete data->oldCacheEntries;
+ data->oldCacheEntries = new ArrayList();
+ }
+ }
+
+ return data->cache;
+}
+
+static BYTE* CompareEntry( CacheEntry* entry, DWORD numEvidence, DWORD evidenceSize, LPCBYTE evidenceBlock, HANDLE cache, DWORD* size)
+{
+ STANDARD_VM_CONTRACT;
+
+ _ASSERTE (entry);
+
+ if (entry->header.numItemsInKey == numEvidence &&
+ entry->header.keySize == evidenceSize)
+ {
+ if (entry->key == NULL)
+ {
+ // We were lazy in reading the entry. Read the key now.
+ entry->key = new BYTE[entry->header.keySize];
+
+ _ASSERTE (cache != INVALID_HANDLE_VALUE);
+
+ if (SetFilePointer( cache, entry->cachePosition, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ return NULL;
+
+ if (ReadFileData( cache, entry->key, entry->header.keySize ) != S_OK)
+ return NULL;
+
+ entry->cachePosition += entry->header.keySize;
+ }
+
+ _ASSERTE (entry->key);
+
+ if (memcmp( entry->key, evidenceBlock, entry->header.keySize ) == 0)
+ {
+ if (entry->data == NULL)
+ {
+ // We were lazy in reading the entry. Read the data also.
+ entry->data = new BYTE[entry->header.dataSize];
+
+ if (SetFilePointer( cache, entry->cachePosition, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ return NULL;
+
+ if (ReadFileData( cache, entry->data, entry->header.dataSize ) != S_OK)
+ return NULL;
+
+ entry->cachePosition += entry->header.dataSize;
+ }
+
+ entry->used = TRUE;
+ *size = entry->header.dataSize;
+
+ return entry->data;
+ }
+ }
+ return NULL;
+}
+
+BOOL QCALLTYPE SecurityConfig::GetCacheEntry(INT32 id, DWORD numEvidence, LPCBYTE pEvidence, DWORD cbEvidence, QCall::ObjectHandleOnStack retPolicy)
+{
+ QCALL_CONTRACT;
+
+ BOOL success = FALSE;
+
+ BEGIN_QCALL;
+
+ HANDLE cache = INVALID_HANDLE_VALUE;
+
+ BYTE* retval = NULL;
+ DWORD size = (DWORD) -1;
+
+ Data* data = (Data*)GetData( id );
+
+ if (data == NULL)
+ {
+ goto CLEANUP;
+ }
+
+ {
+
+ ArrayList::Iterator iter;
+
+ if ((data->state & Data::UsingCacheFile) == 0)
+ {
+ // We know we don't have anything in the config file, so
+ // let's just look through the new entries to make sure we
+ // aren't getting any repeats.
+
+ // Then try the existing new entries
+
+ iter = data->newCacheEntries->Iterate();
+
+ while (iter.Next())
+ {
+ // newCacheEntries do not need the cache file so pass in NULL.
+ retval = CompareEntry( (CacheEntry*)iter.GetElement(), numEvidence, cbEvidence, pEvidence, NULL, &size );
+
+ if (retval != NULL)
+ {
+ success = TRUE;
+ goto CLEANUP;
+ }
+ }
+
+ goto CLEANUP;
+ }
+
+ // Its possible that the old entries were not read in completely
+ // so we keep the cache file open before iterating through the
+ // old entries.
+
+ cache = OpenCacheFile( data );
+
+ if ( cache == NULL )
+ {
+ goto CLEANUP;
+ }
+
+ // First, iterator over the old entries
+
+ {
+ CrstHolder ch(&dataLock_);
+
+ iter = data->oldCacheEntries->Iterate();
+ while (iter.Next())
+ {
+ retval = CompareEntry( (CacheEntry*)iter.GetElement(), numEvidence, cbEvidence, pEvidence, cache, &size );
+ if (retval != NULL)
+ {
+ success = TRUE;
+ goto CLEANUP;
+ }
+ }
+
+ // LockHolder goes out of scope here
+ }
+
+ // Then try the existing new entries
+ iter = data->newCacheEntries->Iterate();
+ while (iter.Next())
+ {
+ // newCacheEntries do not need the cache file so pass in NULL.
+ retval = CompareEntry( (CacheEntry*)iter.GetElement(), numEvidence, cbEvidence, pEvidence, NULL, &size );
+ if (retval != NULL)
+ {
+ success = TRUE;
+ goto CLEANUP;
+ }
+ }
+
+ // Finally, try loading existing entries from the file
+
+ {
+ CrstHolder ch(&dataLock_);
+
+ if (SetFilePointer( cache, data->cacheCurrentPosition, NULL, FILE_BEGIN ) == INVALID_SET_FILE_POINTER)
+ {
+ goto CLEANUP;
+ }
+
+ do
+ {
+ CacheEntry* entry = LoadNextEntry( cache, data );
+ if (entry == NULL)
+ {
+ data->state = (Data::State)(Data::CacheExhausted | data->state);
+ break;
+ }
+
+ retval = CompareEntry( entry, numEvidence, cbEvidence, pEvidence, cache, &size );
+ if (retval != NULL)
+ {
+ success = TRUE;
+ break;
+ }
+ } while (TRUE);
+
+ // LockHolder goes out of scope here
+ }
+ }
+
+CLEANUP:
+ if (success && retval != NULL)
+ {
+ _ASSERTE( size != (DWORD) -1 );
+ retPolicy.SetByteArray(retval, size);
+ }
+
+ END_QCALL;
+
+ return success;
+}
+
+void QCALLTYPE SecurityConfig::AddCacheEntry(INT32 id, DWORD numEvidence, LPCBYTE pEvidence, DWORD cbEvidence, LPCBYTE pPolicy, DWORD cbPolicy)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ Data* data = (Data*)GetData( id );
+
+ DWORD sizeOfEntry = 0;
+ NewHolder<CacheEntry> entry;
+
+ if (data == NULL)
+ {
+ goto lExit;
+ }
+
+ // In order to limit how large a long running app can become,
+ // we limit the total memory held by the new cache entries list.
+ // For now this limit corresponds with how large the max cache file
+ // can be.
+
+ sizeOfEntry = cbEvidence + cbPolicy + sizeof( CacheEntryHeader );
+
+ if (data->newEntriesSize + sizeOfEntry >= MAX_CACHEFILE_SIZE)
+ {
+ goto lExit;
+ }
+
+ entry = new CacheEntry();
+
+ entry->header.numItemsInKey = numEvidence;
+ entry->header.keySize = cbEvidence;
+ entry->header.dataSize = cbPolicy;
+
+ entry->key = new BYTE[entry->header.keySize];
+ entry->data = new BYTE[entry->header.dataSize];
+
+ memcpyNoGCRefs(entry->key, pEvidence, cbEvidence);
+ memcpyNoGCRefs(entry->data, pPolicy, cbPolicy);
+
+ {
+ CrstHolder ch(&dataLock_);
+
+ // Check the size again to handle the race.
+ if (data->newEntriesSize + sizeOfEntry < MAX_CACHEFILE_SIZE)
+ {
+ data->state = (Data::State)(Data::CacheUpdated | data->state);
+ IfFailThrow(data->newCacheEntries->Append( entry.GetValue() ));
+ entry.SuppressRelease();
+ data->newEntriesSize += sizeOfEntry;
+ }
+ }
+
+lExit: ;
+
+ END_QCALL;
+}
+
+ArrayListStatic SecurityConfig::entries_;
+CrstStatic SecurityConfig::dataLock_;
+
+void SecurityConfig::Init( void )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ dataLock_.Init(CrstSecurityPolicyCache);
+ entries_.Init();
+}
+
+void SecurityConfig::Cleanup( void )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ ArrayList::Iterator iter = entries_.Iterate();
+
+ GCX_PREEMP();
+
+ CrstHolder ch(&dataLock_);
+
+ while (iter.Next())
+ {
+ ((Data*) iter.GetElement())->Cleanup();
+ }
+}
+
+void SecurityConfig::Delete( void )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END
+
+ ArrayList::Iterator iter = entries_.Iterate();
+
+ while (iter.Next())
+ {
+ delete (Data*) iter.GetElement();
+ }
+
+ entries_.Destroy();
+ dataLock_.Destroy();
+}
+
+void QCALLTYPE SecurityConfig::_GetMachineDirectory(QCall::StringHandleOnStack retDirectory)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ WCHAR machine[MAX_LONGPATH];
+
+ HRESULT hr = GetMachineDirectory(machine, MAX_LONGPATH);
+ if (FAILED(hr))
+ ThrowHR(hr);
+
+ retDirectory.Set(machine);
+
+ END_QCALL;
+}
+
+void QCALLTYPE SecurityConfig::_GetUserDirectory(QCall::StringHandleOnStack retDirectory)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ WCHAR user[MAX_LONGPATH];
+
+ BOOL result = GetUserDirectory(user, MAX_LONGPATH);
+ if (result)
+ retDirectory.Set(user);
+
+ END_QCALL;
+}
+
+HRESULT SecurityConfig::GetMachineDirectory(__out_ecount(bufferCount) __out_z WCHAR* buffer, size_t bufferCount)
+{
+ STANDARD_VM_CONTRACT;
+
+ HRESULT hr;
+
+ DWORD length = (DWORD)bufferCount;
+ hr = GetInternalSystemDirectory(buffer, &length);
+ if (FAILED(hr))
+ return hr;
+
+ // Make sure we have enough buffer to concat the string.
+ // Note the length including the terminating zero.
+ if((bufferCount - wcslen(buffer) - 1) < wcslen(W("config\\")))
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+
+ wcscat_s(buffer, bufferCount, W("config\\"));
+
+ return S_OK;
+}
+
+BOOL SecurityConfig::GetVIUserDirectory(__out_ecount(bufferCount) __out_z WCHAR* buffer, size_t bufferCount)
+{
+ STANDARD_VM_CONTRACT;
+
+ WCHAR scratchBuffer[MAX_LONGPATH];
+ BOOL retval = FALSE;
+
+ DWORD size = MAX_LONGPATH;
+
+ if (!GetUserDir(buffer, bufferCount, TRUE))
+ goto CLEANUP;
+
+ wcscpy_s( scratchBuffer, COUNTOF(scratchBuffer), W("\\Microsoft\\CLR Security Config\\") );
+
+ if (bufferCount < wcslen( buffer ) + wcslen( scratchBuffer ) + 1)
+ {
+ goto CLEANUP;
+ }
+
+ wcscat_s( buffer, bufferCount, scratchBuffer );
+
+ retval = TRUE;
+
+CLEANUP:
+ return retval;
+}
+
+BOOL SecurityConfig::GetUserDirectory(__out_ecount(bufferCount) __out_z WCHAR* buffer, size_t bufferCount)
+{
+ STANDARD_VM_CONTRACT;
+
+ StackSString ssScratchBuffer;
+ BOOL retval = FALSE;
+
+ WCHAR* wszScratchBuffer = ssScratchBuffer.OpenUnicodeBuffer( (COUNT_T)bufferCount );
+ retval = GetVIUserDirectory(wszScratchBuffer, bufferCount);
+ ssScratchBuffer.CloseBuffer( (COUNT_T)wcslen( wszScratchBuffer ) );
+
+ if (!retval)
+ return retval;
+
+ ssScratchBuffer.Append( W("v") );
+ ssScratchBuffer.Append( VER_PRODUCTVERSION_NO_QFE_STR_L );
+ ssScratchBuffer.Append( W("\\") );
+
+#ifdef _WIN64
+ ssScratchBuffer.Append( W("64bit\\") );
+#endif // _WIN64
+
+ if (ssScratchBuffer.GetCount() + 1 > bufferCount)
+ return FALSE;
+
+ wcscpy_s( buffer, bufferCount, ssScratchBuffer.GetUnicode() );
+
+ return TRUE;
+}
+
+BOOL QCALLTYPE SecurityConfig::WriteToEventLog(LPCWSTR wszMessage)
+{
+ QCALL_CONTRACT;
+
+ BOOL retVal = FALSE;
+
+ BEGIN_QCALL;
+
+ retVal = ReportEventCLR(
+ EVENTLOG_WARNING_TYPE, // event type
+ 0, // category
+ (DWORD)1000, // event identifier
+ NULL, // no user security identifier
+ &StackSString(wszMessage)); // message to log
+
+ END_QCALL
+
+ return retVal;
+}
+
+#ifdef _DEBUG
+HRESULT QCALLTYPE SecurityConfig::DebugOut(LPCWSTR wszFileName, LPCWSTR wszMessage)
+{
+ HRESULT retVal = E_FAIL;
+
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ HandleHolder file(WszCreateFile( wszFileName, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ));
+
+ if (file == INVALID_HANDLE_VALUE)
+ {
+ goto lExit;
+ }
+
+ SetFilePointer( file, 0, NULL, FILE_END );
+
+ DWORD cbMessage;
+ DWORD cbWritten;
+
+ cbMessage = (DWORD)wcslen(wszMessage) * sizeof(WCHAR);
+ if (!WriteFile( file, wszMessage, cbMessage, &cbWritten, NULL ))
+ {
+ goto lExit;
+ }
+
+ if (cbMessage != cbWritten)
+ {
+ goto lExit;
+ }
+
+ retVal = S_OK;
+
+lExit: ;
+ END_QCALL;
+
+ return retVal;
+}
+#endif
+
+#endif // FEATURE_CAS_POLICY