Condividi tramite


Procedure consigliate per l'affidabilità

Le regole di affidabilità seguenti sono orientate a SQL Server; Tuttavia, si applicano anche a qualsiasi applicazione server basata su host. È estremamente importante che i server, ad esempio SQL Server, non perdano risorse e non vengano arrestati. Tuttavia, questa operazione non può essere eseguita scrivendo codice back-out per ogni metodo che modifica lo stato di un oggetto. L'obiettivo non è scrivere codice gestito affidabile al 100% che verrà ripristinato da eventuali errori in ogni posizione con codice di back-out. Sarebbe un compito scoraggiante con poche probabilità di successo. Common Language Runtime (CLR) non può fornire facilmente garanzie sufficienti per il codice gestito per rendere fattibile la scrittura di codice perfetto. Si noti che a differenza di ASP.NET, SQL Server usa un solo processo che non può essere riciclato senza arrestare un database per un lungo periodo di tempo inaccettabile.

Con queste garanzie più deboli e l'esecuzione in un unico processo, l'affidabilità si basa sulla terminazione dei thread o sul riciclaggio dei domini dell'applicazione quando necessario, adottando precauzioni per garantire che risorse del sistema operativo, come gestori o memoria, non vengano perse. Anche con questo vincolo di affidabilità più semplice, esiste ancora un requisito di affidabilità significativo:

  • Non far trapelare mai le risorse del sistema operativo.

  • Identificare tutti i blocchi gestiti in tutti i moduli in CLR.

  • Non interrompere mai lo stato condiviso del dominio tra applicazioni, consentendo il AppDomain riciclo senza problemi.

Anche se in teoria è possibile scrivere codice gestito per gestire ThreadAbortException, StackOverflowException, e OutOfMemoryException eccezioni, è irragionevole aspettarsi che gli sviluppatori scrivano codice così robusto per tutta un'applicazione. Per questo motivo, le eccezioni fuori controllo comportano la terminazione del thread in esecuzione; e se il thread terminato stava modificando lo stato condiviso, il che può essere determinato dal fatto che il thread detiene un blocco, l'oggetto AppDomain viene scaricato dal sistema. Quando viene terminato un metodo che modifica lo stato condiviso, lo stato sarà danneggiato perché non è possibile scrivere codice di backout affidabile per gli aggiornamenti allo stato condiviso.

In .NET Framework versione 2.0, l'unico host che richiede affidabilità è SQL Server. Se l'assembly verrà eseguito in SQL Server, è consigliabile eseguire il lavoro di affidabilità per ogni parte di tale assembly, anche se sono presenti funzionalità specifiche disabilitate durante l'esecuzione nel database. Questa operazione è necessaria perché il motore di analisi del codice esamina il codice a livello di assembly e non può distinguere il codice disabilitato. Un'altra considerazione per la programmazione di SQL Server è che SQL Server esegue tutti gli elementi in un unico processo e AppDomain il riciclo viene usato per pulire tutte le risorse, ad esempio gli handle di memoria e del sistema operativo.

Non è possibile dipendere da finalizzatori o distruttori o try/finally blocchi per il codice di ripristino. Potrebbero essere interrotti o non chiamati.

Le eccezioni asincrone possono essere generate in posizioni impreviste, possibilmente ogni istruzione del computer: ThreadAbortException, StackOverflowExceptione OutOfMemoryException.

I thread gestiti non sono necessariamente thread Win32 in SQL; potrebbero essere fibre.

Lo stato condiviso modificabile del dominio multi-applicazione o a livello di processo è estremamente difficile da modificare in modo sicuro e deve essere evitato quando possibile.

Le condizioni di memoria insufficiente non sono rare in SQL Server.

Se le librerie ospitate in SQL Server non aggiornano correttamente lo stato condiviso, è probabile che il codice non venga ripristinato fino al riavvio del database. Inoltre, in alcuni casi estremi, è possibile che il processo di SQL Server non riesca, causando il riavvio del database. Il riavvio del database può arrestare un sito Web o influire sulle operazioni aziendali, danneggiando la disponibilità. Una perdita lenta di risorse del sistema operativo, come memoria o handle, può portare il server a non riuscire ad allocare gli handle, senza possibilità di ripristino, oppure causare un progressivo deterioramento delle prestazioni del server, riducendo così la disponibilità dell'applicazione per il cliente. È chiaro che si vogliono evitare questi scenari.

