linux c虚拟内存操作-mprotect()、mlock()、mlockatt()

2018-06-20 11:40:42

改变内存保护:mprotect()

mprotect()系统调用修改起始位置为addr,长度为length字节的虚拟内存区域中分页上的保护。addr取值必须为分页大小的整数倍,length会被向上舍入到系统分页大小的下一个整数倍。prot参数是一个位掩码,跟mmap()一样。具体参考 linux c内存映射

#include <sys/mman.h>

int mprotect(void *addr, size_t length, int prot);

//Returns 0 on success, or –1 on error

如果一个进程在访问一块内存时违背了内存保护,会收到SIGSEGV 信号。

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>

#include <sys/mman.h>


void errExit(char *msg){
    perror(msg);
    exit(1);
}

#define LEN (1024 * 1024)
#define SHELL_FMT "cat /proc/%ld/maps | grep zero"
#define CMD_SIZE (sizeof(SHELL_FMT) + 20)
                            /* Allow extra space for integer string */

int main(int argc, char *argv[]){
    char cmd[CMD_SIZE];
    char *addr;

    /* Create an anonymous mapping with all access denied */

    addr = mmap(NULL, LEN, PROT_NONE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED)
        errExit("mmap");

    /* Display line from /proc/self/maps corresponding to mapping */

    printf("Before mprotect()\n");
    snprintf(cmd, CMD_SIZE, SHELL_FMT, (long) getpid());
    system(cmd);

    /* Change protection on memory to allow read and write access */

    if (mprotect(addr, LEN, PROT_READ | PROT_WRITE) == -1)
        errExit("mprotect");

    printf("After mprotect()\n");
    system(cmd);                /* Review protection via /proc/self/maps */

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
Before mprotect()
7f21841d9000-7f21842d9000 ---s 00000000 00:04 667301                     /dev/zero (deleted)
After mprotect()
7f21841d9000-7f21842d9000 rw-s 00000000 00:04 667301                     /dev/zero (deleted)


内存锁:mlock()、mlockatt()

某些时候将进程的虚拟内存部分或全部锁定,以确保它们总是位于物理内存中是非常有用的。它可以提高性能。对被锁住的分页的访问可以确保永远不会因为分页故障而发生延迟。如果一个包含敏感数据的虚拟内存分页永远不会被交换出去,那么该分页的副本就不会被写入到磁盘,造成安全隐患。攻击者可能会故意通过运行一个消耗大量内存的程序来构造一种场景,从而强制其他进程占据的内存被交换到磁盘,内核不保证会清除交换空间中保存的数据,因此即使在进程终止之后也可能从交换空间中读取信息。(一般来讲,只有特权进程才能够从交换设备读取数据。)

RLIMIT_MEMLOCK  

在 linux c进程资源 中有提到过 RLIMIT_MEMLOCK,这里进行详细讲解。

1.特权进程能够锁住内存数量是没有限制的。(即 RLIMIT_MEMLOCK 会被忽略)
2.非特权进程由软限制 RLIMIT_MEMLOCK(会向下舍入到最近测系统分页) 定义

也就是现在非特权进程也有能力锁定一小段内存来保证一些敏感信息不会写入到磁盘。

对于mlock()、mlockall()、以及mmap() MAP_LOCKED操作来说都是进程级别的。而shmctl() SHM_LOCK 操作是用户级别的。

#include <sys/mman.h>

int mlock(void *addr, size_t length);
int munlock(void *addr, size_t length);

//Both return 0 on success, or –1 on error

addr为起始地址,内核会向下对齐到内存分页边界,length为长度。假设分页大小为4096,mlock(2000,4000) 会将0-8191 之间的字节锁住。

通过查看/proc/PID/status 文件的VmLck 条目可以查看当前进程已经锁住的内存数量。

除了munlock()系统调用外,下面的情形会自动解除内存锁

1.进程终止
2.当被锁住的内存分页通过 munmap() 被解除映射时。
3.当被锁住的分页被使用mmap() MAP_FIXED 标记的映射覆盖时。

内存锁不会被通过 fork()创建的子进程继承,也不会在 exec() 执行期间被保留。当多个进程共享一组分页时(如 MAP_SHARED 映射),只要还存在一个进程持有着这些分页上的内存锁,那么这些分页就会保持被锁进内存的状态。

内存锁不在单个进程上叠加。如果一个进程重复地在一个特定虚拟地址区域上调用 mlock(),那么只会建立一个锁,并且只需要通过一个 munlock() 调用就能够删除这个锁。另一方面,如果使用 mmap() 将同一组分页(即同样的文件)映射到单个进程中的几个不同的位置,然后分别给所有这些映射加锁,那么这些分页会保持被锁进 RAM 的状态直到所有的映射都被解锁为止。

内存锁的加锁单位为分页以及无法叠加的事实意味着独立地将 mlock()和munlock() 调用应用于同一个虚拟分页上的不同数据结构在逻辑上是不正确的。如假设在同一个虚拟内存分页中存在两个数据结构,指针p1、p2 分别指向了这两个结构,接着执行下面的调用。

mlock(*p1, len1);
mlock(*p2, len2); //不起作用
munlock(*p1, len1);

上面的调用都会成功,但最后整个分页都会被解锁。


给一个进程的所有内存加锁和解锁

#include <sys/mman.h>

int mlockall(int flags);
int munlockall(void);

//Both return 0 on success, or –1 on error

mlockall系统调用根据flags位掩码的取值将一个进程的当前内存或将来要申请的内存或两者都锁进内存。flags 参数取值如下:

MCL_CURRENT 只锁定当前占用的内存。
MCL_FUTURE 锁定将来申请的内存。


检测内存驻留:mincore()

#include <sys/mman.h>

int mincore(void *addr, size_t length, unsigned char *vec);

//Returns 0 on success, or –1 on error

返回起始地址为addr 长度为length字节的虚拟地址范围中分页的内存驻留信息。addr 地址必须分页对齐,length实际上会被向上舍入到系统分页大小的下一个整数倍。

内存驻留信息会通过vec返回,它是一个数组,其大小为 (length + PAGE_SIZE-1 / PAGE_SIZE)

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>

#include <sys/mman.h>


void errExit(char *msg){
    perror(msg);
    exit(1);
}

static void displayMincore(char *addr, size_t length){
    unsigned char *vec;
    long pageSize, numPages, j;

    pageSize = sysconf(_SC_PAGESIZE);

    numPages = (length + pageSize - 1) / pageSize;
    vec = malloc(numPages);
    if (vec == NULL)
        errExit("malloc");

    if (mincore(addr, length, vec) == -1)
        errExit("mincore");

    for (j = 0; j < numPages; j++) {
        if (j % 64 == 0)
            printf("%s%10p: ", (j == 0) ? "" : "\n", addr + (j * pageSize));
        printf("%c", (vec[j] & 1) ? '*' : '.');
    }
    printf("\n");

    free(vec);
}

int main(int argc, char *argv[]){
    char *addr;
    size_t len, lockLen;
    long pageSize, stepSize, j;

    if (argc != 4 || strcmp(argv[1], "--help") == 0){
        printf("%s num-pages lock-page-step lock-page-len\n", argv[0]);
        return 0;
    }
        

    pageSize = sysconf(_SC_PAGESIZE);
    if (pageSize == -1)
        errExit("sysconf(_SC_PAGESIZE)");

    len =      atoi(argv[1]) * pageSize;
    stepSize = atoi(argv[2]) * pageSize;
    lockLen =  atoi(argv[3]) * pageSize;

    addr = mmap(NULL, len, PROT_READ, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED)
        errExit("mmap");

    printf("Allocated %ld (%#lx) bytes starting at %p\n",
            (long) len, (unsigned long) len, addr);

    printf("Before mlock:\n");
    displayMincore(addr, len);

    /* Lock pages specified by command-line arguments into memory */

    for (j = 0; j + lockLen <= len; j += stepSize)
        if (mlock(addr + j, lockLen) == -1)
            errExit("mlock");

    printf("After mlock:\n");
    displayMincore(addr, len);

    exit(EXIT_SUCCESS);
}

分配了32个分页,每组为8个分页,并给3个连续分页加锁。

[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 32 8 3
Allocated 131072 (0x20000) bytes starting at 0x7f4a4e122000
Before mlock:
0x7f4a4e122000: ................................
After mlock:
0x7f4a4e122000: ***.....***.....***.....***.....


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/5e   

 

©著作权归作者所有
收藏
推荐阅读
  • err
    linux c内存映射

    内存映射一般是用来做ipc进程间通信的,类似于一种共享内存技术。内存映射分为4种(假设有进程A和进程B):1.私有文件映射:2个进程都以一个文件中的内容来初始化内存内容,...

  • err
    linux c管道和FIFO

    每个 shell 用户都对命令中使用管道比价熟悉,如下面这个统计一个目录中文件的数目的命令所示。ls | wc -l为了执行上面的命令,shell 创建了2个进程分别执行...

  • linux c共享库(动态库)高级特性(2)

    动态加载库当一个可执行文件开始运行之后,动态链接器会加载程序的动态依赖列表中的所有共享库,但有些时候延迟加载库是比较有用的,如只在需要的时候再加载一个插件。动态链接器的这项功能是通过一组 API 来实...

  • err
    linux c共享库(动态库)基础(1)

    在很多情况下,源代码文件也可以被多个程序共享。因此要降低工作量的第一步就是将这些源代码文件只编译一次,然后在需要的时候将它们链接进不同的可执行文件中。虽然这项技术能够竹省...

  • linux c进程资源

    每个进程都会消耗诸如内存和CPU时间之类的系统资源,本文将介绍与资源相关的系统调用。#include &lt;sys/resource.h&gt; int getrusage(int who, st...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。