【问题标题】:Executable Ada code on the stack堆栈上的可执行 Ada 代码
【发布时间】:2016-05-01 03:29:38
【问题描述】:

我刚刚观看了来自last year's 32C3 的关于security considerations for railway systems 的演讲。 在第 25 分钟,演讲者简短地谈到了艾达。他具体说:

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

这里有一个link (YouTube) 到谈话的各个部分。 This 是背景中的幻灯片。如您所见,我不确定其中一个词。也许是trampolines


现在我直截了当的问题:这句话有什么道理吗?如果是这样,谁能详细说明 Ada 语言的这个神秘特性以及它明显影响的安全机制?

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

虽然有一些方法可以规避这种布局(例如,请参阅此“C on stack”问题和此“C on heap”答案),但我相信现代操作系统通常会通过executable space protection 阻止此类尝试,除非堆栈明确made executable。 - 但是,对于嵌入式系统,如果代码没有保存在 ROM 上,这可能仍然是个问题(谁能澄清一下?)。

【问题讨论】:

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


    【解决方案1】:

    它们被称为“蹦床”。这是我对它们用途的理解,虽然我不是 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 可以调用一些 outside 子程序间接调用 Inner。但是你在使用的时候一定要非常小心,因为如果你在错误的时间调用它,后果会很严重。

    不管怎样,问题就出现了,因为当Inner'Access存储在一个变量中,后来用来间接调用Inner时,调用Inner时必须使用带有静态链接的隐藏参数。那么间接调用者怎么知道要传递什么静态链接呢?

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

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

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

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

    【讨论】:

    • 在 GCC 5.2.0 上为 OS X 生成的 x86_64 汇编代码调用 __enable_execute_stack;我发现了一些相关的东西here
    【解决方案2】:

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

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

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

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

    但是

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

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

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

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

    编辑:Adam 的彻底回答表明 Gnat 仍然如此,所以我的乐观情绪应该有所缓和,直到进一步的信息。

    【讨论】:

    • 我将不得不编辑我的答案...我不确定 GNAT 是否仍然如此。
    【解决方案3】:

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

    您可以通过使用来检测您的代码何时执行此操作

    pragma Restrictions (No_Implicit_Dynamic_Code);
    

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

    过去,您只需将配置 pragma 包含在文件 gnat.adc 中即可设置它们。如果你使用 gnatmake,你也可以使用开关 -gnatec=foo.adcgprbuild 看不到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
    

    【讨论】:

    • 我对删除我的答案有两种看法:阅读它和您链接的 PR 上的评论 #4,Adacore 和 FSF Gnat 编译器在这方面可能存在差异,以及一些改进的激动;-) 无论如何,我相信No_Implicit_Dynamic_Code 应该解决(或至少检测到)问题。
    • @Brian,该评论来自 2015 年 9 月,因此 FSF GCC 中可能会在一段时间内出现更改.. 可能不是版本 6。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 2011-09-22
    • 2020-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    相关资源
    最近更新 更多