6

我正在考虑一些我必须在 C++ 中实现的 RPC 代码,我想知道通过网络将它发送到相同的二进制代码是否安全(以及在哪些假设下)(假设它完全相同并且它们运行相同)建筑学)。我想虚拟内存应该在这里有所作为。

我只是出于好奇才问它,因为无论如何它都是一个糟糕的设计,但我想知道它在理论上是否可行(以及它是否可以扩展到其他类型的指向静态数据的指针,而不是程序可能包含的函数)。

4

6 回答 6

6

一般来说,由于许多原因,它并不安全,但在有限的情况下它会起作用。首先,我将假设您在协议中使用某种签名或加密来确保数据流的完整性;如果没有,那么您已经遇到了严重的安全问题这些问题只能通过传递函数指针来复杂化。

如果完全相同的程序二进制文件在连接的两端运行,如果函数在主程序中(或在从静态库链接的代码中)而不是在共享库中,并且如果程序不是作为位置构建的-独立可执行文件(PIE),那么函数指针在两端将是相同的,并且通过网络传递它应该可以工作。请注意,这些是非常严格的条件,必须作为使用程序的一部分记录在案,而且它们非常脆弱;例如,如果有人升级了一侧的软件,同时忘记升级连接另一侧的版本,事情就会严重而危险地中断。

我会完全避免这种类型的低级 RPC 以支持更高级别的命令结构或抽象 RPC 框架,但如果你真的想这样做,一个稍微安全一点的方法是传递函数名并使用dlsym或等效于查看他们起来。如果符号驻留在主程序二进制文件中而不是库中,则根据您的平台,您可能需要-rdynamic(GCC) 或类似选项以使它们可用于dlsym. libffi也可能是抽象这一点的有用工具。

此外,如果您想避免依赖dlsymor libffi,您可以将自己的“符号表”硬编码在二进制文件中,作为static const线性表或哈希表将符号名称映射到函数指针。ELF 中用于此目的的哈希表格式非常易于理解和实现,因此我可能会考虑基于此实现您的实现。

于 2012-10-24T23:48:54.710 回答
1

它指向什么?

它是指向一块静态程序内存的指针吗?如果是这样,请不要忘记它是地址,而不是偏移量,因此您首先需要相应地在两者之间进行转换。

其次,如果它不是一块静态内存(即:在构建时而不是运行时创建的静态分配数组),那根本不可能。

最后,您如何确保两段代码相同?两个二进制文件是否相同(例如:)diff -a binary1 binary2。即使它们是位相同的,取决于每台机器上的虚拟内存管理,整个程序的程序内存段可能不存在于单个页面中,或者跨多个页面的对齐方式对于每个系统可能不同。

这真是个坏主意,不管你怎么切。这就是消息传递和 API 的用途。

于 2012-10-24T23:30:46.297 回答
1

这是高度依赖于系统的。在具有虚拟寻址的系统上,每个进程每次执行时都认为它在同一个地址上运行,这可能适用于可执行代码。Darren Kopp 关于 ASLR 的评论和链接很有趣——快速阅读 Wikipedia 文章表明 Linux 和 Windows 版本专注于数据而不是可执行代码,除了 Linux 上的“面向网络的守护进程”,而在 Windows 上,它仅适用于“特别链接为启用 ASLR”。

尽管如此,静态链接最好确保“相同的二进制代码”——如果加载了不同的共享对象/库,或者它们以不同的顺序加载(可能是由于动态加载——dlopen由配置文件或命令行参数中的不同顺序驱动等等)你可能已经吃饱了。

于 2012-10-24T23:31:16.590 回答
1

我不知道有任何形式的 RPC 可以让你通过网络发送一个指针(至少不做类似强制转换之类的事情int)。如果您确实int在发送端转换为,并将其转换回远端的指针,则与将任何其他任意指针转换int为指针几乎相同:如果您尝试取消引用它,则为未定义的行为。

通常,如果您传递一个指向 RPC 函数的指针,它将被编组——即,它指向的数据将被打包、发送、放入内存,以及指向传递给的数据的本地副本的指针另一端的功能。这就是 IDL 变得有点丑陋的原因/方式的一部分——您需要告诉它如何确定当/如果您传递指针时要通过线路发送多少数据。大多数人都知道以零结尾的字符串。对于其他类型的数组,您通常需要指定数据的大小(不知何故)。

于 2012-10-24T23:32:35.707 回答
1

通过网络发送指针通常是不安全的。两个主要原因是:

  • 可靠性:由于程序或其库或内存中动态分配的对象的不同位置,数据/函数指针可能不会指向另一台机器上的同一实体(数据结构或函数)。可重定位代码 + ASLR 可能会破坏您的设计。至少,如果你想指向一个静态分配的对象或一个函数,如果你的平台是 Windows 或在你的任何操作系统上做类似的事情,你应该将它的偏移量发送到图像库。
  • 安全性:如果您的网络是开放的并且有黑客(或者他们已经闯入了您的网络),他们可以冒充您的第一台机器并使第二台机器挂起或崩溃,从而导致拒绝服务,或者执行任意代码并获得访问权限敏感信息或篡改或劫持机器并将其变成发送垃圾邮件或攻击其他计算机的邪恶机器人。当然,这里有对策也有对策,但是……

如果我是你,我会设计一些不同的东西。而且我会确保传输的数据是不重要的或加密的,并且接收部分在使用它之前对其进行必要的验证,因此没有缓冲区溢出或执行任意事情。

于 2012-10-24T23:58:04.497 回答
1

如果您正在寻找一些正式的保证,我无法帮助您。您必须查看您正在使用的编译器和操作系统的文档 - 但是我怀疑您会找到必要的保证 - 可能除了一些专门的嵌入式系统操作系统。

但是,我可以为您提供一种情况,我 99.99% 确信它可以正常工作:

  • 视窗
  • 32位进程
  • 函数位于没有重定位信息的模块中
  • 有问题的模块已经在客户端加载和初始化
  • 有问题的模块在两边都是 100% 相同的
  • 一个不会做非常疯狂的事情的编译器(例如 MSVC 和 GCC 都应该没问题)

如果要调用 DLL 中的函数,可能会遇到问题。根据上面的列表,模块(=DLL)可能没有重定位信息,这当然使得它不可能重定位(这是我们需要的)。不幸的是,这也意味着加载 DLL 将失败,如果“首选加载地址”被其他东西使用。所以这有点冒险。

但是,如果该函数驻留在 EXE 中,则应该没问题。32 位 EXE 不需要重定位信息,并且大多数不包括它(MSVC 默认设置)。顺便说一句:ASLR 在这里不是问题,因为 a) ASLR 仅移动标记为要移动的模块,并且 b) ASLR 无法在没有重定位信息的情况下移动 32 位 Windows 模块,即使它想要移动。

以上大部分内容只是确保该函数在双方都具有相同的地址。剩下的唯一问题——至少我能想到的——是:假设字节模式是如果我们获取了所需函数的地址,我们会得到相同的结果吗?这肯定是 C++ 标准不能保证的,但我不希望当前的实际编译器会出现任何实际问题。

话虽如此,我建议这样做,除非安全性和稳健性真的不重要。

于 2012-10-25T00:11:36.540 回答