2012-10-24 31 views
6

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

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

+6

无论好的或坏的想法,它都无法工作。即使没有[ASLR](http://en.wikipedia.org/wiki/Address_space_layout_randomization),您也必须非常幸运,始终让指针始终保持相同的地址。有了ASLR,你几乎可以保证它永远不会。 –

+0

没有加密的网络没有安全。调用函数没有某种承诺它会工作(因为指针*会*移动你)也不安全。使用协议。 – Incognito

+1

别担心,我不打算这样做,我只是想知道是否有可能以及在什么情况下。 – Jack

回答

6

一般而言,由于多种原因,这并不安全,但其工作量有限。首先,我假定您在协议中使用某种签名或加密来确保数据流的完整性;如果不是的话,那么你的安全问题已经严重已经,只能通过传递函数指针来加以解决。

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

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

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

1

这是什么指针?

它是指向一块静态程序存储器的指针吗?如果是这样,不要忘记它是一个地址,而不是偏移量,所以你首先需要相应地在两者之间进行转换。其次,如果它不是一块静态内存(即:在构建时创建的静态分配数组,而不是运行时),它根本不可能。

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

这是一个坏主意,不管你如何切片。这是消息传递和API的用途。

1

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

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

1

我不知道任何形式的RPC,可以让你通过网络发送一个指针(至少不需要像第一次投​​射到int之类的东西)。如果您在发送端转换为int,并将其转换回远端的指针,则与将任何其他任意int转换为指针的操作几乎相同:如果尝试对其进行取消引用,则为未定义行为。

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

1

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

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

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

1

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

但我可以为您提供一个场景,我99岁。99%肯定它会没有任何问题的工作:

  • 的Windows
  • 32位进程
  • 功能位于不具有拆迁信息
  • 有问题的模块,一个模块中已经加载&在客户端
  • 有问题的模块两侧100%相同初始化
  • 编译器不会做很疯狂的东西(如MSVC和GCC都应该是罚款)

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

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

上面的大部分内容只是确保函数在两边都具有相同的地址。剩下的唯一问题 - 至少我能想到 - 是:通过一个指针调用一个函数是安全的,我们通过对从网络接收到的一些字节进行初始化,如假设字节模式如果我们已经获得了所需功能的地址,那么我们会得到什么?这肯定是C++标准所不能保证的,但我不希望当前真实世界的编译器遇到任何实际问题。

这就是说,我会不是推荐这样做,除非安全性和健壮性真的不重要的情况。