在线文档教程
Sqlite
其他 | Miscellaneous

Pointer Passing Interfaces

Pointer Passing Interfaces

1.概述

2.在SQLite中传递指针的简史

2.1.提升威胁级别

2.2.防止伪造的指针

2.3.指针泄漏

3.新的指针传递接口

3.1.指针类型

3.1.1.指针类型是静态字符串

3.2.析构函数

4.限制使用指针值

5.总结

1. Overview

三个新的“_pointer()”接口被添加到SQLite 3.20.0(2017-08-01):

  • sqlite3_bind_pointer(),

  • sqlite3_result_pointer(), and

  • sqlite3_value_pointer().

邮件列表上的问题和困惑很快就出现在这些新界面背后的目的,它们为什么被引入以及它们解决了什么问题。本文试图回答这些问题并澄清混淆。

2. A Brief History Of Pointer Passing In SQLite

SQLite扩展有时可以方便地在子组件之间或扩展与应用程序之间传递非SQL值。一些例子:

  • 在FTS3扩展,匹配操作符(它的全文搜索)需要匹配的条目的细节传达给段(),偏移量(),和matchinfo()功能,使这些功能可以将本场比赛的细节转化为有用的输出。

  • 为了让应用程序向FTS5添加新的扩展,例如新的标记生成器,应用程序需要一个指向“fts5_api”对象的指针。

  • 在CARRAY扩展中,应用程序需要告诉扩展名包含扩展实现的表值函数的数据的C语言数组的位置。

传递这些信息的传统方式是将C语言指针转换为BLOB或64位整数,然后使用sqlite3_bind_blob(),sqlite3_result_blob(),sqlite3_value_blob()等常用接口通过SQLite移动该BLOB或整数。整数等值。

2.1. Upping The Threat Level

传递指针就好像它们是整数或BLOB一样简单,有效,并且在应用程序组件彼此友好的环境中运行良好。但是,将指针作为整数和BLOB传递会导致恶意的SQL文本伪造无效指针,从而导致恶作剧。

例如,snippet()函数的第一个参数应该是FTS3表的特殊列,其中包含一个指向fts3cursor对象的指针,该对象包含有关当前全文搜索匹配的信息。该指针以前作为BLOB传递。例如,如果FTS3表被命名为“t1”并且具有名为“cx”的列,则可以这样写:

SELECT snippet(t1) FROM t1 WHERE cx MATCH $pattern;

但是如果黑客能够运行任意SQL,他可能会运行一个稍微不同的查询,如下所示:

SELECT hex(t1) FROM t1 WHERE cx MATCH $pattern;

由于指针作为BLOB(在旧版本的SQLite中)在t1表的t1列中传递,所以此查询将以十六进制显示指针的值。然后,攻击者可以修改该指针,以尝试使用snippet()函数修改应用程序地址空间的其他部分中的内存,而不是它应该在其上运行的fts3cursor对象:

SELECT snippet(x'6092310100000000') FROM t1 WHERE cx MATCH $pattern;

从历史上看,这不被视为威胁。争论的是,如果敌对代理能够向应用程序中注入任意SQL文本,那么该代理已经完全控制了应用程序,因此让敌对代理伪造指针并不会为代理提供任何新功能。

对于大多数情况来说,潜在的攻击者确实无法注入任意的SQL,因此大多数SQLite的使用都不受上述攻击的影响。但是有一些明显的例外。以机智:

  • Webkit 的WebSQL接口允许任何网页在浏览器中为Chrome和Safari运行任意SQL。这个任意的SQL应该在沙箱内运行,即使被利用也不会造成任何伤害,但是沙箱的安全性不如人们想象的那么安全。在2017年春天,一队黑客能够通过一系列的漏洞利用iMac,其中一个涉及破坏作为BLOB值传递给通过WebSQL接口运行的SQLite数据库的snippet()FTS3函数的指针在Safari内部。

  • 在Android上,我们被告知,有很多服务会盲目地运行任意SQL,这些SQL通过从互联网的恶劣角落下载的不可信任的应用程序传递给它们。假设Android服务更加谨慎,关于从不受密码的来源运行SQL。这位作者没有任何相反的具体例子,但他听说过他们存在的谣言。即使所有的Android服务都更加谨慎并且适当地审核了它们运行的​​所有SQL,但是为了验证它们是否安全,很难对它们进行审计。因此,具有安全意识的人员热衷于通过传递任意SQL文本来确保没有漏洞。

  • 化石版本控制系统(设计和支持SQLite开发的目的书面)允许适度信任的用户进入任意的SQL生成故障单报告。该SQL使用sqlite3_set_authorizer()接口进行了消毒,并且没有发现任何漏洞。但这是潜在的敌对代理能够向系统中注入任意SQL的一个例子。

2.2. Preventing Forged Pointers