Regole delle procedure consigliate

L'introduzione si è focalizzata su ciò che la revisione del codice del codice gestito eseguito nel server avrebbe dovuto individuare per aumentare la stabilità e l'affidabilità del framework. Tutti questi controlli sono procedure consigliate in generale e un requisito imprescindibile per il server.

In caso di blocco non attivo o vincolo di risorsa, SQL Server interrompe un thread o elimina un oggetto AppDomain. In questo caso, è garantito che venga eseguito solo il codice di back-out in un'area di esecuzione vincolata.

Usare SafeHandle per evitare perdite di risorse

Nel caso di un AppDomain scaricamento, non è possibile dipendere da finally blocchi o finalizzatori eseguiti, pertanto è importante astrarre l'accesso a tutte le risorse del sistema operativo tramite la SafeHandle classe anziché IntPtr, HandleRefo classi simili. Ciò consente a CLR di tenere traccia e chiudere gli handle usati anche nel caso di AppDomain disinstallazione. SafeHandle utilizzerà un finalizzatore critico che verrà sempre eseguito dal CLR.

L'handle del sistema operativo viene archiviato nell'handle sicuro dal momento in cui viene creato fino al momento in cui viene rilasciato. Non esiste una finestra di tempo durante la quale potrebbe verificarsi una perdita di handle di ThreadAbortException. Inoltre, platform invoke effettuerà il conteggio delle referenze dell'handle, il che consente di tenere traccia precisa della durata dell'handle, prevenendo un problema di sicurezza con una condizione di gara tra Dispose e un metodo che sta attualmente utilizzando l'handle.

La maggior parte delle classi che attualmente hanno un finalizzatore per pulire semplicemente un handle del sistema operativo non richiederà più il finalizzatore. Invece, il finalizzatore sarà sulla classe derivata SafeHandle.

Si noti che SafeHandle non è una sostituzione per IDisposable.Dispose. Ci sono ancora potenziali conflitti di risorse e vantaggi in termini di prestazioni nel liberare esplicitamente le risorse del sistema operativo. È sufficiente tenere presente che finally i blocchi che eliminano in modo esplicito le risorse potrebbero non essere eseguiti fino al completamento.

SafeHandle consente di implementare il proprio metodo ReleaseHandle che esegue il lavoro per liberare l'handle, come passare lo stato a una routine di liberazione di handle del sistema operativo o liberare un gruppo di handle in un ciclo. Il CLR assicura che questo metodo venga eseguito. È responsabilità dell'autore dell'implementazione ReleaseHandle assicurarsi che l'handle venga rilasciato in tutte le circostanze. Se non viene eseguita questa operazione, l'handle verrà perduto, il che spesso comporta la perdita delle risorse del sistema associate all'handle. Pertanto, è fondamentale strutturare SafeHandle le classi derivate in modo che l'implementazione ReleaseHandle non richieda l'allocazione di risorse che potrebbero non essere disponibili in fase di chiamata. Si noti che è consentito chiamare metodi che potrebbero non riuscire nell'implementazione di ReleaseHandle purché il codice possa gestire tali errori e completare il contratto per rilasciare l'handle nativo. Ai fini del debug, ReleaseHandle ha un Boolean valore restituito che può essere impostato su false se si verifica un errore irreversibile che impedisce il rilascio della risorsa. In questo modo verrà attivato l'Assistente Debugging Gestito releaseHandleFailed, se abilitato, per facilitare l'identificazione del problema. Non influisce in altro modo sul runtime; ReleaseHandle non verrà chiamato di nuovo per la stessa risorsa e di conseguenza l'handle verrà trapelato.

SafeHandle non è appropriato in determinati contesti. Poiché il ReleaseHandle metodo può essere eseguito su un GC thread finalizzatore, tutti gli handle che devono essere rilasciati in un determinato thread non devono essere avvolti in un SafeHandle.

I wrapper chiamabili in fase di esecuzione (RCWs) possono essere gestiti automaticamente dal CLR senza bisogno di codice aggiuntivo. Per il codice che usa platform invoke e considera un oggetto COM come un IUnknown* oggetto o IntPtr, il codice deve essere riscritto per usare un RCW. SafeHandle potrebbe non essere adeguato per questo scenario a causa della possibilità di un metodo di rilascio non gestito che richiama il codice gestito.

