我的SQL Server 2008 R2 CLR有时可以运行,但不能运行,但是为什么?

时间:2019-09-04 14:26:42

标签: sql-server security sql-server-2008-r2 sqlclr

我有一个CLR DLL(称为clr.dll),我们之前已加载并使用它。我正在使用它加载大量XML文件(对此我们没有XSD,因此我们不能在SSIS中使用复杂的XML处理器,因为这需要XSD)。我正在将其加载到也称为CLR的数据库中。有关信息,CLR函数通过每个节点具有一行来将XML文件作为表返回。 CLR还具有一些测试功能

以前,这种方法有效,现在会产生错误。错误似乎是间歇性发生的,即使在服务器上执行的唯一操作是对CLR的调用,也会发生错误。我有一些测试函数,这些函数实际上不占用任何资源,并且也不起作用。也就是说,这不是资源问题。

CLR装有PERMISSION_SET = EXTERNAL_ACCESS。数据库设置为TRUSTWORTHY ON。看来,在SQL Server 2008 R2中,我们无法使用哈希码加载CLR,并且我的dll仍然超出了HASHBYTES支持的8000个字符(由其他文章建议)。

在CLR中,这些函数属于名为UserDefinedFunctions的类。

这是我用来设置CLR的脚本的简化版本。数据库具有正确的权限。总之,它:(1)删除CLR数据库中的所有功能,(2)删除并重新连接CLR,(3)创建链接到程序集的功能并适当地设置它们的权限。

USE CLR
GO

-- drop all functions
DECLARE @FunctionName NVARCHAR(400)
DECLARE @SQL NVARCHAR(MAX)

WHILE EXISTS    (SELECT *
                FROM    sysobjects
                WHERE   xtype IN ('FS', 'FT'))
BEGIN
    SET @FunctionName = (SELECT TOP 1 name
                        FROM    sysobjects
                        WHERE   xtype IN ('FS', 'FT'))

    SET @SQL = 'DROP FUNCTION dbo.' + @FunctionName

    EXEC sp_executesql @SQL
END 
GO

-- belt and braces, this should be the case anyway
ALTER DATABASE CLR SET TRUSTWORTHY ON;
GO

-- drops and load the assembly, first time
IF EXISTS   (SELECT *
            FROM    sys.assemblies
            WHERE   name = 'CLR')
    DROP ASSEMBLY CLR
GO

CREATE ASSEMBLY CLR from 'e:\clr\clr.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO

-- ======== Scalar-functions ================================================================
CREATE FUNCTION dbo.DateTimeToString(@dt DATETIME, @fmt NVARCHAR(MAX)) RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME CLR.UserDefinedFunctions.DateTimeToString;
GO


CREATE FUNCTION dbo.FileExists(@Filename NVARCHAR(MAX)) RETURNS BIT
AS EXTERNAL NAME CLR.UserDefinedFunctions.FileExists;
GO


CREATE FUNCTION dbo.FileGetCreated(@Filename NVARCHAR(MAX)) RETURNS DATETIME
AS EXTERNAL NAME CLR.UserDefinedFunctions.FileGetCreated;
GO


CREATE FUNCTION dbo.FileGetModified(@Filename NVARCHAR(MAX)) RETURNS DATETIME
AS EXTERNAL NAME CLR.UserDefinedFunctions.FileGetModified;
GO


CREATE FUNCTION dbo.FileGetSize(@Filename NVARCHAR(MAX)) RETURNS BIGINT
AS EXTERNAL NAME CLR.UserDefinedFunctions.FileGetSize;
GO


CREATE FUNCTION Reflection(@Data NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) -- test - returns the string passed to it
AS EXTERNAL NAME CLR.UserDefinedFunctions.Reflection;
GO


-- ======== Table-valued functions -- ReadTextFile
CREATE FUNCTION dbo.ReadTextFile(@Filename NVARCHAR(4000))
RETURNS 
TABLE
(
    LineIndex INT,
    Data NVARCHAR(4000)
)
AS
EXTERNAL NAME CLR.UserDefinedFunctions.ReadTextFile
GO


