Осознал достаточно большой пробел в знаниях (собственно, я ни на что и не претендовал ), поэтому прошу подсказать что нужно сделать.
Попробую вразумительно описать ситуацию.
Перевел проект на .NET 4.6 (VS 2005). Один из тестов релизной сборки выявил проблему. Его сценарий:
Этот тест проверяет наличие прав на вызов методов объекта.
1. Создается домен с минимальными правами.
2. В этом домене создается вспомогательный объект. В его задачу входит вызов (в контексте тестового домена) методов проверяемых объектов.
// Create helper object in sandbox AppDomain so that code can be executed in that AppDomain.var helperType
=typeof(tagTest_001__Worker);
var Helper
=(tagTest_001__Worker)testDomain.CreateInstanceAndUnwrap(helperType.Assembly.FullName,
helperType.FullName);
3. В рамках основного домена я вызываю методы объекта Helper, ловлю исключения System.Security.SecurityException и смотрю стек откуда исключение было выкинуто
public void Exec__OleDbConnection_GetNativeSession(xdb.OleDbConnection cn)
{
var x=cn.GetNativeSession();
//System.Console.WriteLine("OMG!");
x.Dispose();
}//Exec__OleDbConnection_GetNativeSession
В релизной сборке вместо сбоя при вызове cn.GetNativeSession, я получаю сбой при вызове x.Dispose:
lcpi_data_oledb_tests.OleDbPermission.Test_003__CheckDemand.Test_001__OleDb:
System.MethodAccessException : Методу "lcpi_data_oledb_tests.OleDbPermission.Test_003__CheckDemand+tagTest_001__Worker.Exec__OleDbConnection_GetNativeSession(lcpi.data.oledb.OleDbConnection)" не удалось получить доступ к методу "System.Runtime.InteropServices.SafeHandle.Dispose()".
Если раскомментировать System.Console.WriteLine, то все начинает работать как ожидается — метод GetNativeSession выкидывать исключение SecurityException:
exception data
Ошибкой завершилось следующее действие:
Demand
Ошибкой завершилось первое разрешение следующего типа:
lcpi.data.oledb.OleDbPermission
Ошибкой завершилось первым следующее разрешение:
<IPermission class="lcpi.data.oledb.OleDbPermission, lcpi.data.oledb.net4_6, Version=1.0.1.2490, Culture=neutral, PublicKeyToken=ff716095e8002e7e"
version="1"
AllowBlankPassword="False">
<add KeyRestrictions=""
KeyRestrictionBehavior="AllowOnly"/>
</IPermission>
Для сбойной сборки был предоставлен следующий набор:
<PermissionSet class="System.Security.PermissionSet"
version="1">
<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1"
Flags="SkipVerification, Execution, SerializationFormatter"/>
</PermissionSet>
Ошибкой завершилась следующая сборка или AppDomain:
lcpi.data.oledb.ntest.net4_6, Version=1.0.0.1326, Culture=neutral, PublicKeyToken=ff716095e8002e7e
Причиной этого явился следующий метод:
Void Exec__OleDbConnection_GetNativeSession(lcpi.data.oledb.OleDbConnection)
Ошибкой завершилась сборка со следующим параметром Zone:
MyComputer
Ошибкой завершилась сборка со следующим параметром URL: file:///D:/Users/Dima/Work/.NET/data.oledb.net/ntest/bin/vs2015_net46.0_Release/lcpi.data.oledb.ntest.net4_6.DLL
Похоже, что тестовый метод практически полностью (вместе с реализацией cn.GetNativeSession()) встраивается в точку вызова. И cn.GetNativeSession (целиком и полностью) работает в основном домене, у которого есть все права на всё.
Что мне нужно сделать, чтобы JIT (или кто там решает вопросы инлайна реализаций) это не делал?
То есть как заставить cn.GetNativeSession работать в контексте тестового домена?
Я пробовал указывать атрибуты SecuritySafeCritical и MethodImpl(MethodImplOptions.NoInlining) — не помогает.
Если выключить оптимизацию (или использовать отладочные сборки или раскомментировать System.Console.WriteLine), то все работает как ожидается.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Похоже, что тестовый метод практически полностью (вместе с реализацией cn.GetNativeSession()) встраивается в точку вызова. И cn.GetNativeSession (целиком и полностью) работает в основном домене, у которого есть все права на всё.
А tagTest_001__Worker унаследован от MarshalByRef?
Если нет — надо. Если да — оччень похоже на баг RyuJIT.
Попробуйте отключить, если проблема пропала — смело пишем на гитхаб/ms connect.
Здравствуйте, Sinix, Вы писали: S>Здравствуйте, Коваленко Дмитрий, Вы писали: КД>>Похоже, что тестовый метод практически полностью (вместе с реализацией cn.GetNativeSession()) встраивается в точку вызова. И cn.GetNativeSession (целиком и полностью) работает в основном домене, у которого есть все права на всё. S>А tagTest_001__Worker унаследован от MarshalByRef? S>Если нет — надо. Если да — оччень похоже на баг RyuJIT.
Да, наследует.
public class tagTest_001__Worker:MarshalByRefObject
Я хотел потом дополнительно об этом сказать, но затушевался S>Попробуйте отключить,
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--
The GUI only runs under .NET 2.0 or higher. The
useLegacyV2RuntimeActivationPolicy setting only
applies under .NET 4.0 and permits use of mixed
mode assemblies, which would otherwise not load
correctly.
-->
<startup useLegacyV2RuntimeActivationPolicy="true">
<!-- Comment out the next line to force use of .NET 4.0 -->
<supportedRuntime version="v4.0.30319" />
</startup>
<runtime>
<useLegacyJit enabled="1" />
<!-- Ensure that test exceptions don't crash NUnit -->
<legacyUnhandledExceptionPolicy enabled="1" />
<!-- Run partial trust V2 assemblies in full trust under .NET 4.0 -->
<loadFromRemoteSources enabled="true" />
<!-- Look for addins in the addins directory for now -->
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="lib;addins" />
</assemblyBinding>
</runtime>
</configuration>
S>если проблема пропала — смело пишем на гитхаб/ms connect.
Охжеж....тыть. И здесь грабли.
Попробую мозги в кучу собрать, и если получится — напишу. Это же надо изолированный тест захреначить...
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Коваленко Дмитрий, Вы писали: КД>Попробую мозги в кучу собрать, и если получится — напишу. Это же надо изолированный тест захреначить...
Вообщем, тяжело быть идиотом. Соорудил выделенный тест (VS2015).
Самое интересное в том, что можно указать платформу 4.0 (в VS2015). Проблема не исчезает.
А вот тесты для FW4.0, собранные в VS2010, работают нормально.
Так что собранное в VS2010!=собранному в VS2015. Догадывался, но теперь вот убедился.
-----
Выделенный тест все еще привязан к моим сборкам (на нугете — lcpi.data.oledb). Насколько я догоняю, проблему в рамках одного exe воспроизвести не получится — нужны внешние сборки, для которых нужно устанавливать разрешения в тестовом домене.
Скрытый текст
using System;
using System.Security;
using System.Security.Permissions;
using xdb=lcpi.data.oledb;
//using xdb=System.Data.OleDb;namespace ConsoleApp
{
public class Program
{
public class tagHelper : MarshalByRefObject
{
public void Exec(xdb.OleDbConnection cn)
{
//var x=cn.BeginTransaction();var x=cn.GetNativeSession();
//Console.WriteLine("Exec.point #2");
x.Dispose();
}//Exec
};//class tagHelperpublic static int Main()
{
try
{
var cn=new xdb.OleDbConnection("provider=lcpi.ibprovider.3;location=d:\\database\\employee.fdb");
cn.Open();
var testDomain=helper__create_domain();
// Create helper object in sandbox AppDomain so that code can be executed in that AppDomain.var helperType
=typeof(tagHelper);
var Helper
=(tagHelper)testDomain.CreateInstanceAndUnwrap(helperType.Assembly.FullName,
helperType.FullName);
helper__exec(()=> {Helper.Exec(cn);});
}
catch (Exception exc)
{
Console.WriteLine("ERROR: {0} - {1}",exc.Source,exc.Message);
}//catchreturn 0;
}//Main
//----------------------------------------------------------------------private static AppDomain helper__create_domain()
{
var permissions
=new PermissionSet(PermissionState.None);
var spflags = 0
| SecurityPermissionFlag.SkipVerification //required for .NET 3.5
| SecurityPermissionFlag.Execution
| SecurityPermissionFlag.SerializationFormatter //required for EnlistTransaction
;
permissions.AddPermission(new SecurityPermission(spflags));
var appDomainSetup
=new AppDomainSetup();
appDomainSetup.ApplicationBase
=AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
//lcpi.data.oledb.net4_6.dllvar a1_name=typeof(xdb.OleDbConnection).Assembly.GetName();
//lcpi.lib.net4_6.dll HELPER ASSEMBLYvar a2_name=typeof(lcpi.lib.com.ClsCtxCode).Assembly.GetName();
var a3_name=typeof(System.Runtime.InteropServices.SafeHandle).Assembly.GetName();
//-----var a1_sname=new System.Security.Policy.StrongName
(new System.Security.Permissions.StrongNamePublicKeyBlob
(a1_name.GetPublicKey()),
a1_name.Name,
a1_name.Version);
var a2_sname=new System.Security.Policy.StrongName
(new System.Security.Permissions.StrongNamePublicKeyBlob
(a2_name.GetPublicKey()),
a2_name.Name,
a2_name.Version);
var a3_sname=new System.Security.Policy.StrongName
(new System.Security.Permissions.StrongNamePublicKeyBlob
(a3_name.GetPublicKey()),
a3_name.Name,
a3_name.Version);
//----------------------------------------return AppDomain.CreateDomain("TestDomain",
AppDomain.CurrentDomain.Evidence,
appDomainSetup,
permissions,
a1_sname,
a2_sname);
}//helper__create_domain
//----------------------------------------------------------------------private static void helper__exec(Action a)
{
try
{
Console.WriteLine("helper__exec.point #1");
a();
}
catch (System.Security.SecurityException ex)
{
Console.WriteLine("EXEC_ERROR1: {0}",ex.FirstPermissionThatFailed);
Console.WriteLine("EXEC_ERROR2: {0}",ex.Message);
}
}//helper__exec
};//class Program
}//namespace Console
Свежесгенерированный exe выдает следующее:
helper__exec.point #1
ERROR: ConsoleApplication — Методу "ConsoleApp.Program+tagHelper.Exec(lcpi.data.oledb.OleDbConnection)" не удалось получить доступ к методу "System.Runtime.InteropServices.SafeHandle.Dispose()".
Если в ConsoleApplication.exe.config добавить предложенный
EXEC_ERROR2: Сбой при запросе разрешения типа "lcpi.data.oledb.OleDbPermission, lcpi.data.oledb.net4, Version=1.0.1.2490, Culture=neutral, PublicKeyToken=ff716095e8002e7e".
Весь исправленный конфиг:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<useLegacyJit enabled="1" />
</runtime>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" /></startup>
<system.data>
<DbProviderFactories>
<remove invariant="lcpi.data.oledb" />
<add name="LCPI OleDb Data Provider" invariant="lcpi.data.oledb" description="LCPI .NET Data Provider for OLE DB" type="lcpi.data.oledb.OleDbFactory, lcpi.data.oledb.net4, Version=1.0.1.2490, Culture=neutral, PublicKeyToken=ff716095e8002e7e" />
</DbProviderFactories>
</system.data></configuration>
cn.GetNativeSession() возвращает объект, класс которого наследует SafeHandle.
-----
Изначально я неправильно понял суть проблему. Я думал проблема в встраивании кода в точку вызова.
Проблема, похоже, в другом — метод tagHelper::Exec вообще не компилируется (с оптимизацией) из за вызова SafeHandle.Dispose.
Поскольку Sinix говорит, что проблема вроде как известна, то хочется на этом остановиться и забить до лучших времен.
Хотя если так подумать — наверное компилятор тоже по своему прав. И проблема в некорректном тесте. Но как её правильно обойти — не догоняю.
Пытался в CreateDomain передавать разрешения на mscorlb (a3_sname) — не помогает. Ошибка та же — не удалось получить доступ к методу "System.Runtime.InteropServices.SafeHandle.Dispose()"
PS. Со стороны этот пост выглядит так, как будто в Липецке начали уборку травы....
-- Пользователи не приняли программу. Всех пришлось уничтожить. --