【问题标题】:How to deal with the file size changing between a stat and a subsequent mmap?如何处理 stat 和后续 mmap 之间的文件大小变化?
【发布时间】:2015-02-16 19:35:47
【问题描述】:

要确定mmap 调用的大小,我使用stat,并将获取的大小作为要创建的映射的相应长度传递。如果调用之间的文件大小发生变化,我的理解是它要么只映射文件的一部分,要么在缩小的情况下我将没有映射的实际大小来引用并在访问范围时获得SIGBUS这不是底层对象的一部分。

如何干净利落地处理这种情况?

【问题讨论】:

  • mmap后文件大小发生变化,你想怎么处理?
  • 对不起,我应该澄清一下我正在使用带有 PROT_READ 和 MAP_PRIVATE 的 mmap,我的印象是 mmap 调用过程外部的写入不会影响映射...?这不正确吗?
  • 否 - 即使在 mmap() 调用可能在您的进程中可见之后写入文件。 PROT_READ 仅表示您的进程不允许写入内存; MAP_PRIVATE 意味着您的进程所做的任何写入都不会反映在文件中;它不保证写入文件不会影响您的进程(尽管在某些实现中可能是这种情况)。
  • 谢谢,我发现我错过了 POSIX 中的以下行:“未指定在建立 MAP_PRIVATE 映射后对基础对象所做的修改是否可以通过 MAP_PRIVATE 映射可见”。

标签: c linux file posix mmap


【解决方案1】:

简短的回答是,您通常无法防范此类事情。如果文件可以在stat()mmap() 之间更改长度(可能是由于某些外部进程),为什么不能在mmap() 之后更改长度?换句话说(下面有详细的解释),你所要求的并不足以保护你免受对手的伤害。

如果你真的想检查 mmap 之后的映射是否仍然有效(即文件没有变短),你可以(ab)在 linux 上使用 remap_file_pages(未经测试);但是,由于上述原因,如果有人在不久之后将其截断,那将无济于事。

另请参阅手册页中的此警告:

未指定更改映射的基础文件大小对对应于文件添加或删除区域的页面的影响。

您需要某种形式的文件锁定来保护自己。正如您在 cmets 中所说,您正在与一个不尊重咨询锁定的对手打交道,除非您使用(罕见的)mandatory locking

我认为这里完全安全的唯一方法是:

  • 不要使用mmap
  • 更改文件的权限或复制文件,使攻击者无法访问它/副本。

为了进一步说明攻击者在你mmap之后改变文件长度的问题,考虑下面的测试程序。这将创建一个长度为 LONGFILE 的文件,打开它,然后:

  • 模拟对手截断它,然后mmap就是它;或
  • mmap 就是它,然后模拟对手截断它

两个实例中,都会产生分段错误。因此,如您所说,如果您担心您的对手可能会在您打开文件后更改文件的长度,那么您根本不应该mmap'ing 它。

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


/*
  void *mmap(void *addr, size_t length, int prot, int flags,
       int fd, off_t offset);
  int munmap(void *addr, size_t length);
*/

#undef TRUNCATE_BEFORE_MMAP
#define TRUNCATE_AFTER_MMAP
char *testfile = "/tmp/mmaptest";
#define SHORTFILE 10
#define LONGFILE 81920


int
main (int argc, char **argv)
{
  int fd;
  int sum;
  size_t size = LONGFILE;
  int i;
  char *buf;

  if ((fd = open (testfile, O_WRONLY | O_CREAT, 0777)) < 0)
    {
      perror ("initial open");
      exit (1);
    }
  close (fd);
  if (truncate (testfile, LONGFILE) < 0)
    {
      perror ("truncate");
      exit (1);
    }

  if ((fd = open ("/etc/services", O_RDONLY)) < 0)      /* a short file */
    {
      perror ("open");
      exit (1);
    }

#ifdef TRUNCATE_BEFORE_MMAP
  if (truncate (testfile, SHORTFILE) < 0)
    {
      perror ("truncate");
      exit (1);
    }
#endif

  if (MAP_FAILED == (buf = mmap (NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)))
    {
      perror ("mmap");
      exit (1);
    }

#ifdef TRUNCATE_AFTER_MMAP
  if (truncate (testfile, SHORTFILE) < 0)
    {
      perror ("truncate");
      exit (1);
    }
#endif

  for (i = 0; i < size; i++)
    {
      sum += buf[i];
    }

  if (munmap (buf, size) < 0)
    {
      perror ("munmap");
      exit (1);
    }
  if (close (fd) < 0)
    {
      perror ("close");
      exit (1);
    }
  exit (0);
}

【讨论】:

  • 据我所知文件锁定是自愿的,需要合作过程。我正在考虑文件由攻击者控制的场景,攻击者可以通过这种方式影响程序的行为。请参阅我的其他评论,了解为什么我认为 mmap 之后的进程外部写入无关紧要,如果我错了,请纠正我。
  • 如果文件是被攻击者控制的,那么你几乎是死在水里,就好像文件被截断了一样,即使mmap之后,你可以得到一个SEGV。我已经更清楚了,并添加了一个代码示例来演示。
  • 还重新处理外部写入和MAP_PRIVATE,这些不会影响文件的内容,但会影响映射数据的长度。看我的测试。
  • mmap 不一定返回 (void *)0(POSIX 中 NULL 宏的定义),但 MAP_FAILED 出错。 SIGSEGV 真的会发生在进程私有映射中吗?
  • @nxbit 很好地了解错误检查 - 我会修复。我得到一个SIGBUS。你不能保证它实际上会作为它的 UB 发生。在我的情况下,它发生在 20480 字节中。
【解决方案2】:

mmap 对于长度不同的东西不是很容易使用。如果您控制流程的所有方面,则可以使用信号量在读取器和写入器之间进行并发控制,包括让写入器将正确的长度写入标头字段。如果您正在映射某个任意进程可以扩大或缩小的文件,您可能需要使用信号处理程序来保护自己免受这种情况的影响,更不用说对内容进行任意更改了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-24
    • 1970-01-01
    • 2015-05-18
    • 1970-01-01
    • 2014-09-08
    • 1970-01-01
    • 2012-10-24
    • 1970-01-01
    相关资源
    最近更新 更多