Syscall mremap()无法正常工作。它是否被漏洞,还有其他重新重建内存的方法吗?

发布于 2025-02-13 07:24:05 字数 1627 浏览 0 评论 0原文

我的示例使用非posix mremap()调用来连接用mmap()分配的匿名内存的块,然后连续区域。基于可用的文档我希望这能正常工作。但是MREMAP()操作'成长'出乎意料的失败,很可能是由于内存内的内内表示(称为VM/VMA),其中一个连续的用户空间区域仍然是两个独立的VM/VMA内部内核。

算法如下:

  1. 拥有内存A和B,生长A至(A +B)大小(M的移动a的移动)
  2. 将B移至A还具有C内存的末端的新添加的空间
  3. ,将A成长为A(A +) b + c)大小(此步骤失败:efault 14不良地址
  4. 将C移至精确代码末尾的新添加的空间,

请在

由于“增长”呼叫第一次起作用,但第二次不起作用,我得出结论,第二个呼叫中的问题是从两个独立的内内区域构建内存,所以mremap无法处理。这样的参数。

此类诊断可能在内核评论中有所支持,但是我不确定如何解释mm/mremap.c:mremap_to()

        /* We can't remap across vm area boundaries */
        if (old_len > vma->vm_end - addr)
                return ERR_PTR(-EFAULT);

另一方面,文档中没有这样的要求,使用户空间调用取决于内部内核表示形式,这将是令人惊讶的。

我们也可以在文档中阅读 https://man7.orgg/linux/linux/ man-pages/man2/mremap.2.html

EFAULT Some address in the range old_address to
       old_address+old_size is an invalid virtual memory address
       for this process.  You can also get EFAULT even if there
       exist mappings that cover the whole address space
       requested, but those mappings are of different types.

使用复数“映射”清楚地表明应该可以使用由多个映射组成的区域(提供的类型相同)。

因此,有人可以帮助我:

  • MREMAP()错误还是我正在错误地使用它?
  • 或者,也许这是“作者需要它的东西”,而我的期望是不现实的?
  • 在用户空间中是否有其他方法可以重新映射任意内存?

My example uses non-POSIX mremap() call to connect chunks of anonymous memory allocated with mmap() into a one continues region. Based on the available documentation I expected this to work properly. However mremap() operation 'grow' unexpectedly fails, most likely due to in-kernel representation (known as VM/VMA) of memory chunk, where one continuous userspace region is still two separate VM/VMA inside a kernel.

Algorithm is as follows:

  1. Having memory A and B, grow A to (A+B) size (move of A may happens)
  2. Move B to a newly added space at the end of A
  3. Having also C memory, grow A to (A + B + C) size (This step fails: EFAULT 14 Bad address)
  4. Move C to a newly added space at the end of A

Exact code is available on github.

Since 'grow' call works the first time, but doesn't work at the second time, I conclude that problem in second call is that memory is constructed from two separate in-kernel regions so mremap cannot handle such parameter.

Such diagnosis may have some support in the kernel comments, however I am not sure how to interpret mm/mremap.c: mremap_to():

        /* We can't remap across vm area boundaries */
        if (old_len > vma->vm_end - addr)
                return ERR_PTR(-EFAULT);

On the other hand, there is no such requirement in the documentation, and it would be surprising to make the user space call dependent on an internal kernel representation.

We may also read in documentation https://man7.org/linux/man-pages/man2/mremap.2.html:

EFAULT Some address in the range old_address to
       old_address+old_size is an invalid virtual memory address
       for this process.  You can also get EFAULT even if there
       exist mappings that cover the whole address space
       requested, but those mappings are of different types.

Use of plural 'mappings' clearly suggests that it should be possible to use an area composed of multiple mappings (provided types are the same).

So can someone help me on this:

  • Is mremap() bugged or I am using it incorrectly?
  • Or maybe this is a call that "does what author needed it for" and my expectations are unrealistic?
  • Is there any other way of remapping arbitrary memory in userspace?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

如若梦似彩虹 2025-02-20 07:24:05

当您移动新映射时,这就是发生的事情:

   ┌───┐
0. │ A │                 Existing mapping.
   └───┘
   ┌───┐         ┌───┐
1. │ A │     ... │ B │   Create new mapping.
   └───┘         └───┘
   ┌───────┐     ┌───┐
2. │ A   _ │ ... │ B │   Extend original mapping address range.
   └───────┘     └───┘
   ┌───┬───┐
3. │ A │ B │             Move new mapping over extended address range.
   └───┴───┘
   ┌───┬───┐     ┌───┐
4. │ A │ B │ ... │ C │   Create new mapping.
   └───┴───┘     └───┘
   ┌───┬───┐     ┌───┐
5. │ A │ B │ ... │ C │   Cannot extend mapping A, because B is in the way.
   └───┴───┘     └───┘

即,即使您在连续地址中确实具有正确的映射,也可以通过在A上移动b,而不是“合并”映射,但您只需移动它们即可。他们保留为单独的映射。

如果您读取内核伪file /proc/proc/self/maps,您会发现您的映射区域确实被分为多个映射。我不知道用户空间要求内核与单个映射合并不同的映射,除了仅做mmap(... map_fixed ...)在整个区域中,但是这将清除现有内容为零。

您可以尝试在该区域中仅生长最终映射(b在上面的步骤5中),但仅使用零标志(没有MREMAP_MAYMOVEMREMAP_FIXED)。如果失败了,则需要执行mmap(null,total_grown_length,prot_none,map_nonymous | map_private | map_noreserve,-1,0)获得一个全新的虚拟地址范围而且,您确实有一个映射,一个一个。 (请注意,MMAP()调用不保留RAM,只有地址空间。)如果也失败了,那么,剩下的虚拟地址空间还不够。

如果新映射是匿名内存,则明显的解决方案是仅将memcpy() copy 复制 从单独的映射中使用,然后删除该新映射。或者更好的是,只需首先将原始映射扩展,然后使用扩展区域即可。

When you move the new mapping, this is what happens:

   ┌───┐
0. │ A │                 Existing mapping.
   └───┘
   ┌───┐         ┌───┐
1. │ A │     ... │ B │   Create new mapping.
   └───┘         └───┘
   ┌───────┐     ┌───┐
2. │ A   _ │ ... │ B │   Extend original mapping address range.
   └───────┘     └───┘
   ┌───┬───┐
3. │ A │ B │             Move new mapping over extended address range.
   └───┴───┘
   ┌───┬───┐     ┌───┐
4. │ A │ B │ ... │ C │   Create new mapping.
   └───┴───┘     └───┘
   ┌───┬───┐     ┌───┐
5. │ A │ B │ ... │ C │   Cannot extend mapping A, because B is in the way.
   └───┴───┘     └───┘

I.e., even though you do have the correct mappings in continuous addresses, by moving B on top of A you do not "merge" the mappings, you only move them. They stay as separate mappings.

If you read the kernel pseudofile /proc/self/maps, you'll see that your mapping region is indeed split into multiple mappings. I am not aware of any way for userspace to ask the kernel to coalesce different mappings to a single one, aside from just doing a mmap(...MAP_FIXED...) over the entire region, but that will clear the existing contents to zero.

You can try growing only the final mapping in the region (B in step 5. above), but only with zero flags (without MREMAP_MAYMOVE or MREMAP_FIXED). If that fails, then you need to do a mmap(NULL, total_grown_length, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0) to obtain a completely new virtual address range you can mremap() each and every one of the mappings you do have, one by one. (Note that that mmap() call does not reserve RAM, only address space.) If that also fails, well, there just isn't enough virtual address space left anymore.

If the new mappings are anonymous memory, then the obvious solution is to just use memcpy() to copy the data from the separate mapping, and then unmap that new mapping. Or better yet, just extend the original mapping in the first place, and use the extended area.

不知在何时 2025-02-20 07:24:05

我已经复制了您的代码中有趣的部分:

// This test attaches new mapped memory to 'a' chunk.
// However, it fails at second time for unclear reason.
// First, let's create initial mmapped region 'a'
void* a = mmap(NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, /*not a file mapping*/-1, 0);
err(a, 1);

// Creation of 'b' and attaching it at the end of 'a'
// 'a' need to be enlarged first, relocation is possible
void* b = mmap(NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, /*not a file mapping*/-1, 0);
err(b, 2);    
a = mremap(a, size, 2 * size, MREMAP_MAYMOVE);                      // Grow 'a'
err(a, 3);
b = mremap(b, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, a + size); // Attach 'b' at the end
err(b, 4);

在这一点上,您有两个4K映射,“ A”紧随其后的是“ B”。将“ b”放在覆盖“ A”的一部分中,将其缩小到4K。

// Creation of 'c' and attaching it at the end of 'a'
// 'a' need to be enlarged first, relocation is possible
void* c = mmap(NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, /*not a file mapping*/-1, 0);
err(c, 5);    
a = mremap(a, 2 * size, 3 * size, MREMAP_MAYMOVE);                   // <- this fails: EFAULT 14 Bad address

失败了,因为“ A”仅为4K,而不是8K。使此更改有效:

a = mremap(a, size, 3 * size, MREMAP_MAYMOVE);

I have reproduced the interesting part of your code:

// This test attaches new mapped memory to 'a' chunk.
// However, it fails at second time for unclear reason.
// First, let's create initial mmapped region 'a'
void* a = mmap(NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, /*not a file mapping*/-1, 0);
err(a, 1);

// Creation of 'b' and attaching it at the end of 'a'
// 'a' need to be enlarged first, relocation is possible
void* b = mmap(NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, /*not a file mapping*/-1, 0);
err(b, 2);    
a = mremap(a, size, 2 * size, MREMAP_MAYMOVE);                      // Grow 'a'
err(a, 3);
b = mremap(b, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, a + size); // Attach 'b' at the end
err(b, 4);

At this point you have two 4K mappings, 'a' is immediately followed by 'b'. Putting 'b' where you did overwrote part of 'a' shrinking it to 4K.

// Creation of 'c' and attaching it at the end of 'a'
// 'a' need to be enlarged first, relocation is possible
void* c = mmap(NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, /*not a file mapping*/-1, 0);
err(c, 5);    
a = mremap(a, 2 * size, 3 * size, MREMAP_MAYMOVE);                   // <- this fails: EFAULT 14 Bad address

This fails because 'a' is only 4k, not 8K. Making this change works:

a = mremap(a, size, 3 * size, MREMAP_MAYMOVE);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文