Regola di analisi del codice

Usare SafeHandle per incapsulare le risorse del sistema operativo. Non usare HandleRef o campi di tipo IntPtr.

Assicurarsi che i finalizzatori non debbano essere eseguiti per evitare la perdita di risorse del sistema operativo

Esaminare attentamente i finalizzatori per assicurarsi che, anche se non vengono eseguiti, una risorsa critica del sistema operativo non viene persa. A differenza di un normale AppDomain scaricamento quando l'applicazione viene eseguita in uno stato di stabilità o quando un server come SQL Server si spegne, gli oggetti non vengono finalizzati durante un AppDomain scaricamento improvviso. Assicurarsi che le risorse non vengano perse in caso di scaricamento improvviso, perché la correttezza di un'applicazione non può essere garantita, ma l'integrità del server deve essere mantenuta senza perdere risorse. Usare SafeHandle per liberare tutte le risorse del sistema operativo.

Assicurarsi che le clausole finally non devono essere eseguite per evitare perdite di risorse del sistema operativo

finally Non è garantito che le clausole vengano eseguite all'esterno delle Constrained Execution Regions (CERs), quindi gli sviluppatori di librerie non devono basarsi sul codice all'interno di un blocco finally per liberare risorse non gestite. L'uso SafeHandle è la soluzione consigliata.

Regola di analisi del codice

Usare SafeHandle per pulire le risorse del sistema operativo anziché Finalize. Non usare IntPtr; usare SafeHandle per incapsulare le risorse. Se la clausola finally deve essere eseguita, inserirla in un CER (Code Execution Region).

Tutti i blocchi devono passare attraverso il codice di blocco gestito esistente

CLR deve sapere quando il codice si trova in un blocco in modo che sappia rimuovere il AppDomain anziché interrompere semplicemente il thread. L'interruzione del thread potrebbe essere pericolosa perché i dati gestiti dal thread potrebbero essere lasciati in uno stato incoerente. Pertanto, l'intero AppDomain deve essere riciclato. Le conseguenze dell'errore di identificazione di un blocco possono essere deadlock o risultati non corretti. Usare i metodi BeginCriticalRegion e EndCriticalRegion per identificare le aree di blocco. Sono metodi statici nella Thread classe che si applicano solo al thread corrente, consentendo di impedire a un thread di modificare il numero di blocchi di un altro thread.

Enter e Exit hanno questa notifica CLR incorporata; pertanto, è consigliato il loro utilizzo insieme all'istruzione lock, che impiega questi metodi.

Altri meccanismi di blocco, ad esempio gli spin lock e AutoResetEvent devono chiamare questi metodi per notificare al CLR che viene immessa una sezione critica. Questi metodi non accettano blocchi; informano clr che il codice è in esecuzione in una sezione critica e che l'interruzione del thread potrebbe lasciare incoerente lo stato condiviso. Se è stato definito un tipo di blocco personalizzato, ad esempio una classe personalizzata ReaderWriterLock , usare questi metodi di conteggio blocchi.

Regola di analisi del codice

Contrassegnare e identificare tutte le serrature usando BeginCriticalRegion e EndCriticalRegion. Non usare CompareExchange, Incremente Decrement in un ciclo. Non eseguire una platform invoke delle varianti Win32 di questi metodi. Non usare Sleep in un ciclo. Non usare campi volatili.

Il codice di pulizia deve trovarsi in un blocco finally o catch, e non seguire un blocco catch.

Il codice di pulizia non deve mai seguire un blocco catch; dovrebbe trovarsi in un blocco finally o nel blocco catch stesso. Dovrebbe trattarsi di una normale procedura consigliata. Un finally blocco è in genere preferibile perché esegue lo stesso codice sia quando viene generata un'eccezione che quando viene in genere rilevata la fine del try blocco. In caso di un'eccezione imprevista, ad esempio un ThreadAbortException, il codice di pulizia non verrà eseguito. Qualsiasi risorsa non gestita dovrebbe essere idealmente racchiusa in un finally per evitare perdite. Si noti che la parola chiave C# using può essere usata in modo efficace per eliminare gli oggetti, inclusi gli handle.

