2

I have come across some interesting behaviour in SQL Server 2019 - it does not seem to occur in earlier versions.

If, in database1, I call a function in the same database, which calls a function in database2, which SELECTS a table in database2, I get "The SELECT permission was denied on the object '{TableName}', database '{DbName}', schema 'dbo'."

If, instead, I call the function in database2 directly (without using a function in database1), the query executes successfully.

Architecture diagram

My question is: what is the logic behind this? I don't understand why I am allowed to read a table in another database, without the SELECT permission, through a function, but not when I call that function using a function in my current database! Is it due to the function preventing the passing on of permissions? I am assuming at the moment that this is an intended change - but I don't understand the logic behind it.

Below is some code demonstrating the behaviour in a simple way.

/*******************************************
SET UP
*******************************************/
CREATE DATABASE TestDb1
GO
CREATE DATABASE TestDb2
GO

CREATE LOGIN [TestLogin] WITH PASSWORD = '123456a.'
GO

--Create users in each database and add to roles.
USE TestDb1
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db1Role
ALTER ROLE Db1Role ADD MEMBER [TestUser]

USE TestDb2
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db2Role
ALTER ROLE Db2Role ADD MEMBER [TestUser]

--Create table in db1, but do no GRANTs on it.
USE TestDb1
CREATE TABLE dbo._testDb1Table (Col1 INT)
GO

--Create a function in db1, and GRANT EXECUTE.
CREATE FUNCTION dbo._TestDb1Function()
RETURNS INT
AS
BEGIN
    DECLARE @Result INT = (SELECT TOP (1) Col1 FROM dbo._testDb1Table)
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb1Function TO Db1Role
GO

--Create a function in db2, and GRANT EXECUTE.
USE TestDb2
GO
CREATE FUNCTION dbo._TestDb2Function()
RETURNS INT
AS
BEGIN
    DECLARE @Result INT = (SELECT TestDb1.dbo._TestDb1Function())
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb2Function TO Db2Role
GO

/*******************************************
TESTS
*******************************************/
USE TestDb2

--Querying TestDb1 by calling the TestDb2 function directly works.
EXECUTE AS LOGIN = 'TestLogin'
SELECT TestDb1.dbo._TestDb1Function()
REVERT
GO

--Querying TestDb2 through a scalar function in db2 doesn't work.
--The SELECT permission was denied on the object '_testDb1Table', database 'TestDb1', schema 'dbo'.
EXECUTE AS LOGIN = 'TestLogin'
SELECT dbo._TestDb2Function()
REVERT
GO

/*******************************************
TIDY UP
*******************************************/
USE [master]
DROP LOGIN [TestLogin]
DROP DATABASE TestDb1
DROP DATABASE TestDb2
4

2 回答 2

1

根据 GSerg 和 Larnu 的有用评论,此行为似乎是由 SQL Server 2019 中添加的标量 UDF 内联功能引起的。

可以通过在数据库级别、函数定义中或使用查询提示禁用标量 UDF 内联来修复它。

编辑:根据 Razvan Socol 的回答,这已在 SQL Sever 2019 CU9 中修复。

这是与原始问题中给出的代码相同的代码,但将这 3 个可能的解决方案插入到适当的位置(已注释掉)。取消注释这 3 个解决方案中的任何一个都允许脚本在 SQL Server 2019 中运行而不会出错。

/*******************************************
SET UP
*******************************************/
CREATE DATABASE TestDb1
CREATE DATABASE TestDb2
GO
--SOLUTION 1: Turn off scalar UDF inlining at the database level.
--USE TestDb2
--ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;
GO

CREATE LOGIN [TestLogin] WITH PASSWORD = '123456a.'
GO

--Create users in each database and add to roles.
USE TestDb1
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db1Role
ALTER ROLE Db1Role ADD MEMBER [TestUser]

USE TestDb2
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db2Role
ALTER ROLE Db2Role ADD MEMBER [TestUser]

--Create table in db1, but do no GRANTs on it.
USE TestDb1
CREATE TABLE dbo._testDb1Table (Col1 INT)
GO

--Create a function in db1, and GRANT EXECUTE.
CREATE FUNCTION dbo._TestDb1Function()
RETURNS INT
AS
BEGIN
    DECLARE @Result INT = (SELECT TOP (1) Col1 FROM dbo._testDb1Table)
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb1Function TO Db1Role
GO

--Create a function in db2, and GRANT EXECUTE.
USE TestDb2
GO
CREATE FUNCTION dbo._TestDb2Function()
RETURNS INT
--SOLUTION 2: Turn off scalar UDF inlining for the function.
--WITH INLINE = OFF
AS
BEGIN
    DECLARE @Result INT = (SELECT TestDb1.dbo._TestDb1Function())
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb2Function TO Db2Role
GO

/*******************************************
TESTS
*******************************************/
USE TestDb2

--Querying TestDb1 by calling the TestDb2 function directly works.
EXECUTE AS LOGIN = 'TestLogin'
SELECT TestDb1.dbo._TestDb1Function()
REVERT
GO

--Querying TestDb2 through a scalar function in db2 doesn't work.
--The SELECT permission was denied on the object '_testDb1Table', database 'TestDb1', schema 'dbo'.
EXECUTE AS LOGIN = 'TestLogin'
SELECT dbo._TestDb2Function()
--SOLUTION 3: Turn off scalar UDF inlining for the query which calls the function.
--OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING')); --Added line
REVERT
GO

/*******************************************
TIDY UP
*******************************************/
USE [master]
DROP LOGIN [TestLogin]
DROP DATABASE TestDb1
DROP DATABASE TestDb2
于 2020-07-15T08:16:15.913 回答
1

这是 SQL Server 2019 中的一个错误,由标量 UDF 内联引起。它已在SQL Server 2019 CU9(2021 年 2 月发布)中修复。有关更多详细信息,请参阅KB4538581

于 2021-02-12T09:51:18.627 回答