-- ReadXmlDoc
CREATE FUNCTION dbo.ReadXmlDoc(@Filename NVARCHAR(4000))
RETURNS 
TABLE
(
    NodeIndex INT,
    ParentIndex INT,
    DepthIndex INT, 
    ChildCount INT, 
    SiblingIndex INT, 
    SiblingCount INT, 
    IsTerminalNode BIT,
    Tag NVARCHAR(4000),
    IndexPath NVARCHAR(4000),
    SimplePath NVARCHAR(4000), 
    UniquePath NVARCHAR(4000), 
    ExtendedPath NVARCHAR(4000), 
    TextValue NVARCHAR(4000)
)
AS
EXTERNAL NAME CLR.UserDefinedFunctions.ReadXmlDoc
GO

-- SELECT and EXECUTE permissions are set here, but omitted for security reasons

然后,我调用名为Reflection的最小测试函数,该函数仅返回传递给它的字符串:

SELECT  CLR.dbo.Reflection('hello')

无论我调用哪个函数或在哪个数据库中执行,都会引发此错误:

  

Msg 10314,第16级,状态11,第1行
  尝试加载程序集ID 65544时,Microsoft .NET Framework中发生错误。服务器可能资源不足,或者PERMISSION_SET = EXTERNAL_ACCESS或UNSAFE可能不信任该程序集。再次运行查询,或查看文档以查看如何解决程序集信任问题。有关此错误的更多信息:

     

System.IO.FileLoadException:无法加载文件或程序集'clr,Version = 0.0.0.0,Culture = neutral,PublicKeyToken = null'或其依赖项之一。发生与安全性有关的错误。 (来自HRESULT的异常:0x8013150A)   System.IO.FileLoadException:

     

在System.Reflection.Assembly._nLoad(AssemblyName文件名,字符串codeBase,证据assemblySecurity,程序集locationHint,StackCrawlMark&stackMark,布尔throwOnFileNotFound,布尔值用于自省)
  在System.Reflection.Assembly.InternalLoad处(AssemblyName assemblyRef,证据assemblySecurity,StackCrawlMark和stackMark,布尔值用于自省)
  在System.Reflection.Assembly.InternalLoad处(字符串assemblyString,证据assemblySecurity,StackCrawlMark&stackMark,布尔值用于自省)
  在System.Reflection.Assembly.Load(String assemblyString)

正如我所说,这不是资源问题,也不是权限问题,它有时会发生。目前,我本周无法运行它。上周重新应用放置并创建脚本解决了该问题。

当我按照下面所罗门的建议执行此代码时:

SELECT  name,
        is_trustworthy_on, 
        is_db_chaining_on, 
        compatibility_level, 
        owner_sid, 
        collation_name 
FROM    sys.databases 
WHERE   name IN (N'CLR', N'Other_DB')

我得到:

name      is_trustworthy_on  is_db_chaining_on  compatibility_level  owner_sid                                                   collation_name
Other_DB  0                  0                  100                  0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1C5FB0000  Latin1_General_CI_AS
CLR       1                  0                  100                  0x0105000000000005150000009530FDDAA5F3AE3A5859ABA17C2E0100  Latin1_General_CI_AS

添加了2019年10月2日。此外,查询1:

SELECT  lgn.[name], 
        lgn.[type_desc], 
        lgn.[sid], 
        CASE lgn.[sid] WHEN SUSER_SID() THEN 1 ELSE 0 END AS [IsCurrent] 
FROM    sys.server_principals lgn 
WHERE   lgn.[sid] IN (0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1C5FB0000, 0x0105000000000005150000009530FDDAA5F3AE3A5859ABA17C2E0100, SUSER_SID());

结果1:

name       type_desc      sid                                                         IsCurrent
GRP\clone  WINDOWS_LOGIN  0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1C5FB0000  0
GRP\mark   WINDOWS_LOGIN  0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1CD790200  1

查询2:

USE [CLR];
GO

SELECT  DB_NAME() AS [DB], usr.[name], usr.[type_desc], usr.[sid], CASE usr.[sid] WHEN USER_SID() THEN 1 ELSE 0 END AS [IsCurrent]
FROM    sys.database_principals usr 
WHERE   usr.[sid] IN (0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1C5FB0000, 0x0105000000000005150000009530FDDAA5F3AE3A5859ABA17C2E0100, USER_SID()) 
OR      usr.[name] = N'dbo';

