【问题标题】:Verifying that memory has been initialized in C验证内存已在 C 中初始化
【发布时间】:2009-01-24 07:23:42
【问题描述】:

我编写了一个 API,它需要初始化一个上下文,然后将其传递给每个 API 调用。调用者为上下文分配内存,然后将其与其他参数一起传递给 init 函数,这些参数描述了他们希望以后的 API 调用如何表现。上下文是不透明的,所以客户不能在那里乱七八糟;它仅用于 API 函数的内部使用。

我遇到的问题是调用者正在分配上下文,但没有初始化它。因此,后续的 API 函数将无意义的垃圾当作真实的上下文来引用。

我正在寻找一种方法来验证传递给 API 函数的上下文是否已实际初始化。我不确定这是否可能。我想到的两个想法是:

  1. 使用预定义常量并将其存储在上下文的“魔术”字段中,以便在 API 调用时进行验证。
  2. 使用上下文内容的校验和,将其存储在“魔术”字段中并在调用时对其进行验证。

不幸的是,我知道这些选项中的任何一个都可能导致误报验证,要么是因为内存中的随机垃圾与“神奇”数字匹配,要么是因为上下文恰好与先前初始化的上下文占用相同的空间。我认为后一种情况的可能性更大。

这是否简单地归结为概率问题?在大多数情况下我可以避免误报,但不是全部?是否值得使用一个仅仅给我一个合理的准确概率的系统,或者这只会让调试其他问题变得更加困难?

【问题讨论】:

    标签: c memory-management


    【解决方案1】:

    我认为最好的解决方案是将 create()/delete() 函数添加到您的 API 并使用 create 来分配和初始化结构。您可以在结构的开头放置签名,以验证传递给您的指针是否指向使用 create() 分配的内存,并在释放内存之前使用 delete() 覆盖签名(或整个缓冲区)。

    您实际上无法避免 C 中的误报,因为调用者 malloc 的内存“发生”以您的签名开始;但是让你的签名相当长(比如 8 个字节)并且几率很低。不过,通过提供 create() 函数将分配从调用者手中解放出来将会有很长的路要走。

    而且,是的,您最大的风险是初始化的缓冲区在不使用 delete() 的情况下被释放,并且随后的 malloc 碰巧重用了该内存块。

    【讨论】:

      【解决方案2】:

      您的上下文变量目前可能是某种指向已分配内存的指针。取而代之的是,使其成为可以显式验证的令牌或句柄。每次初始化上下文时,您都会返回一个新令牌(不是实际的上下文对象)并将该令牌存储在内部列表中。然后,当客户稍后为您提供上下文时,您可以通过查看列表来检查它是否有效。如果是,则可以将令牌转换为实际上下文并使用,否则返回错误。

      typedef Context long;
      
      typedef std::map<Context, InternalContext> Contexts;
      Contexts _contexts;
      
      Context nextContext()
      {
        static Context next=0;
        return next++;
      }
      
      Context initialise()
      {
        Context c=nextContext();
        _contexts.insert(make_pair(c, new InternalContext));
        return c;
      }
      
      void doSomethingWithContext(Context c)
      {
        Contexts::iterator it=_ _contexts.find(c);
        if (it==_contexts.end())
          throw "invalid context";
        // otherwise do stuff with the valid context variable
        InternalContext *internalContext=*it.second;
      }
      

      使用此方法,不会存在无效内存访问的风险,因为您只会正确使用有效的上下文引用。

      【讨论】:

        【解决方案3】:

        查看 Matt Bishop 在Robust Programming 上的论文。使用票据或令牌(在某些方面类似于文件句柄,但也包括一个 nonce - 使用一次的数字)允许您的库代码确保它使用的令牌是有效的。实际上,您代表用户分配数据结构,并将票证传回给用户,每次调用您定义的 API 时都必须提供该票证。

        我有一些基于该系统的代码。标头包括 cmets:

        /*
        ** Based on the tickets in qlib.c by Matt Bishop (bishop@ucdavis.edu) in
        ** Robust Programming.  Google terms: Bishop Robust Nonce.
        ** http://nob.cs.ucdavis.edu/~bishop/secprog/robust.pdf
        ** http://nob.cs.ucdavis.edu/classes/ecs153-1998-04/robust.html
        */
        

        我还构建了一个基于竞技场的内存分配系统,使用票证来识别不同的竞技场。

        【讨论】:

          【解决方案4】:

          您可以定义一个新的 API 调用来获取未初始化的内存并以您需要的任何方式对其进行初始化。然后,客户端 API 的一部分是客户端必须调用上下文初始化函数,否则会导致未定义的行为。

          【讨论】:

            【解决方案5】:

            为了回避重用先前上下文的内存位置的问题,除了释放上下文之外,您还可以重置它并删除“神奇”数字,当然假设用户使用您的 API 释放上下文.这样当系统为下一个上下文请求返回相同的内存块时,幻数检查将失败。

            【讨论】:

              【解决方案6】:

              看看你的系统对未初始化的内存做了什么。 m$ 会:Uninitialized memory blocks in VC++

              【讨论】:

              • 这仅适用于调试版本 - 你不能依赖它来发布代码
              猜你喜欢
              • 1970-01-01
              • 2018-02-18
              • 1970-01-01
              • 2010-09-08
              • 2022-01-13
              • 1970-01-01
              • 2017-07-20
              • 2019-06-22
              • 1970-01-01
              相关资源
              最近更新 更多