sys_mprotect(即调用号为10mprotect系统调用)是Linux内核提供的一个核心功能,它允许程序动态地修改其虚拟地址空间中某个内存区域的访问权限。这在很多场景中都至关重要,如实现JIT编译器、沙箱机制,或者防止某些类型的缓冲区溢出攻击。通过调用sys_mprotect,程序可以将一段内存设置为只读,以保护重要数据不被意外修改;或者将一段内存设置为可执行,以允许动态生成的代码运行。理解这个系统调用有助于深入理解现代操作系统的内存管理和安全机制。

  • sys_mprotect

    • 函数原型

      1
      int mprotect(const void *addr, size_t len, int prot);
    • 参数

      • addr: 要修改权限的内存区域的起始地址。这个地址必须是页对齐的,否则系统调用会失败并返回EINVAL错误。

      • len: 要修改权限的内存区域的长度(字节)。这个长度也必须是页大小的整数倍

      • prot:映射区域的访问权限:

        • PROT_NONE:页面不可访问
        • PROT_READ:页面可读
        • PROT_WRITE:页面可写
        • PROT_EXEC:页面可执行
        • PROT_SEM:页面可用于原子操作同步(自 Linux 2.5.7 起支持)
    • 返回值

      • 成功时返回0
      • 失败时返回-1,并设置errno(如ENOMEM, EFAULT, EINVAL, EACCES)。
  • 用例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    	.equ SYS_MPROTECT, 10
    .equ SYS_EXIT, 60
    .equ PROT_READ, 0x01
    .equ PROT_READ_WRITE, 0x03
    .equ PAGE_SIZE, 4096

    .section .data
    .p2align 12 # 强制页对齐
    my_page:
    .ascii "This is a memory page to be protected.\\n"
    .space (PAGE_SIZE - (. - my_page)) # 填充到页大小

    .section .text
    .globl _start

    _start:
    # 将 my_page 设置为只读
    mov $SYS_MPROTECT, %rax
    lea my_page(%rip), %rdi # addr = &my_page
    mov $PAGE_SIZE, %rsi # len = 4096
    mov $PROT_READ, %rdx # prot = PROT_READ
    syscall

    # 检查返回值(%rax),如果失败则退出
    cmp $0, %rax
    jne exit_error

    # 这里可以尝试向 my_page 中写入内容
    # call write_page

    # 现在尝试将 my_page 设置为可读写
    mov $SYS_MPROTECT, %rax
    lea my_page(%rip), %rdi # addr = &my_page
    mov $PAGE_SIZE, %rsi # len = 4096
    mov $PROT_READ_WRITE, %rdx # prot = PROT_READ | PROT_WRITE
    syscall

    # 这里可以再次尝试向 my_page 中写入内容
    call write_page

    # 退出程序
    exit_success:
    mov $SYS_EXIT, %rax
    xor %rdi, %rdi
    syscall

    exit_error:
    mov $SYS_EXIT, %rax
    mov $1, %rdi
    syscall

    # 尝试修改 my_page 这段内存中第一个字节的值
    write_page:
    lea my_page(%rip), %rdi
    mov $0, (%rdi)
    ret

    分析

    上述代码演示了如何使用sys_mprotect来动态改变内存区域的权限。

    程序首先在.data段定义了一个名为my_page的内存区域,并使用.p2align 12指令确保其地址是4096字节(即一个内存页)的整数倍对齐。然后,它用.space指令填充剩余空间,使该区域恰好为一个页的大小。

    接下来,程序调用sys_mprotect,将my_page的地址加载到%rdi,长度PAGE_SIZE加载到%rsi,并将权限标志PROT_READ(只读)加载到%rdx。执行syscall后,my_page这块内存将变为只读。

    如上,我们可以在这个时候尝试call write_page ,但是由于前面已经将这块目标内存设置为只读,修改操作不会生效,且会触发段错误SIGSEGV 造成整个程序的强制性崩溃。反之,如果我们在前面不调用sys_mprotect ,而在这里尝试call write_page ,程序并不会报错,且可以实现对内存的正常修改。

    在这之后,程序再次调用sys_mprotect,将权限标志更新为PROT_READ | PROT_WRITE,从而将my_page的权限从只读修改为可读写。此时再尝试call write_page ,发现内存可以被正常修改,且不会触发段错误。

    整个过程展示了sys_mprotect如何灵活地控制内存权限,这在底层编程中非常有用。