C#CLR抛出安全异常,除非标记为UNSAFE

时间:2015-11-16 02:57:25

标签: c# .net sql-server clr sqlclr

我在SQL Server 2012中有一个从事务中调用的C#CLR(.NET 4.5)存储过程。 CLR调用需要TLS 1.2的WebService。

如果我使用 permission_set = UNSAFE 创建程序集,一切正常,但我真的想避免这种情况,而是使用 EXTERNAL_ACCESS 。然而,这是一个真正的挑战。

使用 EXTERNAL_ACCESS

时有两个问题

1)我从CLR写入日志文件(用于维护和调试目的),由于不允许锁定,因此无效

private static readonly object Locker = new object();
public static string LogMessage(string message)
{
    message = String.Format("{0}: {1}" + Environment.NewLine, DateTime.Now, message);

    lock (Locker)
    {
        var file = new FileStream(GetConfigValue("LogFile"), FileMode.Append, FileAccess.Write);
        var sw = new StreamWriter(file);
        sw.Write(message);
        sw.Flush();
        sw.Close();
    }
    return message;
}

错误消息:

  

System.Security.HostProtectionException:尝试执行CLR主机禁止的操作。

     

受保护的资源(仅在完全信任的情况下可用)是:所有需求的资源是:同步,ExternalThreading

我可以改为登录到数据库表,但由于对CLR的调用来自事务,因此错误会导致回滚,这也会回滚日志记录。我可以使用事件日志,但如果可能的话,我真的不想这样做。

2)我调用的Web服务需要TLS 1.2,但不允许设置ServerCertificateValidationCallback

ServicePointManager.ServerCertificateValidationCallback = AcceptAllCertifications;

错误消息:

  

System.Security.SecurityException:请求类型为'System.Security.Permissions.SecurityPermission,mscorlib,Version = 4.0.0.0,Culture = neutral,PublicKeyToken = b77a5c561934e089'的权限失败。
  System.Security.SecurityException:
  在System.Security.CodeAccessSecurityEngine.Check(对象需求,StackCrawlMark& stackMark,Boolean isPermSet)          在System.Security.CodeAccessPermission.Demand()          在System.Net.ServicePointManager.set_ServerCertificateValidationCallback(RemoteCertificateValidationCallback value)          在AcmeClr.StoredProcedures.ProcessPayment(SqlMoney金额,SqlString ticketNo,SqlString& resultCode,SqlString& resultText)

在这个问题上是否存在 - 有没有办法在不使用UNSAFE的情况下完成这项工作?

1 个答案:

答案 0 :(得分:1)

对于问题#1(使用锁来管理同时写入同一日志文件的多个线程/会话),除了UNSAFE模式之外,没有办法使用锁。然而,好消息是你可能不需要首先使用锁。所有锁定真正完成的是确保同时发生的两个LogMessage()呼叫不会发生冲突,其中一个发生错误。这应该可以通过使用允许传入FileStream constructor选项/枚举的FileShare的另一个重载来解决。您应该能够传递FileShare.ReadWrite以防止错误。您已将DateTime.Now连接到message,以便在较晚的消息之后写入更早的消息时帮助解决序列问题。

但在该特定争用之外,即使使用锁定,您仍然会遇到两个会话执行SQLCLR WebService存储过程的问题,几乎同时交错其消息。你如何区分消息?我建议在存储过程的启动时创建一个新的Guid并将其连接到每个消息中,以便您可以将特定调用的消息与存储过程相关联(我假设您的代码有多个位置,调用LogMessage())并将这些消息与其他调用区分开(无论是否并发)。

如果出于任何原因,上面的两个部分(FileShare.ReadWrite并将Guid连接到message)不会阻止错误,那么您可以忘记FileShare选项(虽然我仍然希望将其设置为Read,以便我可以轻松地检查文件正在编写)而不是将Guid连接到message,将其附加到{{1}的末尾} value,就在文件扩展名之前。然后,特定呼叫的消息自动相互关联,并与其他并发呼叫区分开。它只是意味着你有一堆日志文件。

另外三个想法:

  1. 通过在GetConfigValue("LogFile")方法中使用lock,实际上增加了阻塞,因为跨不同会话的此方法的并发调用必须等待锁所有者释放锁。毕竟,这首先是锁定点,对吧?除了处理事务之外,你真的不想延长它们,绝对必要,并且通过SQLCLR进行Web服务调用的本质是你已经使事务依赖于网络延迟和响应性。外部系统(即使是内部系统)。

  2. 你提到过:

      

    我可以改为登录到数据库表,但由于对CLR的调用来自事务,因此错误会导致回滚,这也会回滚日志记录。

    不一定。如果你使用进程内" Context Connection",那么是的,这是真的。如果您使用常规/外部连接,如果在连接字符串中指定LogMessage()关键字,或者您指定Enlist,则也是如此。但是,如果指定Enlist = true;,那么它应该是单独/断开连接的事务,如果调用存储过程的事务被回滚,则不会回滚。

  3. 虽然这对使用SQLCLR代码的情况可能没有帮助,但我至少会提到对于纯T-SQL代码,当想要在回滚发生时解决丢失日志记录这个问题时,你有最初将这些记录写入表变量的选项。表变量不受事务限制,因此回滚不会影响它们。缺点是,如果进程以这样一种方式终止,即它在到达回滚后的部分之前完全终止,它将表变量的记录插入到真实的表中,那么你确实会丢失这些记录。在这种情况下,有时可以更好地写入日志文件或使用SQLCLR使用Enlist = false;进行常规/外部连接。
  4. 对于问题#2(设置Enlist = false;),遗憾的是,您无法做到这一点。尝试使用ServicePointManager.ServerCertificateValidationCallbackUseSSL属性设置为true以支持FTPS时,我遇到了同样的问题。在执行此操作时,我还没有找到一种方法需要将程序集标记为FtpWebRequest

    附注:

    • 我同意将大会保持为PERMISSION_SET = UNSAFE而不是EXTERNAL_ACCESS的愿望,但鉴于这是您的代码,您没有使用UNSAFE 1)用于引入任何第三方库或不受支持的.NET Framework库,实际风险级别非常低。

    • 此外,希望您使用非对称密钥或基于证书的登录,以允许程序集具有非UNSAFE权限,而将数据库设置为{ {1}}。

    • 除非问题中显示的代码是简化的,否则只发布必要的代码"为了传达即时问题,您缺少围绕外部资源的错误处理。如果进程崩溃,则日志文件将被锁定,直到App Domain被卸载,如果您未能正确处置SAFETRUSTWORTHY ON。您可以在这5行中添加try / finally结构,也可以让编译器使用StreamWriter构造/宏来为您执行此操作。