指针传递中关闭安全间隙的第一次尝试是为了防止指针值被伪造。这是通过让发送者使用sqlite3_result_subtype()为每个指针附加一个子类型并使接收者使用sqlite3_value_subtype()来验证该子类型并拒绝具有不正确子类型的指针来实现的。由于无法将子类型附加到使用纯SQL的结果,因此可以防止使用SQL伪造指针。发送指针的唯一方法是使用C代码。如果攻击者可以设置一个子类型,那么他也可以在没有SQLite的帮助下伪造一个指针。

使用子类型来识别有效的指针阻止了WebSQL的利用。但事实证明这是一个不完整的解决方案。

2.3. Pointer Leaks

指针上的子类型的使用防止了使用纯SQL的指针伪造。但是,子类型不会阻止攻击者读取指针的值。换句话说,指针值的子类型可防止使用如下SQL语句进行攻击:

SELECT snippet(x'6092310100000000') FROM t1 WHERE cx MATCH $pattern;

snippet()的BLOB参数没有正确的子类型,因此snippet函数会忽略它,不会更改任何数据结构,并且无害地返回NULL。

但是,使用子类型不会阻止使用如下SQL代码读取指针的值:

SELECT hex(t1) FROM t1 WHERE cx MATCH $pattern;

你会问,会有什么危害?SQLite开发人员(包括这位作者)想知道同样的事情。但随后安全研究人员指出,指针的知识可以帮助攻击者规避地址空间随机化防御。这被称为“指针泄漏”。指针泄漏本身并不是一个漏洞,但它可以帮助攻击者有效利用其他漏洞。

3. The New Pointer-Passing Interfaces

允许扩展组件将私有信息安全地传递给另一个,并且不引入指针泄漏需要新的接口:

  • sqlite3_bind_pointer(S,I,P,T,D) →将类型T的指针P绑定到准备语句S的第I个参数。D是P的可选析构函数。

  • sqlite3_result_pointer(C,P,T,D)→返回类型T的指针P作为函数C的参数。D是P的可选析构函数。

  • sqlite3_value_pointer(V,T)→返回与值V关联的类型T的指针,或者如果V没有关联的指针,或者V上的指针的类型与T不同,则返回NULL。

对于SQL,由sqlite3_bind_pointer()和sqlite3_result_pointer()创建的值与NULL不可区分。试图使用hex() 函数读取指针值的SQL语句将获得SQL NULL答案。发现一个值是否有关联指针的唯一方法是使用带有适当类型字符串T的sqlite3_value_pointer() 接口。

由sqlite3_value_pointer()读取的指针值不能由纯SQL生成。因此,SQL不可能伪造指针。

纯sql无法读取由sqlite3_bind_pointer()和sqlite3_result_pointer()生成的指针值。因此,SQL不可能泄漏指针的值。

通过这种方式,新的指针传递接口似乎解决了在SQLite中将指针值从一个扩展传递到另一个扩展的所有安全问题。

3.1. Pointer Types

sqlite3_bind_pointer(),sqlite3_result_pointer()和sqlite3_value_pointer()的最后一个参数中的“指针类型”用于防止用于一个扩展的指针被重定向到不同的扩展。例如,如果没有使用指针类型,攻击者仍然可以像这样使用SQL访问包含FTS3和CARRAY扩展的系统中的指针信息:

SELECT ca.value FROM t1, carray(t1,10) AS ca WHERE cx MATCH $pattern

在上面的语句中,由MATCH运算符生成的FTS3游标指针将发送到carray()表值函数中,而不是其目标收件人片段()。carray()函数将指针视为指向整数数组的指针,并逐个返回每个整数,从而泄漏FTS3游标对象的内容。由于FTS3游标对象包含指向其他对象的指针,因此上面的语句将是一个指针泄漏。

除了上面的语句不起作用,这要归功于指针类型。由MATCH运算符生成的指针具有“fts3cursor”类型,但carray()函数期望接收类型为“carray”的指针。由于sqlite3_result_pointer()上的指针类型与sqlite3_value_pointer()调用上的指针类型不匹配,因此sqlite3_value_pointer()会在carray()中返回NULL,并因此表示CARRAY扩展名已通过无效指针。

3.1.1. Pointer types are static strings

指针类型是静态字符串,理想情况下应该是字符串文字直接嵌入在SQLite API调用中,而不是从其他函数传入的参数。考虑使用整数值作为指针类型,但静态字符串提供了更大的名称空间,这减少了无关扩展之间意外的类型名称冲突的机会。

通过“静态字符串”,我们的意思是一个零终止的字节数组,它在程序生命周期中是固定不变的。换句话说,指针类型字符串应该是一个字符串常量。相反,“动态字符串”是从堆中分配的内存中保存的零终止字节数组,它必须被释放以避免内存泄漏。不要使用动态字符串作为指针类型字符串。