Anche se AppDomain il riciclo può pulire le risorse nel thread del finalizzatore, è comunque importante inserire il codice di pulizia nella posizione corretta. Si noti che se un thread riceve un'eccezione asincrona senza detenere un blocco, il CLR tenta di terminare il thread stesso senza dover riciclare il AppDomain. Garantire che le risorse vengano pulite prima piuttosto che in un secondo momento consente di rendere disponibili più risorse e di gestire meglio la durata. Se non si chiude esplicitamente un handle a un file in un percorso di codice che può generare errori, potrebbe essere necessario aspettare che il SafeHandle finalizzatore lo pulisca. Alla successiva esecuzione del codice, potrebbe non riuscire ad accedere allo stesso file esattamente se il finalizzatore non è ancora stato eseguito. Per questo motivo, assicurarsi che il codice di pulizia esista e funzioni correttamente consentirà di eseguire il ripristino dagli errori in modo più pulito e rapido, anche se non è strettamente necessario.

Regola di analisi del codice

Il codice di pulizia dopo catch deve trovarsi in un finally blocco. Inserire le chiamate per rilasciare le risorse in un blocco finally. catch i blocchi devono terminare con un'istruzione throw o rethrow. Anche se ci saranno eccezioni, ad esempio il codice che rileva se è possibile stabilire una connessione di rete in cui è possibile ottenere un numero elevato di eccezioni, qualsiasi codice che richiede l'intercettazione di una serie di eccezioni in circostanze normali deve indicare che il codice deve essere testato per verificare se avrà esito positivo.

Process-Wide lo stato condiviso modificabile tra i domini dell'applicazione deve essere eliminato o utilizzato un'area di esecuzione vincolata

Come descritto nell'introduzione, può essere molto difficile scrivere codice gestito che monitora lo stato condiviso a livello di processo tra domini applicazione in modo affidabile. Lo stato condiviso a livello di processo è qualsiasi tipo di struttura di dati condivisa tra domini applicazione, nel codice Win32, all'interno di CLR o nel codice gestito tramite comunicazione remota. Qualsiasi stato condiviso modificabile è molto difficile da scrivere correttamente nel codice gestito e qualsiasi stato condiviso statico può essere eseguito solo con grande attenzione. Se si dispone di uno stato condiviso a livello di processo o a livello di computer, trovare un modo per eliminarlo o proteggere lo stato condiviso usando un'area di esecuzione vincolata (CER). Si noti che qualsiasi libreria con stato condiviso non identificato e corretto potrebbe causare un host, ad esempio SQL Server, che richiede lo scaricamento pulito AppDomain per arrestarsi in modo anomalo.

Se il codice usa un oggetto COM, evitare di condividere l'oggetto COM tra domini applicazione.

I blocchi non funzionano a livello del processo o tra i domini di applicazione.

In passato, Enter e l'istruzione lock è stata usata per creare blocchi di processo globali. Ciò si verifica, ad esempio, quando si bloccano classi agile, come istanze di assembly non condivisi, oggetti, stringhe interne e alcune stringhe condivise tra domini applicativi tramite la comunicazione remota. Questi blocchi non sono più a livello di processo. Per identificare la presenza di un blocco di dominio interapplicazione a livello di processo, determinare se il codice all'interno del blocco usa qualsiasi risorsa esterna, persistente, ad esempio un file su disco o eventualmente un database.

Si noti che l'esecuzione di un blocco all'interno di un AppDomain può causare problemi se il codice protetto usa una risorsa esterna perché tale codice può essere eseguito simultaneamente in più domini applicazione. Questo può essere un problema durante la scrittura in un file di log o l'associazione a un socket per l'intero processo. Queste modifiche indicano che non esiste un modo semplice, usando codice gestito, per ottenere un blocco globale del processo, diverso dall'uso di un'istanza denominata Mutex o Semaphore . Creare codice che non venga eseguito contemporaneamente in due domini di applicazione, oppure usare le classi Mutex o Semaphore. Se non è possibile modificare il codice esistente, non usare un mutex denominato Win32 per ottenere questa sincronizzazione perché l'esecuzione in modalità fiber significa che non è possibile garantire lo stesso thread del sistema operativo acquisirà e rilascia un mutex. È necessario utilizzare la classe gestita Mutex, oppure un oggetto denominato ManualResetEvent, AutoResetEvent o Semaphore per sincronizzare il blocco del codice in modo che il CLR ne sia consapevole, piuttosto che sincronizzare il blocco usando codice non gestito.

