如何处理 stat 和后续 mmap 之间的文件大小变化?

How to deal with the file size changing between a stat and a subsequent mmap?

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

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

mmap 对于长度不同的东西不太好用。如果您控制进程的所有方面,则可以使用信号量在读者和作者之间进行并发控制,包括让作者将正确的长度写入 header 字段。如果您正在映射某个任意进程可以增长或收缩的文件,您可能需要使用信号处理程序来保护自己免受此影响,更不用说对内容的任意更改了。

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

如果你真的想检查映射在 mmap 之后是否仍然有效(即文件没有变短),你可以(ab)在 linux 上使用 remap_file_pages(未经测试);但是,由于列出的原因,如果有人很快将其截断,这对您没有帮助。

另请参阅联机帮助页中的警告:

The effect of changing the size of the underlying file of a mapping on the pages that correspond to added or removed regions of the file is unspecified.

您将需要某种形式的文件锁定来保护您自己。正如您在评论中所说,您正在与一个不尊重建议锁定的对手打交道,除非您使用(罕见的)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);
}