结果2:

DB   name      type_desc     sid                                                         IsCurrent
CLR  dbo       WINDOWS_USER  0x0105000000000005150000009530FDDAA5F3AE3A5859ABA17C2E0100  0
CLR  GRP\mark  WINDOWS_USER  0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1CD790200  1

查询3:

USE [Other_DB];
GO

SELECT  DB_NAME() AS [DB], 
        usr.[name], 
        usr.[type_desc], 
        usr.[sid], 
        CASE usr.[sid] WHEN USER_SID() THEN 1 ELSE 0 END AS [IsCurrent] 
FROM    sys.database_principals usr 
WHERE   usr.[sid] IN (0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1C5FB0000, 0x0105000000000005150000009530FDDAA5F3AE3A5859ABA17C2E0100, USER_SID()) 
OR      usr.[name] = N'dbo';

结果3:

DB        name  type_desc     sid                                                         IsCurrent
Other_DB  dbo   WINDOWS_USER  0x0105000000000005150000009530FDDAA5F3AE3A5859ABA1C5FB0000  0

2 个答案:

答案 0 :(得分:0)

所以我学到了以下内容:

  • 以下内容适用于SQL2008R2中为EXTERNAL_ACCESS或UNSAFE的CLR。
  • 在创建程序集并将SQL函数连接到CLR时,它不会加载CLR。这是有道理的。为什么要在需要之前耗尽内存。
  • 首次执行对CLR的调用时,它将针对CLR数据库的SID检查正在执行的查询数据库的SID。这两个SID可以相同也可以不同。它们可能有所不同的主要原因是,正在从中运行查询的数据库可能已从另一台服务器还原。如果他们不同意,则会发生此错误:
Msg 10314, Level 16, State 11, Line 1
An error occurred in the Microsoft .NET Framework while trying to load assembly id 65544. The server may be running out of resources, or the assembly may not be trusted with PERMISSION_SET = EXTERNAL_ACCESS or UNSAFE. Run the query again, or check documentation to see how to solve the assembly trust issues. For more information about this error: 
System.IO.FileLoadException: Could not load file or assembly 'clr, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An error relating to security occurred. (Exception from HRESULT: 0x8013150A)
System.IO.FileLoadException: 
   at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
   at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
   at System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
   at System.Reflection.Assembly.Load(String assemblyString)

答案 1 :(得分:0)

测试已确认此问题与包含SQLCLR程序集的数据库(在这种情况下为CLR数据库)的所有者直接相关。最初,CLR数据库是Windows(活动目录/ AD)帐户的所有者。将数据库所有者切换到sa后,此行为停止发生。

虽然缩小了问题区域的范围(例如,程序集所在的数据库所有者),并且找到了解决方案(例如,将数据库所有者更改为sa),但尚未导致该行为的确切情况集确定后,到目前为止,根据提供的信息,我无法重现此行为。

目前,我还可以肯定地说,包含Assembly的数据库(TRUSTWORTHY ON DB)的CLR设置是主要因素。我相信启用TRUSTWORTHY后,设置为EXTERNAL_ACCESSUNSAFE的程序集会进行额外的外部安全检查。 TRUSTWORTHY仅在此处使用,因为大会尚未签署。我相信,如果TRUSTWORTHYOFF(即被禁用),则数据库所有者将不是成为一个因素,并且在数据库中甚至无法实现此行为。第一名。

