├── .gitignore ├── LICENSE ├── README.md ├── bitvisor ├── defconfig.md ├── ept.md ├── mmio.md ├── nw_driver.md ├── pro1000_ippass.md └── vmm_main.md ├── exploit └── vm-escape-qemu-case-study.md ├── hv ├── how-to-re-inject-event.md ├── injection-vmcs.md ├── nw_driver.md ├── poem-about-xen.md ├── re-inject-pf-xen.md ├── virtio.md └── warikomi.md ├── misc ├── kvm.md └── virtualization.md └── xen └── dom0setup.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 morimolymoly 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memo 2 | various memo. 3 | There are lots of document in “Japanese”! 4 | 5 | # Virtualization MISC 6 | * [仮想化概要](./misc/virtualization.md) 7 | * [KVMとは](./misc/kvm.md) 8 | * [VMCS's fields related to exception injection](hv/injection-vmcs.md) 9 | * [WIP:How to re-inject event](hv/how-to-re-inject-event.md) 10 | * [わりこみ](hv/warikomi.md) 11 | * [ネットワークドライバらへん](hv/nw_driver.md) 12 | * [virtio](hv/virtio.md) 13 | 14 | # Exploit 15 | * [WIP:VM escape - QEMU Case Study(PhrackMagazine)](./exploit/vm-escape-qemu-case-study.md) 16 | 17 | # Xen 18 | * [WIP:RE-injection of page-fault on xen](hv/re-inject-pf-xen.md) 19 | * [Poem about xen when I create original hypervisor for fun](hv/poem-about-xen.md) 20 | 21 | # BitVisor 22 | コードはAine用にcloneしたところから. 23 | * [BitVisorのネットワークドライバ](bitvisor/nw_driver.md) 24 | * [BitVisorのpro1000(ippass)](bitvisor/pro1000_ippass.md) 25 | * [BitVisorのエントリポイントらへん](bitvisor/vmm_main.md) 26 | * [defconfig](bitvisor/defconfig.md) 27 | * [MMIO](bitvisor/mmio.md) 28 | * [INT3トラップしてNICのレジスタをダンプするやつ](https://github.com/morimolymoly/pro1000bitvisor) 29 | * [EPT](bitvisor/ept.md) 30 | 31 | ## LICENSE 32 | MIT 33 | -------------------------------------------------------------------------------- /bitvisor/defconfig.md: -------------------------------------------------------------------------------- 1 | # defconfig 2 | コンフィグ定義 3 | こいつは設定するとバイナリに埋め込まれる 4 | 構造体としては`include/share/config.h`で定義されている. 5 | -------------------------------------------------------------------------------- /bitvisor/ept.md: -------------------------------------------------------------------------------- 1 | # BitVisorのEPT周りのコールドリーディング 2 | ## vt_ept構造体 3 | * `cnt` eptのエントリのカウント? 4 | * `cleared` 5 | * `ncr3tbl` eptポインタの仮想アドレス 6 | * `ncr3tbl_phys` eptポインタ(`0x000000000000201AULL`)にセットするための物理アドレス 7 | * `void *tbl[NUM_OF_EPTBL]` 8 | * `phys_t tbl_phys[NUM_OF_EPTBL]` 9 | * `cur.level` 10 | * `cur.gphys` 11 | * `cur.entry[EPT_LEVELS];` 12 | 13 | ## init 14 | `vt_ept_init`関数(core/vt_ept.c)で初期化が行われる. 15 | `vt_ept`構造体の`ncr3tbl`に1ページ分のallocがされる.`ncr3tbl_phys`はそれの物理アドレス. 16 | 17 | ## vt_paging_flush_guest_tlb 18 | * invvpid 19 | * invept 20 | 21 | ## vt_ept_updatecr3関数 22 | `vt_paging_flush_guest_tlb`してからguest_pdpte0~3を書き換える. 23 | 24 | ## vt_ept_clear_all関数 25 | クリアして`vt_paging_flush_guest_tlb`実行 26 | 27 | ## vt_ept_violation関数 28 | `do_ept_violation`(core/vt_main.c) -> `vt_paging_npf`(core/vt_paging.c) -> `vt_ept_violation`(core/vt_ept.c) 29 | MMIOのフックとかをやってる. 30 | 31 | ## vt_ept_map_page_clear_cleared関数 32 | 33 | ## vt_ept_map_page_sub関数 34 | 35 | 36 | ## vt_ept_map_page関数 37 | 38 | -------------------------------------------------------------------------------- /bitvisor/mmio.md: -------------------------------------------------------------------------------- 1 | # MMIO 2 | ## 構造体 3 | ### mmio_handle 4 | * phys_t gphys - ゲストのBARの物理アドレス 5 | * uint len - 長さ 6 | * void *data - data構造体のポインタ(各デバイスによってことなる??) 7 | * mmio_handler_t hanler - MMIOのレジスタにアクセスしてきた場合のハンドラ(特定のレジスタのエミュレートをする.ほかはパススルー) 8 | * bool unregisterd - dirty bit 9 | * bool unlocked_handler - pro1000ではつかってねえ.わからん. 10 | -------------------------------------------------------------------------------- /bitvisor/nw_driver.md: -------------------------------------------------------------------------------- 1 | # 種類(モジュール) 2 | * null - パケットを破棄 3 | * pass - パススルー(shadowingする) 4 | * ip - bitvisorがNICを専有 5 | * ippass - パススルーだけどBitvisorもNICがつかえる(これBareflankで実装するよ) 6 | * vpn - vpn 7 | 8 | # ネットワークAPI(net/netapi.c) 9 | ここに定義.受信と送信コールバックを定義.モジュールが実態を実装. 10 | 11 | # ippass概要(ip/net_main.c) 12 | * 受信コールバック - net_ip_phys_recv 13 | * 送信コールバック - net_ip_virt_recv 14 | 15 | ## net_ip_phys_recv 16 | NICがパケット受信したときによばれる. 17 | `p->pass`つまりippassなら`p->virt_func->send()`でゲストにパケット送信. 18 | `p->input_ok`つまりlwipがokならlwipが操作するようにキューに追加.(lwipは別スレッドが立ってるっぽい) 19 | 20 | ## net_ip_virt_recv 21 | `p->pass`つまりippassならpassのようにdescriptor shadowinをして,NICに送信. 22 | 23 | #モジュール登録 24 | `net_register`(net/netapi.c)から登録.INITFUNCで各モジュールは自身を初期化. 25 | `phys_func`と`virt_func`が関数テーブルの`nicfunc`構造体. 26 | 各ドライバがこれを実装. 27 | -------------------------------------------------------------------------------- /bitvisor/pro1000_ippass.md: -------------------------------------------------------------------------------- 1 | # pro1000 ippass 2 | 3 | ## 構造体 4 | ディスクリプタリングはみたまんまね 5 | ### data 6 | dataは6っこ確保される.BARに対応している. 7 | * i - BARの番号 8 | * e - dirty bit? 9 | * io - ioかどうか 10 | * hd - ハンドルするI/Oポート番号 11 | * disable - 有効ならずっとtrue 12 | * h - mmio_handle構造体のポインタ[MMIO参照](./mmio.md) 13 | * map - MMIO領域のホストのアドレス(mapmem_gphys関数でマップされる) 14 | * maplen - 長さ 15 | * mapaddr - BARのアドレス(ゲストの物理アドレス(一応だがこれバスアドレスな)) 16 | * d - data2へのポインタ 17 | 18 | ### data2 19 | * spinlock_t lock - ロック 20 | * u8 *buf 21 | * long buf_premap 22 | * uint len 23 | * bool dext1_ixsm, dext1_txsm 24 | * uint dext0_tucss, dext0_tucso, dext0_tucse 25 | * uint dext0_ipcss, dext0_ipcso, dext0_ipcse 26 | * uint dext0_mss, dext0_hdrlen, dext0_paylen, dext0_ip, dext0_tcp 27 | * bool tse_first, tse_tcpfin, tse_tcppsh 28 | * u16 tse_iplen, tse_ipchecksum, tse_tcpchecksum 29 | * struct desc_shadow tdesc[2], rdesc[2] 30 | * struct data *d1 - data構造体のポインタ(data*6の配列になってる) 31 | * struct netdata *nethandle 32 | * bool initialized 33 | * net_recv_callback_t *recvphys_func, *recvvirt_func 34 | * void *recvphys_param, *recvvirt_param 35 | * u32 rctl, rfctl, tctl 36 | * u8 macaddr[6] - MACアドレス 37 | * struct pci_device *pci_device 38 | * u32 regs_at_init[PCI_CONFIG_REGS32_NUM] 39 | * bool seize - vitioとかttyで使うとき(BitVisorが完全にNICを掌握するときってことかな) 40 | * bool conceal 41 | * LIST1_DEFINE (struct data2) 42 | * void *virtio_net 43 | * char virtio_net_bar_emul 44 | * struct pci_msi *virtio_net_msi 45 | 46 | ## 初期化 47 | `PCI_DRIVER_INIT (vpn_pro1000_init);`でドライバ起動. 48 | `vpn_pro1000_init`がよばれる. 49 | こいつをとうろくする. 50 | ```c 51 | static struct pci_driver pro1000_driver = { 52 | .name = driver_name, 53 | .longname = driver_longname, 54 | .driver_options = "tty,net,virtio", 55 | .device = "class_code=020000,id=" 56 | /* 31608004.pdf */ 57 | "8086:105e|" /* Dual port */ 58 | "8086:1081|" 59 | ... 60 | .new = pro1000_new, 61 | .config_read = pro1000_config_read, // PCI Configuration Spaceをよむ 62 | .config_write = pro1000_config_write,// PCI Configuration Spaceをかく 63 | ``` 64 | 65 | `vpn_pro1000_new`関数がPCI Configuration Spaceにアクセスしてどうこうな部分. 66 | 67 | ```c 68 | static void 69 | vpn_pro1000_new (struct pci_device *pci_device, bool option_tty, 70 | char *option_net, bool option_virtio) 71 | { 72 | int i; 73 | struct data2 *d2; 74 | struct data *d; 75 | void *tmp; 76 | struct pci_bar_info bar_info; 77 | struct nicfunc *virtio_net_func; 78 | 79 | if ((pci_device->config_space.base_address[0] & 80 | PCI_CONFIG_BASE_ADDRESS_SPACEMASK) != 81 | PCI_CONFIG_BASE_ADDRESS_MEMSPACE) { 82 | printf ("vpn_pro1000: region 0 is not memory space\n"); 83 | return; 84 | } 85 | if ((pci_device->base_address_mask[0] & 86 | PCI_CONFIG_BASE_ADDRESS_MEMMASK) & 0xFFFF) { 87 | printf ("vpn_pro1000: region 0 is too small\n"); 88 | return; 89 | } 90 | printf ("PRO/1000 found.\n"); 91 | 92 | #ifdef VTD_TRANS 93 | if (iommu_detected) { 94 | add_remap(pci_device->address.bus_no ,pci_device->address.device_no ,pci_device->address.func_no, 95 | vmm_start_inf() >> 12, (vmm_term_inf()-vmm_start_inf()) >> 12, PERM_DMA_RW) ; 96 | } 97 | #endif // of VTD_TRANS 98 | 99 | d2 = alloc (sizeof *d2); 100 | memset (d2, 0, sizeof *d2); 101 | d2->nethandle = net_new_nic (option_net, option_tty); 102 | alloc_pages (&tmp, NULL, (BUFSIZE + PAGESIZE - 1) / PAGESIZE); 103 | memset (tmp, 0, (BUFSIZE + PAGESIZE - 1) / PAGESIZE * PAGESIZE); 104 | d2->buf = tmp; 105 | d2->buf_premap = net_premap_recvbuf (d2->nethandle, tmp, BUFSIZE); 106 | spinlock_init (&d2->lock); 107 | d = alloc (sizeof *d * 6); 108 | for (i = 0; i < 6; i++) { 109 | d[i].d = d2; 110 | d[i].e = 0; 111 | pci_get_bar_info (pci_device, i, &bar_info); 112 | reghook (&d[i], i, &bar_info); 113 | } 114 | d->disable = false; 115 | d2->d1 = d; 116 | pro1000_enable_dma_and_memory (pci_device); 117 | get_macaddr (d2, d2->macaddr); 118 | pci_device->host = d; 119 | pci_device->driver->options.use_base_address_mask_emulation = 1; 120 | d2->pci_device = pci_device; 121 | d2->virtio_net = NULL; 122 | if (option_virtio) { 123 | d2->virtio_net = virtio_net_init (&virtio_net_func, 124 | d2->macaddr, 125 | pro1000_intr_clear, 126 | pro1000_intr_set, 127 | pro1000_intr_disable, 128 | pro1000_intr_enable, d2); 129 | } 130 | if (d2->virtio_net) { 131 | pro1000_disable_io (pci_device, d); 132 | d2->virtio_net_msi = pci_msi_init (pci_device, pro1000_msi, 133 | d2); 134 | if (d2->virtio_net_msi) 135 | virtio_net_set_msix (d2->virtio_net, 0x5, 136 | pro1000_msix_disable, 137 | pro1000_msix_enable, d2); 138 | pci_device->driver->options.use_base_address_mask_emulation = 139 | 0; 140 | net_init (d2->nethandle, d2, &phys_func, d2->virtio_net, 141 | virtio_net_func); 142 | d2->seize = true; 143 | } else { 144 | d2->seize = net_init (d2->nethandle, d2, &phys_func, NULL, 145 | NULL); 146 | } 147 | if (d2->seize) { 148 | pci_system_disconnect (pci_device); 149 | /* Enabling bus master and memory space again because 150 | * they might be disabled after disconnecting firmware 151 | * drivers. */ 152 | pro1000_enable_dma_and_memory (pci_device); 153 | seize_pro1000 (d2); 154 | net_start (d2->nethandle); 155 | } 156 | LIST1_PUSH (d2list, d2); 157 | return; 158 | } 159 | ``` 160 | 161 | ## MMIO関連 162 | ```c 163 | for (i = 0; i < 6; i++) { 164 | d[i].d = d2; 165 | d[i].e = 0; 166 | pci_get_bar_info (pci_device, i, &bar_info); 167 | reghook (&d[i], i, &bar_info); 168 | } 169 | ``` 170 | `reghook`でフック関数が登録されるが,`mmio_register(core/mmio.c)`がmmio関連を定義. 171 | `pci_get_bar_info`でBARを読み取る. 172 | 173 | ### pci_get_bar_info 174 | barが64bitかとか,メモリ空間かとかそういうのをみる 175 | ```c 176 | void 177 | pci_get_bar_info (struct pci_device *pci_device, int n, 178 | struct pci_bar_info *bar_info) 179 | { 180 | pci_get_bar_info_internal (pci_device, n, bar_info, 0, NULL); 181 | } 182 | static u64 183 | pci_get_bar_info_internal (struct pci_device *pci_device, int n, 184 | struct pci_bar_info *bar_info, u16 offset, 185 | union mem *data) 186 | { 187 | enum pci_bar_info_type type; 188 | u32 low, high, mask, and; 189 | u32 match_offset; 190 | u64 newbase; 191 | 192 | if (n < 0 || n >= PCI_CONFIG_BASE_ADDRESS_NUMS) 193 | goto err; 194 | if (!(pci_device->base_address_mask_valid & (1 << n))) 195 | goto err; 196 | low = pci_device->config_space.base_address[n]; 197 | high = 0; 198 | mask = pci_device->base_address_mask[n]; 199 | match_offset = 0x10 + 4 * n; 200 | if ((mask & PCI_CONFIG_BASE_ADDRESS_SPACEMASK) == 201 | PCI_CONFIG_BASE_ADDRESS_MEMSPACE) { 202 | if ((mask & PCI_CONFIG_BASE_ADDRESS_TYPEMASK) == 203 | PCI_CONFIG_BASE_ADDRESS_TYPE64 && 204 | n + 1 < PCI_CONFIG_BASE_ADDRESS_NUMS) 205 | high = pci_device->config_space.base_address[n + 1]; 206 | and = PCI_CONFIG_BASE_ADDRESS_MEMMASK; 207 | type = PCI_BAR_INFO_TYPE_MEM; 208 | } else { 209 | and = PCI_CONFIG_BASE_ADDRESS_IOMASK; 210 | type = PCI_BAR_INFO_TYPE_IO; 211 | } 212 | if (!(mask & and)) 213 | goto err; 214 | bar_info->base = newbase = (low & mask & and) | (u64)high << 32; 215 | if (offset == match_offset) 216 | newbase = (data->dword & mask & and) | (u64)high << 32; 217 | else if (offset == match_offset + 4) 218 | newbase = (low & mask & and) | (u64)data->dword << 32; 219 | /* The bit 63 must be cleared for CPU access. If it is set, 220 | * the BAR access is for size detection. */ 221 | if (newbase == (mask & and) || (newbase & 0x8000000000000000ULL)) 222 | goto err; 223 | bar_info->len = (mask & and) & (~(mask & and) + 1); 224 | bar_info->type = type; 225 | return newbase; 226 | err: 227 | bar_info->type = PCI_BAR_INFO_TYPE_NONE; 228 | return 0; 229 | } 230 | ``` 231 | 232 | ### reghook 233 | ハンドラ登録 234 | ```c 235 | static void 236 | reghook (struct data *d, int i, struct pci_bar_info *bar) 237 | { 238 | if (bar->type == PCI_BAR_INFO_TYPE_NONE) 239 | return; 240 | unreghook (d); 241 | d->i = i; 242 | d->e = 0; 243 | if (bar->type == PCI_BAR_INFO_TYPE_IO) { 244 | d->io = 1; 245 | d->hd = core_io_register_handler (bar->base, bar->len, 246 | iohandler, d, 247 | CORE_IO_PRIO_EXCLUSIVE, 248 | driver_name); 249 | } else { 250 | d->mapaddr = bar->base; 251 | d->maplen = bar->len; 252 | d->map = mapmem_gphys (bar->base, bar->len, MAPMEM_WRITE); 253 | if (!d->map) 254 | panic ("mapmem failed"); 255 | d->io = 0; 256 | d->h = mmio_register (bar->base, bar->len, mmhandler, d); 257 | if (!d->h) 258 | panic ("mmio_register failed"); 259 | } 260 | d->e = 1; 261 | } 262 | ``` 263 | -------------------------------------------------------------------------------- /bitvisor/vmm_main.md: -------------------------------------------------------------------------------- 1 | # ENTRYPOINTからBSPとAP 2 | マルチプロセッサー(MP)の初期化において選択された一番目のプロセッサーのこと。 3 | ほかはAP. 4 | BSPはvmm_main,APは,apinitproc0に飛ぶ. 5 | とりあえずコードで本質じゃないところは省いていく. 6 | 7 | # BSP 8 | ## vmm_main 9 | ```c 10 | asmlinkage void 11 | vmm_main (struct multiboot_info *mi_arg) 12 | { 13 | uefi_booted = !mi_arg; 14 | if (!uefi_booted) 15 | memcpy (&mi, mi_arg, sizeof (struct multiboot_info)); 16 | initfunc_init (); 17 | call_initfunc ("global"); 18 | start_all_processors (bsp_proc, ap_proc); 19 | } 20 | ``` 21 | `call_initfunc ("global");`で`global`なinitfuncを呼び出す. 22 | globalなinitfuncとは,`INITFUNC ("global3", get_shiftflags);`のような,`INITFUNC`マクロによって登録された,idに`global`というprefixがついた関数である. 23 | ttyとかそこらへんの関数が呼び出されるっぽい. 24 | `INITFUNC ("global`でgrepすればそこらへんがでてくる. 25 | 26 | ## start_all_processors 27 | ```c 28 | void 29 | start_all_processors (void (*bsp_initproc) (void), void (*ap_initproc) (void)) 30 | { 31 | initproc_bsp = bsp_initproc; 32 | initproc_ap = ap_initproc; 33 | bsp_continue (bspinitproc1); 34 | } 35 | ``` 36 | `initproc`変数にbsp,ap双方のinit関数のアドレスを指定したあと,`bsp_cpntinue`関数でスタックを調整してから`bspinitproc1`関数へjumpする. 37 | 38 | ## bspinitproc1 39 | ```c 40 | static asmlinkage void 41 | bspinitproc1 (void) 42 | { 43 | printf ("Processor 0 (BSP)\n"); 44 | ... 45 | if (!uefi_booted) { 46 | apinit_addr = 0xF000; 47 | ap_start (); 48 | } else { 49 | ... 50 | } 51 | initproc_bsp (); 52 | panic ("bspinitproc1"); 53 | } 54 | ``` 55 | `ap_start`でapの初期化関数をキック.`initproc_bsp`は`start_all_processors`で設定された,`bsp_proc`関数. 56 | 57 | ## bsp_proc 58 | ```c 59 | static void 60 | bsp_proc (void) 61 | { 62 | call_initfunc ("bsp"); 63 | call_parallel (); 64 | call_initfunc ("pcpu"); 65 | } 66 | ``` 67 | `bsp`prefixがidについているinitfuncを呼び出し,`parerel`prefixのinitfuncを呼び出してから,`pcpu`prefixの関数を呼び出す. 68 | `INITFUNC ("pcpu`でgrepすると, 69 | ```c 70 | INITFUNC ("pcpu0", cpu_mmu_spt_init_pcpu); 71 | INITFUNC ("pcpu0", nmi_init_pcpu); 72 | INITFUNC ("pcpu0", thread_init_pcpu); 73 | INITFUNC ("pcpu0", initipi_init_pcpu); 74 | INITFUNC ("pcpu2", virtualization_init_pcpu); 75 | INITFUNC ("pcpu3", panic_init_pcpu); 76 | INITFUNC ("pcpu4", time_init_pcpu); 77 | INITFUNC ("pcpu4", cache_init_pcpu); 78 | INITFUNC ("pcpu5", create_pass_vm); 79 | ``` 80 | なるほど,いろいろと初期化してる. 81 | 中でも`virtualization_init_pcpu`はVT-xやAMD−Vの初期化関連だろう. 82 | `create_pass_vm`はVMの本格的な立ち上げ処理だろうか. 83 | 84 | ```c 85 | static void 86 | create_pass_vm (void) 87 | { 88 | ... 89 | #endif 90 | current->vmctl.start_vm (); 91 | panic ("VM stopped."); 92 | } 93 | ``` 94 | ビンゴ! 95 | ここでVMを走らせてる. 96 | `start_vm`は`vmctl_func`構造体の中の関数ポインタだ. 97 | どうやらVT-xとSVMでインターフェイスを作っているようだ. 98 | VT-xでは`vt_start_vm`関数のようだ. 99 | ```c 100 | void 101 | vt_start_vm (void) 102 | { 103 | vt_paging_start (); 104 | vt_mainloop (); 105 | } 106 | ``` 107 | 108 | `vt_mainloop`ではおそらくvmexitが起こるまでwhileでもしているのかな. 109 | ```c 110 | static void 111 | vt_mainloop (void) 112 | { 113 | enum vmmerr err; 114 | ulong cr0, acr; 115 | u64 efer; 116 | bool nmi; 117 | 118 | for (;;) { 119 | schedule (); 120 | ... 121 | if (current->initipi.get_init_count ()) 122 | vt_init_signal (); 123 | if (current->halt) { 124 | vt__halt (); 125 | current->halt = false; 126 | continue; 127 | } 128 | ... 129 | /* when the state is switching, do single step */ 130 | if (current->u.vt.vr.sw.enable) { 131 | vt__nmi (); 132 | vt_interrupt (); 133 | vt__event_delivery_setup (); 134 | vt_msr_own_process_msrs (); 135 | nmi = vt__vm_run_with_tf (); 136 | vt_paging_tlbflush (); 137 | if (!nmi) { 138 | vt__event_delivery_check (); 139 | vt__exit_reason (); 140 | } 141 | } else { /* not switching */ 142 | vt__nmi (); 143 | vt_interrupt (); 144 | vt__event_delivery_setup (); 145 | vt_msr_own_process_msrs (); 146 | nmi = vt__vm_run (); 147 | vt_paging_tlbflush (); 148 | if (!nmi) { 149 | vt__event_delivery_check (); 150 | vt__exit_reason (); 151 | } 152 | } 153 | } 154 | } 155 | ``` 156 | `vt__vm_run`,`vt__vm_run_with_tf`関数でvmenterする. 157 | vmexitするとここから帰ってきて,`vt__exit_reason`でexit reasonに応じた動作をするっぽい. 158 | -------------------------------------------------------------------------------- /exploit/vm-escape-qemu-case-study.md: -------------------------------------------------------------------------------- 1 | # はじめに 2 | 現在,仮想マシンは個人でも商用利用でも多くの場面で利用されています.ネットワーク・セキュリティベンダーはマルウェア解析のためにを制限的かつ制御可能なマシンとしてVMを利用しています.ここで疑問が生じます.“マルウェアはVMを飛び出してホスト上で任意のコードが実行できるのでは??”と. 3 | 去年,CrowdStrikeのJson GeffnerはQEMUの仮想フロッピーディスクのコードにVMEscapeの可能性がある重大なバグを報告しました.ネットワーク・セキュリティ界隈で注目されたのにもかかわらず(名前がマーベルのVENOMを彷彿とさせるからでしょうが),これははじめてのVMEscapeではないのです. 4 | 2011年,Nelson ELhageはQEMUのPCIデバイスホットプラグの脆弱性をexploitしたことを報告しました.このエクスプロイトは[ここ](https://github.com/nelhage/virtunoid/blob/master/virtunoid.c)から閲覧できます. 5 | 6 | 最近では,Qihoo360所属のXu LiuとShengping WangがHITB2016でKVM/QEMUのエクスプロイトを発表しました.このエクスプロイトは2つのネットワークカードデバイスエミュレータ(RTL8139とPCNET)に存在する2つの脆弱性(CVE-2015-5165 and CVE-2015-7504)を利用していました.発表ではホスト上で任意のコード実行までの概要を説明しましたが,exploitを再現するための技術的かつ詳細な説明はなされませんでした. 7 | 8 | この記事では,CVE-2015-5165(メモリリーク脆弱性)とCVE-2015-7504(ヒープオーバーフロー脆弱性)の2つの脆弱性について詳解します.この2つのエクスプロイトを組み合わせることによりVMから抜け出しホスト上で任意のコードを実行します!またQEMUのネットワークカードエミュレータ上の脆弱性をエクスプロイトする方法や,今後の新たな脆弱性で利用できるようなテクニックを説明します.例えば,共有メモリや共有コードを用いたインタラクティブバインドシェルなどです. 9 | 10 | # KVM/QEMuについて 11 | KVMはLinuxのカーネルモジュールで,完全仮想化基盤をユーザ空間上のプログラムに提供するための基盤です.KVM上ではWindowsやLinuxを無改造で複数のVMを動かすことができます.KVMのユーザスペース上のコンポーネントは主にQEMUで,これは特にデバイスミュレーションを担当します. 12 | 13 | ## 実験環境(筆者が付け加えた項目があります) 14 | サンプルコードを試すための簡単な実験環境を用意しましょう!原文ママではハマる人もいると思うので詳細に書きます.それではいきますよ. 15 | 16 | まず,僕たちが扱う脆弱性は既に修正されているので,修正前のソースコードをcheckoutしなければなりません.それからQEMUをx86_64だけをターゲットにしてDEBUGモードをオンにしてビルドします. 17 | 18 | ```sh 19 | $ git clone git://git.qemu-project.org/qemu.git 20 | $ cd qemu 21 | $ git checkout bd80b59 22 | $ mkdir -p bin/debug/native 23 | $ cd bin/debug/native 24 | $ ../../../configure --target-list=x86_64-softmmu --enable-debug \ 25 | $ --disable-werror --disable-xen 26 | $ make 27 | ``` 28 | 29 | 記事時点ではGCC 4.9.2でビルドしている. 30 | 次に仮想マシンを起動する. 31 | ``` 32 | $ ./qemu-system-x86_64 -enable-kvm -m 2048 -display vnc=:89 \ 33 | $ -netdev user,id=t0, -device rtl8139,netdev=t0,id=nic0 \ 34 | $ -netdev user,id=t1, -device pcnet,netdev=t1,id=nic1 \ 35 | $ -drive file=,format=qcow2,if=ide,cache=writeback 36 | ``` 37 | 38 | このコマンドで2GBのRAMとRTL8139とPCNETの2つのNICを持つVMを生成した.記事ではDebian 7 running a 3.16 kernel on x_86_64の環境である. 39 | 40 | ## QEMUのメモリレイアウト 41 | ゲストの物理アドレスはQEMU専用の仮想アドレスにmmapされる.QEMUがゲストのためにメモリ確保しているときはPROT_EXEC(実行フラグ)が立っていないことに注意してほしい. 42 | ``` 43 | Guest' processes 44 | +--------------------+ 45 | Virtual addr space | | 46 | +--------------------+ 47 | | | 48 | \__ Page Table \__ 49 | \ \ 50 | | | Guest kernel 51 | +----+--------------------+----------------+ 52 | Guest's phy. memory | | | | 53 | +----+--------------------+----------------+ 54 | | | 55 | \__ \__ 56 | \ \ 57 | | QEMU process | 58 | +----+------------------------------------------+ 59 | Virtual addr space | | | 60 | +----+------------------------------------------+ 61 | | | 62 | \__ Page Table \__ 63 | \ \ 64 | | | 65 | +----+-----------------------------------------------++ 66 | Physical memory | | || 67 | +----+-----------------------------------------------++ 68 | ``` 69 | この図がおおまかなマッピングである. 70 | またQEMUはBIOSとROM用にメモリを確保する. 71 | `/proc/PID/maps`から確認できる.[memory](https://github.com/qemu/qemu/blob/master/docs/devel/memory.txt) 72 | 73 | ## アドレス解決 74 | QEMUには2つのアドレス解決レイヤがある. 75 | * ゲスト仮想アドレス(GVA)からゲスト物理アドレス(GPA)への変換をするレイヤ.今回のエクスプロイトではDMAアクセスするネットワークカードデバイスをつくる. 76 | * ゲストの物理アドレス(GPA)からホストの仮想アドレス(HVA)へと変換するレイヤ.改変した構造体のデータをここに挿入して,そのアドレスを知る必要がある. 77 | 78 | x64では仮想アドレスは4KBページならPageEntry(PE),PageDirectory(PD),PageDirectoryPointer(PDP),PML4(PageMapLevel4)で表現される. 79 | LinuxではCAP_SYS_ADMINケイパビリティを持つユーザプロセスが物理アドレスフレームが解決できるpagemapにアクセスできる.pagemapには以下のような内容が記されている. 80 | - Bits 0-54 : physical frame number if present. 81 | - Bit 55 : page table entry is soft-dirty. 82 | - Bit 56 : page exclusively mapped. 83 | - Bits 57-60 : zero 84 | - Bit 61 : page is file-page or shared-anon. 85 | - Bit 62 : page is swapped. 86 | - Bit 63 : page is present. 87 | 88 | 以下のプログラムはメモリをmmapし,“Where am I?”という文字列を格納し,その物理アドレスを表示する. 89 | 90 | ```c 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | 99 | #define PAGE_SHIFT 12 100 | #define PAGE_SIZE (1 << PAGE_SHIFT) 101 | #define PFN_PRESENT (1ull << 63) 102 | #define PFN_PFN ((1ull << 55) - 1) 103 | 104 | int fd; 105 | 106 | uint32_t page_offset(uint32_t addr) 107 | { 108 | return addr & ((1 << PAGE_SHIFT) - 1); 109 | } 110 | 111 | uint64_t gva_to_gfn(void *addr) 112 | { 113 | uint64_t pme, gfn; 114 | size_t offset; 115 | offset = ((uintptr_t)addr >> 9) & ~7; 116 | lseek(fd, offset, SEEK_SET); 117 | read(fd, &pme, 8); 118 | if (!(pme & PFN_PRESENT)) 119 | return -1; 120 | gfn = pme & PFN_PFN; 121 | return gfn; 122 | } 123 | 124 | uint64_t gva_to_gpa(void *addr) 125 | { 126 | uint64_t gfn = gva_to_gfn(addr); 127 | assert(gfn != -1); 128 | return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr); 129 | } 130 | 131 | int main() 132 | { 133 | uint8_t *ptr; 134 | uint64_t ptr_mem; 135 | 136 | fd = open("/proc/self/pagemap", O_RDONLY); 137 | if (fd < 0) { 138 | perror("open"); 139 | exit(1); 140 | } 141 | 142 | ptr = malloc(256); 143 | strcpy(ptr, "Where am I?"); 144 | printf("%s\n", ptr); 145 | ptr_mem = gva_to_gpa(ptr); 146 | printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem); 147 | 148 | getchar(); 149 | return 0; 150 | } 151 | ``` 152 | このプログラムをゲストでroot権限で実行し,ホストからgdbアタッチしてみる. 153 | ```c 154 | root@yayoi:/home/moly# gdb -p 11641 155 | (gdb) info proc mappings 156 | process 11641 157 | Mapped address spaces: 158 | 159 | Start Addr End Addr Size Offset objfile 160 | 0x55a3b038e000 0x55a3b1087000 0xcf9000 0x0 /home/moly/phrack/aa/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64 161 | 0x55a3b1286000 0x55a3b1397000 0x111000 0xcf8000 /home/moly/phrack/aa/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64 162 | 0x55a3b1397000 0x55a3b1551000 0x1ba000 0xe09000 /home/moly/phrack/aa/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64 163 | 0x55a3b1551000 0x55a3b1583000 0x32000 0x0 164 | 0x55a3b33a7000 0x55a3b45a0000 0x11f9000 0x0 [heap] 165 | ... 166 | 0x7f4747e00000 0x7f47c7e00000 0x80000000 0x0 <- HVA!!! 167 | ... 168 | 0x7f47e2e46000 0x7f47e2e47000 0x1000 0x0 169 | 0x7ffd67ae3000 0x7ffd67b05000 0x22000 0x0 [stack] 170 | 0x7ffd67b6a000 0x7ffd67b6d000 0x3000 0x0 [vvar] 171 | 0x7ffd67b6d000 0x7ffd67b6f000 0x2000 0x0 [vdso] 172 | 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall] 173 | 174 | (gdb) x/s 0x7f4747e00000+0x2bb02260 175 | 0x7f4773902260: "Where am I?" 176 | ``` 177 | gdbの`info proc mappings`でメモリマップを表示し,ゲストの物理アドレス(GPA)がマップされたホストの仮想アドレス(HVA)を見つける. 178 | HVAにプログラムの出力したゲストの物理アドレスを足して,文字列表示してみると"Where am I?"が見事表示される.つまりゲストの物理アドレスを自らの手で解決したことになる! 179 | 180 | 181 | # メモリリークエクスプロイト 182 | これからCVE-2015-5165のエクスプロイトを行う.この脆弱性はRTL8139ネットワークカードのエミュレータに脆弱性があるため,エクスプロイトするためには脆弱性をつくようなデータをQEMUに挿入する必要がある.次の2つのアドレスがエクスプロイトの鍵となる. 183 | 184 | - .textセクションのベースアドレス(shellcode挿入用) 185 | - ゲスト用の物理ベースアドレス(細工されたデータのアドレスをしるため) 186 | 187 | ## 脆弱性のあるコード 188 | リアルテックのネットワークカードには`C`(受信)と`C+`(送信)というふたつのモードがある.`C+`モード,つまり送信モードの際,この脆弱なコードは誤って実際よりの多くのIPパケットデータを送ろうとする. 189 | 問題は`hw/net/rtl8139.c`にある`rtl8139_cplus_transmit_one`関数だ. 190 | 191 | ```c 192 | /* ip packet header */ 193 | ip_header *ip = NULL; 194 | int hlen = 0; 195 | uint8_t ip_protocol = 0; 196 | uint16_t ip_data_len = 0; 197 | 198 | uint8_t *eth_payload_data = NULL; 199 | size_t eth_payload_len = 0; 200 | 201 | int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12)); 202 | if (proto == ETH_P_IP) 203 | { 204 | DPRINTF("+++ C+ mode has IP packet\n"); 205 | 206 | /* not aligned */ 207 | eth_payload_data = saved_buffer + ETH_HLEN; 208 | eth_payload_len = saved_size - ETH_HLEN; 209 | 210 | ip = (ip_header*)eth_payload_data; 211 | 212 | if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) { 213 | DPRINTF("+++ C+ mode packet has bad IP version %d " 214 | "expected %d\n", IP_HEADER_VERSION(ip), 215 | IP_HEADER_VERSION_4); 216 | ip = NULL; 217 | } else { 218 | hlen = IP_HEADER_LENGTH(ip); 219 | ip_protocol = ip->ip_p; 220 | ip_data_len = be16_to_cpu(ip->ip_len) - hlen; 221 | } 222 | } 223 | ``` 224 | IPヘッダには`hlen`と`ip->ip_len`の2つのメンバがある.`hlen`はIPヘッダの長さ(オプション無しのパケットで20byte)で,`ip->ip_len`はipヘッダを含むIPパケットすべての長さである.先程のコードではIPデータの長さ(`ip_data_len`)を計算するのに,`ip->ip_len >= hlen`であることを確認していない.`ip_data_len`は符号なし整数なので整数オーバーフローが起こる.したがって,実際に送ることのできるより多くのデータが送信可能である.つまり送信バッファにてバッファオーバーフロー攻撃が可能となる. 225 | 次に,`ip_data_len`はTCPデータの長さの計算の際に使用されるが,IPからTCPにパケットがくるまれるとき,データがTCPの構造体へとコピーされるわけだが,このデータがMTUより大きい場合,1つ1つmallocされるheap領域にコピーされる. 226 | 227 | ```c 228 | int tcp_data_len = ip_data_len - tcp_hlen; 229 | int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen; 230 | 231 | int is_last_frame = 0; 232 | 233 | for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; 234 | tcp_send_offset += tcp_chunk_size) { 235 | uint16_t chunk_size = tcp_chunk_size; 236 | 237 | /* check if this is the last frame */ 238 | if (tcp_send_offset + tcp_chunk_size >= tcp_data_len) { 239 | is_last_frame = 1; 240 | chunk_size = tcp_data_len - tcp_send_offset; 241 | } 242 | 243 | memcpy(data_to_checksum, saved_ip_header + 12, 8); 244 | 245 | if (tcp_send_offset) { 246 | memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, 247 | (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, 248 | chunk_size); 249 | } 250 | 251 | /* more code follows */ 252 | } 253 | ``` 254 | 255 | したがって,変な長さのパケットを作ることができれば(`ip->ip_len = hlen - 1`のような),64KBものQEMUでのヒープオーバーフローを起こせる.1パケットも送信することなく,43の分割されたパケットを送信できる. 256 | 257 | ## ネットワークカードを設定する 258 | 細工したパケッを送り,データを読み出すには送受信用のNICのバッファを用意する必要がある.また,あるフラグを立てて,確実に先程の脆弱なコードが実行されるようにしなければならない. 259 | 260 | ``` 261 | +---------------------------+----------------------------+ 262 | 0x00 | MAC0 | MAR0 | 263 | +---------------------------+----------------------------+ 264 | 0x10 | TxStatus0 | 265 | +--------------------------------------------------------+ 266 | 0x20 | TxAddr0 | 267 | +-------------------+-------+----------------------------+ 268 | 0x30 | RxBuf |ChipCmd| | 269 | +-------------+------+------+----------------------------+ 270 | 0x40 | TxConfig | RxConfig | ... | 271 | +-------------+-------------+----------------------------+ 272 | | | 273 | | skipping irrelevant registers | 274 | | | 275 | +---------------------------+--+------+------------------+ 276 | 0xd0 | ... | |TxPoll| ... | 277 | +-------+------+------------+--+------+--+---------------+ 278 | 0xe0 | CpCmd | ... |RxRingAddrLO|RxRingAddrHI| ... | 279 | +-------+------+------------+------------+---------------+ 280 | ``` 281 | 282 | 上の図がRTL8139のレジスタである. 283 | - TxConfig: Tx(送信)のフラグ TxLoopBack,TxCRC,etc. 284 | - RxConfig: Rx(受信)のフラグ AcceptBroadcast, AcceptMulticast , etc. 285 | - CpCmd: C+(送信モード)用のコマンドレジスタ 指定されたコマンドを行わせる CplusRxEnd (enable receive), CplusTxEnd (enable transmit), etc. 286 | - TxAddr0: Txディスクリプタの物理アドレス 287 | - RxRingAddrLO: ディスクリプタテーブルの物理アドレスの下位32bit 288 | - RxRingAddrHI: Rxディスクリプタテーブルの物理アドレスの上位32bit 289 | - TxPoll: NICに送信させるように通知するレジスタ 290 | 291 | ```c 292 | struct rtl8139_desc { 293 | uint32_t dw0; 294 | uint32_t dw1; 295 | uint32_t buf_lo; //物理アドレスの下位32bit 296 | uint32_t buf_hi; //物理アドレスの上位32bit 297 | }; 298 | ``` 299 | 送受信ディスクリプタは上のような構造体になっている. 300 | `buf_lo`と`buf_lo`はディスクリプタのアドレスで,ディスクリプタはページアラインドである必要がある. 301 | `dw0`はバッファのサイズを追加のフラグ(バッファがだれに保持されているかを示すownership flagなど)でエンコードしたものである. 302 | ネットワークカードは`in()`や`out()`などのI/Oポートアクセス関数で設定できる. 303 | 304 | 次のコードはTxディスクリプタを1つ設定するコードである. 305 | ```c 306 | #define RTL8139_PORT 0xc000 307 | #define RTL8139_BUFFER_SIZE 1500 308 | 309 | struct rtl8139_desc desc; 310 | void *rtl8139_tx_buffer; 311 | uint32_t phy_mem; 312 | 313 | rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE, RTL8139_BUFFER_SIZE); 314 | phy_mem = (uint32)gva_to_gpa(rtl8139_tx_buffer); 315 | 316 | memset(&desc, 0, sizeof(struct rtl8139_desc)); 317 | 318 | desc->dw0 |= CP_TX_OWN | CP_TX_EOR | CP_TX_LS | CP_TX_LGSEN | 319 | CP_TX_IPCS | CP_TX_TCPCS; 320 | desc->dw0 += RTL8139_BUFFER_SIZE; 321 | 322 | desc.buf_lo = phy_mem; 323 | 324 | iopl(3); 325 | 326 | outl(TxLoopBack, RTL8139_PORT + TxConfig); 327 | outl(AcceptMyPhys, RTL8139_PORT + RxConfig); 328 | 329 | outw(CPlusRxEnb|CPlusTxEnb, RTL8139_PORT + CpCmd); 330 | outb(CmdRxEnb|CmdTxEnb, RTL8139_PORT + ChipCmd); 331 | 332 | outl(phy_mem, RTL8139_PORT + TxAddr0); 333 | outl(0x0, RTL8139_PORT + TxAddr0 + 0x4); 334 | 335 | ``` 336 | 337 | ## エクスプロイト 338 | エクスプロイトコードはまずTx/Rxバッファの設定をレジスタを通じて行う.次に悪意のあるパケットを送りつけて,Rxバッファよりリークされたデータを読み出す. 339 | 読み出されたデータを確認すると,いくつかの関数ポインタがみえてくる.これらは以下に示すQEMU内部の構造体のメンバである. 340 | 341 | ```c 342 | typedef struct ObjectProperty 343 | { 344 | gchar *name; 345 | gchar *type; 346 | gchar *description; 347 | ObjectPropertyAccessor *get; 348 | ObjectPropertyAccessor *set; 349 | ObjectPropertyResolve *resolve; 350 | ObjectPropertyRelease *release; 351 | void *opaque; 352 | 353 | QTAILQ_ENTRY(ObjectProperty) node; 354 | } ObjectProperty; 355 | ``` 356 | 357 | QEMUはデバイスやメモリ領域をオブジェクトで管理している.起動時に,こうしたオブジェクトを生成し,デバイスやメモリ領域にプロパティを割り当てる.例えば,次のコードはメモリ領域オブジェクトに`may-overlap`というプロパティを設定する. 358 | 359 | ```c 360 | object_property_add_bool(OBJECT(mr), "may-overlap", 361 | memory_region_get_may_overlap, 362 | NULL, /* memory_region_set_may_overlap */ 363 | &error_abort); 364 | ``` 365 | 366 | このプロパティはオブジェクトのgetterメソッドから取得できる. 367 | 368 | RTL8139NICエミュレータはヒープ領域から64KBを悪意のあるパケットのために確保する.この領域は他のfreeされたプロパティの上に確保されている可能性が高いです. 369 | 370 | このエクスプロイトではリークされたメモリから`ObjectProperty`構造体(80bytes)を探し当てます.`ObjectProperty`構造体には関数ポインタがあるので,たとえASLRが有効であっても`.text`セクションのベースアドレスを得ることができます.ページアラインドであるということは,下位12bit目は固定です(4kbページングより).`.text`セクションのベースアドレスがわかれば,QEMUの関数が利用できます.またPLT領域からlibcの関数アドレスすらもわかるのです!また私達は`PHY_MEM`+`0x78`のようなアドレスが何回も出現すれば,`PHY_MEM`はゲストの物理アドレス(GPA)のベースアドレスだとわかった.(ほんまか?どうして?わからん……) 371 | さて,ここまででQEMUの`.text`セグメントのベースアドレスと,QEMUのゲスト物理アドレス(GPA)がわかった. 372 | -------------------------------------------------------------------------------- /hv/how-to-re-inject-event.md: -------------------------------------------------------------------------------- 1 | # どのようにしてイベントをを再挿入するか? 2 | ## 大まかな流れ 3 | `vm_entry_interruption_information`を設定し,VMENTRYするだけ. 4 | 5 | ## ハードウェア例外 6 | TBD 7 | 8 | ## ソフトウェア例外 9 | TBD 10 | 11 | ## ソフトウェア割り込み 12 | TBD 13 | 14 | ## 外部割り込み 15 | これだけいろいろ大変だとsatさんのドキュメントで読んだことがある 16 | TBD 17 | 18 | ## privileged_software_exception 19 | とは?? 20 | TBD 21 | 22 | ## non_maskable_interrupt 23 | とは? 24 | TBD 25 | -------------------------------------------------------------------------------- /hv/injection-vmcs.md: -------------------------------------------------------------------------------- 1 | # 例外のインジェクションに関わるVMCSについて 2 | ## vm_entry_interruption_information 3 | `vm_entry_interruption_information`には,以下の項目がある. 4 | * vector ベクター 5 | * ただの例外番号 6 | * システムコール番号に対応する 7 | * int3なら3bit目にbitが立つ 8 | * interruption_type 9 | * 例外の種類 10 | * external_interrupt = 0ULL; 11 | * non_maskable_interrupt = 2ULL; 12 | * hardware_exception = 3ULL; 13 | * software_interrupt = 4ULL; 14 | * privileged_software_exception = 5ULL; 15 | * software_exception = 6ULL; 16 | * deliver_error_code_bit 17 | * エラーコードをスタックに積むかどうかをきめる 18 | * reserved 19 | * TBD 20 | * valid_bit 21 | * このbitがたっているときのみ,インジェクションが起こる 22 | * 毎回フラッシュされる 23 | 24 | ## vm entry exception error code 25 | exceptionのエラーコード. 26 | PageFaultならPFEC(Page Fault Error Code) 27 | 28 | ## vm entry instruction length 29 | 命令の長さを格納する. 30 | そのぶんRIPが進む. 31 | -------------------------------------------------------------------------------- /hv/nw_driver.md: -------------------------------------------------------------------------------- 1 | # ネットワークドライバ概要 2 | ハイパーバイザでのネットワークドライバの実装等をまとめる 3 | 4 | ## ネットワークドライバのいろは 5 | ### レジスタとかLinuxカーネルでのあつかい 6 | ネットワークドライバはハードウェアを初期化して,システムコールに応じてコールバック関数を提供する(インターフェースはLinuxだとhogehogeで定義) 7 | ドライバはMMIO領域とDMA領域をもっている.ディスクリプタリングやそれを実現するためのレジスタ等はMMIOマッピングされた物理メモリ領域に存在する.アドレスはバスアドレスになっているが,x86だとバスアドレスと物理アドレスは同等である. 8 | 9 | ### わりこみとか 10 | NICはパケットを受信したらデータをDMAしてから,受信時に割り込みを発生させる. 11 | ネットワークドライバはそれを検知してリングバッファをみにいく. 12 | んで受信処理をおこなう.(kbufとかにリングバッファの受信バッファをみにいく(これはNICによりDMAされてるよ!!)) 13 | 送信時はkbufにつつまれたパケットをカーネルがリングバッファのバッファにコピーして,リングバッファのレジスタ(THDとか)を更新してから,割り込みをかける.その後,NICがDMAしてリングバッファからデータをNIC側にコピーして送信する. 14 | そういやこの送受信の割り込みをすべてトラップするのはパフォーマンス上ネックになる.この送受信の割り込みはIDTにより管理されているのは周知の事実であるが,これをPCI Configuration SpaceのBARの使っていない領域にshadowingさせてトラップするELIとか有名ね.ベアメタルの97%の速度を叩き出してた.(すごい) 15 | 16 | ## Descriptor shadowing 17 | ディスクリプタをshadowingすることによってハイパーバイザがゲストのパケットの送受信を管理することができる.送信時はゲストがTDT(MMIOレジスタ)を更新したときにEPT Violationを起こしてトラップ,受信時はゲストが割り込み確認のときにMMIOレジスタをみにいったときにEPT Violationしてトラップ.NICはゲストのではなくshadowingされたディスクリプタをみることになる. 18 | 19 | ## ゲストにみせるか専有するか,それともvirtioか? 20 | ゲストに見せる場合は初期化とかをゲストに任せることができてハイパーバイザは楽ができる.virtioや専有はハイパーバイザが初期化を含むドライバを開発する必要があってだるい. 21 | -------------------------------------------------------------------------------- /hv/poem-about-xen.md: -------------------------------------------------------------------------------- 1 | # Xenについて思うこと 2 | ## VMCSが構造化されてないのがヤダヤダヤダ 3 | bareflankだとnamespaceで区切って,インターフェースも割といい感じに作れられているけど,xenはvmreadとかして頑張って読んでいる.そこは隠蔽しておいてほしいし,拡張する側にとってもうれしいと思う. 4 | まあ一応,`x86_event`構造体とかあるけど,まあCだし,構造体にメソッドを賭けないので仕方がないとは思うんだけど. 5 | つまりシステムプログラミングにおける言語のチョイスとして,Cはもうだるすぎ太郎ではないかと思う. 6 | というわけでボキはRustを使って頑張ろうかなあと思っているんだが,どうだ? 7 | -------------------------------------------------------------------------------- /hv/re-inject-pf-xen.md: -------------------------------------------------------------------------------- 1 | # xenでのpage faultのre-injection手法 2 | Xenでのページフォールトの再挿入方法をコードリーディングにより紐解く. 3 | 一言で言えば,`vm_entry_interruption_information`を設定し,インジェクションを行う. 4 | 5 | ## エントリポイント 6 | まずは`xen/xen/include/asm-x86/hvm/hvm.h`の中にある`hvm_inject_page_fault`関数に注目する. 7 | ```C 8 | static inline void hvm_inject_page_fault(int errcode, unsigned long cr2) 9 | { 10 | struct x86_event event = { 11 | .vector = TRAP_page_fault, 12 | .type = X86_EVENTTYPE_HW_EXCEPTION, 13 | .error_code = errcode, 14 | .cr2 = cr2, 15 | }; 16 | 17 | hvm_inject_event(&event); 18 | } 19 | ``` 20 | これがどうやらページフォールトを再挿入している関数であることがわかる. 21 | この関数の中で,`x86_event`構造体を組み立て,`hvm_inject_event`関数に渡している. 22 | ちなみに`errcode`はPFEC(Page Fault Error Code)で,`cr2`はページフォールトが起こったアドレスが格納されるcr2レジスタの中身である. 23 | 24 | ## `x86_event`構造体とはなにか? 25 | `xen/arch/x86/x86_emulate/x86_emulate.h`の中で定義されている構造体だ. 26 | 定義は以下のようになっている. 27 | ```C 28 | struct x86_event { 29 | int16_t vector; 30 | uint8_t type; /* X86_EVENTTYPE_* */ 31 | uint8_t insn_len; /* Instruction length */ 32 | int32_t error_code; /* X86_EVENT_NO_EC if n/a */ 33 | unsigned long cr2; /* Only for TRAP_page_fault h/w exception */ 34 | }; 35 | ``` 36 | その名の通り,イベントを表す構造体である. 37 | `cr2`にはページフォールトが起こったアドレスが格納される. 38 | 普通の例外や割り込みでは使われないはずだ. 39 | 40 | ## `hvm_inject_event`関数 41 | 以下のコードが該当する関数である. 42 | なお,Assertなコードは本質ではないため除外している. 43 | ```C 44 | void hvm_inject_event(const struct x86_event *event) 45 | { 46 | struct vcpu *curr = current; 47 | const uint8_t vector = event->vector; 48 | // 49 | // nested vmなコードのところなので省略…… 50 | // 51 | hvm_funcs.inject_event(event); 52 | } 53 | ``` 54 | `hvm_funcs`は`struct hvm_function_table hvm_funcs __read_mostly;`である. 55 | つまり`hvm_function_table`を探す. 56 | これは`xen/include/asm-x86/hvm/hvm.h`で定義されている,`x86/x86_64 CPU virtualization assist specifics`なインターフェースである. 57 | これはIntel VT-xとAMD SVMを隠蔽している. 58 | 今回はIntel Vt-xに注目していく. 59 | 60 | ## Intel vt-xでの`hvm_function_table` 61 | `xen/arch/x86/hvm/vmx/vmx.c`の中に定義されている. 62 | ```C 63 | static struct hvm_function_table __initdata vmx_function_table = { 64 | .name = "VMX", 65 | // 66 | // 省略 67 | // 68 | .inject_event = vmx_inject_event, 69 | // 70 | // 省略 71 | // 72 | }; 73 | ``` 74 | このようにただの関数のテーブルであるが,目的の`inject_event`の実態は`vmx_inject_event`であることがわかる. 75 | 76 | ## inject_event関数 77 | ```C 78 | /* 79 | * Generate a virtual event in the guest. 80 | * NOTES: 81 | * - INT 3 (CC) and INTO (CE) are X86_EVENTTYPE_SW_EXCEPTION; 82 | * - INT nn (CD nn) is X86_EVENTTYPE_SW_INTERRUPT; 83 | * - #DB is X86_EVENTTYPE_HW_EXCEPTION, except when generated by 84 | * opcode 0xf1 (which is X86_EVENTTYPE_PRI_SW_EXCEPTION) 85 | */ 86 | static void vmx_inject_event(const struct x86_event *event) 87 | { 88 | unsigned long intr_info; 89 | struct vcpu *curr = current; 90 | struct x86_event _event = *event; 91 | 92 | switch ( _event.vector | -(_event.type == X86_EVENTTYPE_SW_INTERRUPT) ) 93 | { 94 | case TRAP_debug: 95 | if ( guest_cpu_user_regs()->eflags & X86_EFLAGS_TF ) 96 | { 97 | __restore_debug_registers(curr); 98 | write_debugreg(6, read_debugreg(6) | DR_STEP); 99 | } 100 | if ( !nestedhvm_vcpu_in_guestmode(curr) || 101 | !nvmx_intercepts_exception(curr, TRAP_debug, _event.error_code) ) 102 | { 103 | unsigned long val; 104 | 105 | __vmread(GUEST_DR7, &val); 106 | __vmwrite(GUEST_DR7, val & ~DR_GENERAL_DETECT); 107 | __vmread(GUEST_IA32_DEBUGCTL, &val); 108 | __vmwrite(GUEST_IA32_DEBUGCTL, val & ~IA32_DEBUGCTLMSR_LBR); 109 | } 110 | if ( cpu_has_monitor_trap_flag ) 111 | break; 112 | /* fall through */ 113 | case TRAP_int3: 114 | if ( curr->domain->debugger_attached ) 115 | { 116 | /* Debug/Int3: Trap to debugger. */ 117 | domain_pause_for_debugger(); 118 | return; 119 | } 120 | break; 121 | 122 | case TRAP_page_fault: 123 | ASSERT(_event.type == X86_EVENTTYPE_HW_EXCEPTION); 124 | curr->arch.hvm_vcpu.guest_cr[2] = _event.cr2; 125 | break; 126 | } 127 | /* 128 | if ( nestedhvm_vcpu_in_guestmode(curr) ) 129 | intr_info = vcpu_2_nvmx(curr).intr.intr_info; 130 | else*/ 131 | __vmread(VM_ENTRY_INTR_INFO, &intr_info); 132 | 133 | if ( unlikely(intr_info & INTR_INFO_VALID_MASK) && 134 | (MASK_EXTR(intr_info, INTR_INFO_INTR_TYPE_MASK) == 135 | X86_EVENTTYPE_HW_EXCEPTION) ) 136 | { 137 | _event.vector = hvm_combine_hw_exceptions( 138 | (uint8_t)intr_info, _event.vector); 139 | if ( _event.vector == TRAP_double_fault ) 140 | _event.error_code = 0; 141 | } 142 | /* 143 | if ( _event.type >= X86_EVENTTYPE_SW_INTERRUPT ) 144 | __vmwrite(VM_ENTRY_INSTRUCTION_LEN, _event.insn_len); 145 | 146 | if ( nestedhvm_vcpu_in_guestmode(curr) && 147 | nvmx_intercepts_exception(curr, _event.vector, _event.error_code) ) 148 | { 149 | nvmx_enqueue_n2_exceptions (curr, 150 | INTR_INFO_VALID_MASK | 151 | MASK_INSR(_event.type, INTR_INFO_INTR_TYPE_MASK) | 152 | MASK_INSR(_event.vector, INTR_INFO_VECTOR_MASK), 153 | _event.error_code, hvm_intsrc_none); 154 | return; 155 | } 156 | else*/ 157 | __vmx_inject_exception(_event.vector, _event.type, _event.error_code); 158 | 159 | if ( (_event.vector == TRAP_page_fault) && 160 | (_event.type == X86_EVENTTYPE_HW_EXCEPTION) ) 161 | HVMTRACE_LONG_2D(PF_INJECT, _event.error_code, 162 | TRC_PAR_LONG(curr->arch.hvm_vcpu.guest_cr[2])); 163 | /* 164 | else 165 | HVMTRACE_2D(INJ_EXC, _event.vector, _event.error_code);*/ 166 | } 167 | ``` 168 | 最初のswitch-caseで`TRAP_page_fault`に行き, 169 | ```C 170 | curr->arch.hvm_vcpu.guest_cr[2] = _event.cr2; 171 | ``` 172 | で,VCPUのCR2にトラップ時のcr2の値が格納される. 173 | 次に,Bareflankでの`vm_entry_interruption_information`を読み出す.(`vmread`命令を直接叩いている.隠蔽しようとかならなかったわけ?) 174 | これはintelの仕様書を読むよりもbareflankのコードを読むほうが構造化されており把握しやすいので参照してください. 175 | `intr_info`に割り込みの情報が保存される. 176 | 次に,`valid_bit`が`false`で,ハードウェアエクセプションタイプならば,下記のコードが実行される. 177 | `valid_bit`の指すところがいまいち把握できていないが,bareflankで起こったページフォールトを確認すると,`valid_bit`は`false`だった. 178 | したがって,下記のコードが実行される. 179 | ```C 180 | _event.vector = hvm_combine_hw_exceptions( 181 | (uint8_t)intr_info, _event.vector); 182 | if ( _event.vector == TRAP_double_fault ) 183 | _event.error_code = 0; 184 | ``` 185 | 186 | `hvm_combine_hw_exceptions`関数は 187 | ```C 188 | /* 189 | * Combine two hardware exceptions: @vec2 was raised during delivery of @vec1. 190 | * This means we can assume that @vec2 is contributory or a page fault. 191 | */ 192 | uint8_t hvm_combine_hw_exceptions(uint8_t vec1, uint8_t vec2) 193 | { 194 | const unsigned int contributory_exceptions = 195 | (1 << TRAP_divide_error) | 196 | (1 << TRAP_invalid_tss) | 197 | (1 << TRAP_no_segment) | 198 | (1 << TRAP_stack_error) | 199 | (1 << TRAP_gp_fault); 200 | const unsigned int page_faults = 201 | (1 << TRAP_page_fault) | 202 | (1 << TRAP_virtualisation); 203 | 204 | /* Exception during double-fault delivery always causes a triple fault. */ 205 | if ( vec1 == TRAP_double_fault ) 206 | { 207 | hvm_triple_fault(); 208 | return TRAP_double_fault; /* dummy return */ 209 | } 210 | 211 | /* Exception during page-fault delivery always causes a double fault. */ 212 | if ( (1u << vec1) & page_faults ) 213 | return TRAP_double_fault; 214 | 215 | /* Discard the first exception if it's benign or if we now have a #PF. */ 216 | if ( !((1u << vec1) & contributory_exceptions) || 217 | ((1u << vec2) & page_faults) ) 218 | return vec2; 219 | 220 | /* Cannot combine the exceptions: double fault. */ 221 | return TRAP_double_fault; 222 | } 223 | ``` 224 | これはトラップしたイベントと挿入したいイベントとで競合が発生するときに,融合させる関数. 225 | TODO:知見はコードにまとまっているが,解説する 226 | 227 | 次に,ソフトウェアエクセプション,ICEBP,INT3 (CC), INTO (CE)ならばBareflankでいうところの`vm_entry_instruction_length`に命令の長さが格納される. 228 | ページフォールトは違うので格納されない. 229 | 230 | 次に,`__vmx_inject_exception`が実行される. 231 | ```C 232 | #define MASK_INSR(v, m) (((v) * ((m) & -(m))) & (m)) 233 | 234 | static void __vmx_inject_exception(int trap, int type, int error_code) 235 | { 236 | unsigned long intr_fields; 237 | struct vcpu *curr = current; 238 | 239 | /* 240 | * NB. Callers do not need to worry about clearing STI/MOV-SS blocking: 241 | * "If the VM entry is injecting, there is no blocking by STI or by 242 | * MOV SS following the VM entry, regardless of the contents of the 243 | * interruptibility-state field [in the guest-state area before the 244 | * VM entry]", PRM Vol. 3, 22.6.1 (Interruptibility State). 245 | */ 246 | 247 | intr_fields = INTR_INFO_VALID_MASK | 248 | MASK_INSR(type, INTR_INFO_INTR_TYPE_MASK) | 249 | MASK_INSR(trap, INTR_INFO_VECTOR_MASK); 250 | if ( error_code != X86_EVENT_NO_EC ) 251 | { 252 | __vmwrite(VM_ENTRY_EXCEPTION_ERROR_CODE, error_code); 253 | intr_fields |= INTR_INFO_DELIVER_CODE_MASK; 254 | } 255 | 256 | __vmwrite(VM_ENTRY_INTR_INFO, intr_fields); 257 | } 258 | ``` 259 | この関数で行うことは,エラーコードが必要ならそれを書き込み,valid_bitを立てて,イベントタイプとベクター番号を設定している.挿入の要のコード. 260 | 261 | 最後に,`HVMTRACE_LONG_2D`が実行される. 262 | これは`__trace_var`を内部で呼び出していて,trace buffer(デバッグ用)に書き込むための関数である. 263 | -------------------------------------------------------------------------------- /hv/virtio.md: -------------------------------------------------------------------------------- 1 | # virtio 2 | -------------------------------------------------------------------------------- /hv/warikomi.md: -------------------------------------------------------------------------------- 1 | # 割り込み 2 | 3 | * 外部割り込み 4 | * ハードからCPUへ 5 | * NMI(まずく不可能な割り込み) 6 | * マスク可能な割り込み 7 | * 内部割り込み 8 | * システムコールとかSIGTRAPとか 9 | * ソフトウェア割り込み 10 | * int 11 | * 例外 12 | * 0/0, overflow, etc... 13 | 14 | * ベクター 15 | * 例外: 0~19 16 | * NMI: 2 17 | * ソフトウェア割り込み: 0-255 使用可能 18 | * 外部割り込み: 16-255 使用可能 19 | 20 | * マスク 21 | * STI/CLIのあれ 22 | * 割り込み禁止にできるやーつ. EFLAGSのIFを操作 23 | 24 | -------------------------------------------------------------------------------- /misc/kvm.md: -------------------------------------------------------------------------------- 1 | # KVMとは? 2 | KVMはLinuxカーネルの機能で,Linuxカーネルをハイパーバイザにしたてる技術. 3 | 4 | # コンポーネント 5 | ## KVMカーネルモジュール 6 | VT-xやAMD-Vを使用して仮想マシンを実行するための基盤を提供する.VCPU管理を担当(CPUのような機能をゲストに提供する) 7 | ## qemu-kvm 8 | ユーザプロセスとして動く,I/Oデバイスのエミュレータを実装.メモリ割り当てやデバイスエミュレートをゲストに提供する.(ゲストにとってI/Oとシステムメモリに相当) 9 | 10 | # 歴史 11 | ## QEMU 12 | QEMUはエミュレータで,プロセッサやデバイスを実装している.しかし完全にデバイスとCPUをエミュレートするため,様々な環境を再現することができるが速度は遅い. 13 | 14 | ## kqemu 15 | kqemuはQEMUのアクセラレータで,カーネルモジュールとして提供される.QEMUのプロセッサエミュレートをkqemuにすることで,ゲストの命令を,QEMUのソフトウェア実装されたプロセッサでなく,ホストのプロセッサ上で実行する. 16 | 17 | ## qemu-kvm 18 | keqmuの実装が肥大化する中で,IntelやAMDからのプロセッサでの仮想化支援技術提供により,これを用いるqemu-kvmが開発される. 19 | qemu-kvmによりkqemuは存在意義をなくし,開発中止に至る. 20 | -------------------------------------------------------------------------------- /misc/virtualization.md: -------------------------------------------------------------------------------- 1 | # 仮想化概要 2 | ## Type1とType2 3 | * Type1 - ベアメタルハイパーバイザ.ハードウェアの上で直接動作する.(Xen,KVM,Hyper-V,Bareflank,BitVisor) 4 | * Type2 - ホストOSの上でプロセスとして動く.(VirtualBox, VMWare, QEMU, Bochs) 5 | 6 | ## 完全仮想化と準仮想化 7 | ### 完全仮想化 8 | ハードウェアからBIOS,UEFIなどすべてをエミュレートする仮想化. 9 | VirtualBoxやVMWareなどが有名. 10 | ハードウェアの仮想化はコストが高い. 11 | ### 準仮想化 12 | ハードウェアの仮想化を行わず,仮想ハードウェアにアクセスするように改変したOSを動かすもの. 13 | 完全仮想化よりはやい. 14 | -------------------------------------------------------------------------------- /xen/dom0setup.md: -------------------------------------------------------------------------------- 1 | # dom0setup 2 | `xen/arch/x86/boot/x86_64.S` こいつがbootするためのやつ. 3 | multibootに準拠しているやつなのでわりとわかりやすい. 4 | `call __start_xen`でxenのstartupを行っている. 5 | 6 | ## __start_xen 7 | クソでかい関数でスタートアップの処理がつらつらと記述されている. 8 | おなじみ`trap_init`とかを呼び出したりしている. 9 | 全部読むのは結構だるい. 10 | 11 | * init_xen_time 12 | * init_guest_cpuid 13 | 14 | ``` 15 | /* Create initial domain 0. */ 16 | dom0 = domain_create(get_initial_domain_id(), &dom0_cfg, !pv_shim); 17 | if ( IS_ERR(dom0) || (alloc_dom0_vcpu0(dom0) == NULL) ) 18 | panic("Error creating domain 0\n"); 19 | ``` 20 | --------------------------------------------------------------------------------