Безопасность обработки исключений
Обновлен: Ноябрь 2007
В Visual C++ и Visual Basic выражение фильтра, расположенное выше по стеку, вычисляется до выполнения любого оператора finally. Блок catch, связанный с этим фильтром, выполняется после оператора finally. Дополнительные сведения см. в разделе Использование исключений, фильтруемых пользователем. В этом разделе рассматриваются вопросы влияния такого порядка на обеспечение безопасности. Рассмотрим следующий пример псевдокода, который иллюстрирует порядок выполнения операторов фильтров и операторов finally.
void Main()
{
try
{
Sub();
}
except (Filter())
{
Console.WriteLine("catch");
}
}
bool Filter () {
Console.WriteLine("filter");
return true;
}
void Sub()
{
try
{
Console.WriteLine("throw");
throw new Exception();
}
finally
{
Console.WriteLine("finally");
}
}
Этот код выводит следующие данные.
Throw
Filter
Finally
Catch
Фильтр выполняется перед оператором finally, поэтому любая ситуация, каким-либо образом изменяющая состояние, может создать проблемы с безопасностью, так как этим может воспользоваться другой код. Например:
try
{
Alter_Security_State();
// This means changing anything (state variables,
// switching unmanaged context, impersonation, and
// so on) that could be exploited if malicious
// code ran before state is restored.
Do_some_work();
}
finally
{
Restore_Security_State();
// This simply restores the state change above.
}
В этом псевдокоде фильтр, находящийся выше в стеке, может выполнять произвольный код. Есть и другие примеры операций, в которых достигается аналогичный эффект: временная работа под другой идентификацией, установка внутреннего флага, отменяющего некоторые проверки защиты, смена языка и региональных параметров, связанных с потоком, и т.д. Рекомендуемое решение — ввести обработчик исключения, изолирующий изменения в состоянии потока кода от блоков фильтрации вызывающих. Однако важно правильно установить обработчик исключения, иначе решить эту проблему не удастся. В следующем примере код переключает язык и региональные параметры пользовательского интерфейса, но аналогичным образом возможны и другие изменения состояния потока.
YourObject.YourMethod()
{
CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;
try {
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
// Do something that throws an exception.
}
finally {
Thread.CurrentThread.CurrentUICulture = saveCulture;
}
}
Public Class UserCode
Public Shared Sub Main()
Try
Dim obj As YourObject = new YourObject
obj.YourMethod()
Catch e As Exception When FilterFunc
Console.WriteLine("An error occurred: '{0}'", e)
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentUICulture)
End Try
End Sub
Public Function FilterFunc As Boolean
Console.WriteLine("Current Culture: {0}", Thread.CurrentThread.CurrentUICulture)
Return True
End Sub
End Class
Правильным решением в этом случае является включение существующего блока try/finally в блок try/catch. Простое указание catch-throw в существующем блоке try/finally не решает проблему, как показано в следующем примере.
YourObject.YourMethod()
{
CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;
try
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
// Do something that throws an exception.
}
catch { throw; }
finally
{
Thread.CurrentThread.CurrentUICulture = saveCulture;
}
}
Проблема не решается, так как оператор finally не выполняется до тех пор, пока функция FilterFunc не получит управление.
В следующем примере эта проблема устранена, так как оператор finally гарантировано выполняется до проверки исключения блоками фильтрации исключений вызывающего.
YourObject.YourMethod()
{
CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;
try
{
try
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
// Do something that throws an exception.
}
finally
{
Thread.CurrentThread.CurrentUICulture = saveCulture;
}
}
catch { throw; }
}