其他说明

  • 在此上下文中不应使用术语“ CLR”。术语通常应为“程序集”,“ SQLCLR对象”,“。NET代码”或仅“ SQLCLR”。 CLR是“公共语言运行时”,用于执行编译到程序集中的代码。这类似于Java的JVM,我们不会将Java程序或应用程序称为“ JVM”。
  •   

    在创建程序集并将SQL函数连接到CLR时,它不会加载CLR。

    否,但这仅是因为在SQL Server启动时一直加载CLR并一直运行(除非您启用了“轻量级池” /“光纤模式”,并且不应该启用)。

    此外,当您CREATEALTER某个程序集时,会创建一个应用程序域 ,因为需要验证该程序集。但是,程序集本身并未加载到App Domain中。

  •   

    第一次执行CLR调用时,它将对照CLR数据库的SID检查正在执行查询的数据库的SID。

    不。不检查“当前”数据库所有者的SID(好吧,除非正在使用跨数据库所有权链接,否则不这样做)。根据{{​​1}}中的owner_sid记录检查包含程序集的数据库所有者的SID,以确保它们匹配,但启用sys.databases时仅 。在这种情况下,如果所有者SID在TRUSTWORTHYsys.databases之间不匹配(在包含Assembly的DB中),那么您将收到一条错误消息,指出这些SID必须匹配。

      

    这两个SID可以相同也可以不同。它们可能有所不同的主要原因是,正在运行查询的数据库可能已从另一台服务器还原。

    不完全是。所有者最初由创建(通过sys.database_principals,附加或还原)数据库的登录名设置。但是,更改数据库的所有者很容易,因此我不认为一个数据库来自另一个服务器就是因为拥有另一个所有者。

      

    如果他们不同意,则会发生此错误:

    不。不需要这两个数据库的所有者相同。

  • 关于主要问题的顶部代码块中所示的“删除所有功能”代码块:

    1. 请勿使用CREATE DATABASE / sysobjects / dbo.sysobjects,因为这是一个兼容性视图,该视图仅允许与为2005版之前的SQL Server版本编写的代码向后兼容( 9.0)。从SQL Server 2005开始,您应该使用sys.sysobjectssys.objects而不是旧的sys.{anything}视图。
    2. sys{anything}的数据类型应该为@FunctionName(这是sysname的同义词),因为它几乎用于SQL Server中的所有实体名称。
    3. 不要简单地寻找某种类型的对象,因为它不能非常可靠/稳定,因为它无法区分与不同程序集关联的T-SQL包装对象。您现在只有一个Assembly,但是当首选方法几乎不需要额外的努力时,它仍然是不必要的过于简单的方法。您应该从NVARCHAR(128)系统目录视图开始。例如:

      sys.assembly_modules

      从该查询开始,很容易从SELECT obj.[name] FROM sys.assembly_modules asmd INNER JOIN sys.assemblies assm ON assm.[assembly_id] = asmd.[assembly_id] INNER JOIN sys.objects obj ON obj.[object_id] = asmd.[object_id] WHERE assm.[name] = N'{assembly_name_here}' 获取模式名称和/或使用obj.[schema_id]语句在CASE之间切换DROP语句的对象类型,FUNCTIONPROCEDURE,具体取决于所需的内容。

    4. 我知道这只是一个部署脚本,但是仍然可以使用一个批处理/执行来删除所有功能,而不是使用缓慢的逐行循环:

      TRIGGER
  • 尽可能避免使用
  • DECLARE @SQL NVARCHAR(MAX) = N''; SELECT @SQL += N'DROP FUNCTION dbo.' + obj.[name] + N';' + NCHAR(0x0D) + NCHAR(0x0A) -- CR+LF FROM sys.assembly_modules asmd INNER JOIN sys.assemblies assm ON assm.[assembly_id] = asmd.[assembly_id] INNER JOIN sys.objects obj ON obj.[object_id] = asmd.[object_id] WHERE assm.[name] = N'{assembly_name_here}'; PRINT @SQL; -- DEBUG (else comment out and UNcomment line below) --EXEC (@SQL); 。有关详细信息,请参见“ PLEASE, Please, please Stop Using Impersonation, TRUSTWORTHY, and Cross-DB Ownership Chaining”。而是用证书(即Module Signing)对程序集签名。请参阅:

  • 尽管在上面直接提到的两篇文章中都提到了这一点,但我发现 最好从SET TRUSTWORTHY ON文字(也就是十六进制字节)而不是DLL上部​​署程序集文件系统,因为该外部DLL现在是脚本的依赖项,甚至可能与脚本中T-SQL包装对象的创建不同步。也很难对部署脚本进行版本控制和/或在系统之间进行传输。要将编译后的DLL轻松地转换为可部署的VARBINARY文字,请参见我为此目的创建的开源工具:BinaryFormatter
  • 有关一般使用SQLCLR的更多信息,请访问:SQLCLR Info