很多软件项目需要处理外部生成的数据,因此不能被完全信任。 例如,将用户提供的图片文件转换为不同的格式,或者是执行由用户生成的软件代码等。
如果软件库对这类数据的解析过程特别复杂的话,它很有可能会成为某些特定安全漏洞的受害者:安全漏洞诸如内存损坏,又或者是跟解析逻辑相关的的问题(路径遍历问题)。这些安全漏洞可能会造成很严重的安全问题。
为了缓解这些问题,开发人员通常使用软件隔离的方法,这种方法又被称作沙箱技术。 通过使用沙盒的方式,开发人员可以限制那些需要解析外部数据的代码,确保它们只能访问特定的文件、网络连接和其他操作系统资源。 这样最坏的情形就是,即使潜在的攻击者获取了这些代码的执行权限,他们也只能访问沙箱范围内的资源,沙箱技术将它们一并隔离了,这有效保护了其余的软件基础设施。
沙箱技术必须对攻击具有高度的防御性,能够充分保护操作系统的其它部分,同时又需要容易使用,从而让软件开发人员快速上手。 对于当前很多流行的软件隔离工具来说,它们要么无法充分隔离操作系统的其余部分,要么需要额外消耗大量时间来为每个项目重新定义安全边界。
实现一次,到处运行
为帮助这个目标的实现,谷歌开源了项目Sandboxed API(沙箱API),该项目已在谷歌内部广泛使用,久经考验。通过Sandboxed API,开发者可以为单个软件库创建安全策略,也就是说我们能够在软件类库内部为功能创建可重用的和安全的实现,而它的隔离粒度又刚好能保护其它被使用的软件基础设施。
Sandboxed API主要用于访问沙箱类库中的某个软件函数,我们还开源了沙箱项目的核心Sandbox2。 Sandox2现在已经是Sandboxed API的一部分,提供了底层沙箱原语。它又被认为是一种底层API,可以单独使用来隔离任意的Linux进程。
实现机制
目前Sandboxed API主要用于C语言编写的软件类库(或提供C绑定),未来可能会实现对更多编程语言的支持。
从全局的角度看,Sandboxed API把沙箱里的库以及库的调用者分离到两个不同的系统进程中:宿主机二进制进程和Sandboxee(沙箱)进程。 宿主机端的API对象重新封装了实际的库调用,并通过进程间通信发送到Sandboxee,然后由Sandboxee中的RPC Stub(存根)对调用重组并把它转发到原始库。
API对象(SAPI对象)和RPC Stub都由项目本身提供,前者由接口生成器自动生成。 用户只需要提供一个沙箱策略,一组允许底层库使用的系统调用,以及允许访问和使用的资源。 一旦这些条件就绪,基于Sandboxed API的库就可以很容易地在其他项目中重用了。
SAPI对象的API同原始库的API非常类似。 例如,当使用流行的压缩库zlib时,如下代码片段实现了一个数据块的压缩(为简洁起见,我们在这里忽略了错误处理) :
void Compress(const std::string\u0026amp; chunk, std::string* out) { z_stream zst{}; constexpr char kZlibVersion[] = \u0026quot;1.2.11\u0026quot;; CHECK(deflateInit_(\u0026amp;zst, /*level=*/4, kZlibVersion, sizeof(zst)) == Z_OK); zst.avail_in = chunk.size(); zst.next_in = reinterpret_cast\u0026lt;uint8_t*\u0026gt;(\u0026amp;chunk[0]); zst.avail_out = out-\u0026gt;size(); zst.next_out = reinterpret_cast\u0026lt;uint8_t*\u0026gt;(\u0026amp;(*out)[0]); CHECK(deflate(\u0026amp;zst, Z_FINISH) != Z_STREAM_ERROR); out-\u0026gt;resize(zst.avail_out); deflateEnd(\u0026amp;zst);}
而如果使用Sandboxed API,代码将会变成:
void CompressSapi(const std::string\u0026amp; chunk, std::string* out) { sapi::Sandbox sandbox(sapi::zlib::zlib_sapi_embed_create()); CHECK(sandbox.Init().ok()); sapi::zlib::ZlibApi api(\u0026amp;sandbox); sapi::v::Array\u0026lt;uint8_t\u0026gt; s_chunk(\u0026amp;chunk[0], chunk.size()); sapi::v::Array\u0026lt;uint8_t\u0026gt; s_out(\u0026amp;(*out)[0], out-\u0026gt;size()); CHECK(sandbox.Allocate(\u0026amp;s_chunk).ok() \u0026amp;\u0026amp; sandbox.Allocate(\u0026amp;s_out).ok()); sapi::v::Struct\u0026lt;sapi::zlib::z_stream\u0026gt; s_zst; constexpr char kZlibVersion[] = \u0026quot;1.2.11\u0026quot;; sapi::v::Array\u0026lt;char\u0026gt; s_version(kZlibVersion, ABSL_ARRAYSIZE(kZlibVersion)); CHECK(api.deflateInit_(s_zst.PtrBoth(), /*level=*/4, s_version.PtrBefore(), sizeof(sapi::zlib::z_stream).ValueOrDie() == Z_OK)); CHECK(sandbox.TransferToSandboxee(\u0026amp;s_chunk).ok()); s_zst.mutable_data()-\u0026gt;avail_in = chunk.size(); s_zst.mutable_data()-\u0026gt;next_in = reinterpet_cast\u0026lt;uint8_t*\u0026gt;(s_chunk.GetRemote()); s_zst.mutable_data()-\u0026gt;avail_out = out-\u0026gt;size(); s_zst.mutable_data()-\u0026gt;next_out = reinterpret_cast\u0026lt;uint8_t*\u0026gt;(s_out.GetRemote()); CHECK(api.deflate(s_zst.PtrBoth(), Z_FINISH).ValueOrDie() != Z_STREAM_ERROR); CHECK(sandbox.TransferFromSandboxee(\u0026amp;s_out).ok()); out-\u0026gt;resize(s_zst.data().avail_out); CHECK(api.deflateEnd(s_zst.PtrBoth()).ok());}
通过对比我们可以发现,在使用Sandboxed API时,需要额外的代码来设置沙箱本身以及将内存传入或传出到Sandboxee,但除此之外,代码流保持不变。
安装测试
我们只需要几分钟时间就可以启动并运行Sandboxed API。假设已经安装了Bazel:
sudo apt-get install python-typing python-clang-7 libclang-7-dev linux-libc-devgit clone github.com/google/sandboxed-api \u0026amp;\u0026amp; cd sandboxed-apibazel test //sandboxed_api/examples/stringop:main_stringop
上述操作会自动安装必要的依赖项,并运行测试项目。 更详细内容可查阅入门指南,尤其是关于Sandboxed API的使用示例。
未来计划
Sandboxed API以及Sandbox2已经被谷歌内部的很多团队所使用。虽然这个项目已经很成熟,但开发团队对其未来发展仍然充满期待,而不仅仅限于维护:
- 支持更多的操作系统—目前为止,只支持 Linux。 开发团队将研究如何把Sandboxed API引入到类Unix系统,如BSD(FreeBSD、OpenBSD),macOS以及Windows系统。移植到Windows系统是一项更复杂的任务,需要更多的基础工作。
- 新的沙盒技术—随着硬件虚拟化技术的普及,通过沙盒将代码限制在虚拟机中变成了可能。
- 系统构建—我们目前使用Bazel构建项目,包括处理依赖项。 但这并不是每个人都想要的方式,对CMake编译的支持是我们的首要任务。
- 推广—用Sandboxed API来保护开源项目,该项目属于补丁奖励计划,参与者将有机会获得开发奖励。
参与进来
除了对Sandboxed API和Sandbox2的优化,该项目开发团队一直致力于增加更多的功能:支持更多的编程运行时、不同的操作系统以及可替代的容器技术。
源码码请查看Sandboxed API GitHub仓库。欢迎大家积极参与,贡献代码或任何关于项目改进和扩展的建议。