Evitare l'uso di lock(typeof(MyType))

Anche gli oggetti privati e pubblici Type negli assembly condivisi con una sola copia del codice condiviso in tutti i domini applicazione presentano problemi. Per gli assembly condivisi, è presente una sola istanza di un Type per processo, ovvero più domini applicazione condividono esattamente la stessa Type istanza. L'acquisizione di un lock su un'istanza Type acquisisce un lock che influisce sull'intero processo, non solo su AppDomain. Se si AppDomain accetta un blocco su un Type oggetto, il thread viene interrotto bruscamente, non rilascia il blocco. Questo blocco può quindi causare il deadlock di altri domini applicativi.

Un buon modo per accettare blocchi nei metodi statici comporta l'aggiunta di un oggetto di sincronizzazione interno statico al codice. Potrebbe essere inizializzato nel costruttore della classe, se uno è presente; in caso contrario, può essere inizializzato come segue:

private static Object s_InternalSyncObject;
private static Object InternalSyncObject
{
    get
    {
        if (s_InternalSyncObject == null)
        {
            Object o = new Object();
            Interlocked.CompareExchange(
                ref s_InternalSyncObject, o, null);
        }
        return s_InternalSyncObject;
    }
}

Quindi, quando si prende un blocco, utilizzare la proprietà InternalSyncObject per ottenere un oggetto su cui effettuare il blocco. Non è necessario utilizzare la proprietà se è stato inizializzato l'oggetto di sincronizzazione interno nel costruttore della classe. Il doppio controllo del codice di inizializzazione del blocco dovrebbe essere simile all'esempio seguente:

public static MyClass SingletonProperty
{
    get
    {
        if (s_SingletonProperty == null)
        {
            lock(InternalSyncObject)
            {
                // Do not use lock(typeof(MyClass))
                if (s_SingletonProperty == null)
                {
                    MyClass tmp = new MyClass(…);
                    // Do all initialization before publishing
                    s_SingletonProperty = tmp;
                }
            }
        }
        return s_SingletonProperty;
    }
}

Nota su lock(this)

In genere è accettabile accettare un blocco su un singolo oggetto accessibile pubblicamente. Tuttavia, se l'oggetto è un oggetto singleton che potrebbe causare un deadlock di un intero sottosistema, è consigliabile usare anche il modello di progettazione precedente. Ad esempio, un blocco sul solo oggetto SecurityManager potrebbe causare un deadlock all'interno di AppDomain, rendendo inutilizzabile l'intero AppDomain. È consigliabile non accettare un blocco su un oggetto accessibile pubblicamente di questo tipo. Tuttavia, un blocco su una singola raccolta o matrice non deve in genere presentare un problema.

Regola di analisi del codice

Non prendere blocchi sui tipi che possono essere usati tra i domini delle applicazioni oppure che non presentano un forte senso di identità. Non chiamare Enter su un Type, MethodInfo, PropertyInfo, String, ValueType, Thread o qualsiasi oggetto che deriva da MarshalByRefObject.

Rimuovere le chiamate GC.KeepAlive

Una quantità significativa di codice esistente non utilizza KeepAlive quando dovrebbe o lo utilizza quando non è appropriato. Dopo la conversione in SafeHandle, le classi non devono chiamare KeepAlive, presupponendo che non abbiano un finalizzatore, ma si basano su SafeHandle per finalizzare gli handle del sistema operativo. Anche se il costo delle prestazioni di conservazione di una chiamata a KeepAlive può essere trascurabile, la percezione che una chiamata a KeepAlive sia necessaria o sufficiente per risolvere un problema di durata che potrebbe non esistere più rende il codice più difficile da gestire. Tuttavia, quando si usano i wrapper chiamabili CLR di interoperabilità COM (RCWs), KeepAlive è ancora necessario nel codice.

Regola di analisi del codice

Rimuovere KeepAlive.

Usare l'attributo HostProtection

