我刚刚从security considerations for railway systems观看last year's 32C3的演讲。 在第25分钟,演讲者简要介绍了阿达。他特别说:
典型的Ada实现有一种称为"(tramp / trunk /)的机制 ?)line"。这意味着它将在[堆栈]上执行代码 不太适合C程序。并且[...]如果您想将Ada代码与C链接 图书馆,其中一个安全机制不起作用。
以下是对话各部分的link(YouTube)。 This是背景中的幻灯片。如你所见,我不确定其中一个词。也许是trampolines?
现在我的直言不讳问题:这句话有什么道理吗?如果是这样,任何人都可以详细说明这种神秘的Ada语言特征及其明显影响的安全机制吗?
到目前为止,我一直认为代码存在于代码段(又名&#34; text&#34;)中,而数据(包括堆栈)则放在数据段< / em>在不同的内存位置(如this graphic所示)。关于memory management in Ada的阅读表明它应该没有太大的不同。
虽然有办法绕过这样的布局(参见例如此&#34; C on stack&#34;问题和这个&#34; C on heap&#34;答案),我相信现代操作系统通常会通过executable space protection阻止此类尝试,除非堆栈明确made executable。 - 但是,对于嵌入式系统,如果代码没有保存在ROM上,那么它可能仍然是一个问题(任何人都可以澄清吗?)。
答案 0 :(得分:8)
他们被称为“蹦床”。这是我对它们的用途的理解,虽然我不是GNAT专家,所以我的一些理解可能是错误的。
背景:Ada(与C不同)支持嵌套子程序。嵌套的子程序能够访问封闭子程序的局部变量。例如:
procedure Outer is
Some_Variable : Integer;
procedure Inner is
begin
...
Some_Variable := Some_Variable + 1;
...
由于每个过程都有自己的堆栈帧,它拥有自己的局部变量,因此Inner
必须有一种方法可以获得Outer
的堆栈帧,以便它可以访问{{ 1}},当Some_Variable
调用Outer
或Inner
调用其他一些调用Outer
的嵌套子程序时。典型的实现是将隐藏参数传递给Inner
,通常称为“静态链接”,指向Inner
的堆栈帧。现在Outer
可以使用它来访问Inner
。
当您使用Some_Variable
类型的Inner'Access
时,乐趣就开始了。这可用于将access procedure
的地址存储在Inner
类型的变量中。其他子程序以后可以使用该变量间接调用produredure。如果您使用access procedure
,则必须在'Access
内声明变量 - 您无法将过程访问存储在Outer
之外的变量中,因为之后有人可以在之后调用它Outer
已退出,其局部变量不再存在。 GNAT和其他Ada编译器具有Outer
属性,可以绕过此限制,因此'Unrestricted_Access
可以调用间接调用Outer
的外部子程序。但是在使用它时你必须非常小心,因为如果你在错误的时间调用它,结果将会造成严重破坏。
无论如何,问题出现是因为当Inner
存储在变量中并且后来用于间接调用Inner'Access
时,必须在调用Inner
时使用带有静态链接的隐藏参数。那么间接调用者如何知道要传递的静态链接呢?
一个解决方案(Irvine编译器,可能还有其他)是使这种访问类型的变量有两个值 - 过程地址和静态链接(所以Inner
是一个“胖指针”,而不是一个简单的指针)。然后,除了其他参数(如果有的话)之外,对该过程的调用将始终通过静态链接。 [在Irvine编译器的实现中,如果指针内的静态链接实际指向全局过程,则该指针内的静态链接将为null,以便代码知道在这种情况下不会传递隐藏参数。]缺点是这不起作用将过程地址作为回调参数传递给C例程(在位于C图形库之上的Ada库中非常常见的事情,如gtk)。 C库例程不知道如何处理像这样的胖指针。
GNAT使用,或一次性使用,蹦床来解决这个问题。基本上,当它看到access procedure
时,它会动态生成新代码(“蹦床”)。此蹦床使用正确的静态链接调用Inner'Unrestricted_Access'
(链接的值将嵌入代码中)。然后访问值将是一个瘦指针,只有一个地址,这是蹦床的地址。因此,当C代码调用回调时,它会调用trampoline,然后将隐藏参数添加到参数列表并调用Inner
。
这解决了问题,但在堆栈上生成蹦床时会产生安全问题。
编辑:在提及现在时的GNAT实施时我犯了错误。我几年前看过这个问题,我真的不知道GNAT是否仍然以这种方式做事。 [西蒙有更好的信息。]顺便说一句,我认为可以使用蹦床而不是把它们放在堆栈上,我认为这会减少安全问题。当我上次调查时,如果我没记错,Windows已经开始阻止执行堆栈上的代码,但它也允许程序请求可用于动态生成可执行代码的内存。
答案 1 :(得分:4)
2003年关于安全应用程序的Ada的演示文稿(D. Wheeler, SigAda 2003)在第7页支持这一点:(引用)
Ada和安全性如何匹配不佳?
...
Ada实现通常需要在堆栈上执行代码(“trampolines”,例如对嵌套子程序的访问值)。
在其他(C)字中,对于函数指针,子程序嵌套在其他子程序中。
(推测:可能这些函数指针在堆栈上,因此当你离开外子程序的范围时它们会超出范围)
无论其
快速搜索还显示了此gcc邮件列表消息:
[Ada] remove trampolines from front end
2007年,它指的是通过消除这个有问题的功能,使Gnat可执行文件在具有DEP(数据执行保护)的系统上运行。
这不是一个权威的答案,但似乎虽然“典型的”Ada实施做了(或做到了),但至少Gnat 2007年的这一方可能不是这样,这要归功于新硬件上的保护系统驱动对编译器进行必要的更改。
或者:一次肯定是真的,但今天可能不再是真的,至少对于Gnat来说。
我欢迎来自真正专家的更深入的答案......
编辑:亚当的彻底回答表明Gnat仍然如此,所以我的乐观态度应该得到缓和,直到获得更多信息。
答案 2 :(得分:2)
FSF GCC 5在记录here的情况下产生蹦床。当蹦床实际上使用时,这就成了问题。特别是,当代码获取嵌套子程序的’Access
或’Unrestricted_Access
时。
您可以使用
检测代码何时执行此操作pragma Restrictions (No_Implicit_Dynamic_Code);
需要用作配置编译指示(尽管在编译时不一定会收到警告,请参阅PR 67205)。该编译指示记录在here。
您以前只需将它们包含在文件gnat.adc
中即可设置配置编译指示。如果您使用的是 gnatmake ,也可以使用开关-gnatec=foo.adc
。 gprbuild 没有看到gnat.adc
;而是在项目文件中的package Builder
中设置全局配置,
package Builder is
for Global_Configuration_Pragmas use "foo.adc";
end Builder;
违规最终会出现像
这样的编译错误$ gprbuild -P trampoline tramp
gcc -c tramp.adb
tramp.adb:26:12: violation of restriction "No_Implicit_Dynamic_Code" at /Users/simon/cortex-gnat-rts/test-arduino-due/gnat.adc:1