【问题标题】:C# CLR throws security exception unless marked as UNSAFEC# CLR 抛出安全异常,除非标记为 UNSAFE
【发布时间】:2016-02-17 02:33:32
【问题描述】:

我在 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 主机禁止的操作。

受保护的资源(仅在完全信任的情况下可用)是:所有需要的资源是:同步、外部线程

我可以改为记录到数据库表,但由于对 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,布尔 isPermSet) 在 System.Security.CodeAccessPermission.Demand() 在 System.Net.ServicePointManager.set_ServerCertificateValidationCallback(RemoteCertificateValidationCallback 值) 在 AcmeClr.StoredProcedures.ProcessPayment(SqlMoney 金额, SqlString ticketNo, SqlString& resultCode, SqlString& resultText)

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

【问题讨论】:

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


    【解决方案1】:

    对于问题 #1(使用锁来管理同时写入同一个日志文件的多个线程/会话),除了UNSAFE 模式外,没有其他方法可以使用锁。但是,好消息是您可能一开始就不需要使用锁。所有锁真正完成的是确保同时发生的两个LogMessage() 调用不会发生冲突,其中一个会出错。这应该可以通过使用FileStream constructor 的另一个重载来解决,该重载允许传入FileShare 选项/枚举。您应该能够传入FileShare.ReadWrite 以防止出现错误。您已经将 DateTime.Now 连接到 message 中,因此如果较早的消息在后面的消息之后写入,这应该有助于解决序列问题。

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

    如果出于任何原因上述两部分(FileShare.ReadWrite 并将 Guid 连接到 message)不能防止错误,那么您可以忘记 FileShare 选项(尽管我仍然更喜欢将其设置为Read 所以我可以轻松地检查正在写入的文件)而不是将 Guid 连接到 message 中,而是将其附加到 GetConfigValue("LogFile") 值的末尾,就在文件扩展名之前。然后,特定呼叫的消息会自动相互关联,并与其他并发呼叫区分开来。这只是意味着你有一堆日志文件。

    对此的其他三个想法:

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

    2. 你提到:

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

      不一定。如果您使用进程内“上下文连接”,那么是的,这是真的。如果您使用常规/外部连接,如果您在连接字符串中指定Enlist 关键字或指定Enlist = true;,则也是如此。但是,如果您指定Enlist = false;,那么它应该是单独的/断开连接的事务,如果调用存储过程的事务回滚,则不会回滚。

    3. 虽然这在使用 SQLCLR 代码的情况下可能无济于事,但我至少要提一下,对于纯 T-SQL 代码,当想要解决回滚时丢失日志记录的问题时,您有最初将这些记录写入表变量的选项。表变量不是事务绑定的,因此回滚不会影响它们。不利的一面是,如果进程以这样一种方式终止,即它在到达回滚后将表变量的记录插入真实表的部分之前完全终止,那么您确实会丢失这些记录。这就是有时更好地写入日志文件或使用 SQLCLR 使用Enlist = false; 进行常规/外部连接的地方。

    对于问题 #2(设置 ServicePointManager.ServerCertificateValidationCallback),不幸的是,您对此无能为力。我在尝试使用FtpWebRequestUseSSL 属性设置为true 以支持FTPS 时遇到了同样的问题。执行此操作时,我还没有找到解决方法将程序集标记为PERMISSION_SET = UNSAFE


    旁注:

    • 我同意将程序集保留为 EXTERNAL_ACCESS 而不是 UNSAFE,但鉴于这是您的代码 并且您没有使用 UNSAFE 加入任何 3rd 方库或不受支持的 .NET Framework 库,实际风险级别都非常低。

    • 另外,希望您使用基于非对称密钥或证书的登录以允许程序集具有非SAFE 权限,并且将数据库设置为TRUSTWORTHY ON .

    • 除非问题中显示的代码经过简化,以便仅发布“必要”的代码以传达当前问题,否则您将缺少围绕外部资源的错误处理。如果进程崩溃,日志文件将被锁定,直到应用程序域被卸载,如果您未能正确处置StreamWriterFileStream。您可以在这 5 行周围添加 try / finally 结构,也可以使用 using() 构造 / 宏让编译器为您完成。

    【讨论】:

      猜你喜欢
      • 2011-01-24
      • 2013-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多