HostProtectionAttribute HPA fornisce l'uso di azioni di sicurezza dichiarative per determinare i requisiti di protezione host, consentendo all'host di impedire addirittura a codice completamente attendibile di chiamare determinati metodi non appropriati per l'host specificato, ad esempio Exit o Show per SQL Server.

HPA influisce solo sulle applicazioni non gestite che ospitano Common Language Runtime e implementano la protezione host, ad esempio SQL Server. Se applicata, l'azione di sicurezza comporta la creazione di una richiesta di collegamento in base alle risorse host esposte dalla classe o dal metodo . Se il codice viene eseguito in un'applicazione client o in un server non protetto dall'host, l'attributo "evapora"; non viene rilevato e pertanto non applicato.

Importante

Lo scopo di questo attributo è applicare linee guida del modello di programmazione specifiche dell'host, non il comportamento di sicurezza. Sebbene venga usata una richiesta di collegamento per verificare la conformità ai requisiti del modello di programmazione, non è un'autorizzazione HostProtectionAttribute di sicurezza.

Se l'host non ha requisiti del modello di programmazione, le richieste di collegamento non si verificano.

Questo attributo identifica quanto segue:

  • Metodi o classi che non rientrano nel modello di programmazione host, ma sono altrimenti non dannose.

  • Metodi o classi che non soddisfano il modello di programmazione host e potrebbero causare la destabilizzazione del codice utente gestito dal server.

  • Metodi o classi che non rientrano nel modello di programmazione host e potrebbero causare una destabilizzazione del processo server stesso.

Annotazioni

Se si sta creando una libreria di classi che deve essere chiamata dalle applicazioni che possono essere eseguite in un ambiente protetto dall'host, è necessario applicare questo attributo ai membri che espongono HostProtectionResource categorie di risorse. I membri della libreria di classi .NET Framework con questo attributo determinano la verifica solo del chiamante immediato. Il membro della tua libreria deve anche verificare il chiamante immediato nello stesso modo.

Per altre informazioni su HPA, vedere HostProtectionAttribute.

Regola di analisi del codice

Per SQL Server, tutti i metodi usati per introdurre la sincronizzazione o il threading devono essere identificati con HPA. Sono inclusi i metodi che condividono lo stato, vengono sincronizzati o gestiti processi esterni. I HostProtectionResource valori che influiscono su SQL Server sono SharedState, Synchronizatione ExternalProcessMgmt. Tuttavia, qualsiasi metodo che espone qualsiasi HostProtectionResource elemento deve essere identificato da un HPA, non solo da quelli che usano risorse che influiscono su SQL.

Non bloccare a tempo indeterminato nel codice non gestito

Il blocco nel codice non gestito anziché nel codice gestito può causare un attacco Denial of Service perché CLR non è in grado di interrompere il thread. Un thread bloccato impedisce a CLR di scaricare AppDomain, almeno senza eseguire alcune operazioni estremamente non sicure. Il blocco dell'uso di una primitiva di sincronizzazione di Windows è un esempio chiaro di un elemento che non è possibile consentire. Se possibile, è consigliabile evitare il blocco durante una chiamata a ReadFile su un socket. Idealmente, l'API di Windows dovrebbe fornire un meccanismo affinché un'operazione di questo tipo si interrompa automaticamente allo scadere del tempo previsto.

Qualsiasi metodo che chiama in modalità nativa deve usare idealmente una chiamata Win32 con un timeout ragionevole e finito. Se l'utente è autorizzato a specificare il timeout, l'utente non deve essere autorizzato a specificare un timeout infinito senza autorizzazioni di sicurezza specifiche. Come linea guida, se un metodo si blocca per più di circa 10 secondi, è necessario usare una versione che supporta i timeout o è necessario un supporto CLR aggiuntivo.

Ecco alcuni esempi di API problematiche. Le pipe (sia anonime che denominate) possono essere create con un timeout; tuttavia, il codice deve assicurarsi di non chiamare mai CreateNamedPipeWaitNamedPipe con NMPWAIT_WAIT_FOREVER. Inoltre, può verificarsi un blocco imprevisto anche se viene specificato un timeout. La chiamata WriteFile su una pipe anonima bloccherà fino a quando non vengono scritti tutti i byte, ovvero se il buffer contiene dati non letti, la WriteFile chiamata bloccherà fino a quando il lettore non ha liberato spazio nel buffer della pipe. I socket devono sempre usare alcune API che rispettano un meccanismo di timeout.