多位评论员表示希望为指针类型使用动态字符串,并让SQLite获得类型字符串的所有权,并在完成使用时自动释放类型字符串。该设计因以下原因被拒绝:

  • 指针类型不打算灵活和动态。指针类型的目的是作为设计时间常量。应用程序不应在运行时合成指针类型字符串。提供对动态指针类型字符串的支持会导致开发人员通过创建运行时合成指针类型字符串来滥用指针传递接口。要求指针类型字符串是静态的,鼓励开发人员通过在设计时选择固定指针类型名称并将这些名称编码为常量字符串来做正确的事情。

  • SQLite中SQL级别的所有字符串值都是动态字符串。要求类型字符串是静态的,因此很难创建一个可以合成任意类型指针的应用程序定义的SQL函数。我们不希望用户创建这样的SQL函数,因为这些函数会损害系统的安全性。因此,使用静态字符串的要求有助于防止指针传递接口与不正确设计的SQL函数的完整性。静态字符串的要求并不完美,因为一个复杂的程序员可以对它进行编码,而新手程序可以简单地处理内存泄漏。但通过声明指针类型字符串必须是静态的,

  • 让SQLite拥有类型字符串的所有权将会对所有应用程序施加性能成本,即使是不使用指针传递接口的应用程序。SQLite传递值作为sqlite3_value对象的实例。该对象具有析构函数,因为几乎所有的sqlite3_value对象都被使用,所以经常会被调用。如果析构函数需要检查是否存在需要释放的指针类型字符串,那么每次调用析构函数时都需要烧掉一些额外的CPU周期。这些周期加起来。如果指针传递是一个常用的编程范例,我们会愿意承担额外CPU周期的代价,但指针传递很少见,

如果你觉得在你的应用程序中需要动态指针类型的字符串,那么这是一个强烈的指示,你正在滥用指针传递接口。您的预期用途可能不安全。请重新考虑你的设计。确定你是否真的需要首先通过SQL传递指针。或者可能找到本文所描述的指针传递接口以外的其他机制。

3.2. Destructor Functions

sqlite3_bind_pointer()和sqlite3_result_pointer()例程的最后一个参数是指向SQLite完成后用于处理P指针的过程的指针。这个指针可以是NULL,在这种情况下,不会调用析构函数。

当D参数不是NULL时,这意味着指针的所有权正被传递给SQLite。当它完成指针的使用时,SQLite将负责释放与指针相关的资源。如果D参数为NULL,则表示指针的所有权保留在调用方中,调用方负责处理指针。

请注意,析构函数D用于指针值P,而不是类型字符串T.类型字符串T应该是具有无限生命周期的静态字符串。

如果通过向sqlite3_bind_pointer()或sqlite3_result_pointer()提供非NULL D参数将指针的所有权传递给SQLite,则所有权保留在SQLite中,直到对象被销毁。没有办法将所有权从SQLite转移出来并再次转移回应用程序。

4. Restrictions On The Use of Pointer Values

使用sqlite3_bind_pointer(),sqlite3_result_pointer()和sqlite3_value_pointer()接口捎带SQL NULL值的指针是暂时的和短暂的。指针永远不会写入数据库。指针将无法存活排序。后一个事实就是为什么没有sqlite3_column_pointer()接口,因为无法预测查询规划器是否会在从查询返回值之前插入排序操作,所以不可能知道指针值通过sqlite3_bind_pointer()或sqlite3_result_pointer()插入到查询中将存活到结果集中。

指针值必须直接从生产者流入其消费者,而不需要中间操作员或功能。任何指针值的转换都会破坏指针并将该值转换为普通的SQL NULL。

指向sqlite3_bind_pointer()和sqlite3_result_pointer()接口的指针和指针类型参数都由调用者“拥有”。换句话说,调用者负责确保两个值保持有效,直到通过sqlite3_value_pointer()进行最后一次访问。

5. Summary

从这篇文章中得到的重要信息:

  • 互联网是一个日益敌对的地方。现在,开发人员应该假设攻击者会找到在应用程序中执行任意SQL的方法。应用程序的设计应该防止任意SQL的执行升级为更严重的漏洞。

  • 一些SQLite扩展受益于传递指针:

- The [FTS3](fts3) MATCH operator passes pointers into [snippet()](fts3#snippet), [offsets()](fts3#offsets), and [matchinfo()](fts3#matchinfo). - The [carray table-valued function](carray) needs to accept a pointer to an array of C-language values from the application. - The [remember() extension](https://sqlite.org/src/file/ext/misc/remember.c) needs a pointer to a C-language integer variable in which to remember the value it passes. - Applications need to receive a pointer to the "fts5\_api" object in order to add extensions, such as custom tokenizers, to the [FTS5](fts5) extension.

  • 指针不应该通过将它们编码为某些其他SQL数据类型(例如整数或BLOB)来交换指针。相反,使用旨在促进安全指针传递的接口:sqlite3_bind_pointer(),sqlite3_result_pointer()和sqlite3_value_pointer()。

  • 指针传递的使用是一种先进的技术,应该不经常使用和谨慎使用。指针传递不应该随意或不小心使用。指针传递是一种尖锐的工具,如果误用会留下深刻的伤痕。

  • 作为每个指针传递接口的最后一个参数的“指针类型”字符串应该是直接出现在API调用中的不同的特定于应用程序的字符串文字。指针类型不应该是从更高级别的函数传入的参数。

SQLite is in the Public Domain.