Проблема с permission и оптимизацией кода
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 07.08.15 08:47
Оценка: 20 (1)
Привет всем.

Осознал достаточно большой пробел в знаниях (собственно, я ни на что и не претендовал ), поэтому прошу подсказать что нужно сделать.

Попробую вразумительно описать ситуацию.

Перевел проект на .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 и смотрю стек откуда исключение было выкинуто

Грубо говоря как то так:
     Exec__Fail(()=>{Helper.Exec__OleDbConnection_GetNativeSession(cn);},
                     "point #GetNativeSession. cn.GetNativeSession",
                     "lcpi.data.oledb.core.Core_Connection__Opened.GetNativeSession");

4. Проблемный метод выглядит так:
  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>

Было предъявлено следующее требование:
<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), то все работает как ожидается.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re: Проблема с permission и оптимизацией кода
От: Sinix  
Дата: 07.08.15 09:20
Оценка: 92 (5)
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Похоже, что тестовый метод практически полностью (вместе с реализацией cn.GetNativeSession()) встраивается в точку вызова. И cn.GetNativeSession (целиком и полностью) работает в основном домене, у которого есть все права на всё.


А tagTest_001__Worker унаследован от MarshalByRef?
Если нет — надо. Если да — оччень похоже на баг RyuJIT.

Попробуйте отключить, если проблема пропала — смело пишем на гитхаб/ms connect.
Re[2]: Проблема с permission и оптимизацией кода
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 07.08.15 09:54
Оценка: 69 (3) +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Коваленко Дмитрий, Вы писали:


КД>>Похоже, что тестовый метод практически полностью (вместе с реализацией cn.GetNativeSession()) встраивается в точку вызова. И cn.GetNativeSession (целиком и полностью) работает в основном домене, у которого есть все права на всё.


S>А tagTest_001__Worker унаследован от MarshalByRef?

S>Если нет — надо. Если да — оччень похоже на баг RyuJIT.

Да, наследует.
public class tagTest_001__Worker:MarshalByRefObject

Я хотел потом дополнительно об этом сказать, но затушевался

S>Попробуйте отключить,


Добавил в nunit.exe.config:
  <configuration>
    <runtime>
      <useLegacyJit enabled="1" />
    </runtime>
  </configuration>

О майн God, проблема пропала!
  Весь конфиг
<?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.
Охжеж....тыть. И здесь грабли.

Попробую мозги в кучу собрать, и если получится — напишу. Это же надо изолированный тест захреначить...
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[3]: Проблема с permission и оптимизацией кода
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 07.08.15 12:22
Оценка: +1
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Попробую мозги в кучу собрать, и если получится — напишу. Это же надо изолированный тест захреначить...


... Я два (два, Карл!) часа угробил на попытку откомпилировать новый C# консольный проект в VS2005. DNX съел мой мозг.

Гори оно все в аду.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[3]: Проблема с permission и оптимизацией кода
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 07.08.15 18:37
Оценка: 23 (2) :)
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Попробую мозги в кучу собрать, и если получится — напишу. Это же надо изолированный тест захреначить...


Вообщем, тяжело быть идиотом. Соорудил выделенный тест (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 tagHelper

  public 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);
   }//catch

   return 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.dll
  var a1_name=typeof(xdb.OleDbConnection).Assembly.GetName();

  //lcpi.lib.net4_6.dll               HELPER ASSEMBLY
  var 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 добавить предложенный
  <configuration>
    <runtime>
      <useLegacyJit enabled="1" />
    </runtime>
  </configuration>

То вывод тот, который ожидается:

helper__exec.point #1
EXEC_ERROR1: <IPermission class="lcpi.data.oledb.OleDbPermission, lcpi.data.oledb.net4, Version=1.0.1.2490, Culture=neutral, PublicKeyToken=ff716095e8002e7e"
version="1"
AllowBlankPassword="False">
<add KeyRestrictions=""
KeyRestrictionBehavior="AllowOnly"/>
</IPermission>

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. Со стороны этот пост выглядит так, как будто в Липецке начали уборку травы....
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[4]: Проблема с permission и оптимизацией кода
От: Sinix  
Дата: 13.08.15 17:03
Оценка:
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Выделенный тест все еще привязан к моим сборкам (на нугете — lcpi.data.oledb).


Кажется, пофиксили. Должно прийти в обновлениях, детали тут.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.