Regola di analisi del codice

Il blocco senza timeout nel codice non gestito è un attacco Denial of Service. Non eseguire chiamate platform invoke a WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, MsgWaitForMultipleObjects, e MsgWaitForMultipleObjectsEx. Non usare NMPWAIT_WAIT_FOREVER.

Identificare le funzionalità di STA-Dependent

Identificare qualsiasi codice che usi appartamenti COM a thread singolo (STA). Gli stA sono disabilitati nel processo di SQL Server. Le funzionalità che dipendono da CoInitialize, ad esempio i contatori delle prestazioni o gli Appunti, devono essere disabilitate all'interno di SQL Server.

Assicurarsi che i finalizzatori siano privi di problemi di sincronizzazione

In versioni future di .NET Framework potrebbero esistere più thread finalizzatori, ovvero i finalizzatori per istanze diverse dello stesso tipo vengono eseguiti simultaneamente. Non devono essere completamente thread-safe; Il Garbage Collector garantisce che un solo thread esegua il finalizzatore per una determinata istanza dell'oggetto. Tuttavia, i finalizzatori devono essere codificati per evitare race condition e deadlock durante l'esecuzione simultanea su più istanze di oggetti diverse. Quando si usa uno stato esterno, ad esempio la scrittura in un file di log, in un finalizzatore, è necessario gestire i problemi di threading. Non fare affidamento sulla finalizzazione per fornire la sicurezza dei thread. Non usare l'archiviazione locale del thread, gestita o nativa, per archiviare lo stato nel thread del finalizzatore.

Regola di analisi del codice

I finalizzatori devono essere privi di problemi di sincronizzazione. Non usare uno stato modificabile statico in un finalizzatore.

Evitare la memoria non gestita, se possibile

La memoria non gestita può essere persa, proprio come un handle del sistema operativo. Se possibile, provare a usare la memoria nello stack usando stackalloc o un oggetto gestito aggiunto, ad esempio l'istruzione fissa o un GCHandle utilizzando un byte[]. Alla fine, GC sistema queste cose. Tuttavia, se è necessario allocare memoria non gestita, è consigliabile usare una classe che deriva da SafeHandle per eseguire il wrapping dell'allocazione di memoria.

Si noti che esiste almeno un caso in cui SafeHandle non è adeguato. Per le chiamate al metodo COM che allocano o liberano memoria, è comune che una DLL alloca la memoria tramite CoTaskMemAlloc un'altra DLL libera tale memoria con CoTaskMemFree. L'uso SafeHandle in queste posizioni sarebbe inappropriato perché tenterà di legare la durata della memoria non gestita alla durata dell'oggetto SafeHandle anziché consentire all'altra DLL di controllare la durata della memoria.

Esaminare tutti gli usi del catch(Exception)

I blocchi catch che intercettano tutte le eccezioni anziché un'eccezione specifica ora intercettano anche le eccezioni asincrone. Esaminare ogni blocco catch(Exception), cercando la mancanza di codici importanti per il rilascio o il backout delle risorse che potrebbero essere ignorati, nonché un comportamento potenzialmente errato all'interno del blocco catch stesso per la gestione di un ThreadAbortException, StackOverflowException o OutOfMemoryException. Si noti che è possibile che questo codice stia registrando o facendo alcune ipotesi che possa visualizzare solo determinate eccezioni o che ogni volta che si verifica un'eccezione non è riuscita per un motivo specifico. Questi presupposti potrebbero dover essere aggiornati per includere ThreadAbortException.

