第四部分 理论第14章 数据库安全数据库不仅仅存储数据,事实上它为用户提供信息。因此,数据库安全不仅要考虑保护敏感数据,而且要考虑使用户以受控制的方式检索数据的机制。这里强调了把数据库安全和操作系统安全区分开来。比起对数据的访问控制,我们应该更强调对信息的访问控制。尽管保护数据的安全是一个重要问题,但我们绝对应该把控制集中在请求访问的目标上。
目标:
分析数据库系统特有的安全问题理解各种安全观点如何应用到关系数据库系统的访问控制上去分析在统计数据库中保护信息安全的问题考察在数据库管理系统和操作系统中的安全机制之间潜在的相互作用
14.1 简介数据库是以某种有特定意义的方式组织起来的数据的集合。一个数据库管理系统(DBMS)负责管理数据并给用户以检索信息的手段。如果对信息的访问完全失控,用户将不再把某些数据放入数据库中,数据库只能提供很少有用的服务了。例如,数据库常常存储了许多个人信息,像公司雇员档案、大学学生档案,以及税务局税收资料等。很多国家已经制定了个人隐私法案,强制维护这种数据库的机构承担保护个人数据安全的责任。因此,很早以来,数据库安全就在计算机安全中占有重要的一席之地。这是一个特别的一席之地,因为数据库安全是不同于操作系统安全的。下面对这种说法给予具体说明。
操作系统确实管理数据。用户执行操作系统功能,创建文件、删除文件,或者为读、写访问而打开一个文件。但是这些操作都不涉及文件的内容。说得更明确一些,访问控制的决定是由操作系统做出的,这些决定取决于对用户的识别、文件属性的定义、访问控制列表、安全标识符等等,但是与文件内容无关。这样做不是因为基于某种基本的安全理论,而只是一种合理的工程考虑。
数据库中的表项携带有信息。数据库用户执行的是与数据库表项内容有关的操作。数据库最典型的应用恐怕要算是数据库搜索了。因此,由数据库管理系统在同时考虑数据库表项内容的情况下做出访问控制的决定就是十分合理的了。一个很通俗的例子就是工资数据库,达到一定数额的工资就需要保密了。总之,在人机环境中,数据库安全是侧重在用户端的(见图14.1)。
初看起来,在数据库中保护敏感信息似乎很容易。在工资数据库中,你只要简单地往查询语句上增加条件来检查工资额。如果你知道要保护哪些数据,这种方法确实是可行的。然而,入侵者可能会对许多信息的不同片断感兴趣。下面列出了可能的信息源:
准确的数据:存储在数据库中的数值。
边界:数值的下界或上界,像工资就是有用的信息。
负面的材料:如果数据库包含罪犯定罪数字,那么,这种涉及某人有罪的信息就是敏感的。
存在性:数据的存在性本身也可能是敏感信息。
可能的数值:能够从其他查询的结果中猜测一些信息。
最后,你必须防止所有的不测事件。如果数据库允许统计查询,信息的保护就变得更加困难。比如,一次统计查询返回所有工资总和,或者所有工资的平均数。这种查询的巧妙组合可能会暴露你试图保护的信息。这个话题我们将在14.4节中进一步讨论。
我们已经对敏感信息可能会从数据库泄露的许多途径提出了警告。你应该很重视安全性问题,然而,你也不要忘了数据库就是要为正当目的提供服务的。如果对数据的访问限制得过严,虽然不会泄露敏感信息,但是会降低数据库的价值。因此,你不得不力求准确,也就是说,在尽可能只泄露无用信息的情况下保护好敏感信息。
图14.1 在人-机标度中数据库安全的位置数据库表项携带了实体外部到计算机系统的信息,像仓库的库存,学生考试成绩,银行帐户结余,或者航班的空座数。数据库表项应该正确地反映这些客观的事实。数据库安全结合特定应用的完整性保护来达到:
内部的一致性:数据库表项遵循某些事先确定的规则。比如,库存值不能小于零。
外部的一致性:数据库表项必须是正确的。比如,数据库中的库存值必须与仓库中的库存一致。当更新数据库时数据库管理系统可以避免一些错误,但是你不能仅仅依赖数据库管理系统来保持数据库处于一致性的状态。这种特性称为精确性。
在1.4节的分层模型中,数据库管理系统DBMS可以放在操作系统之上的服务层中。DBMS
必须满足操作系统无法解决的特定数据库(数据库特定的,database-specific)安全需求。DBMS可以结合在操作系统中的几种保护机制来加强安全性,如果操作系统不具备适当的控制机制或者如果这样做变得很麻烦的话,就依靠DBMS自身的机制。此外,DBMS可以是在应用层定义安全控制的工具。图14.2揭示了这样的事实:数据库安全包括了在相当不同的抽象层次中的安全机制。
图14.2 数据库安全的位置
14.2 关系数据库在构造数据库的各种模型中,使用得最为广泛的是关系模型。我们再次假定读者熟悉关系数据库的有关概念,这里仅对其作简要介绍。详细叙述请见参考文献[37]。
关系数据库:关系数据库是一个被它的用户感知为一个表集合的数据库,并且只是各种表而已。
关系数据库的定义归诸于用户对它的理解,而不是它的实际的(物理,physical)组织。这样也恰好成为讨论数据库安全的适当的抽象。
形式上,一个关系R,是的子集,这里是n维属性域。关系中的元素是n元组,其中,也就是说,第i个属性值是来自的元素。元组中的元素通常被称为域。当一个域没有任何值时,我们会在这个位置上放一个特殊的值——空值来表示。空值的含义是“这里没有该项”,而不是“该项是未知的”。
在图14.3中的关系表是一家旅行社数据库的一部分。关系Diary有四个属性,name,day,flight和status,每个属性的取值范围如下:
Name:所有有效的客户名。
Day,一周中的每一天,比如,Mon,Tue,Wed,Thu,Fri,Sat,Sun。
Flight:航班号,两个字符后再加上1—4个数字。
Status:公务(business)或是私人(private)。
用来描述如何在关系数据库中检索和更新信息的标准语言是SQL语言,在文献[52]中又被称为结构化查询语言。SQL对数据处理的操作包括了以下几个方面。
SELECT:从一个关系中检索数据例如:
SELECT Name,Status
FROM Diary
WHERE Day = ‘Mon’
返回结果
图14.3
UPDATE:更新一个关系中的域值例如,
UPDATE Diary
SET Status = private
WHERE Day = ‘Sun’
把所有星期天的旅游团标记为私人旅游团(长途旅行,journey)。
DELETE:从一个关系中删除元组例如:
DELETE FROM Diary
WHERE Name = ‘Alice’
从Diary中删除所有Alice参加的旅游团。
INSERT:在一个关系中添加元组例如:
INSERT INTO Flights (Flight,Destination,Days)
VALUES (‘GR005’,‘GOH’,‘12-45-’)
在关系Flights中插入了一个新的元组,其中Departs域仍然未给出。
在所有情况下,可能有更复杂的结构。本书的目的并不在于阐述SQL的复杂性,对此我们只会给出一个例子加以说明。比如,为找出谁会去Thule,执行以下语句:
SELECT Name
FROM Diary
WHERE Flight IN
(SELECT Flight
FROM Flights
WHERE Destination = ‘THU’)
数据关系常常用表来表示。属性相当于表中的列,用属性名作为列的标题。表中的行相当于数据库中的元组(记录)。在关系模型中,一个关系不包括到指向其他表的连接或指针。表与表之间的关系(relations)只能通过另一个关系给出。在关系数据库里,可能存在不同种类的关系:
基本关系:又被称为实际关系(real relations),它们是被命名的自主(自治的,autonomous)关系:它们本来就存在,而不是从其他关系导出的,它们有其自身的存储数据。
视图:它们是被命名的导出关系,根据其他被命名的关系定义:它们没有其自身的存储数据。
快照:与视图一样,快照是被命名的导出关系,根据其他被命名的关系定义:它们有其自身的存储数据。
查询结果:一个查询操作的结果;它们可能有也可能没有名字。本质上它们不是常驻在数据库中的。
例如:一个在Diary表中查询谁以及何时正在旅行的快照操作定义如下:
CREATE SNAPSHOT Travellers
AS SELECT name,day
FROM Diary
14.2.1 数据库关键字
在每个关系中,我们必须找到一个能识别所有元组的唯一方式。有时,一个单独的属性可以用来作为这样的标识符。甚至可能像这样的属性有很多,我们可以从中选取一个来达到识别的目的。而另一方面,我们也许需要不止一个属性一起来组成一个唯一的标识符。
定义 一个关系的主关键字是指该关系的一个唯一的、最小的标识符。关系R中的主关键字K必须满足以下条件:
唯一性:在任何时候,关系R中没有一个元组的值对K而言是相同的。
最小性:如果K是一组合关键字,在满足唯一性的条件下,K中的每个成员都不能少。
在上面关系Diary的例子中,名字和日期的组合可以作为主关键字(假定旅客每天只能跟一个旅游团出行)。在关系Flights中,主关键字是飞机的航班号。
每个关系必须有一个主关键字,就像没有关系会包含复合元组(duplicate tuples)一样。这是由我们对关系的正规定义得出来的。当一个关系的主关键字作为另一关系中的一个属性时,那么它就是那个关系的外关键字。在我们的例子中,飞机的航班号在关系Flights中是主关键字,但在关系Diary中就是外关键字。
14.2.2 完整性规则
在关系数据库中,你可以通过定义完整性规则来实现内部的一致性,并且帮助保持外部的一致性(精确性)。这些规则的大多数是针对应用程序而言,但有两条规则是关系数据库模型特有的。
实体完整性规则 基本关系的主关键字属性组合中的任一属性或属性组合子集都不允许有空值。
这个规则可以保证我们能在基本关系中找到所有的元组。这些在基本关系中的元组与“现实”中的实体一一对应,并且我们不会在数据库中建立一个我们不能识别的实体描述。
引用完整性规则 数据库中不能包含不匹配的外关键字值。
一个外关键字值申明了一个进入其他表的引用(reference)。一个不匹配的外关键字值是指,在被引用的表中它不是作为一个主关键字值。它是一个对不存在元组的引用。
除这两条规则外,人们可能需要更多的特定应用的(应用特定的/专用的,application-specific)完整性规则。这些完整性规则非常重要,因为它们能让数据库一直处于可用状态。典型地,你会使用这些完整性规则做以下事情:
域检查:防止错误的数据输入。在我们的例子中,可以通过一条规则来检查输入值是否是business或private,从而防止向Diary关系的status属性插入任意值。
范围检查:在统计数据库中,你可能需要一些规则来检查,查询结果是否是基于一个足够大的样本空间来计算的。先来看图14.4的Students关系,你可以定义这样一条规则,当样本空间不大于三时,就返回平均成绩的默认值67。
一致性检查:在不同关系中的表项可能指的是外界的同一个方面,因此,也应该在这方面表现出一致性。在我们的例子中,可以检查旅客启程旅行的日期是否与他们航班起飞的日期相吻合。例如,Alice乘坐的星期一的GR123型航班,就与该航班只在星期一和星期四起飞相一致。而Carol预定星期二的BX201型航班,就与该航班在星期一、星期三和星期六起飞不一致。一条像这样比较各自的域的完整性规则,可以避免旅行社发生这样的错误。
像这类的完整性规则是在应用层中控制的。数据库管理系统为指定和实现这些规则提供了基础。例如,完整性触发器就是这样的一个程序,它可以附带在一个数据库的对象中,用来检查该对象的一些特殊的完整性特性。当一个更新(UPDATE)、插入(INSERT)或删除(DELETE)操作企图更改该对象时,这个程序就被触发并执行这一检查。
在指出了关于机密性和完整性之间的潜在冲突之后,我们将不再就这个话题做更深入的探讨。当计算一个完整性规则而要求访问敏感信息时,你就会面临这样的两难境地:是为了保护敏感信息而不完全(和不正确)地计算规则,还是为了维护数据库的一致性而泄漏一些敏感信息。
14.3 访问控制为保护敏感信息,数据库管理系统必须对用户如何访问数据库进行控制。要弄清楚这些控制是如何实现的,你可以分两个层次来考虑访问数据库:
对基本关系的数据处理操作;
复合操作,如视图或快照操作。
简短回顾一下1.4.1节。你可以从两个角度来看访问控制:
限制用户可获得的操作,或者为每个单独的数据项定义保护的要求。
在一个数据库管理系统中,对复合操作的控制规范了用户可以如何使用数据库。另一方面,对基本关系的操作检查更能保护数据库中的表项。通过选取你想要控制的访问操作类型,你也影响了将要被执行的策略的重点所在。反之,策略的重点也会影响你要控制的操作类型。不管如何选择,有两个特性是你应该争取达到的:
完全性:保护数据库中的所有的域。
一致性:没有相互冲突的对数据项的访问进行管理的规则。
如果在数据库中没有元素会被以不同的方式访问,从而导致不同的访问控制的选取,这样的安全策略才是一致的。合理的访问要求不应被阻止,也不能使指定的访问策略让人有机可乘。
14.3.1 SQL的安全模型
基本的SQL安全模型和关系数据库的安全模型很相似。它基于三个实体实现了自主访问控制(discretionary access control):
用户实体:数据库的用户。在登录过程中用户身份得到认证。数据库管理系统可以运行自己的登录进程,或是接受由操作系统认证的用户身份。
操作实体:包括选取(SELECT)、更新(UPDATE)、删除(DELETE)和插入(INSERT)。
对象实体:表、视图、以及表和视图的列(属性)。SQL3还将进一步包括用户定义的约束条件(constructs)。
用户会在对象上执行一些操作,而数据库管理系统应该决定是否允许这样的操作。当用户创建了一个对象,该用户就被指定为此对象的所有者,并且最初只有此对象的所有者可以访问它。其他用户必须先被授予优先权(privilege)才可以访问它。优先权的形式如下:
(授权者,被授权者,对象,操作,优先权级别)
SQL安全模型的两大支柱是优先权和视图。它们为定义面向应用的安全策略提供了框架。
14.3.2 优先权的颁发与撤销
在SQL中,我们通过GRANT和REVOKE操作来管理优先权。优先权对应着特殊的操作,并且可以将它限制到表中的某个属性。在我们的例子中,允许两个旅行社Art和Zoe,检查和更新表Diary中的某些部分:
GRANT SELECT,UPDATE (Day,Flight)
ON TABLE Diary
TO Art,Zoe
优先权可以有选择地被撤销:
REVOKE UPDATE
ON TABLE Diary
FROM Art
更特别的是,可以让被授予优先权的人再去颁发优先权给第三者。在SQL中是由GRANT操作来实现的。例如,
GRANT SELECT
ON TABLE Diary
TO Art
WITH GRANT OPTION
旅行社Art可以依次把对表Diary的优先权颁发给Zoe,
GRANT SELECT
ON TABLE Diary
TO Zoe
WITH GRANT OPTION
当表Diary的所有者将颁发给Art的优先权撤销时,所有由Art颁发的优先权也会被撤销。因此,优先权被一层一层地撤销(cascade),而撤销时所需要的信息被保存在数据库中。
你还应该注意到,一旦其他用户被授权访问数据,这些数据的所有者就再无法控制从这些数据导出的信息将会如何被使用,即使所有者对原始数据仍有一定的控制。你不再需要任何对访问原始表的“写”命令要求,而可以直接从一张表中读出数据,以及将这些数据拷贝到另一张表中。
14.3.3 通过视图的访问控制
视图是导出关系。SQL中创建视图的操作格式如下:
CREATE VIEW view_name[(column[,column]…)]
AS subquery
[WITH CHECK OPTION];
你可以在关系数据库中通过直接对基本关系中的表项授予优先权来实现访问控制。然而,很多安全策略可以更好地由视图以及这些视图的优先权来表现。在视图中的子查询定义可以描述非常复杂的访问条件。举一个简单的例子,我们在关系Diary中组建一个包括所有公务旅行的视图:
CREATE VIEW business_trips AS
SELECT * FROM Diary
WHERE status=’business’
WITH CHECK OPTION;
通过视图,访问控制可以适当地被放置在应用层。数据库管理系统只提供执行这些控制的工具。视图吸引我们的原因有以下几点:
视图很灵活,并且允许我们在描述层定义访问控制,这样可以更贴近应用的要求。
视图可以实现上下文依赖的和数据依赖的安全策略。
视图可以执行控制调用(controlled invocation)。
安全的视图可以取代安全标识(标签,security labels)。
可以很容易地对数据进行重新分类。
在图14.4给出的例子中,面向应用的访问控制可以通过视图表示如下,
CREATE VIEW High_Flyers AS
SELECT * FROM Students WHERE Grade >
(SELECT Grade FROM Students WHERE Name=current_user());
结果只显示那些平均成绩比正在使用该视图的学生成绩高的学生,或者:
CREATE VIEW My_Journeys AS
SELECT * FROM Diary
WHERE Customer=current_user();
只显示在图14.3中由正在使用此视图的旅客预订的那些旅行线路。可以通过在数据库中添加访问控制表来实现自主访问控制。这儿的视图指的就是此关系。通过这种方式,你可以实现基于组成员关系的访问控制,也可以通过策略来调整用户的权限使其和颁发和撤销访问权限一样。
图14.4 关系Student
进一步说,视图可以定义为或指的就是安全标识。在我们的例子中,可以通过创建视图把到Thule的商业航班标记为机密的:
CREATE VIEW Flights_≥CONFIDENTIAL AS
SELECT * FROM Diary
WHERE Destination=’THU’ AND Status=′business′;
通过视图来控制读取访问时,除了正确地获取安全政策外,便不会再有其他技术上的特殊困难了。当视图使用插入(INSERT)或更新(UPDATE)操作对数据库进行写入时,情况就不同了。首先,存在一些不能更新的视图,因为它们不包含这样的信息,这些信息对于维护相应的基本关系的完整性是必需的。例如,一个不包含基本关系的主关键字的视图是不能被更新的。其次,即使一个视图是可更新的,仍会有一些有趣的安全问题在里面。一个可以访问Diary数据库的旅行社只能通过business_trips视图查看到列在表14.5中的数据。是否应该允许旅行社通过以下操作来更新视图?
UPDATE business_trips
SET Status=’private’
WHERE Name=’Alice’ AND Day=’Thu’
如果允许的话,那么Alice的记录就会从视图中消失。事实上,在这种情况下是不允许修改的,因为视图的定义已经指定了检查(CHECK)选项。如果一个视图的定义包含了WITH CHECK OPTION,那么更新(UPDATE)和插入(INSERT)操作只能对数据库写入那些符合视图定义的表项。如果检查(CHECK)选项被忽略了,就可能出现盲写(blind writes)。
在SQL安全模型中,视图不仅是一个对象,同时还被看成是一个程序。当使用视图所有者的优先权,而不是用调用该视图的用户的优先权来计算该视图时,那么你会找到另一种实现控制调用的方法。
图14.5 视图(View)示例
视图的访问条件必须在SQL的限定范围内加以说明。如果这种做法过于严格,那么用更易读的语言编写的软件包(存储的过程)就是数据库管理系统为控制数据库的访问而提供的另一选择。同样,用户被授予执行软件包的权利(特权,privilege),而软件包运行时具有其所有者的权限。
计算机系统的任何一层都有控制调用。它在微处理器系统和数据库管理系统中同样有用。 258页
迄今为止,我们阐述了如何使视图作为一个有效的安全机制的各方面的问题。很自然,视图也有它自身的缺点:
访问检查会变得相当复杂,而且很慢。
视图定义要求必须检查“正确性”。它们真的揭示了我们想要的安全策略吗?
由于完全性和一致性不是自动实现的,视图之间可能会重叠或是不能描述整个数据库。
数据库管理系统中的安全相关部分(TCB)会变得很庞大。
视图适合于用在“普通的商业”环境中。它们可以根据应用的需要而剪裁而不要求对数据库管理系统做任何更改。因此视图的定义是数据库结构定义通用方法中的一部分,它最好地满足了商业需求。
然而,当要确定谁可以访问单个数据项时,可能就变得困难。因此,视图不适合这种情况,即当认为保护数据项比控制用户的操作更有必要时。在第15章中,我们将会把数据库的安全集中在保护数据项上。
14.4 统计数据库的安全性本书迄今为止还未考虑统计数据库中出现的安全问题。统计数据库一个最显著的特点是信息可以经由一张表中某个属性(列)的统计(聚集)查询重新获得。在SQL中的聚集函数有:
COUNT,一列中值的个数,
SUM,一列中值的求和,
AVG,一列中值的平均,
MAX,一列中的最大值,
MIN,一列中的最小值。
统计查询的查询谓词(query predicate)专指那些将被用于聚集计算的元组,查询集合(query set)是指和查询谓词匹配的元组。在内核中,统计数据库提出以下的安全问题:
数据库包含的数据通常是敏感的,因此不允许直接对数据项进行访问。
对数据库的统计查询是允许的,正由于统计查询自身的特点,这些查询读取单个数据项。
在这种情况下,推断信息(infer information)就成为可能,我们还将说明单个地管理访问请求将不再有效。我们也会采用更加实用的信息流的观点讨论问题。第4章中的机密性模型对无论什么样的信息流都尽力阻止。在统计数据库中,必须有一些信息流从数据流向它们的聚集。我们只能尽量把这种情况减小到一个可以接受的水平。
图14.4中的Students数据库将为这节提供例子。我们允许对所有属性的统计查询,除了在Units和Grade Ave中的单个表项。列不能被直接读取。下面的统计查询是计算所有MBA学生的平均成绩:
Q1,SELECT AVG(Grade Ave.)
FROM Students
WHERE Programme=’MBA’
在这个例子中,查询谓词是:Programme=’MBA’。
14.4.1 聚集和推断统计数据库安全的两个重要概念是聚集和推断。聚集(Aggregation)提出一个观察结果,即对数据库中一组值计算结果的聚集的敏感度,可能和单个元素的敏感度不同。你通常会遇到这种情况,聚集的敏感度低于单个元素的敏感度。当聚集作是从敏感度低的商业数据的收集中推导出来敏感的可用的信息时,与之相反的情况也会发生。
聚集是数据库中的另一种关系,例如视图。因此,你可以使用在这一章中建议的安全机制来控制对聚集的访问。然而,一个攻击者可以探究不同的敏感度从而获得对更多的敏感项的访问。推断问题(inference problem)是指从非敏感数据中导出敏感信息。考虑以下的攻击类型:
直接攻击:在一个小样本空间里执行聚集计算导致关于单个数据项的信息被泄漏。
间接攻击:这种攻击组合和几个聚集操作相关的信息。
跟踪攻击:间接攻击中特别有效的一种类型。
线性系统脆弱性攻击:这种方法比跟踪攻击更进一步,利用查询集合间的代数关系来构造等式,以产生所想要的信息。
14.4.2 跟踪攻击我们现在来说明如何利用统计查询从图14.4给出的学生关系的例子中来得到敏感信息。假设我们知道Carol是计算机科学系的女生。通过组合下面的两个合法查询:
Q1,SELECT COUNT(*)
FROM Students
WHERE Sex=’F’ AND Programme=’CS’
Q2,SELECT AVG(Grade Ave.)
FROM Students
WHERE Sex=’F’ AND Programme=’CS’
我们从Q1中知道,在数据库中计算机科学系只有一名女生,因此从Q2返回的值70就肯定是该女生的平均成绩。这里的问题是选取标准允许一个集合可以只包含一个元素。所以,仅当统计查询覆盖了一个足够大的子集时,它才被允许执行。然而,我们可以忽略选取标准而简单地查询它的补集,可以像刚才一样从对整个数据库的查询结果和对我们真正感兴趣的集合的补集查询的结果之间的不同中,得到同样的结果。因此,你不仅要求查询所需的元组集合要足够大,而且它的补集也要足够大。
不幸的是,即使这样也不够好。假设每个查询集合和它的补集都必须至少包含三个元素。下列4个查询依次返回的值为:Q3:4,Q4:3,Q5:61,Q6:58。
Q3,SELECT COUNT(*)
FROM Students
WHERE Programme=’CS’
Q4,SELECT COUNT(*)
FROM Students
WHERE Programme=’CS’ AND Sex=’M’
Q5,SELECT AVG(Grade Ave.)
FROM Students
WHERE Programme=’CS’
Q6,SELECT AVG(Grade Ave.)
FROM Students
WHERE Programme=’CS’ AND Sex=’M’
所有操作都是在一个足够大的元组集合里考虑的,因此这些操作是允许的。然而,通过组合上面的四个结果,我们能够计算出Carol的平均成绩是:4*61-3*58=70。
也许你会觉得我们在这个例子中是侥幸的,而且只能创建这样的查询集合,因为Carol是计算机科学系中唯一的女生。现在我们将向读者说明,如何用一种系统的方法组织一次攻击。首先,我们需要一个跟踪者。
定义 一个允许对单个元组追踪信息的查询谓词T被称为对那个元组的单个追踪者(individual tracker)。一个通用追踪者(general tracker)是指一个谓词,它可以使用任何不允许的查询来找到答案。
假设T是一个通用追踪者而R是一个谓词,它唯一标识了我们想要获取的元组r。在我们的例子中,这个谓词是Name=‘Carol’。我们使用谓词R∨T和R∨NOT(T)对数据库做两条查询。我们的目标r是这两条查询都使用的唯一元组。为保证这两条查询都是被允许的,我们选择T以便查询集合和它的补集都足够大,这样查询操作才被允许。对整个数据库的最后一个查询给我们所有的数据来完成攻击。在我们的例子中:
Sex=’F’ AND Programme=’CS’
是一个对Carol的个体追踪者,而Programme=’MIS’是很多通用追踪者中之一。我们继续以下查询:
Q7,SELECT SUM(Units)
FROM Students
WHERE Name=’Carol’ OR Programme=’MIS’
Q8,SELECT SUM(Units)
FROM Students
WHERE Name=’Carol’ OR NOT (Programme=’MIS’)
Q9,SELECT SUM(Units)
FROM Students
并得到Q7:75,Q8:77,以及Q9:136。因此Carol一定已经取得了(75+77)-136=16个学分。经验表明:几乎所有的统计数据库都有一个通用追踪者。
14.4.3 对策关于统计推断攻击的分析已经在早期的关于数据库安全的文献里介绍了很多。其后,研究者们把他们的注意力转移到其他领域,并不是因为已经找到并且实现了完全安全的解决方法,而是因为他们不得不承认在防止推断攻击的对策上他们是心有余而力不足。由于这些局限,你还能对推断问题实际做点什么呢?
首先,你很明显地应该保护好(suppress,镇压,抑制,查禁,使止住,不让人知道,隐匿抑制,忍住; 隐瞒(证据等),不容许存在)敏感信息。这意味着你知道哪些信息是敏感的,哪些是最可能被攻击的,以及这些信息可以怎样被导出。至少,你应该在给出一个统计查询的结果前先检查该查询集合的大小。
其次,你可以伪装数据。你可以随机交换数据库中的表项,这样尽管对于统计查询仍然是对的,但单个查询却会给出错误的结果。还有另一种方法,你可以在查询结果中随机的加入少量的干扰数据,这样返回值就接近于真实值但不完全相等。这些技术的一个缺点是,它们与准确性和可用性相冲突。你可不想在医学数据库中随机交换数据!
通过对设计数据库机制(schema,模式)的选取,可以减少一些聚集问题(参见文献[90])。对数据库结构的静态分析(static analysis)可以发现属性间的敏感关系。然后将那样的属性分别放在不同的表中。只能访问一个表的用户不能再将这些属性联系起来。当然,一个可以对所有相关的表进行访问的用户仍然可以得到相关信息,但作为数据库管理员,在分配优先权时可以更准确,以避免出现上述情况。在我们的例子中,名字和学习成绩之间的关系是敏感的。我们把表Students分成两个表,如图14.6所示,由学生的ID(identity number)连接。
现在将第一个表的安全级设置得足够高,因而只有那些被授权的用户才能把名字和学习成绩联系起来。
最后,可以看到,单个查询并不会引起太多的推断问题,而对几个查询的巧妙组合可能会引起问题。因而你需要追踪用户所知道的内容,或许这可以提供最好的安全性,但这也是最昂贵的选择。你可以在一个审计日志里记录用户的行为,如果检测到一个可疑的查询序列时,你执行查询分析(query analysis)来进行干预。当然,你首先得知道哪些查询会形成可疑的查询序列。要得到更好的保护措施,你的查询分析还将必须考虑两个用户,或一组用户他们同时知道些什么。
图14.6 对学生数据的分离的表
14.5 结合操作系统的完整性
当你从操作系统的角度去看数据库时,你会看见大量的拥有数据表项的操作系统进程和内存资源。从很多方面看,数据库管理系统和操作系统有很多相似的职责。其中之一就是防止用户间的相互干扰以及用户和数据库管理系统间的干扰。
如果你不想浪费精力,就应该把这些任务交给操作系统。在这种方式里,DBMS作为操作系统进程的一个集合(的一组进程)运行。这里有通用的数据库管理任务系统进程,而每个数据库用户被映射为单独的操作系统进程(见图14.7)。现在,操作系统可以区分不同的用户,如果用户要在它自己的文件里存储每一个数据库对象,那么操作系统可以执行所有的访问控制。DBMS只需要把用户的查询翻译成操作系统能够理解的操作。
给每个数据库用户分配一个单独的操作系统进程既浪费内存资源也不能满足众多用户的需求。因此,你需要这样的操作系统进程,它可以处理多个用户的数据库请求(见图14.8)。你节约了内存,但访问控制的责任现在却落在了DBMS上。数据库对象的存贮也有同样的问题。如果对象很小,那么给每个对象分配一个单独的文件很浪费。一旦操作系统失去了对数据库用户的访问控制功能,你就可以自由地在一个操作系统文件中收集一些数据库对象的资料。
图14.7 由操作系统隔离数据库用户
进一步的阅读如果读者需要复习关系数据库模型,则可以参考文献[37]。关于数据库安全方面的很多实践材料都收集在文献[25]中。文献[39]是一本较早关于数据库安全方面的书,但它仍然很有用。它在统计数据库安全方面有很好的参考价值。关于统计数据库安全方面更有用的一本书是文献[90]。
所有主要的数据库方面的销售商都有主页,在其主页上有关于它们产品的信息和对数据库安全很好的介绍。通过了C2评定的数据库销售商的主页如下:
http://www.informix.com
http://www.oracle.com
http://www.sybase.com
图14.8 由DBMS隔离数据库用户
练习题练习14.1 假设有个账户数据库,有记录(用户名,账号,余额,信用度)和使用者(用户,职员,管理者)。定义一个访问结构,例如通过视图,实现:
用户可以读取他们自己的账户,
职员可以读取除了信用度以外的所有属性,还可以修改所有账户的余额,
管理者可以创建一个新纪录,读取所有属性,以及为所有账户更新信用度。
练习14.2 考虑一个有学生纪录的数据库,它包含学生的姓名以及所有课程的成绩。通过视图可以看到所有学生还有谁的课程论文没有成绩。这个视图可以定义为WITH CHECK OPTION吗?对决定是否使用CHECK OPTION的通用标准给出你的建议。
练习14.3 在Students关系(见图14.4)上的所有统计查询,其查询集合至少要有三个元组,只有对属性Grade Ave.的AVG查询例外。找出一个新的通用追踪者并对Homer的平均成绩构造一次追踪攻击。
练习14.4 在数据库安全问题里,你已经见到了对应用层安全控制的例子。这种方法有什么问题?
练习14.5 考虑这样一个数据库,聚集操作被放在了比导出的数据项更高的敏感级别上。一个有权访问数据项的用户,它会潜在地通过单独对数据项的访问来计算聚集函数。你怎样防止这类攻击?
练习14.6 给你一个数据库,可以对表中的每一行单独定义访问权限。访问控制将使用和操作系统一样的安全机制。如果数据库对象存储在操作系统文件中,这种设计会有什么样的后果?(作为准绳,考虑一个为每个文件使用100字节的管理数据的操作系统,和有一千万个纪录的数据库。)这是一个可行的设计方案吗?
第15章 多级安全数据库在本章中,我们将把注意力全部放在数据库目录(entries)的控制上。多级安全政策控制了对数据库的访问。与操作系统中的MLS比较,你现在必须同时解决机密性问题和完整性问题。当数据库的完整特性得以维护时,应避免有其他通过隐秘通道的方法变相访问。如果你采取隐藏高层数据来防止这种情况,那么你就不得不求助于多实例化来保持关系数据库中最基本的完整性特性。如果你无需隐藏高层数据,那么保持完整性就会更简单,但那样的话,你就必须找到一种方法,把高层数据插入数据库的同时不会产生隐秘通道。
目标:
在数据库系统环境中应用多级安全。
关于机密性和完整性之间潜在的冲突分析。
比较两种处理这种冲突的方法。
比较两种实现多级数据库安全的方法。
15.1 基本原理想象在某种情况下,数据库中的元素被认为是如此敏感以至于只有用最行之有效的安全方法才可以保护。一种想法认为在多层安全数据库中,强制访问控制方式将是这个挑战的解决方法。于是大量的精力花在了使强制访问控制方式与关系数据库系统相适应的研究上。SeaView(Secure data VIEW)项目[40]发布了一种多级安全关系数据库管理系统的原型(MLS-RDBMS)。
一种更严格的观点认为,从操作系统安全中引出的概念被用在了错误的抽象层,并指出了一些实例,其中的机密性规则让我们无法阻止完整性被破坏。你在本章结束部分和第16章中会看到这种问题,以及在安全性和一致性之间达成实用的协议(trade-off)的一些例子。
主要数据库销售商们都有他们自己对多级数据库安全支持的版本,而且都通过了桔皮书的B1子类认证。尽管如此,他们还是会告诉你,并没有很多用户使用DBMS的MAC特性,同时还告诫你在决定这样做之前要看到其长远性和艰苦性。
15.2 关系数据库中的MAC
我们来对4.2节中的Bell_LaPadula模型的强制访问控制的策略做一个简短的回顾。简单起见,我们对一个主体的默认许可证和当前许可证不做区分。在BLP模型中的实体是:
一个主体的集合S,即数据库系统的用户;
一个对象的集合O,即各种数据库、基本关系、导出关系、元组、域;
一个安全表的偏序排列(L,≤),习惯上叫做访问级别(访问类别,access classes)。
函数 fs,S → L 表示对每个主体的一个访问级别,函数 fo,O → L 表示对每个对象的一个访问级别。两条强制访问控制策略说明,考虑到访问级别,信息只允许向上流动。
简单规则(没有读出(不向上读,no read-up)):主体s 可以查询对象o,当且仅当s 的访问级高于o,即 fo (o) ≤ fs (s)。
星规则(没有写入(不向下写,no write-down)),主体s 可以修改对象o,当且仅当s 的访问级低于o,即 fs (s) ≤ fo (o)。
这些政策应用在DBMS的数据处理操作中,得到直接的信息流。信息也可以从隐蔽通道流出。如果用户事先不知道访问要求是不合法的而且注定失败的话,拒绝该要求就会建立一个隐秘通道。
15.2.1 对象标识(标记/标签,labelling)
按照SeaView,我们将描述在最细粒度级上的多级数据库安全。在数据库中的每一项都有自己的标识,它可以是一个数据元素、一个元组、一个关系或是一个数据库。这是一个非常灵活的策略,即使在一个元组中也包含了不同安全标识的元素。设R是一个有 n 维属性的多级关系(multi-level relation)。现在,在形式为(v1,c1,v2,c2,...,v n,c n,tc)的R元组中,ci 是第i个域的标识,tc是元组的标识。用户并不知道安全标识的存在。它们是DBMS的内部信息,在其相关的(引用,reference)监视器之后用来管理访问要求:
数据库标识:用于决定是否允许用户在数据库中寻址访问关系。
关系标识:用于决定是否允许用户在数据库中寻址访问元组。
元组标识:用于决定是否允许用户访问元组中的所有元素。
元素标识:用于决定是否允许用户访问元素。
在前面的章节里,我们要求一个安全策略是:
完全的:数据库中所有的域均被保护,而且一致的:没有冲突的数据项访问管理规则。
如果数据库中的所有项都有它们自己的安全标识,则安全标识的设定就是完全的。因此检查完全性是一项如此简单的工作。
15.2.2 一致的寻址
显然,我们必须考虑如何给数据库中不同的项分配安全标识,否则,很容易导致标识的不一致。要弄清楚你定义第一个一致性规则集合的理由,必须记住在寻址一个数据项时,要说明以下各项:
一个数据库 D ;
数据库 D 中的一个关系 R ;
关系 R 中的元组 r 的主关键字;
属性 i,在元组 r 中识别元素 ri。
要得到元素 ri,必须满足下列条件:
fo (D) ≤ fo (R) ≤ fo (ri)
否则,你将不能访问你被授权可见的元素。在我们的习惯中,可以访问元组 r 的用户也有权访问该元组的所有元素。因此,我们要求所有的属性 i:
fo (ri) ≤ fo (r)
元素标识要求我们重新描述关系数据库模型中的内部完整性规则(14.2.2节)。寻址需要主关键字,因此我们有如下规则:
多层实体完整性规则:基本关系中的主关键字中任何成分或子集都不能为空。其所有的组合都有相同的访问级别。在基本关系中,元组中所有其他数据值的访问级别决定了该元组的主关键字的访问级别。
外关键字把本表和另一个表联系起来。如果一个用户知道这个联系,那么该用户就应该遵守下面的规则。
多级相关(引用,reference)完整性规则:外关键字引用的元组必须存在。外关键字的访问级别决定了相应的主关键字的访问级别。
15.2.3 可见数据
下面的规则集合解释了一个数据库对有着不同安全标识的用户看起来是怎样的。
对用户s 来说,它可以在关系R 中寻址元组,我们要求fs (s)≥fo (R)。如果fs (s)<fo (R),那么整个关系R 对用户s 来说就是不可见的。
如果数据元素ri 对用户s 是可见的,我们必须有 fs (s)≥fo (ri)。如果 fs (s)<fo (ri) 并且ri 是元组r 的主关键字的一部分,那么整个元组就是不可见的。如果 fs (s)<fo (ri) 对任何其他数据项都成立,那么这个属性是不可见的,而且会出现空值。
如果基本关系R 的元组r 对用户s 是可见的,那么满足 fs (s′) ≥fs (s) 的用户s′将看到元组r′,它的每个属性和元组r 一样,而且元组r 中没有空值。
我们将通过图15.1来展示这些规则。这里有两个访问级别:Unclassified(U)和Confidential(C)。这里只为引用给出数据元素的安全标识。如前所述,它们对用户是不可见的。有访问标识C 的主体可以看到整个表(即使没有安全标识);有访问标识U 的主体只能看到非机密数据,如在图15.2中所示的那样。
15.2.4 导出关系
现在我们再来看导出关系标识的规则,例如视图、快照,或是查询结果。导出关系是通过评估(evaluating)它的定义来计算的。信息只能往上流,因此在导出关系计算中,用到的所有数据的访问级别必须比其结果的访问级别低。对于视图,这条规则是说:
规则 视图的访问级别决定了所有在视图定义中使用的关系的访问级别。
图15.1 对于主键值Flight的关系Booking
图15.2 从图15.1中对未列为密件的用户的访问的非机密性数据表
读者会注意到,这个模型和统计数据库安全的模型是相反的,在统计数据库安全模型中聚集查询的结果没有单个数据项敏感。
当用户s 计算一个导出关系时,该导出关系的访问级别应该比该用户的访问级别低。否则,用户将看不到该计算的结果。
这里有两种方法来建立用户将看到的导出关系。DBMS可以先计算出独立于用户的访问级别的导出关系,然后,对该导出关系应用安全检查。另一种方法是,在计算导出关系的同时DBMS已经考虑了用户的访问级别。那样结果中就不会有用户不允许看到的数据。不论采取哪种方法,我们都应该得到一样的结果,正如图15.3所示。为详细描述对视图要求的交换性,我们有:
规则 一个给定访问级别的视图实例和评估视图定义结果的访问级别是一样的。
图15.3 导出关系的一致的评估
15.2.5 自主访问控制
在关系数据库中,通过视图和视图的访问权限来最好地定义自主安全策略。事实上,一个只对视图而不对元素级的SQL数据处理操作授予优先级的安全策略,非常符合Clark Wilson安全模型。如何让这样的自主访问控制(DAC)策略和强制访问控制一起工作呢?
如果我们想让一个“低级别”的用户看到视图,那么在15.2.4节中的规则要求,视图中的所有数据项同样被标识为低级别的。对于MAC,这些数据项现在对低级别的用户同样可见,这样,很明显地违反了DAC策略的意图。为解决这个问题,我们介绍一种引用访问模式(reference access mode)来获取对关系的间接访问(indirect access)。那时用户需要一个对视图的优先权,和对在所有基本关系上的引用模式的访问权限。
15.2.6 干净数据强制访问控制禁止写入(向下写)。这是一个非常严格的策略,和平时观察到的信息的敏感性会随时间推移逐渐降低情况不一致。例如,在还没向大众发布前,公司决议是机密的。在很多国家,保密的政府文件要过几十年才会解密。我们要求数据库安全的准确性,不需要在数据分类上有不必要的严格。
因此,有这样一种执行控制写入(controlled write down)的操作,即清洁(sanitisation)操作。清洁操作可由可信的主体来执行,该主体的读取级别严格地决定了他的写入级别。或者,清洁操作可由计算导出关系来执行。在这种情况下,导出关系的级别比导出操作中的数据级别低。为了防止分级过细,一旦安全策略允许,DBMS就会定时应用时间相关的干净原则来降级数据。
15.3 多重实例
在我们多级安全关系数据库模型中,一个“低级别”用户是不知道“高级”数据项的存在的。因而,低级用户可能偶尔会试图去更新包含了高级数据的域。在我们的例子中,没分级的用户可能试图通过输入没有分级的数值来更新GR555航班中缺少的域,如“Dest=N.Y.,和“Seats=0”。DBMS该如何反应?我们有三种选择。
拒绝执行更新:然而,这样做会泄露这种信息,即这里包含了一个“高级”的数据值。
覆盖旧值:这样做虽然没有泄露此处有“高级”数据值存在的信息,但却破坏了数据。
执行更新同时保持旧值:这就是所谓的多重实例。
从安全角度来说第一种选择没有吸引力。如果我们采取强制访问控制来处理所有的问题,那么我们不应该接受如此明显的隐秘通道。第二种选择让那些“高级”用户陷入了这种困境,即低级用户可能在不知情的情况下破坏了高级数据。在评价了头两种选择后,我们来看看多重实例。正如在图15.4中的表所示的例子中,关系Bookings就是那样被更新的。一个机密级的用户能看到在图15.5中显示的数据。
“多重实例”这个词显示了对同一个主关键字可能会有好几个元组。这在图15.5中可以清楚地看到,对主关键字GR555有两个表项,明显违背了主关键字的定义。在我们热心调整强安全策略时,至少乍一看,我们已经破坏了关系数据库模型最基本的东西。
图15.4 在图15.1中给出的表数据的更新版本
图15.5 对机密性用户访问的数据
为了使安全要求和关系数据库的基础相合,我们申明一个元组中所有域的访问级别是主关键字的一部分。扩充的主关键字仍然满足元组的唯一性。在一个关系中寻址(访问)元组,除原本的主关键字外,你还必须定义一个访问级别向量。普通的表是二维结构,你可以把一个多级表看作带有纵坐标的三维结构:
(原来的)主关键字属性访问级别下面的规则来自文献[40],它保证了在多重实例化的关系中数据的一致性恢复:
多重实例的完整性 如果在基本关系中两个元组有相同的主关键字,而且对某些属性来说不同表项有相同的访问级别,那么这属性的数据值也相同。如果在基本关系中两个元组有相同的主关键字,而且如果有一些属性,其各自的表项有不同的访问级别,那么这些属性的值可能会不同,这些值(和访问级别)的任何组合会在关系中又以元组的形式给出。
该规则的第一部分在扩展的关键字空间里保持了一定的顺序。这样,在这个三维结构中每一点最多只有一个值代表一个多级关系。规则的第二部分使我们确信只需一个基本操作就可以从多重实例化的关系中恢复数据。假设用户要求扩展的主关键字中的Dest和Seats的值为:Flight=GR555,FL_Class=U,De_Class=U,Fl_Class=C。
碰巧“Dest=N.Y.”和“Seats=11”相应的值都存在,但却在两个不同的元组中,其主关键字是GR555。DBMS在处理这样的要求时不得不对给出的主关键字检查所有的元组。因此多重实例完整性规则说明所有可以通过一个扩展的主关键字寻址的值的组合实际上都储存在关系里。在本例中,全多重实例化关系成为图15.6中所显示的那样。我们查询的结果可以在这张表的倒数第二行找到。无可否认,该完整性规则使更新相当繁琐并使多重实例化关系表的大小急剧膨胀。
图15.6 显示键值GR555的所有组合的更新数据表
15.4 低端插入当我们把强制访问控制加入关系数据库模型中时,我们决定把安全标识当作DBMS的内部信息。由于这个决定,DBMS不仅把敏感数据和未授权的用户隔离,而且甚至隐藏敏感数据的存在。这样,一方面提供了更好的安全性,另一方面引出了隐秘通道。要关闭隐秘通道,我们不得不采取多重实例,找一条整合多重实例和关系数据库模型的路子。
在关系数据库的基本的完整性特性上,我们已经勉强地避免了致命的冲突,但是麻烦才刚刚开始。如果考虑在14.2.2节中讨论的特定应用的完整性约束,那会怎么样呢?为适合MAC模型,每条完整性约束必须用访问类标识。可以应用以下的一致性规则。
规则 由完整性约束的访问级别必须比应用约束的关系的访问级别高。
应该记住,引进多重实例是因为我们不想泄露敏感数据项的存在。当一条完整性约束被标识到比它引用的数据项要高的层次上时,又会出现相同的问题。如果一个低层的主体想修改一个低层的数据项,而该数据项却受高层的约束,那么DBMS即(既)可能允许主体对数据项做修改并潜在地违反完整性约束,又可能为了维护一致性而被迫泄露高层约束的存在。在这时候,就再没有什么诀窍可以用来避免这个陷阱了。
数据库描绘了外界的事实,而多重实例化数据库却给这些外界事实带来了不确定性。同一个外部实体存在着不同的项。一个数据库对于世界给出了不同的甚至可能是不一致的视图有什么作用呢?多久才有这样的合理的地方来保护高级数据项不因为在相同的位置上放了带有掩饰内容的低级数据项而受偶然的干扰?更进一步,当掩饰内容不能与高级数据项的相关方面相匹配时,就仍然很可能违反一致性。
所有这些问题都是由决定隐藏敏感数据的存在而产生的。你可以不用隐藏数据项或约束条件的存在而只保护它们的内容,来获得多级数据库安全。在这样的情况下,暴露其存在性不会产生非法的信息流,因为无论怎样我们已经决定把这信息给所有主体了。我们将再一次给每个数据项和关系(尽管不给元组)附上安全标识,但是使这些标识对每个有权看到此关系的用户都可见。在这个模型中,现在一个没有保密级别的用户看到了如图15.1的关系,如同图15.7给出的一样。
让我们回到迫使我们使用多重实例的要求上。一个无保密级别的用户试图通过输入没有保密级别的值“Dest = N.Y.”和“Seats = 0”,更新航班GR555的空域。现在这个用户可被明确地告知不要乱动机密数据,关系保持不变,也没有必要改变关系的主关键字。
图15.7 对机密性用户访问的数据修改视图
还剩下一个问题没有解决。如何将数据加入关系又不会产生非法的信息流?这个问题的答案将同样允许我们修正本例中的异常。一个没有保密级别的用户不需要知道主关键字也能确信有保密元组存在,意外地违反引用的完整性特性仍是有可能的。
要插入数据到数据库中,我们建议低端插入策略,该策略最早由SWORD DBMS开始采用(见文献[161])。有一个访问级别为“系统低”的主体创建者,它的任务是在数据库中创建新的项,只有它被授权这样做。要创建高级的数据项,创建者必须首先创建这个数据项并在里面存储“占位符”(placeholder)信息。由此每个人都知道这个数据项的存在。高级主体还不能对这个数据项写。然后,创建者把这个数据项的类别设置为“高”。这样一来,每个人都知道这个数据项的保密级别。现在,高级用户可以将秘密信息写入数据项了,而此数据项不能被低密级的主体访问。
要在关系中创建一个包含低级数据的高级元组,我们将关系分成不同的部分是对的。在我们的例子中,创建者建立了一张新的没有密级的表All_bookings,表中包含有另外两个关系U_Bookings和C_Bookings的名字和访问级别,其中关系U_Bookings包含所有来自Bookings的表项,其主关键字没有保密;而关系C_Bookings包含了所有表项,其关键字是保密的(见图15.8)。
一个没有保密级别的用户知道关系C_Bookings的存在但只能访问关系U_Bookings。
我们同样可以把低端插入的方法应用到特定应用的完整性约束上,隐藏约束条件的内容,但不隐藏它们的存在。低级主体试图修改低级数据项,而该数据项现在服从高级约束时,应该对约束条件发出警告,并防止执行修改操作。
在SeaView的方法里,安全标识成为主关键字的一部分。这会导致关键字空间的严重扩张,改变诸如像update和select数据库操作的过程。通过低端插入就可以创建多级元组,而不需要多重实例和相应的关键字空间和关系表的扩张。当然,低端插入不是没有缺点。所有的数据项必须通过低级主体来创建和正确地设定级别。不是所有的安全政策都和这样的级别设定过程相一致。另外,高级主体的工作在某种程度上会被阻止,因为只要它们创建一个新的数据项,就需要低级别的帮助者。
15.5 实现方面的问题
这一章使你对多级安全关系数据库系统的理论有了初步认识。随着理论研究的深入,读者会在16章中看到其他方面的问题。现在我们回到MLS-RDBMS的实践中。要实现这样的系统有两种可供选择的方法。
第一种选择是把数据库系统看成是运行在多级安全操作系统上的一种服务。通过操作系统的相关监视来加强强制访问控制MAC。在这种策略中,操作系统必须处理单级主体和客体。
图15.8 插入低存储策略的示例
对每个访问级别都有一个独立的单级DBMS进程运行。
多级关系作为单级操作系统文件的汇总存储。
DBMS不得不使用由操作系统支持的访问级别的偏序。
这种操作模式是用来达到B1级的系统安全级别的。只要我们考虑强制访问控制MAC时,DBMS不是可信计算基TCB(Trusted Computing Base)的一部分。它也同样不是一种很有效的模式,因为我们通常走在很多由DBMS提供的优化技术的前面。而且,对表的访问可以被翻译成对文件的多级访问操作。在这种模式下,DBMS的每个实例只能看见它们自己这级或更低级的数据。因此,它不能检测对较高访问级别的元组实体完整性的违反情况,此时,会自动产生多重实例。
在第二种方法中,运行一个单独的DBMS进程作为可对所有访问级别上的数据进行访问的可信主体。现在,DBMS 包含了相关的监视程序来实施 MAC。因为DBMS可以看到整个数据库,它可以检测所有违反实体完整性特性的操作。所以,当一个低级别用户不小心想修改一个高级的数据项时,我们可以决定如何应对。DBMS可以:
继续更新并多重实例化数据项,或是拒绝更新并在审计日志里记录这一事件。
在第二种情况里,DBMS监视隐秘通道,而不是立即阻断它。一个单独的多级DBMS更有效,但比第一种方法提供的保证更少。现在DBMS是可信计算基TCB的一部分,安全性依赖于一个大规模的复杂性高的软件系统。这个解决方法不要求一个基于多级安全的操作系统。一个特殊的用户可以拥有组成数据库的所有文件,即“Database”,自主访问控制DAC的策略足以阻止其他操作系统用户对数据库的访问操作。
前面已经提到过审计日志了,我们不应忘记审计数据是另一个在不同安全级之间的潜在的信息通道。因此,审计记录也必须有标识。审计选项(Audit options)定义哪些事件被记录。一个审计记录的安全级必须和用户执行触发审计事件操作的级别一样,而不是定义各个审计选项的用户的级别。
即使当你选择多级安全数据库,你仍然可以查询,而不管多级元组带来的复杂性是否真是必要的,或者是否真是麻烦。在一个设计得当的数据库框架(模式,schema)里,单级元组应该足够的多。关系仍然可以在不同安全级别上包含(包含在不同安全级别上的)元组,而多重实例化的要求仍然存在。然而,元组的多重实例化比起数据项的多重实例化来要容易管理得多,这种版本可以在商用的MLS-RDBMS中找到。
进一步的阅读
SeaView项目的安全模型在文献[40]和[91]中有详细的描述。在文献[25]中同样包含了这个模型。低端插入策略在文献[161]中讲述。关于协调多重实例和完整性的最好的方法,以及关于掩饰内容技术的使用,在文献[70,71]中有更深入的讨论。你可以从80年代末、90年代初的安全会议的学报上找到更多关于多级数据库安全的研究报告。商用B1标准的多级安全数据库产品有:
INFORMIX-OnLine/Secure 4.1和5.0 可以从 http://www.informix.com 上找到
Trusted Oracle 7 可以从 http://www.oracle.com 上找到
Sybase Secure SQL Server 11.0.6 版本可以从 http://www.sybase.com 上找到各自的证书同样被压缩在源码里,特别是在必须使用的结构里,用以达到B1安全。
练习题练习 15.1 为SQL的操作:SELECT、UPDATE、INSERT、DELETE 和 CREATE 定义强制访问控制的规则。
练习 15.2 设 R 是一个有n维属性a1,…,an 的关系。关系R、属性a1,…,an和在关系中的所有数据项都被标识了。说明这些安全标识必须遵守的一致性规则。
练习 15.3 关系 Accounts 有主关键字 Customer_Id 和属性 Name、Balance 和 Rating。有三个访问级别:Unclassified(U)、Confidential(C)和 Secret(S)。DBMS在元素一级使用多重实例。表初始化为空表。执行了以下的修改操作,其中每个域的安全级在方括号中给出。
C01[U] Kane [U] 15K [U] A [U]
C15[S] Hall [S] 300K [S] AA [C]
C23[U] Blake [U] 38K [C] B [C]
C23[U] Blake [U] 9K [U] A [U]
为什么在第二个修改操作中表项“AA [C]”不一致?改正其错误。
在执行完这四个修改操作后,那些表项会保存到数据库中?
当在执行完这四个修改操作后,处于 U、C 和 S 级的用户读取表时将会看到什么内容?
练习 15.4 一个低端插入的 DBMS 将会如何处理在前面练习中提出的请求?
练习 15.5 为关系数据库定义一个安全模型,使用元组作为标识的最小单位。
练习 15.6 多重实例维护了关系数据库模型的实体完整性规则。定义这样一个系统,你可以实施特定应用的完整性规则,这些规则是对位于分类的不同级别的数据项计算得到的,这些计算不会产生隐秘通道。
练习 15.7 在多级安全数据库中,不同安全级的数据项能够满足数据库查询的选取标准。如果其中一些数据项的安全级别比用户发出的查询操作的安全级别还高,DBMS就不能给出正确的结果。讨论存在的可选择的方法,它修改查询操作以便得到一个有效的结果,而敏感数据又不会被暴露(参见文献[77])。
练习 15.8 为实现多重实例化,必须修改对数据库的基本访问操作。为实现低端插入方法,当用户创建一张新表时,必须遵循一系列特定的步骤。在这两种情况下,系统的哪部分应该被修改?哪种解决方案更好,可以达到高的安全保证?
第16章 并发控制和多级安全在本书最初的章节里,你已经知道了安全是有代价的。在前一章,你看到多级安全和数据库完整性之间的冲突。这一章将讨论多级安全和并发控制之间的相互作用。这两个领域都有很强的理论基础,因此我们可以非常精确地在数据库系统的安全和实用性之间把握理论和实践的平衡点。
目标:
理解为什么在并发控制和多级安全之间可能会有冲突。
简要介绍并发控制。
考查两种并发控制的方案,这样当维护多级安全的同时又实现了可串行性,并分析它们各自的缺点。
分析一个对多级安全系统的、非串行的并发控制机制。
16.1 目的当用户在数据库中检索信息时,很少有人能够容忍其较慢的响应速度。因而数据库管理系统必须有足够多的备份,以满足两个或是更多的用户想同时访问同一个数据项的要求。在这种情况下该如何处理?其中一个用户应该等待吗?这将会降低数据库的可用性。比如,考虑一个飞机订票系统,你必须等待读取某个航班信息,仅仅因为另外有个用户在察看该航班信息。
另一方面,让两个用户同时访问同一个数据项可能导致在数据库中存储不一致的信息。
考虑两个事务对同一个银行账户存钱的例子。每个事务读取现在的余额,增加存入的数量,并把总金额写回数据库。假设现在的余额是$85。第一个事务存了$100进帐户,第二个存了$25进帐户,这将会发生什么样的情况呢?
第一个事务读取余额,得到$85。
第二个事务读取余额,得到$85。
第一个事务计算新的余额,$85 + $100 = $185,并将$185写回数据库。
第二个事务计算新的余额,$85 + $25 = $110,并将$110写回数据库。
现在数据库里只包含了$110而那$100就没有了。幸而,会有进一步的数据,即现金支付事故,提示曾有过两个事务:和这些事务有关的数据与账户的余额不一致。而账户的余额同样会与帐户拥有者预期的不一致。数据库保持数据一致性的状态是数据库的基本特性。并发控制就是给用户尽可能多的访问权限,而又不危害数据库的一致性。我们将会简要介绍这一主题,为多级安全并发控制算法做好准备。
16.2 并发控制我们的目标是,让用户假定他们每个程序都是原子执行的,也就是说,在那时刻好像没有其他程序在并发地执行。这种对原子操作的抽象被称为事务。我们将假设那样的事务总是正确的,也就是说,它让数据库保持了一种一致的状态。
一个事务就是一个程序,它使用数据库的操作来处理数据库的表项。对我们来说,最相关的数据库操作有read、write、commit和abort。对于后面部分,我们有如下约定:
ri [x] 表示由事务Ti 对数据项x执行的读操作(read),
wi [x] 表示由事务Ti 对数据项x执行的写操作(write),
ci 表示当事务Ti 成功结束时执行的提交操作(commit),
ai 表示当事务Ti 异常终止时执行的中断操作(abort)。
表达式pi [x]或qi [x]代表由事务Ti 发出的对数据项x的一个操作,可能是读操作(read),也可能是写操作(write)。
我们假设数据库管理系统执行的每个操作都是原子操作。在这一抽象层上,DBMS就表现得像在串行地执行这些操作一样。DBMS通过把属于不同事务的操作交错执行来交错事务(interleaves transactions)。交错事务可能使对数据库的并发访问更加高效。正如你刚才看到的那样,它也是违反一致性的一个潜在根源。
并发控制协调进程的动作,即并行运作,访问共享的数据,因此会潜在地相互干扰。它控制并发事务的交错,给我们一种事务是串行执行的错觉,一个事务接一个事务,好像根本没有交错一样。
定义:并发事务的交错执行被称为是可串行化的,如果它和在数据库上对这些事务执行了一个串行的操作有同样的效果。
可串行化的执行是正确的,因为它们让数据库处于同一状态,如同事务是串行地执行一样。在每个单个事务保持一致性的假设下,事务的任何串行执行也将保持一致性。结论和BLP模型的基本安全定理(4.2节)完全一样。回到我们的例子中来,在两个存款事务的串行执行中,第一个事务读到$85,加上$100,并写回$185。现在第二个事务读到$185,加上$25,最后写入$210。
为实现可串行性,我们应该留意由冲突操作引起的问题。
定义:如果两个操作属于不同的事务,对相同的数据项操作,而且其中至少有一个是写操作(write),则称这两个操作是冲突的。
在我们的例子中,两个事务都去写数据项“余额”,引起由其他事务发出的读和写操作而产生的冲突。
16.2.1 积极的和保守的调度器
DBMS处理并发控制的部分包括一个事务管理器(TM),它处理事务并将对这些事务的操作传递给一个调度器。该调度器决定是否或是什么时候要求数据管理器(DM)去执行这些操作。当调度器从TM处收到一个操作要求,它会执行以下三步操作(它有如下三个数据管理选择):
立即对该操作进行调度。
延迟该操作,晚些时候再重新考虑它。
拒绝该操作。
积极的调度器通过立即调度操作来尽力避免延迟这些操作,放弃对稍候收到的操作重新排序。积极的调度器可能会胶着在这样的境地,即无望完成所有活动的事务的串行执行。(当一个事务已经开始但还没有完成或中止,这样的事务就是活跃的。)这时,调度器必须中止事务并重新开始一个或多个事务(回卷)。积极的调度器实现优化的并发控制。优化的并发控制适合于这种情况,即冲突不是太频繁的发生以至于常常执行回卷。
一个保守的调度器往往会延迟操作,为稍后再收到这些操作并对其重新排序时得到更多的回旋余地。保守的调度器很少可能出现这种情况,它为了产生一个串行的执行而拒绝这些操作。保守的调度器极端情形就是串行地处理事务。在积极的和保守的调度器之间有明显的折中:
积极的调度器避免延迟操作,但冒在以后拒绝这些操作的风险;
保守的调度器通过故意延迟操作来避免拒绝这些操作。
为了避免过于严格,保守的调度器必须在它还没收到操作时尽可能精确地预见这些操作。所需要的主要信息是每个事务的读集(readset)和写集(writeset),也就是说,一个事务将要读的或写的数据项集合。妨碍建立一个非常保守的调度器的原因是,一个给定程序的不同执行会导致事务访问不同的数据项集合。因此,事务必须事先申明它可能读或写的所有数据的集合。这常常会导致事务夸大其读集和写集。如果事务使用高级查询语言和DBMS交互,同样的问题很可能出现。当事务夸大其读集和写集时,调度器会比必要时更加保守,因为在预见到其他操作将不再被调度时,它可能延迟这些操作。
16.2.2 两阶段锁两阶段锁是今天用在商业DBMS产品中最普通的并发控制机制。锁是一种工具,它常常用于同步对共享数据的访问。每个数据项都有一个和它相关的锁。让rli [x] 代表数据项x上的读锁,wli [x] 代表由事务Ti 得到的在x上的写锁。我们用rui [x]和wui [x]代表由Ti 在x上发放的读锁和写锁的操作。同样的,表达式pli [x]和qli [x]代表x要求的对pi [x]和qi [x]操作的锁,对应的开锁操作是pui [x]和qui [x]。
规则 如果由不同的事务发布的两个锁作用在同一个数据项上,则它们发生冲突,其中至少一个是写锁。
更正式的,两个锁pli [x]和qlj [y]发生冲突,如果,,并且操作p和q的类型冲突。调度器必须保证没有把相互冲突的锁授权给不同的事务,以防对数据项的冲突访问。
基本的两阶段锁(2PL)调度器是根据以下的规则,通过控制事务获得和释放锁来管理锁的。
规则1 当调度器从TM那儿接到操作pi [x]时,它会测试pli [x]是否与某个已经设定的qlj [x]锁相冲突。如果是,它会延迟pi [x],强制Ti等待,直到它能够设定它所需要的锁。否则,调度器设置pli [x],然后把pi [x] 送给DM。
规则2 一旦调度器为Ti设置了锁pli [x],起码它不会释放该锁,除非DM说明它已经拥有了对应于操作pi [x]的锁。
规则3 一旦调度器为事务释放了锁,其后它可能不会获得对该事务更多的锁。
规则(1)避免两个事务冲突地并发访问一个数据项。规则(2)对规则(1)进行了补充,确保DM在数据项上是按照调度器提交它们的顺序执行的那些操作。规则(3),即两阶段规则,将每个事务的执行划分成两个阶段:
扩展阶段,在此期间事务获得锁;
收缩阶段,在此期间事务释放锁。
可以看出,2PL把那些属于并发事务的冲突操作按照这些事务一定的串行执行的顺序排列。这个结论是以下定理的基础。
定理 两阶段锁促进了事务以串行的方式交错地执行。
然而,基本的两阶段锁不能防止死锁,你需要另外的机制来解决这个问题。事实上,这就是在我们例子中刚好会发生的问题。事务T1 和T2都对“余额”获得读锁。因此,每个事务都被阻塞,因为它们需要写锁,而该写锁与其他事务拥有的读锁冲突。
16.2.3 多版本时间戳顺序技术多版本时间戳顺序技术是另一种并发控制的机制。多版本数据库存储了每个数据项的多个版本。DBMS对用户隐藏了这一事实。事务被写,不需要指明是哪个特定的版本,DBMS把事务的执行映射到数据项相应的版本上。DBMS在选择事务访问哪个版本的数据项时,会考虑一致性条件。因此,访问同一个数据项的事务可能会与数据项的不同版本打交道,这样增加了导致数据库不一致状态的潜在因素。我们要求,在多版本数据库中事务的交替执行应有以下性质。
定义 在多版本数据库上的事务集合的交替执行是串行化的一个复制(副本),如果它与在只有单个版本数据项的数据库中事务的串行执行一样的话。
多版本时间戳顺序技术(MVTO)是个适合于多版本数据库的并发控制算法。数据项可以有多个版本。我们用x.i代表数据项x的第i个版本。MVTO调度器都使用以下的时间戳,但在调度器分配时间戳的方式上却不相同。
给定每个事务一个唯一的起始时间。没有两个事务会被分配同一个起始时间。起始时间可以从系统时钟导出,也可以从计时器导出,它指的是事务被调度的第一个操作的时间,而不必是事务实际开始的时间。
每个写操作产生一个新的数据项版本,该数据项被盖上写时间戳。写时间戳不一定要与写操作发生的实际时间一致。
每个读操作被设置一个读点(read point)。读的要求(读请求)映射到数据项的一个版本,此数据项在读点之前有着最大的写时间戳。典型地,一个事务的所有读操作使用同一个读点。
数据项的每个版本都有一个读时间戳。一个读操作更新它要读的数据项版本的读时间戳。新的读时间戳是操作当前的读时间戳或读点中最新的一个。再次强调,读时间戳不一定是上一次读版本的时间。
根据定义,数据项版本的写时间戳总是在它读时间戳的前面。
考虑一个MVTO调度算法,它使用事务的起始时间作为所有读操作的读点和所有写操作的写时间戳。将我们银行账户的例子用在多版本数r2[b.1]据库中,可以写出如下操作序列:
操作,start1 r1[b.1] start2 r2[b.1] w1[b.2] w2[b.3]
时钟周期,1 2 3 4 5 6
假设,余额b.1版本的写时间戳为0,或一个更早的值。b.1的读时间戳首先更新为1,即第一个事务的起始时间,然后改为3。b.2版本的写时间戳是1,在我们的例子中它的值为$185。b.3版本的写时间戳是3,它的值是$110。
如这例子所示,如果一个事务读数据项,而随后第二个事务在第一个事务终止之前去写那个数据项的新版本时,问题就会产生了。因而,如果新的写时间戳落是在写操作试图去写的数据项版本的写和读时间戳之间的话,MVTO调度器必须拒绝写操作,中止(异常终止/放弃,abort)发出写请求的事务。更正规地,一个在数据项x上产生了一个新的版本x.new的写操作必须被拒绝,如果存在版本使得:
写时间戳(x.i) < 写时间戳(x.new) < 读时间戳(x.i) 。
用这个原则和这种方式来设置时间戳,我们可以看到MVTO调度器保证了一个版本的串性行。
在我们的例子中,第一个事务试图去写一个新的余额时它将被中止,因为:
写时间戳(b.1) = 0 < 写时间戳(b.2) = 1 < 读时间戳(b.1) = 3。
16.3 MLS并发控制并发控制同步对共享数据的访问。同步性要求某种通信方式。至少某个事务可能被告知它不能再继续进行,因为某个数据项当前不能获得的。这样的消息组成了从阻塞数据项的事务到等待数据项的事务的信息流。多级安全担心非法的信息流。显然,并发控制和多级安全的方向各不相同。我们可能协调这两者的目标,并研究出多级安全并发控制算法吗?
16.3.1 总体观察在标准的MLS模型中,所有的主体和对象都有安全标识。数据库事务和操作的访问级别(安全标识)可以这样决定:
当用户登录时,启动一个进程,并以用户登录时的安全权限(许可证,clearance)运行。
当这个进程启动一个事务时,该事务从进程那儿继承它的访问级别。
当这个事务发出一个操作时,该操作又从事务那儿继承它的访问级别。
现在,回头看15.5节。要使多级安全最强壮,主体必须只能是单级的。对每个需要访问数据库的主体,操作系统必须给出有那个主体的访问级别的DBMS版本的示例。因此,我们不是有一个独立的DBMS,而是有很多不同安全级的DBMS。
图16.1 在MLS环境中的调度
并发控制机制必须维护数据库的全局特性。适用于整个数据库的特性称为全局特性。只适用于数据库子集的特性称为局部特性,例如,在一个特定安全级上的所有项。尽管不同访问级别的单级主体会合作和协调行动来维护全局特性,当安全政策不允许他们交互时,全局特性很难或者说是不可能实现的。如果没有这些安全上的要求,单个调度器会采取两阶段锁来保证可串行性。然而,在我们的安全模型中,调度器是一个具有安全标识主体。
当所有的主体都是单级的,就不可能有全局调度器。
对每个访问级别都有一个单级调度器,它只调度它自己级别上的事务(见图16.1)。
单级调度器必须共同确保全局的可串行性。
现在问题不在安全而在于调度,我们假设已由TCB保证了安全性。我们重新考察开始提出的策略,并调整它们以适应新的要求。BLP安全政策对处理冲突的方法有以下的重要特性。
写低/读高:高级调度器可以看到低级调度器的执行,因此它可以重复那些执行步骤。我们不希望低级调度器看到高级调度器的执行,因此它的决定是独立于高级事务的。
写高/读低:是通过BLP安全政策来禁止的。在这里,安全性限制了冲突的数量,实际上帮助了调度器。
在同一安全级上冲突的访问操作可以通过在那一级上的单级调度器来解决。
我们已经提过两种主要的调度技术,锁和时间戳。考虑下列发生在锁协议里的情况。在h (high)级的事务Th读数据项x;然后在l (low)级的事务Tl(l<h)去写同一数据项x。
锁技术能解决这个冲突吗?
答案取决于由Th设置的读锁的安全级。如果该锁被分为较低级,那么低级的调度器就能看到锁,并能阻塞由Tl发出的写操作。然而,对于高级调度器去写一个低级锁产生的写入(向下写),则与BLP的规则相违反。因此,由Th设置的读锁必须分到高级中去。由此,对不能解决这个冲突的低级调度器是不可见的。
采用MVTO调度器,考虑这样一种情况,高级事务Th在低级事务Tl后开始执行,但是在Tl写数据项x之前Th要读x(在l级)。这就导致冲突,因为x当前版本的读时间戳将会是Th的起始时间,但是新版本的写时间戳将是Tl的起始时间,该时间戳尽管是在读时间戳之后才创建的,但它却早于读时间戳。
MVTO能解决这个冲突吗?
我们再一次面临这样的问题,低级的调度器仍然不知道高级事务的存在,因为由高级调度器写的时间戳必须归到高级中。只有高级的调度器可以解决这类冲突。高级调度器有两个选项可以保证一致性:
延迟高级事务直到它不与任何低级事务冲突(最佳并发控制),或是允许高级事务访问旧版本的数据项,这可能会引发冲突(多版本时间戳排序)。
在第一种情况里,高级事务可能被饿死。在第二种情况里,高级事务可能会对过时的数据进行操作。现在对这两种解决方案进行更详细的阐述。
16.3.2 乐观MLS并发控制
最佳并发控制用于单版本数据库,该数据库里每个数据项只有一个拷贝。允许事务继续执行操作并且结果在提交之前是有效的。如果发现了冲突,回卷就会发生,事务也重新开始执行。这个策略是最佳并发控制的例子之一,因为它使得事务在“希望没有冲突会出现”的情况下执行。在具有相同安全级的调度器控制下事务按三个阶段执行:
读阶段:一个主体获得了他想要的数据项的私有拷贝,并更新这些拷贝;调度器用开始读时间戳(start-read timestamp)标出这个阶段的起点;
有效阶段:调度器设置一个start-validation 时间戳作为事务号(transaction number);调度器检查是否有冲突;如果检测到一个冲突,就回卷并重新开始该事务;
写阶段(很可能没有这个阶段):如果没有冲突,事务提交,它拥有的数据项的私有拷贝变成公共的拷贝。
在一个高级事务的有效阶段,如果以下三个条件同时成立,则检测到冲突。
存在一个数据项,它和低级事务产生了冲突。
在所有低级事务已经完成或中止前,高级事务就开始了它的读阶段。因此,高级的读操作可能对不同状态的数据库进行了访问。
高级事务在一些低级事务已经进入有效阶段后,进入它的有效阶段。否则,它的读操作被保证要在数据库的一致状态下执行。
最佳单级调度器可以检测低级事务的冲突,必要时可以中止和它级别一样的事务。我们要求用系统的低(逻辑)时钟为每个访问级别产生时间戳(唯一的事务号)。每个时钟只产生一个号。单级调度器把start-read时间戳、事务号和事务的读集设为和它同一级,还必须读:
比它低级的事务的写集,
比它低级的事务的有效阶段开始时间,也就是它们的事务号,
完全写(complete-write),低级事务的提交(或中止)时间。
调度算法决定了在h访问级别的事务Th的命运,如下所示[60]:
Let Th be currently in the validation stage.
IF there exists a transaction Tl at access class,l<h,such that:
Transaction-number (Tl)<transaction-number (Th),and
Start-read (Th) < complete-write(Tl),and
Writeset (Tl) ( readset (Th) ( Φ,
THEN roll back and restart Th,
ELSE commit Th.
用一个新的例子来说明MLS调度方法。考虑一个有两个银行账户的客户,c(现金)和d(存款),都被标识为“低级”。银行还有一个客户信贷分类r,被标识为“高级”。如果c和d的余额都超过$1000,则信贷分类应为A。否则,分类应为B。只有高级用户可以修改客户信用度。账户开始初值为c:300,d:800,和r:A。低级事务Tl 从c中转了$200到d,同时高级事务Th正在检查其客户信用度。假设有下列的事件序列:
操作,rl [c] rh [d] rl [d] wl [d] wl [c] startvall cl rh [c] wh [r] startvalh
时钟周期:1 2 3 4 5 6 7 8 9 10
事务Tl将读到c:300,d:800,并修改账户为c:100,d:1000。事务Th在修改之前就读到数据d:800和被Tl修改之后的数据c:100。事务会写入冲突的信贷分类并中止。冲突的三种条件在这里都具备,对这个例子的检查结果如下:
事务号(Tl ) = 6 < 事务号(Th ) = 10 ;
开始读(Th ) = 2 < 结束写(Tl ) = 7 ;
写集(Tl ) ∩ 读集(Th ) = {c};
因此,Th必须被中止。
16.3.3 单级调度器(Single-level Scheduler)的MVTO
只有一级调度器的多版本时间戳排序(MVTO-SS)通过确保这样的推理来避免冲突,高级事务决不能因为稍后低级事务的写操作而失效。这通过强制高级事务去读数据项的陈旧版本来实现,这些版本太过陈旧以至于不会再被较低级的事务访问。通过控制事务的开始时间,你可以控制事务读数据项的哪个版本。通过在过去、在任何活跃的较低级事务开始时间之前,给高级事务一个开始时间,你可以肯定,与活跃的低级事务的任何写操作的冲突都不会发生。在这个调度算法中,事务的开始时间戳可能会和它的实际开始时间不同。现在,不仅开始时间,而且读时间戳和写时间戳也不必和其实际发生的时间一致。
对每个访问级别,单级调度器给事务分配和它级别相同的时间戳。当一个事务开始时,它收到一个迟签日期的起始时间,这个位置在所有处于较低级的、活跃的事务之前,在所有和它级别相同且已经开始的事务之后。单级调度器对比它高级的事务一无所知。所以,事务应放在:
恰好在最早的事务之前,这些事务来自严格意义上的较低级别,
恰好在较高级别的事务(包括它自身)之后。
这种调度算法的需求可以通过包含三个部分的顺序戳(orderstamps)来得到满足:
时间戳从系统的低时钟导出;
事务的访问级别;
序列号。
如果两个顺序戳有相同的时间戳,那么有较高访问级别的顺序戳是先开始的那个。如果两个顺序戳有相同的时间戳和访问级别,那么序列号小的那个顺序戳是先开始的那个。序列号要解决这种情况,即不止一个高级事务具有相同迟签日期的起始时间。所以顺序戳产生了一个事务的完全排序。为计算顺序戳,每个调度器需要:
在自身级别里活跃的事务的最早的时间戳(ETS),
最早的较低级别时间戳(ELTS)。
一个新事务的开始时间是这样计算的。具有事务访问级别的调度器在较低级别上检查ETS和ELTS,同时在进程里修改它自己的ELTS,从最低的访问级别开始并往上增加访问级别。修改ELTS必须是一个原子事务。如果这个ELTS存在,它会被用来做新事务的时间戳,否则就用当前的时钟值。然后调度器设置访问级别和序列号以创建顺序戳,指明事务的开始时间。
数据项新版本的写时间戳是事务写数据项的开始时间。事务的读取点是它的开始时间。因此,对于顺序戳,事务中的所有操作都发生在同一时间。
在16.3.2节的例子中,事务Tl的开始时间是(1:l:1),事务Th得到的起始时间是(1:h:1),尽管它的第一个操作发生在时钟周期2。假设c.1和d.1的写时间戳都是(0:l:1)。当Tl修改这两个数据项时,新版本c.2和d.2会得到写时间戳(1:l:1)。所以,Th将只能读到旧版本c.1和d.1,并根据旧值c:300,d:800给出信用度A。图16.2说明了在一个事务中的所有操作是如何映射到同一个顺序戳的。图上的点表示操作被调度执行的时间。箭头指向和操作相关的时间戳。为了便于说明,我们为顺序戳(1:h:1)和(1:l:1)分别画了时间线。
图16.2 在MVTO-SS中分派的印时戳(time stamp)
16.3.4 MVTO-SS的正确性
并发控制算法总是应该仔细分析,特别是在那些安全考虑可能带来副作用的时候。所以,我们将为MVTO-SS找到可串行化定理的证明,下面的论断在文献[78]或[60]中都有阐述。
定理 由MVTO-SS产生的调度器是单拷贝可串行的。它们和一个依顺序戳顺序串行执行是等同的。
我们必须指出的是在MVTO-SS调度中的每个读取操作读取到的都是数据项的同一版本,就像按顺序戳的串行执行一样。我们可以把这一结论应用到多版本的数据库中,并且仍可以证明,它和在单版本数据库中的一个串行执行是一样的。在多版本数据库的事务的串行执行中,事务在它的读取点前总是读到数据项最新版本的值。因而在串行调度中,单版本或多版本数据库之间并没有区别。
假设在由顺序戳定义的串行调度中,事务T1读到的数据项x的版本值是由事务T2写的。现在我们需要说明的是,在MVTO-SS调度中T1也读到了由事务T2写的版本。我们假设情况正好相反,即T1读到的x的版本是由另一个事务T3所写。T1只能读到在T1之前调度的事务的数据项。因此,我们必须考察下面三种情况:
T2 < T3 < T1,T3在T1之前T2之后被调度。但那样的话,T1就能同样在串行调度中读到由T3写的x的版本。这与假设在该串行调度中T1从T2读到x是相矛盾的。
T3 < T2 < T1,T3在T2之前而T2在T1之前被调度。如果T1 从T3读到x,那说明当执行T1的读操作时T2还没有改写x。因为T1可以读到T2 写的数据项,所以T1的访问级别必须比T2 高。因为T1和T2在同一时间激活而T1在T2 之后被调度,所以T1不能在一个严格的较高级别上。因此,T1和T2 还有x都必须在同一级别上。当T1读到的x的版本是由T3所写,这个版本的读时间戳就要被修改为T1的顺序戳。当T2稍后试图写x的一个新版本时,该新版本的写时间戳将是T2的顺序戳,而且写时间戳会在由T3写的x版本的写时间戳和读时间戳之间。这就是一个冲突,MVTO-SS会中止T2 。所以,T2不在串行调度中,而T1也读不到由T2写的x的版本。
T3 < T1,T2不被调度:在交错调度中T1从T3读到x,因为在T1读x时T2还没有被调度。在串行调度中,当T1读到T2 版本的x,T2会收到一个比T1早的顺序戳。仅当T2严格地处在比T1高的访问级别上,MVTO-SS才这样做。另一方面,由于T1要读由T2 改写的数据项,T1的访问级别必须比T2 的高。这里再一次假设在交错调度中T1从T3读x已经引起了矛盾。
16.4 非串行的并发控制可串行性,还有多级控制,已经迫使我们接受了很多并不需要的并发控制算法。
通过最佳MLS调度,总是高级事务因为在不同的访问级别间的冲突而失败。
MVTO-SS调度器中,如果在不同的访问级别间有冲突,高级事务总是只能读取数据项较老的版本。
在多级安全、一致性(可串行性)和可用性之间有很明显的牵制性(见图16.3)。到目前为止,我们一直将注意力放在安全性和一致性上,而以降低对高级用户的可用性为代价。现在我们将考察针对多级安全数据库的非串行的并发控制策略,它没有将高级用户置于如此不利的地位。全局的可串行性可以由下面的更宽松的多版本正确性特性代替:
单级可串行性:在单独的访问级别中事务的交错执行必须是串行的。实际上,在单一访问级别中我们可以应用任意完整性约束,而和多级安全没有干扰。
单级读取一致性:当事务从较低访问级别数据项中读取和完整性约束相关的数据项时,数据项必须是从一致性集合里读到的。
进化性:一旦一个事务读取了数据项的一个特定版本,那么任何依赖于那个事务的其他事务就不能再读取该数据项的早期版本了。
图16.3 数据库的性质(properties)
下面在文献[9]中提到的调度算法是两阶段锁和修改的MVTO算法的结合。它作为这样的调度算法的一个例子,这种调度算法为高级用户提供了更多的更新操作,同时放弃了全局的可串行性。你再次来看多版本的数据库。
每个事务有一个唯一的起始时间戳,而且,如果事务提交的话,它会有一个唯一的提交时间戳。
每个数据项都有多个版本。
数据项的每个版本都由一个写时间戳。
每个写操作产生数据项的一个新版本。
当事务成功提交时,这个新版本就永久地存入数据库中。新版本的写时间戳是事务的提交时间戳。
对于MVTO-SS有两个重要的区别。在MVTO-SS中,事务是在与顺序戳相关的场合下产生的。 现在,事务从它的起始时间一直持续到它的提交时间。第二点,在MVTO-SS中写时间戳是在事务的起始时间之前的。现在,写时间戳反映了新版本进入数据库的时间。
事务被分为查询queries(只有读操作)和更新updates(可能包含写操作)两类。事务的读取点是它的起始时间。采用MVTO来执行查询操作。每个读取要求映射到数据项的最新版本,它们的写时间戳比查询的读取点要早。
更新可以读取(向下读,read down),但只允许去写那些和它们在同一级上的数据。两阶段锁调度那些与更新同一级的读和写操作。当读取一个严格较低级别的数据项时,查询使用同样的MVTO算法。
有了这个调度算法,没有读操作会由于随后的写操作而无效,因为数据项新版本的写时间戳要等到更新提交了才会被盖上。因此,当新版本已经写进数据库时,新版本的写时间戳将会比任何活跃的事务的读取点晚。而且,事务所有的读取操作被映射到同一个时间戳上(起始时间),而事务所有的改写操作被映射到同一个时间戳上(提交时间)。
在一个访问级中,两阶段锁保证了操作的串行调度。两阶段锁唯一不能解决的冲突是,一个高级事务Th读取一个低级的数据项x而一个低级事务Tl去写x。
如果rh [x] 在wl [x] 之前发生,在多版本数据库中将不会有冲突,因为低级别的写操作产生x的一个新版本。
如果wl [x] 在rh [x] 之前发生而且Th读取了由Tl写的 x的版本,那么在任何等价的串行调度中Th必须在Tl之后但在任何随后的低级事务之前出现,那些低级事务写入了x的一个新版本。
因此,仅当调度器允许一个如下的序列,在多版本数据库中事务的交替执行才可以是非串行的,
w1 [x,i] r0 [x,i] w2 [x,j] w2 [y,k] r0 [y,k]
这里T0是一个高级事务,T1和T2是低级事务。在任何等价的串行调度中,T0应该放在T2之前以便它可以从T1读取x,放在T2之后以便它可以从T2读取y。很明显这是个矛盾。 所有T0的读取操作使用相同的读取点。如果这个读取点在T2的提交时间之前,那么T0不能读取版本y.k。如果这个读取点在T2的提交时间之后,那么T0读到的是x.j或x一个迟些时候的版本。
在这节中给出的调度算法产生了单级可串行执行操作。然而,改进的MVTO可以调度非单拷贝可串行操作。考虑一个低级数据项x,两个高级数据项y和z,有如下的事务:
T3,w3 [x] c3 低
T4,r4 [y] r4 [x] w4 [y] c4 高
T5,r5 [z] r5 [x] w5 [y] c5 高和下面合法的执行顺序:
r5 [z] w3 [x] c3 r4 [y] r4 [x] w4 [y] c4 r5 [x] w5 [y] c5 。
在图16.4中你可以看到改进的MVTO是怎样在这三个事务中计算操作的读取点和写时间戳的。在单版本数据库中,任何等价的串行执行必须满足:
T5必须在T3之前,否则,T5会读取由T3写的x的版本,但在MVTO调度中T5读取的是x的老版本。
T3必须在T4之前,否则,T4不能读到由T3写的x的版本。在MVTO调度中,T4从T3读取x 。
T4必须在T5之前,否则,T4会读取由T5写的y的版本,但在MVTO调度中T4读取的是y的老版本。
因此,在单版本数据库中不可能有如图16.4一样效果的T3、T4、T5的串行执行。
图16.4 对于修改的MVTO的调度操作
图16.5 违反进行规则(property)的执行
而且,正如图16.5所示的那样,改进的MVTO不支持进化特性。有一个低级数据项x,两个高级数据项y和z,如下的事务:
T6,w6 [x] c6 低
T7,r7 [x] w7 [y] c7 高
T8,r8 [z] r8 [x] w8 [y] c8 高和下面合法的执行顺序:
r8 [z] w6 [x] c6 r7 [x] w7 [y] c7 r8 [x] w8 [y] c8 。
图16.5说明了是怎样计算在这三个事务中操作的读取点和写时间戳的。事务T7和T8 写y的版本,要依赖于x的值。T8读的是x的第一个版本,T7读的是z的第二个版本,但在T8之前写y。因此,违反了进化特性,y的最近版本更多地依赖于x的一个旧值,而非由T7写的值。
结论在MLS数据库中并发控制给你强加了一些刻板的选择。
你可以实现严格的MLS安全政策和严格的并发控制标准,像可串行性。因此调度算法是可用的,最佳并发控制和MVTO-SS将使高级主体处于非常不利的环境。
你可以放松安全政策,例如考虑全局的调度器,并使用这些标准的调度算法中的一种。那时调度器将是一个可信主体(trusted subject),而且你可以更深入地研究一些机制用来监视隐蔽通道。
你可以放松并发控制标准,给高级用户最新的服务。通常这是最实际的决策。比如,在这节中描述的调度算法已经被可信Oracle ( Trusted Oracal )采用。
进一步的阅读关于并发控制的一本标准的教科书是文献[16]。在MLS数据库中,新近关于并发控制的一篇调研很好的梗概了该领域,请见文献[10]。关于这个主题还有一篇很有用的科学研究实验室(SRI)的报告[60]。除此之外,你应该直接阅读一些研究论文,如[9,68,78,79,144],这些论文同样可以作为你去深入研究著作之前的基本阅读材料。
练习题练习 16.1 设T1是一个高级事务,T2是一个低级事务。调度器使用最佳并发控制。在下面的历史纪录中,哪一个是T1被允许提交的记录?
r1 [x] r2 [x] w2 [x] startval1 startval2 endval1 endval2
r1 [x] r2 [x] w2 [x] startval1 startval2 endval2 endval1
r1 [x] r2 [x] w2 [x] startval2 startval1 endval2 endval1
r1 [x] r2 [x] w2 [x] startval2 endval2 startval1 endval1
练习16.2 解释在MVTO-SS调度算法中,为什么ELTS的计算必须是原子操作。举出一个例子来说明,如果其计算不是原子操作,可能出现哪些违反一致性的情况。
练习16.3 用MVTO-SS调度图16.4和16.5的例子中的操作。解释为什么随后的执行是单拷贝的串行执行。
练习 16.4 设T1、T2和T3分别是U、C和S级的事务(U<C<S)。来自这三个事务的操作到达调度器的顺序如下:
r1 [z] w1 [x] r3 [x] r2 [y] r3 [y] w3 [x] c1 r2 [z] c3 c2 。
你能推断出数据项x、y、z的安全级吗?在哪种顺序下这些操作会看上去像已经执行了,如果用MVTO-SS作为调度算法?
练习16.5 为多级安全数据库定义一个并行控制算法,它可以实现单级的可串行性和单级的读取一致性。你的算法将如何处理下面的情况?来自三个事务T1、T2和T3的操作到达调度器的顺序如下:
r3 [x] r1 [x] w1 [x] c1 r2 [x] w2 [y] w3 [y] c2 c1 。
考虑这种情况,当T1、T2和T3被各自分类为U、C和S级(U<C<S)时会怎样。再考虑当 T1是U级而T2和T3是C级时会怎样。
练习 16.6 设计一个调度算法,使用在16.4节中描述的调度器作为一个构件,并且实现single-copy可串行性,当提前知道事务的调度顺序。(参见文献[9])
第17章 面向对象的安全最后一章讨论的是在计算机安全中一个新的研究方向的前景和不足。面向对象系统是伴随着为控制调用,即方法调用(method calls)的总体机制而出现的,而信息隐藏技术(information hiding)的出现是为了禁止访问比面向对象系统级别更低的层。因此,面向对象系统中的安全显然是一个很值得探索的方向。我们将从常用的、组成对象模型的安全相关特征评定开始,再对强制访问控制进一步探索,比较实现面向对象安全的不同方法。
目标:
简要介绍面向对象的范例。
讨论在什么范围内面向对象的范例本质上支持安全系统的设计。
阐述在对象模型中加强多级安全的两种可替换的方法。
指出在计算机安全研究中的新方向。
17.1 基本原理在前面我们就曾讨论过,为了让访问控制更容易管理,安全策略不应说明单个用户如何访问单个数据项。相反,在控制机制中应该有一个中间层次。Clark-Wilson安全模型第一次阐述了这一方法。基于视图的数据库安全是我们最新的例子。基本上,我们想在下述方面实现安全策略:
只有指定的操作可以访问数据项,以及只允许用户执行指定的操作。
面向对象的范例看来具有我们正在寻求的所有特征。位于对象内部的数据项只能通过为该对象定义的方法来访问。这是实现安全的合适的基础吗?更准确地说,我们定义这些概念能够抵制对安全的攻击吗?如果能够的话,我们有遵循这些规范的实际实现吗?
17.2 对象模型我们对面向对象的系统设计的简要介绍肯定是不完全的,而且仅仅涉及到其中一些最基本的概念。我们从术语上的两点说明开始讨论。不要把在本章中介绍的对象与在像Bell-LaPadula访问控制模型中的对象相混淆。在访问控制架构中,“新的”对象可以既是主体又是对象。第二,作为安全实践者,你或许会从这样的事实里得到一些安慰,即面向对象团体还没有达成普遍认同的术语。因此,下面使用的术语可能不被所有面向对象的团体所认同。然而,面向对象方法中的关键概念却是一样的:
对象(Objects):对象包含属性(实例变量)和方法。面向对象的原型要求你同时考虑如何组织数据和如何处理数据。这是解决安全问题的好的开端。在前面的章节中已经指出,糟糕的数据库机制设计是引发不必要的安全并发症的一个原因。
值(Values):属性可取的值也是对象。
类型(Types):每个对象有它所属的类型(类)。对象是它的类的一个实例。类型也是一个对象。从同样的操作可以在所有对象上执行的意义上来说,一个类中的所有对象在功能上是相等的。
类的层次和继承(Class hierarchy and inheritance):类型形成一个层次结构。一个对象从它的类继承(inherit)属性和方法,而每个超类一直向上继承,直到一个根类(root class)对象。在从超类那里继承来的属性和方法之外,一个子类还可能有自己的一些属性和方法。
基对象(Primitive objects):像数字值、字符和标识符,它们不能再有子类。
方法(Methods):对象的方法是指那些只能在该对象上执行的操作。一个方法只能改变它自己对象里的值。对象只能通过自身的方法来访问和修改,不允许有外部的访问。方法可以通过发送消息(方法请求)来调用其他的对象(和它们的方法)。每个类都有一个create方法来创建它的类型的新实例。
消息(Messages):消息是对象间唯一的通信方式。消息包含一个方法调用的自变量(参数)。
消息隐藏(Information hiding):每个对象有自己的局部地址空间。分隔属于不同实体的存储区域是计算机安全的核心支柱。面向对象系统提供了在单个对象粒度上的隔离。从这个角度看,信息隐藏似乎是强安全性的一个基础。
在图14.3所示的例子中,关系Diary的一个面向对象的执行可以包含一个类对象Journey,它有Name、Dest、Flight、Status属性,以及方法,如:Journey-read、Journey-update和Journey-create。关系中的元组对应于这个类对象的实例,如图17.1所示。这些对象实例会从它们的对象类中继承方法,也就是说,我们将会用方法Journey-read去访问对象(Alice,Mon,GR123,private)。在该对象上不会再有其他的读操作。
当旅行社登录数据库系统时,登录程序会创建一个对象TravelAgent,它会和所有其他的对象交互。为在面向对象的关系Diary中输入一个新表项,对象TravelAgent发送一条消息给对象类Journey,请求创建一个新的实例对象。类对象使用它的创建方法来完成,并可能发送一个返回值,该值包含了给新对象的一个标识符(见图17.2)。现在对象TravelAgent可以使用方法Journey-update来输入与旅行相关的详细资料。
图17.1 在14.3中的关系Diary的面向对象的实现
图17.2 创建类Journey的新的实例
17.3 对象模型中的安全面向对象的系统提供了一些有用的手段来加强安全。通过在对象中的封装数据(encapsulating data)技术我们有机会控制对数据的访问。数据项(属性)只能通过指定的方法来访问。信息隐藏保护了对象的完整性不受外部干扰。面向对象的安全已经在我们的计算机模型的好几层中得到了实现。
Trusted Mach是一种面向对象的、带有安全内核的操作系统。
Java和它的安全体系结构就是构建在面向对象的原理之上的(见11.6节)。
CORBA是一种分布式对象系统的安全体系结构(见10.4节)。
面向对象的系统是否本来就是安全的?封装和信息隐藏是面向对象的模型中的基本概念。你可以定义自己的对象、对象里的方法,可能还有一些额外的策略对象(policy objects)来描述你的安全策略。如果你处理得当,信息隐藏就会处理好剩下的问题,保护系统的完整性。你似乎找到了两个最完美的世界。通过对象,你就有一个手段丰富的环境来确定安全策略,而信息隐藏则是一般的安全机制,它会协助加强所有的这些策略。
只要你,还有攻击者们,只在面向对象的系统提供的抽象层考虑整个系统,这种理解就是正确的。如上所述,取决于这一层在实际系统中的位置。获得对下一层访问的攻击者常常会危及安全性。Java安全的历史(见11.6节和文献[95])提供了很多支持这一论断的实例。总的来说,你不应忘记,大多数面向对象的系统设计得不安全。能够防御有预谋攻击的信息隐藏没有作为必须的安全机制加以实现。设计者们不关心阻断所有的可被耐心的攻击者根据巧妙控制找到的迂回路径。甚至还有我们所知道的可以用来访问较低层数据漏洞(loopholes),这原本是软件编制者用来增加代码有效性的,正如下面从讨论了相关主题的文献[155]中摘录的阐述一样。
即使是强类型语言,如Ada和Modula-3,程序设计者们也常常发现,他们需要使用漏洞在类型系统中减弱类型的孤立性。
在现实中,你必须检查每一种情况,看是否一个面向对象的系统给你比标准安全更多的折衷,丰富的性能但是低的安全保证。
不是每一种展示安全特性的服务都是设计了提供保护的。
17.4 在面向对象系统中的强制访问控制(MAC)
强制访问控制使用安全标识来规范主体如何来访问对象。在对象模型中什么是“主体”和“对象”呢?简单的定义就是,MAC中的主体是发送方法请求的对象,MAC对象是执行方法的对象。当一个用户登录时,登录程序创建一个对象来响应用户并且接收用户的安全许可证。
为重新描述对象模型的MAC策略,来自一个对象的信息只能流向同一安全级或更高安全级的对象。信息流载体是消息和方法调用返回的值。当一个对象改变它的状态或创建一个新对象时,信息流就产生了。
17.4.1标识对象
在对象模型中,一个对象有很多方面(facets)可以被标识。当然包括对象自身、它们的属性、属性的值、方法和消息。更进一步,你可以跟踪对象创建者的层次或该对象的每个属性的层次。这些层次不必等于对象或属性的层次。多级对象(multi-level object)包含了在不同安全级上的方方面面。单级对象(single-level object)有一个应用在对象和它所有方面的安全标识。标识对象必须和类的层次以及一个对象的不同方面之间的关系相一致。对象从它们的类继承属性和方法,建议采取下面的策略:
一个属性的安全级决定了这个属性所有子类中的安全级。如果你可以通过检查类对象来知道一个属性的存在,那么试图在实例对象中隐藏这个属性的存在就没有什么意义。实际上,这会产生一个隐蔽通道。
一个方法的安全级决定了这个方法所有的超类中的安全级。如果一个方法在类对象中已经被标识为“保密的”但未被分类,用户可以通过在一些实例对象中执行它来知道它的安全级,为什么它最初还被标识为保密的呢?在另一方面,允许在一般的类对象中而不是在每个特定的实例中执行方法是很合理的。
这两条策略说明,不同的安全考虑会把在类层次中的安全标识引向不同的方向。在系统中进一步出现的问题是支持多重继承(multiple inheritance)。有这样一个对象,它可以是多个类的实例。
在多级对象中属性可以有不同的安全标识。在这种情况下出现多方更新冲突(Multi-party update conflict)是常见的问题。当两个不同安全级别的主体修改同一个属性,应该让“低级别” 主体知道“高级的”值的存在吗?多重实例解决了这一问题,并截断了隐蔽通道。在单级对象中不会出现这个问题,但会留有这样的问题,即偶然会创建有相同名字却在不同安全级别的对象。要决定一个一致的标识策略,必须回答以下的问题。
对象的安全级别必须由属性的安全级别决定吗?
对象的安全级别必须决定属性的安全级别吗?
十分有趣的是,有很多理由让我们去标识对象的那些方面,即属性、方法、约束,至少在对象自身这一级去描述“真正的”安全要求(和关系数据库很相似),同样有很多理由让我们至多在对象这一级去标识对象的那些方面,这样可以截断隐蔽通道和对高级信息的干扰。还有一种类似的替换方法可以截断这种特殊隐蔽通道。如果一个低级别的主体已经创建了一个高安全级的属性,他知道这个属性的存在,因而没有必要再对低级别的主体隐藏这个属性。
如果你同意面向对象的原型有利于将应用数据组织到相关的实体里这种说法,那么就可以证明你是查询多级对象的值。当然,一个“相关的实体”应该有单一安全标识。因此,现在我们将只考虑单级对象。
17.4.2 消息流的控制为了保持面向对象模型的真实性,强制访问控制(MAC)是通过控制对象间的消息流来实现的。当决定是否传递一个消息时,强制策略参考对象标识和方法的类型(读或写)。方法调用像消息一样被直接完成,它们没有自己的存储器。方法调用必须使用对象来存储数据。
强制访问控制规范对象间的信息流。只有当一条从o1传递到o2的消息实际改变了o2的状态,即o2的某个属性值,信息才从对象o1 流向o2 。信息流可以是:
向前的:来自发布方法调用的对象,通过消息中的参数;
向后的:通过响应方法调用所提供的值;
传递的:通过任何向前或向后的流的链;
间接的:通过改变第三个对象的内部状态。
为防止间接的信息流,你必须检查一个消息调用是否只是访问了被调用的对象,或者是否它也可能改变力那个对象的状态。
由Jajodia和Kogan在文献[69]里提出的消息过滤算法中,每个对象都有一个安全标识,由函数L定义。和以往一样,安全标识形成一个格。为表示两个等级是不可比的,我们记为 L( o1 )<> L( o2 ) 。我们考虑的方法有read、write和create。在方法调用中每个方法都有一个状态(status),它可以是unrestricted(U)或是restricted(R)。状态信息用来控制间接的信息流(假设需要引导系统的方法是unrestricted,例如用户的登录)。
我们为这种情况描述消息过滤算法,即对象o1 执行方法t1,而方法t1送出一条消息g给对象o2,请求执行方法t2 。方法t2 返回值 r 给o1 。算法只让信息流向更高级别的对象。它可能传递或中断消息g,而且对所有不允许的方法调用返回空值。
由于消息过滤器是我们唯一相关的(引用)监视器,它同样必须决定是否同意一个方法在对象中执行。通过让对象送一条消息给它自己,而且如果这条消息从消息过滤器中通过才继续执行,就可以实现这种想法。在这个解决方法中,单个对象中的方法不是TCB的一部分。带有一个更小的TCB为在更高级别上保证实现安全机制提供了机会。
消息过滤算法
case o1 ( o2,/ method call /
if L(o1) = L(o2) let g pass; s(t2),= s(t1);
if L(o1) <> L(o2) block g;
if L(o1) < L(o2)let g pass; r,= nil; s(t2),= s(t1);
if L(o1) > L(o2)let g pass; s(t2),= R;
case o1 = o2,/ method execution /
if t1 = write
if s(t1) = U let g pass;
if s(t1) = R block g;
if t1 = read let g pass;
if t1 = create(o3,L(o1))
if s(t1) = U and L(o3) ( L(o1) then let g pass;
else block g;
图17.3展示了消息过滤算法是怎样防止间接写入的(向下写)。有三个安全级别为L( o3 ) = L( o2 ) < L( o1 )的对象。高级对象o1 送一条消息g1 给低级对象o2,这不会改变o2 的状态但会提供一些参数给来自对象o2 的方法调用t2,而o2 将送一条消息g2 给另一个低级对象o3,请求对o3 一个写操作(write)。这个写操作(write)触发了一条来自对象o3 的消息g3 给它自己,请求方法 t 3 = write。算法执行以下步骤。
第一步 消息g1:o1 ≠o2 而且 L( o1 ) > L( o2 ),因此g1将传递而且s(t2 ):= R。
第二步 消息g2:o2 ≠o3 而且 L( o2 ) = L( o3 ),因此g2将传递而且s(t 3 ):= R。
第三步 消息g3:g3是从o3 送给它自己的,t 3 = write,而且s(t 3 ) = R,因此g3被中断,写
操作(write)也将不予执行。
算法没有说明任何在这个实例中应该送出的特殊的返回消息。
图17.3 在Jajiodia-Kogan模型中媒介访问
17.4.3 MLS操作系统下的面向对象的安全
Millen-Lunt模型(见文献[101])在传统的MLS操作系统之上来实现面向对象的多级安全。因此对象模型的概念被翻译到Bell-LaPadula模型的概念中去。根据Bell-LaPadula,方法调用变成了进程,即Bell-LaPadula主体,它们有自己的内存空间和安全级别。被方法调用访问的对象也即是Bell-LaPadula中的对象。一个方法调用执行如下。
当对象收到一个方法请求,就为处理在消息中的方法调用这唯一目的而创建一个虚拟主体(virtual subject)。
虚拟主体有一个对象,如同它本地对象(home object)一样用来接收方法调用。
当对象o1 中的方法t1 向对象o2 中的方法t2 发出一条请求,将会创建虚拟主体s1 和s2 把方法t1和t2 联系起来。我们称s1 是虚拟主体s2 的调用者(invoker)。
符号和前面章节一样。这儿有单级对象和安全级别的格。函数L为每个对象和每个虚拟主体定义了一个安全级别。
现在阐述对虚拟主体、它们的本地对象和它们的调用者的强制安全策略。由于对发送消息没有控制而且所有的访问决定都是在本地对象里做出的,我们需要能处理一个方法调用返回值的策略。这样的策略可能要求在某些条件下对象返回一个空值。MAC策略集中于下面六个安全特性。
规则1 对象的安全级别必须决定它的类对象的安全级别。
规则2 虚拟主体的安全级别决定了调用主体的安全级别,它同时还决定了其本地对象的安全级别。
规则3 虚拟主体可以执行方法或者只能在它的本地对象中读和写变量。
规则4 只有当虚拟主体的安全级别和它本地对象的安全级别一样时,虚拟主体才可以写它的本地对象。
规则5 只有当虚拟主体和调用主体有相同的安全级别时,虚拟主体才可以送一个返回值给它的调用主体。
规则6 一个新创建的对象的安全级别决定了发出创建要求的虚拟主体的安全级别。
在图17.4中,设o1 是一个对象,它发送一个方法调用给o2 。假设调用主体s1 的安全级别和它的本地对象o1 的安全级别一样。一个有本地对象o2 的新虚拟主体s2 处理当前的方法调用。考虑所有的三种情况,规则2会把虚拟主体s2 分到 max(L(s1 ),L( o2 )) 级别上,即到更高的安全级别上。
L( o1 ) < L( o2 ):像L(s1 ) < L(s2 )一样,规则5规定了从o2 返回给o1 的值将为空,而不管所要求的访问类型是什么。
L( o1 ) ≥ L( o2 ) 而且一个读方法调用:L(s1 ) =L(s2 ),所以规则5允许返回一个合适的值。
L( o1 ) > L( o2 ) 而且一个写方法调用:规则4像L(s2 ) > L( o2 )一样中断写方法。
图17.4 在Millen-Lunt模型中的媒介访问
在Millen-Lunt模型的解释中,对象o1可能从对象o3 读到一个值,通过发送一个方法调用t1给对象o2,该调用触发一个方法调用t2 给对象o3,因而o3 返回一个值给o1而不改变对象o2,因为方法(虚拟主体)有它们自己的内存空间。在前面的模型中,o2 不能作为一个盲目的中介。因此两种模型提出了不同的访问控制方案。当L( o1 ) = L(o3 ) >L( o2 )时,两种模型将如图17.5选取它们的方案。
消息过滤算法将传递携带消息的t1。为携带消息的t2 返回的值将自动设置为空(而且s(t2 ):= R)。因而,返回给第一条消息的值不包含来自o3 的信息。
在Millen-Lunt模型中,三个虚拟主体全部在同一安全级别。因此,s3 可以给s2 返回一个值,这个值可以被传递给s1 。不允许虚拟主体s2 去写它的本地对象,而且也没必要这样做。
图17.5 通过低中介物传递信息
进一步的阅读文献[25]中有一章概述了在面向对象数据库中最新的安全机制。除此之外,读者还可以阅读研究论文。我们建议从文献[18]和[152]开始。
练习题练习 17.1 面向对象的范例能提供有着高保证性(assurance)、特征(feature)丰富的访问控制吗?考察要获得证明这样的结论是正确的条件。你的调查必须在包括安全特性管理的同时,还要包括其技术机制,以及它对可以达到的保证级别的影响。
练习 17.2 进行一个案例研究,演示一个类型限制系统中的缺陷可能怎样削弱基于信息隐藏的安全机制。(从Java的发展中得到的经验就是这样的一个材料[95])
练习 17.3 讨论一个对多级对象一致的标识策略,此多级对象中的属性和值有它们自己的安全标识。
属性的安全级别应该由属性值的安全级别来决定吗?
属性的安全级别应该决定属性值的安全级别吗?
练习 17.4 说明用单级对象来描绘多级对象是可能的。(文献[25])
练习 17.5 设o1 是一个多级对象,其属性和值都有它们自己的安全标识。对象o1 有一个属性a取得值o2 。o1、o2 和a的安全级别应该是什么关系,才可以使得在o1 中的属性a值改变而又不需创建一个新的对象?
练习 17.6 考虑这种情况,对象o1 想通过发送一条创建请求给o2 来创建类型o2 的对象o3 。解释在依赖于o1,o2 和o3 的安全级别的消息流模型中将会怎样处理这条请求。
练习 17.7 使用Millen-Lunt策略,能否通过第三个对象o3 来防止对象o1 在它自己的级别上去写对象o2?讨论下列情形:L( o1 ) = L(o3 ),L( o1 ) < L(o3 ),和L( o1 ) > L(o3 )。