通过网络发送指向静态函数的指针是否安全?

时间:2012-10-24 23:26:13

标签: c++ c function-pointers

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

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

6 个答案:

答案 0 :(得分:6)

一般来说,出于多种原因它并不安全,但是它的作用有限。首先,我将假设您在协议中使用某种签名或加密来确保数据流的完整性;如果没有,你有严重的安全问题已经只能通过传递函数指针来复合。

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

我会完全避免这种类型的低级RPC支持更高级别的命令结构或抽象的RPC框架,但是如果你真的想这样做,一种稍微安全的方法是传递函数名称并使用{ {1}}或等同于查找它们。如果符号位于主程序二进制文件而不是库中,那么根据您的平台,您可能需要dlsym(GCC)或类似选项才能将它们提供给-rdynamicdlsym也可能是用于抽象的有用工具。

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

答案 1 :(得分:1)

它指向什么?

它是指向一块静态程序存储器的指针吗?如果是这样,请不要忘记它是一个地址,而不是一个偏移,所以你首先需要相应地转换它们。

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

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

无论你如何切片,这都是一个坏主意。这就是消息传递和API的用途。

答案 2 :(得分:1)

这是高度依赖系统的。在具有虚拟寻址的系统上,每个进程在每次执行时都认为它在同一地址运行,这可能对可执行代码起作用。 Darren Kopp关于ASLR的评论和链接很有意思 - 快速阅读维基百科的文章表明Linux& Windows版本专注于数据而不是可执行代码,除了Linux上的“面向网络的守护进程”,并且在Windows上它仅在“专门链接到启用ASLR”时才适用。

仍然,“相同的二进制代码”最好通过静态链接保证 - 如果加载了不同的共享对象/库,或者它们以不同的顺序加载(可能是由于动态加载 - dlopen - 由不同的驱动在配置文件或命令行args等中排序。)你可能已经塞满了。

答案 3 :(得分:1)

我不知道任何形式的RPC会让你通过网络发送一个指针(至少没有先做过像int这样的事情)。如果你确实在发送端转换为int,并将其转换回远端的指针,那么将任何其他任意int转换为指针几乎相同:如果未定义的行为,则你试图取消引用它。

通常情况下,如果你将指针传递给RPC函数,它将被编组 - 即它指向的数据将被打包,发送,放入内存,以及指向该本地副本的指针。数据传递给另一端的函数。这就是为什么/如何让IDL变得有点丑陋的一部分 - 你需要告诉它如何通过指针传递有多少数据通过线路发送。大多数人都知道零终止字符串。对于其他类型的数组,通常需要指定数据的大小(以某种方式或其他方式)。

答案 4 :(得分:1)

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

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

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

答案 5 :(得分:1)

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

但是,我可以为您提供一个方案,我99.99%确定它可以正常工作:

  • 32位进程
  • 功能位于没有重定位信息的模块中
  • 相关模块已加载&在客户端初始化
  • 有问题的模块双方100%相同
  • 一个不会做很疯狂的编译器(例如MSVC和GCC都应该没问题)

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

如果函数位于EXE中,那么你应该没问题。 32位EXE不需要重定位信息,大多数不包含它(MSVC默认设置)。 BTW:ASLR不是问题,因为a)ASLR只移动被标记为想要移动的模块,并且b)ASLR无法移动32位Windows模块而没有重定位信息,即使它想要。

上述大多数内容只是确保该功能在两侧都具有相同的地址。唯一剩下的问题 - 至少是我能想到的 - 是:通过我们通过记忆我们从网络收到的一些字节来初始化的指针调用函数是否安全,假设字节模式是否与我们获取所需函数的地址时相同?这肯定是C ++标准所不能保证的,但我不认为现有的实际编译器存在任何实际问题。

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