覆盖内存中的数据

时间:2017-12-08 03:12:30

标签: memory-management garbage-collection ocaml

我在Ocaml写了一个密码管理器。为了使其尽可能安全,我想在内存中存储一​​个字符串(加密密钥),以便它可以被覆盖。 由于Ocaml通过值,并且有一个垃圾收集器,这已经证明是困难的。我加密所有缓冲区和变量,但我仍然需要存储“会话密钥”才能执行此操作。为了防止这种情况被自动密钥搜索程序检测到或进入交换,它是使用随机增量从缓冲区中的一堆随机数据组合而成。实际上,我需要的只是一个变量,可以在组合密钥传递到Nocrypto库之前几秒钟被覆盖...参考是否适用于此?

根据这个cornell "Refs and Arrays" page,refs是可变的,并且与C中的指针类似地工作。话虽如此,我还发现了一个堆栈溢出答案,讨论Ocaml refs,其中答案提到“它们表现得像指向新分配的内存“。这是否意味着每一次,它只是在内存中分配一个新东西,而不是实际改变内存中的东西?如果是这样,你就无法“覆盖”裁判。

我遇到的其他可能的解决方案是Bigarrays和“自定义块”。我不完全确定“自定义块”是否实际上是在垃圾收集范围之外分配的。他们似乎习惯于访问外部C代码。他们被垃圾收集器复制了吗?它们会被“覆盖”吗?在内存中也存在“不透明字节”和不透明对象的概念。我很难绕过这一切如何融合在一起。关于堆栈溢出的内存中自定义块的一个有用但令人困惑的(对我而言)在这里:Are custom blocks ever copied in memory?答案说它们可以被移动。即便如此,他们会被覆盖吗?

最后一个可能的解决方案是使用像Nocrypto库这样的Cstruct来存储它。他们在这个github问题中讨论它:Secret material erasure提问者声明:

  

“当然,大多数关键材料是Cstruct.t,它是一个Bigarray.Array1.t,它在GC堆之外分配”

这是否正确?如果是这样,我似乎无法找到实际执行此操作的源文件。我对Ocaml和一般的函数式编程都很陌生。如果你很好奇,我的程序位于github:ocaml-pass

2 个答案:

答案 0 :(得分:14)

TL; DR;

您不得在OCaml堆中存储任何秘密信息。因此,您必须永远不要将您的秘密复制到任何OCaml堆分配的值中,因此,既不使用字节,也不使用字符串或数组,甚至是临时的。

OCaml内存模型简介

OCaml值统一表示为标记机器字。单词的最低有效位用作标记,用于区分指针(tag = 0)和立即值(tag = 1)。因此,值始终是固定大小,并且是指针或立即数。

立即值将其数据存储在字的最重要部分,即32位系统中的31位,以及64位系统中的63位。指针将数据存储在块中,这些块位于所谓的 OCaml堆中。 OCaml堆是由垃圾收集器(GC)管理的一组块。块是以标题为前缀的数据块。标头指定GC使用的数据大小和一些其他元信息。块可以包含OCaml值(指针或立即值)或不透明数据。

总结一下。所有OCaml值都表示为机器字,可以直接在数据中存储数据,也可以是指向堆分配块的指针。每个指针指向一个且仅指向一个块。几个指针可能指向同一个块。这些值被认为是物理上相等的。任何指针都没有指向某些块。这些块被称为死亡,并由GC回收。

OCaml垃圾收集器简介

GC通过分配,移动和取消分配块来管理块。 GC本身使用一个竞技场,它可以从C内存分配器(malloc)获得,也可以通过memmap syscall直接从内核获取(取决于特定的系统和运行时)。

GC是分代的,这意味着值首先在名为 minor heap 的堆的特殊区域中分配。次要堆是固定大小的连续内存区域,在运行时用三个指针表示:指针beg到次要堆的开头,指针end到次要堆的末尾,指针cur到次要堆的空闲区域的开头。分配块时,cur会增加块的大小。然后用数据初始化块。当次要堆中没有更多可用空间时(即,end - cur小于所需的块大小),则触发次要GC循环。 GC分析存储在Minor Heap中的所有块,并复制至少一个指向 Major Heap 的指针引用的所有块。之后,cur指针设置为beg

在Major Heap中,在一个名为 compaction 的过程中,也可能会多次复制一个块。压实器可能会尝试重新排列其竞技场中的块,以实现更紧凑的堆表示。

安全后果

由于OCaml GC是移动GC,它可以任意复制堆分配的数据。虽然它被称为移动,但实际上它仍然只是复制。即,当一个块从次要堆移动到主堆时,它实际上只是位复制,因此是重复的。次要堆中的块模型可以存在任意时间量,直到被一些新分配的值覆盖。在压缩过程中移动对象时,也会复制该对象,并且在此过程中可能会或可能不会覆盖该对象。当然,不言而喻,一旦一个块死了,它仍然可以在堆中存活一段任意时间,直到GC重用。

这一切都意味着,如果一个秘密在OCaml堆中结束,它将会疯狂,因为GC可以以任意且相当不可预测的方式多次复制它。因此,我们只能以立即值或不受GC控制的区域存储秘密。如前所述,作为指针的所有OCaml值始终指向OCaml堆中的块。块可以直接包含数据,也可以包含指针本身,它将指向内存堆外部。所谓的自定义块,可能会也可能不会将它们的信息存储在OCaml堆中,它会依赖于每个自定义块的特定表示。例如,Bigarray库提供自定义块,用于将其有效负载存储在OCaml堆之外。因此,Bigarray是一个自定义块,它有两个字段:指针和大小。它是一个不透明的块,即GC永远不会将这两个值视为OCaml值,并且永远不会跟随大小和指针。指针指向的数据位于OCaml堆之外,由mallocmemmap分配(实际上,它可以是任意整数,甚至指向堆栈或静态数据) ,它并不重要,只要我们将bigarray视为ptr,len对。

这一切都使Bigarrays成为存储秘密的理想选择。我们可以肯定的是,它们不会被GC移动,我们可以覆盖它们以防止信息在被释放后泄漏。

进一步考虑

我们应该小心,不要让秘密从我们安全的地方复制到OCaml堆中。这意味着,即使我们的主存储器是安全的大型存储器,如果我们将其内容复制到OCaml字符串,信息仍将泄漏。因此,如果我们首先将信息读入OCaml字符串,然后将其复制到bigarray中,则信息仍会泄漏。因此,任何使用OCaml堆分配值的接口都是不安全的,不应使用。例如,我们不能使用OCaml通道来读取或写入秘密(我们应该依赖于Unix模块提供的内存映射或无缓冲IO)。而且,每当您从Bigarray获得string数据类型时,您都会复制您的数据,包含所有后果。

答案 1 :(得分:0)

我会使用类型bytes的值,本质上是一个可变的字节数组:

# let buffer = Bytes.make 16 'x';;
val buffer : bytes = "xxxxxxxxxxxxxxxx"
# Bytes.set buffer 0 'T';;
- : unit = ()
# buffer;;
- : bytes = "Txxxxxxxxxxxxxxx"
# Bytes.fill buffer 0 16 ' ';;
- : unit = ()
# buffer;;
- : bytes = "                "

完成后,您可以使用Bytes.fill覆盖。