4

我刚刚观看了去年32C3关于铁路系统安全注意事项的演讲。在第 25 分钟,演讲者简短地谈到了艾达。具体来说,他说:

典型的 Ada 实现有一种称为“(tramp / trunk / ?)lines”的机制。这意味着它将在 [the] 堆栈上执行代码,这对 C 程序来说不是很好。[...] 如果您想将 Ada 代码与 C 库链接,其中一种安全机制将不起作用。

这是演讲的各个部分的链接(YouTube)。是背景中的幻灯片。如您所见,我不确定其中一个词。也许是蹦床


现在我直截了当地:这句话有道理吗?如果是这样,任何人都可以详细说明 Ada 语言的这个神秘特性及其明显影响的安全机制吗?

到目前为止,我一直认为代码位于代码段(也称为“文本”)中,而数据(包括堆栈)则位于不同内存位置的数据段中(如图所示)。在 Ada 中阅读有关内存管理的内容表明它应该不会有太大的不同。

虽然有一些方法可以规避这样的布局(参见例如这个“ C on stack ”问题和这个“ C on heap ”答案),但我相信现代操作系统通常会通过可执行空间保护来阻止这种尝试,除非堆栈被明确设为可执行。- 但是,对于嵌入式系统,如果代码没有保存在 ROM 上,它可能仍然是一个问题(有人可以澄清吗?)。

4

3 回答 3

8

它们被称为“蹦床”。这是我对它们用途的理解,虽然我不是 GNAT 专家,所以我的一些理解可能是错误的。

背景:Ada(与 C 不同)支持嵌套子程序。嵌套子程序能够访问封闭子程序的局部变量。例如:

procedure Outer is
    Some_Variable : Integer;

    procedure Inner is
    begin
        ...
        Some_Variable := Some_Variable + 1;
        ...

由于每个过程都有自己的堆栈帧,其中包含自己的局部变量,因此必须有一种方法Inner可以获取Outer的堆栈帧,以便它可以Some_Variable在调用时访问 ,Outer或者Inner调用Outer其他一些嵌套的子程序调用Inner。一个典型的实现是将隐藏参数传递给Inner,通常称为“静态链接”,它指向Outer的堆栈帧。现在Inner可以使用它来访问Some_Variable.

当您使用 时,乐趣就开始Inner'Access了,这是一种access procedure类型。这可用于将地址存储在类型Inner的变量中access procedure。其他子程序稍后可以使用该变量间接调用程序。如果使用'Access,则必须在内部声明该变量Outer——您不能将过程访问存储在外部的变量中Outer,因为之后有人可以在Outer退出之后调用它,并且其局部变量不再存在。GNAT 和其他 Ada 编译器具有'Unrestricted_Access绕过此限制的属性,因此Outer可以调用一些间接调用的外部子程序Inner. 但是你在使用它的时候必须非常小心,因为如果你在错误的时间调用它,结果会很严重。

无论如何,问题出现是因为 whenInner'Access存储在变量中,后来用于Inner间接调用,调用时必须使用带有静态链接的隐藏参数Inner。那么间接调用者如何知道要传递什么静态链接呢?

一种解决方案(Irvine Compiler 的,可能还有其他的)是让这种访问类型的变量有两个值——过程地址和静态链接(所以 anaccess procedure是一个“胖指针”,而不是一个简单的指针)。然后对该过程的调用将始终传递静态链接,以及其他参数(如果有)。[在 Irvine Compiler 的实现中,如果指针内部的静态链接实际上指向一个全局过程,则该指针内部的静态链接将为空,因此代码知道在这种情况下不传递隐藏参数。] 缺点是这在以下情况下不起作用将过程地址作为回调参数传递给 C 例程(这在位于 C 图形库(如 gtk)之上的 Ada 库中非常常见)。C 库例程不知道如何处理这样的胖指针。

GNAT 使用或曾经使用过蹦床来解决这个问题。基本上,当它看到 时Inner'Unrestricted_Access',它会即时生成新代码(“蹦床”)。此蹦床Inner使用正确的静态链接调用(链接的值将嵌入代码中)。然后访问值将是一个细指针,只有一个地址,即蹦床的地址。因此,当 C 代码调用回调时,它会调用蹦床,然后蹦床将隐藏参数添加到参数列表并调用Inner.

这解决了问题,但在堆栈上生成蹦床时会产生安全问题。

编辑:当我用现在时提到 GNAT 的实现时我犯了错误。几年前我最后一次看到这个,我真的不知道 GNAT 是否仍然这样做。[Simon 对此有更好的信息。] 顺便说一下,我确实认为可以使用蹦床但不将它们放在堆栈上,我认为这会减少安全问题。当我上次对此进行调查时,如果我没记错的话,Windows 已经开始阻止堆栈上的代码被执行,但它也允许程序请求可用于动态生成可执行代码的内存。

于 2016-01-25T03:34:23.657 回答
4

2003 年关于 Ada 安全应用程序的演示(D. Wheeler,SigAda 2003)在第 7 页支持这一点:(引用)

Ada 和安全性如何匹配不佳?
...
Ada 实现通常需要在堆栈上执行代码(“蹦床”,例如用于访问嵌套子程序的值)。

换句话说,对于函数指针,子程序嵌套在其他子程序中。

(推测:大概这些函数指针在堆栈上,因此当您离开外部子程序的范围时它们超出范围)

然而

快速搜索还显示了这条 gcc 邮件列表消息:
[Ada] remove trampolines from front end dated 2007,它指的是通过精确地消除这个有问题的功能,使 Gnat 可执行文件能够在具有 DEP(数据执行保护)的系统上运行。

这不是一个权威的答案,但似乎虽然“典型的”Ada 实现确实(或确实)这样做了,但至少在 2007 年的这一边可能并非如此,这要归功于更新硬件上的保护系统推动了必要的更改编译器。

或者:曾经绝对正确,但今天可能不再正确,至少对于 Gnat 而言。

我会欢迎真正专家的更深入的回答......

编辑:亚当的彻底回答表明这对 Gnat 仍然是正确的,所以我的乐观情绪应该得到​​缓和,直到进一步的信息。

于 2016-01-24T23:33:20.933 回答
2

FSF GCC 5 在此处记录的情况下生成蹦床。当实际使用蹦床时,这会成为问题;特别是当代码采用’Access’Unrestricted_Access嵌套子程序时。

您可以使用以下方法检测您的代码何时执行此操作

pragma Restrictions (No_Implicit_Dynamic_Code);

它需要用作配置编译指示(尽管您不一定会在编译时收到警告,请参阅PR 67205)。此处记录了编译指示。

您过去常常通过将配置编译指示包含在一个文件中来设置它们gnat.adc。如果您使用的是gnatmake,您也可以使用 switch -gnatec=foo.adcgprbuild没有看到gnat.adcpackage 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
于 2016-01-25T11:51:01.820 回答