堆栈上的可执行Ada代码

时间:2016-01-24 22:12:23

标签: stack ada scada data-segment safety-critical

我刚刚从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上,那么它可能仍然是一个问题(任何人都可以澄清吗?)。

3 个答案:

答案 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调用OuterInner调用其他一些调用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