Prendere in considerazione la modifica di tutte le posizioni che intercettano tutte le eccezioni per intercettare un tipo specifico di eccezione che si prevede verrà generata, ad esempio da metodi FormatException di formattazione delle stringhe. In questo modo si impedisce l'esecuzione del blocco catch per eccezioni inattese e si contribuisce a garantire che il codice non nasconda i bug catturando eccezioni inattese. Come regola generale, non gestire mai un'eccezione nel codice della libreria (il codice che richiede di intercettare un'eccezione può indicare un difetto di progettazione nel codice che si sta chiamando). In alcuni casi può essere necessario intercettare un'eccezione e generare un tipo di eccezione diverso per fornire più dati. In questo caso, utilizzare le eccezioni annidate, archiviando la causa effettiva dell'errore nella proprietà InnerException della nuova eccezione.

Regola di analisi del codice

Esaminare tutti i blocchi catch nel codice gestito che intercettano tutti gli oggetti o intercettano tutte le eccezioni. In C# questo significa contrassegnare sia catch{} che catch(Exception){}. Considerare di rendere il tipo di eccezione specifico o esaminare il codice per garantire che non si comporti in modo scorretto se rileva un tipo di eccezione imprevisto.

Non presupporre che un thread gestito sia un thread Win32: si tratta di una fibra

L'uso dell'archiviazione locale del thread gestito funziona, ma non è possibile usare l'archiviazione locale del thread non gestito o presupporre che il codice venga eseguito nuovamente nel thread del sistema operativo corrente. Non modificare impostazioni locali del thread. Non chiamare InitializeCriticalSection o CreateMutex tramite l'invocazione della piattaforma perché richiedono al thread del sistema operativo che entra in un blocco, di uscire anche dal blocco. Poiché questo non sarà il caso quando si usano fibre, le sezioni critiche win32 e i mutex non possono essere usati direttamente in SQL. Si noti che la classe gestita Mutex non gestisce questi problemi di affinità di thread.

Puoi utilizzare in sicurezza la maggior parte dello stato su un oggetto Thread gestito, inclusa l'archiviazione locale del thread gestito e la cultura UI corrente del thread. È anche possibile usare , ThreadStaticAttributeche rende il valore di una variabile statica esistente accessibile solo dal thread gestito corrente (si tratta di un altro modo per eseguire l'archiviazione locale fiber in CLR). Per motivi del modello di programmazione, non è possibile modificare la cultura corrente del thread durante l'esecuzione in SQL.

Regola di analisi del codice

SQL Server viene eseguito in modalità fiber; non usare l'archiviazione locale del thread. Evitare le chiamate platform invoke a TlsAlloc, TlsFree, TlsGetValue, e TlsSetValue.

Consentire a SQL Server di gestire l'impersonificazione

Poiché l'impersonificazione opera a livello di thread e SQL può essere eseguito in modalità fiber, il codice gestito non dovrebbe impersonare gli utenti e non dovrebbe chiamare RevertToSelf.

Regola di analisi del codice

Consentire a SQL Server di gestire l'impersonificazione. Non usare RevertToSelf, ImpersonateAnonymousToken, DdeImpersonateClientImpersonateDdeClientWindow, , ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, ImpersonateSelf, RpcImpersonateClientRpcRevertToSelf, RpcRevertToSelfEx, o SetThreadToken.

Non chiamare Thread::Suspend

La possibilità di sospendere un thread può apparire un'operazione semplice, ma può causare deadlock. Se un thread che contiene un blocco viene sospeso da un secondo thread e il secondo thread tenta di eseguire lo stesso blocco, si verifica un deadlock. Attualmente, Suspend può interferire con la sicurezza, il caricamento delle classi, la comunicazione remota e la reflection.

Regola di analisi del codice

Non chiamare Suspend. Prendere in considerazione l'uso di una primitiva di sincronizzazione reale, come un Semaphore o un ManualResetEvent.

Proteggere le operazioni critiche con aree di esecuzione vincolate e contratti di affidabilità

Quando esegui un'operazione complessa che aggiorna uno stato condiviso o che deve essere eseguita con successo o fallire completamente in modo deterministico, assicurati che sia protetta da una regione di esecuzione vincolata (CER). Ciò garantisce che il codice venga eseguito in ogni caso, anche un'interruzione brusca del thread o un brusco AppDomain scaricamento.

Un cer è un blocco specifico try/finally immediatamente preceduto da una chiamata a PrepareConstrainedRegions.

In questo modo si istruisce il compilatore JIT a preparare tutto il codice nel blocco finally prima di eseguire il blocco try. In questo modo il codice nel blocco finally viene compilato e verrà eseguito in tutti i casi. Non è insolito in un CER avere un blocco vuoto try. L'uso di un CER protegge dagli aborti asincroni dei thread e dalle eccezioni di mancanza di memoria. Vedere ExecuteCodeWithGuaranteedCleanup per una forma di CER che gestisce anche gli overflow dello stack per codice con profondità eccessiva.

Vedere anche