├── deploy ├── group_vars │ └── all.yml ├── inventory.yml ├── roles │ ├── xen │ │ ├── .gitignore │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ ├── runner │ │ ├── tasks │ │ │ └── main.yml │ │ └── vars │ │ │ └── main.yml │ └── vmsifter │ │ └── tasks │ │ └── main.yml ├── host_vars │ └── localhost.yml ├── requirements.yml ├── ansible.cfg └── site.yml ├── AUTHORS ├── setup.cfg ├── src ├── signal.h ├── private.c ├── forkvm.h ├── vmi.h ├── signal.c ├── private.h ├── vmi.c └── forkvm.c ├── .gitmodules ├── SECURITY.md ├── tests ├── bench │ ├── test_bench_fuzzer.py │ ├── test_bench_yaxpeax.py │ └── test_bench_capstone.py ├── unit │ ├── utils │ │ ├── test_misc.py │ │ ├── test_completion_rate.py │ │ └── test_xen.py │ ├── test_partition.py │ ├── fuzzer │ │ └── test_fuzzer.py │ └── test_tunnel.py └── integration │ └── disasm │ ├── test_yaxpeax.py │ ├── test_bench.py │ ├── test_capstone.py │ └── test_pool.py ├── vmsifter ├── logging.yaml ├── injector │ ├── common.py │ ├── __init__.py │ ├── xenvm.py │ └── types.py ├── config │ ├── settings.toml │ └── __init__.py ├── fuzzer │ ├── random.py │ ├── partition.py │ ├── __init__.py │ ├── csv.py │ └── tunnel.py ├── utils │ ├── __init__.py │ ├── completion_rate.py │ ├── protected_manager.py │ └── xen.py ├── output.py ├── __main__.py ├── executor.py └── worker.py ├── drizzler.sh ├── docs ├── pyproject.toml ├── source │ ├── index.md │ ├── conf.py │ └── reference │ │ └── configuration.md ├── make.bat └── Makefile ├── meson.build ├── scripts └── capstone-test.py ├── patches ├── 0008-xen-Capture-all-exceptions-as-vmexits.patch ├── 0002-xen-x86-vmx-Load-FPU-state-before-entering.patch ├── 0004-xen-allow-guest-setting-perfmon-freeze.patch ├── 0005-xen-Silence-too-verbose-debug-log-on-HVM-save-restore.patch ├── 0007-xen-VPID-pinning.patch ├── 0006-xen-Skip-setting-copying-magic-pages.patch ├── 0001-libvmi-vmexit-instruction-infos.patch ├── 0003-xen-x86-monitor-report-extra-vmexit-information.patch ├── 0001-xtf-VMSifter-test-execution-VM.patch └── 0001-xen-x86-Make-XEN_DOMCTL_get_vcpu_msrs-more-configura.patch ├── COPYING ├── .github └── workflows │ ├── ci.yml │ ├── campaign.yml │ ├── docs.yml │ └── xen.yml ├── CONTRIBUTING.md ├── README.md ├── pyproject.toml ├── run.sh ├── .gitignore ├── CODE_OF_CONDUCT.md └── Dockerfile /deploy/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/inventory.yml: -------------------------------------------------------------------------------- 1 | localhost -------------------------------------------------------------------------------- /deploy/roles/xen/.gitignore: -------------------------------------------------------------------------------- 1 | files/*.deb 2 | -------------------------------------------------------------------------------- /deploy/roles/xen/defaults/main.yml: -------------------------------------------------------------------------------- 1 | xen_deb_url: "{{ TODO }}" 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Tamas K Lengyel, tamas.lengyel@intel.com 2 | Mathieu Tarral, mathieu.tarral@intel.com 3 | -------------------------------------------------------------------------------- /deploy/host_vars/localhost.yml: -------------------------------------------------------------------------------- 1 | # force default local connection plugin for localhost instead of ssh 2 | ansible_connection: local -------------------------------------------------------------------------------- /deploy/roles/runner/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Import monolithprojects.github_actions_runner 2 | import_role: 3 | name: monolithprojects.github_actions_runner 4 | -------------------------------------------------------------------------------- /deploy/requirements.yml: -------------------------------------------------------------------------------- 1 | roles: 2 | - name: monolithprojects.github_actions_runner 3 | version: 1.21.1 4 | src: https://github.com/MonolithProjects/ansible-github_actions_runner 5 | -------------------------------------------------------------------------------- /deploy/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | # pretty errors 3 | stdout_callback = yaml 4 | stderr_callback = yaml 5 | 6 | # using SSH passwords requires host-key-checking 7 | # disable this 8 | host_key_checking = False 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # add flake8 configuration here as it still doesn't support pyproject.toml yet 2 | [flake8] 3 | max-line-length=120 4 | exclude = .git, .venv, build, dist 5 | # black compatibility 6 | extend-ignore = E203 7 | -------------------------------------------------------------------------------- /src/signal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #ifndef SIGNAL_H 6 | #define SIGNAL_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | void setup_signal_handlers(void); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /deploy/roles/runner/vars/main.yml: -------------------------------------------------------------------------------- 1 | github_account: Wenzel 2 | github_owner: intel 3 | github_repo: vmsifter 4 | runner_dir: "/home/{{ lookup('env', 'USER') }}/actions-runner" 5 | runner_labels: ["{{ ansible_facts.product_name | replace('Client Platform', '') | trim | replace(' ', '_') }}"] 6 | runner_state: "started" 7 | -------------------------------------------------------------------------------- /src/private.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | #include "private.h" 7 | 8 | uint32_t domid; 9 | 10 | xc_interface *xc; 11 | vmi_instance_t vmi; 12 | page_mode_t pm; 13 | int interrupted; 14 | addr_t start_rip; 15 | addr_t target_pagetable; 16 | 17 | -------------------------------------------------------------------------------- /src/forkvm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #ifndef FORKVM_H 6 | #define FORKVM_H 7 | 8 | #include 9 | #define LIBXL_API_VERSION 0x041300 10 | #include 11 | 12 | bool fork_vm(uint32_t domid, uint32_t *forkdomid, uint16_t pinned_cpu); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libvmi"] 2 | path = libvmi 3 | url = https://github.com/libvmi/libvmi 4 | ignore = dirty 5 | shallow = true 6 | [submodule "xen"] 7 | path = xen 8 | url = https://github.com/xen-project/xen 9 | ignore = dirty 10 | shallow = true 11 | [submodule "xtf"] 12 | path = xtf 13 | url = https://github.com/andyhhp/xtf 14 | ignore = dirty 15 | shallow = true 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). 6 | -------------------------------------------------------------------------------- /src/vmi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #ifndef VMI_H 6 | #define VMI_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | bool setup_vmi(vmi_instance_t *vmi, char *domain, uint64_t domid, bool init_events, bool init_paging); 14 | void loop(vmi_instance_t vmi); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /tests/bench/test_bench_fuzzer.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | # benchmark for x86 instruction generator 5 | from itertools import islice 6 | 7 | import pytest 8 | from more_itertools import consume 9 | from xensifter.fuzzer.random import random_fuzzer_gen 10 | 11 | 12 | @pytest.mark.parametrize("count", [10**3, 10**4, 10**5, 10**6]) 13 | def test_gen_insn(count: int): 14 | consume(islice(random_fuzzer_gen(), count)) 15 | -------------------------------------------------------------------------------- /vmsifter/logging.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | disable_existing_loggers: false 3 | # log format will be setup by coloredlogs 4 | formatters: 5 | colored: 6 | # from coloredlogs import DEFAULT_LOG_FORMAT 7 | # and remove the [%(process)d] field 8 | format: '%(asctime)s %(name)s %(levelname)s %(message)s' 9 | handlers: 10 | console: 11 | class: logging.StreamHandler 12 | level: NOTSET 13 | stream: ext://sys.stderr 14 | root: 15 | level: INFO 16 | handlers: [console] 17 | formatter: colored 18 | -------------------------------------------------------------------------------- /tests/unit/utils/test_misc.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from vmsifter.utils import _filter_pcpu_for_injector 5 | from vmsifter.utils.xen import XlInfo, XlVcpuInfo 6 | 7 | 8 | # TODO: SMT enabled 9 | def test_filter_pcpu_for_injector(): 10 | info = XlInfo(nr_cpus=8, max_cpu_id=8 * 2 - 1, nr_nodes=1, cores_per_socket=4, threads_per_core=1) 11 | vcpu_list = (XlVcpuInfo("Domain-0", 0, i, i * 2) for i in range(4)) 12 | 13 | result = list(_filter_pcpu_for_injector(info, vcpu_list)) 14 | expected = [8, 10, 12, 14] 15 | assert result == expected 16 | -------------------------------------------------------------------------------- /tests/bench/test_bench_yaxpeax.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from contextlib import suppress 5 | from itertools import islice 6 | 7 | import pytest 8 | from xensifter.disasm.yaxpeax import YaxpeaxDisasmAdaptee 9 | from xensifter.fuzzer.random import random_fuzzer_gen 10 | 11 | 12 | @pytest.fixture 13 | def yaxpeax(): 14 | return YaxpeaxDisasmAdaptee() 15 | 16 | 17 | @pytest.mark.parametrize("count", [10**3, 10**4, 10**5, 10**6]) 18 | def test_yaxpeax_disasm(yaxpeax, count): 19 | for next_buff in islice(random_fuzzer_gen(), count): 20 | yaxpeax.disasm(next_buff) 21 | -------------------------------------------------------------------------------- /drizzler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | # Copyright (C) 2022 Intel Corporation 4 | # SPDX-License-Identifier: MIT 5 | 6 | mkdir -p "$PWD/workdir" 7 | rm $PWD/workdir/results.csv || : 8 | 9 | docker run --rm -ti \ 10 | --privileged \ 11 | -e VMSIFTER_X86.EXEC_MODE=64 \ 12 | -e VMSIFTER_MIN_PREFIX_COUNT=0 \ 13 | -e VMSIFTER_MAX_PREFIX_COUNT=1 \ 14 | -e VMSIFTER_INSN_BUF_SIZE=8095 \ 15 | -e VMSIFTER_FUZZER.DRIZZLER.NUM_SEEDS=10 \ 16 | -e VMSIFTER_FUZZER.DRIZZLER.INJECTIONS=4 \ 17 | -v $PWD/workdir:/workdir \ 18 | --user $(id -u):$(id -g) \ 19 | --group-add sudo \ 20 | vmsifter-dev --fuzzer-mode DRIZZLER "$@" 21 | -------------------------------------------------------------------------------- /src/signal.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #include "private.h" 6 | 7 | static struct sigaction act; 8 | 9 | static void close_handler(int sig) 10 | { 11 | printf("Close signal received\n"); 12 | interrupted = sig; 13 | 14 | if ( vmi ) 15 | vmi_pause_vm(vmi); 16 | } 17 | 18 | void setup_signal_handlers(void) 19 | { 20 | act.sa_handler = close_handler; 21 | sigemptyset(&act.sa_mask); 22 | sigaction(SIGHUP, &act, NULL); 23 | sigaction(SIGTERM, &act, NULL); 24 | sigaction(SIGINT, &act, NULL); 25 | sigaction(SIGALRM, &act, NULL); 26 | } 27 | -------------------------------------------------------------------------------- /tests/bench/test_bench_capstone.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from contextlib import suppress 5 | from itertools import islice 6 | 7 | import pytest 8 | from capstone import CS_ARCH_X86, CS_MODE_64, Cs 9 | 10 | from vmsifter.fuzzer.random import random_fuzzer_gen 11 | 12 | 13 | @pytest.fixture 14 | def cap_disasm(): 15 | return Cs(CS_ARCH_X86, CS_MODE_64) 16 | 17 | 18 | @pytest.mark.parametrize("count", [10**3, 10**4, 10**5, 10**6]) 19 | def test_capstone_disasm(cap_disasm, count): 20 | md = cap_disasm 21 | for next_buff in islice(random_fuzzer_gen(), count): 22 | with suppress(StopIteration): 23 | next(md.disasm(next_buff, 0x0)) 24 | -------------------------------------------------------------------------------- /docs/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vmsifter_docs" 3 | authors = [{ name = "Mathieu Tarral", email = "mathieu.tarral@protonmail.com" }] 4 | version = "0.1.0" 5 | description = "My package description" 6 | readme = "README.rst" 7 | requires-python = ">=3.7" 8 | keywords = ["one", "two"] 9 | license = { text = "BSD-3-Clause" } 10 | classifiers = ["Framework :: Django", "Programming Language :: Python :: 3"] 11 | dependencies = [ 12 | 'furo==2023.9.10', 13 | 'myst-parser==2.0.0', 14 | 'Sphinx==7.2.6', 15 | 'sphinx-copybutton==0.5.2', 16 | 'sphinx_design==0.5.0', 17 | 'sphinxcontrib-mermaid==0.9.2', 18 | ] 19 | 20 | [build-system] 21 | requires = ["setuptools"] 22 | build-backend = "setuptools.build_meta" 23 | -------------------------------------------------------------------------------- /vmsifter/injector/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from abc import abstractmethod 5 | 6 | from attrs import define 7 | 8 | from vmsifter.utils.protected_manager import ProtectedContextManager 9 | 10 | 11 | @define(slots=True) 12 | class InjectorResult: 13 | valid: bool 14 | length: int 15 | 16 | 17 | class InjectorInterface(ProtectedContextManager): 18 | """Defines the interface to communicate with the injector""" 19 | 20 | @abstractmethod 21 | def feed(self, insn): 22 | """Feed a new instruction""" 23 | pass 24 | 25 | @abstractmethod 26 | def get_result(self) -> InjectorResult: 27 | """Get the next injector result""" 28 | pass 29 | -------------------------------------------------------------------------------- /tests/integration/disasm/test_yaxpeax.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from pickle import dump, dumps 5 | 6 | from vmsifter.disasm.adapter import DisasmAdapter, DisasmResult 7 | from vmsifter.disasm.yaxpeax import YaxpeaxDisasmAdaptee 8 | 9 | 10 | def test_disasm_x64(): 11 | yax_adaptee_64 = YaxpeaxDisasmAdaptee() 12 | adapter_64 = DisasmAdapter(yax_adaptee_64) 13 | buffer = b"\x55" 14 | expected_res_64 = DisasmResult(1, "push rbp") 15 | insn = adapter_64.disasm(buffer) 16 | assert insn == expected_res_64 17 | 18 | 19 | def test_pickle_disasm(): 20 | yax_adaptee_64 = YaxpeaxDisasmAdaptee() 21 | adapter_64 = DisasmAdapter(yax_adaptee_64) 22 | _ds = dumps(adapter_64) 23 | -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | 📗 VMSifter's Documentation 2 | ==================== 3 | 4 | Framework for in-VM test execution and monitoring. Inspired by [Sandsifter](https://github.com/xoreaxeaxeax/sandsifter) with additional performance counter monitoring and ring0 instruction execution and the ability to easily add custom test generation and execution pipelines. 5 | 6 | ## Contents 7 | 8 | ```{toctree} 9 | :maxdepth: 2 10 | :caption: Tutorials 11 | 12 | ``` 13 | 14 | ```{toctree} 15 | :maxdepth: 2 16 | :caption: How-to guides 17 | 18 | ``` 19 | 20 | ```{toctree} 21 | :maxdepth: 2 22 | :caption: Reference 23 | 24 | reference/configuration 25 | ``` 26 | 27 | ```{toctree} 28 | :maxdepth: 2 29 | :caption: Context 30 | 31 | ``` 32 | 33 | ```{toctree} 34 | :maxdepth: 2 35 | :caption: Development 36 | 37 | ``` 38 | -------------------------------------------------------------------------------- /src/private.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #ifndef PRIVATE_H 6 | #define PRIVATE_H 7 | 8 | #define _GNU_SOURCE 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include "signal.h" 24 | #include "vmi.h" 25 | #include "forkvm.h" 26 | 27 | extern char *domain; 28 | extern uint32_t domid; 29 | 30 | extern xc_interface *xc; 31 | extern vmi_instance_t vmi; 32 | extern addr_t target_pagetable; 33 | extern addr_t start_rip; 34 | extern page_mode_t pm; 35 | extern int interrupted; 36 | extern bool debug; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /deploy/site.yml: -------------------------------------------------------------------------------- 1 | - name: Install Ansible Galaxy dependencies 2 | hosts: localhost 3 | gather_facts: false 4 | tasks: 5 | - name: Install required Ansible Galaxy roles 6 | ansible.builtin.command: ansible-galaxy install -r requirements.yml 7 | args: 8 | chdir: "{{ playbook_dir }}" 9 | 10 | - name: VMSifter deployment 11 | hosts: all 12 | pre_tasks: 13 | - name: Check for ART_ACCESS_TOKEN env var 14 | set_fact: 15 | art_access_token: "{{ lookup('env', 'ART_ACCESS_TOKEN') }}" 16 | no_log: true 17 | 18 | - name: Fail if ART_ACCESS_TOKEN is not set or empty 19 | fail: 20 | msg: "The ART_ACCESS_TOKEN environment variable is not set or is empty." 21 | when: art_access_token is undefined or art_access_token | trim == '' 22 | 23 | roles: 24 | - xen 25 | - vmsifter 26 | - runner 27 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('injector', 'c', 2 | version : '1.0', 3 | default_options : ['warning_level=2', 'werror=true', 'strip=true', 'buildtype=release', 'b_pie=true']) 4 | 5 | glib = dependency('glib-2.0') 6 | libvmi = dependency('libvmi') 7 | libxc = dependency('xencontrol') 8 | libxl = dependency('xenlight') 9 | 10 | add_project_arguments('-Wno-address-of-packed-member', language: 'c') 11 | # canaries 12 | add_project_arguments('-fstack-protector-strong', language: 'c') 13 | # fortify 14 | add_project_arguments('-D_FORTIFY_SOURCE=2', language: 'c') 15 | # full RELRO 16 | add_project_link_arguments('-Wl,-z,relro,-z,now', language: 'c') 17 | 18 | executable('injector', 19 | 'src/main.c', 20 | 'src/private.c', 21 | 'src/forkvm.c', 22 | 'src/signal.c', 23 | 'src/vmi.c', 24 | dependencies : [ libxc, libxl, glib, libvmi ], 25 | install : true) 26 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /vmsifter/config/settings.toml: -------------------------------------------------------------------------------- 1 | jobs = 1 2 | refresh_frequency = 10000 3 | socket_name = "vmsifter_sock" 4 | insn_buf_size = 15 5 | min_prefix_count = 0 6 | max_prefix_count = 0 7 | debug = false 8 | # post-mortem debugging with Python PDB 9 | pdb = false 10 | csv_log_diff_only = 0 11 | extra_byte = 0 12 | # PCPU allocation settings 13 | smt = false 14 | # display 15 | completion_rate_precision = 4 16 | 17 | [fuzzer.drizzler] 18 | num_seeds = 1 19 | injections = 300 20 | aggressive = false 21 | 22 | [x86] 23 | prefix = [ 24 | 0xF0, # lock 25 | 0xF2, # repne / bound 26 | 0xF3, # rep 27 | 0x2E, # cs / branch taken 28 | 0x36, # ss / branch not taken 29 | 0x3E, # ds 30 | 0x26, # es 31 | 0x64, # fs 32 | 0x65, # gs 33 | 0x66, # data 34 | 0x67, # addr 35 | ] 36 | exec_mode = "32" 37 | 38 | [injector.xenvm] 39 | xtf_path = "./xtf" 40 | perfcts = "0x043010e,0x04301c2,0x04307c1,0x44304a3" 41 | sse = true 42 | syscall = true 43 | fpu_emulation = false 44 | -------------------------------------------------------------------------------- /vmsifter/fuzzer/random.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from __future__ import annotations 5 | 6 | import os 7 | import random 8 | from collections.abc import Generator 9 | 10 | from attrs import define 11 | 12 | from vmsifter.fuzzer.types import AbstractInsnGenerator, FuzzerExecResult 13 | 14 | 15 | # keep slots disabled, too complicated with inheritance 16 | # also we need to inherit from superclass __getstate__ / __setstate__ definitions 17 | # and slots=True automatically inserts attrs generated methods 18 | @define(slots=True, auto_attribs=True, auto_detect=True) 19 | class RandomFuzzer(AbstractInsnGenerator): 20 | def _randbytes(self): 21 | self.insn_length = random.randint(1, self.cache_dyna_insn_buf_size) 22 | self.view[: self.insn_length] = os.urandom(self.insn_length) 23 | 24 | def gen(self) -> Generator[memoryview, FuzzerExecResult, None]: 25 | while True: 26 | yield self.current_insn 27 | self._randbytes() 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= $(ROOT_DIR)/.venv/bin/sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: $(SPHINXBUILD) 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile $(SPHINXBUILD) 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | $(SPHINXBUILD): 23 | python3 -m venv $(ROOT_DIR)/.venv 24 | $(ROOT_DIR)/.venv/bin/python -m pip install $(ROOT_DIR) 25 | 26 | distclean: 27 | $(RM) -rf $(ROOT_DIR)/.venv $(BUILDDIR) 28 | -------------------------------------------------------------------------------- /scripts/capstone-test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2022 Intel Corporation 4 | # SPDX-License-Identifier: MIT 5 | 6 | """ 7 | Instruction Disassembler using Capstone. 8 | 9 | Usage: 10 | capstone-test.py 11 | capstone-test.py (-h | --help) 12 | capstone-test.py --version 13 | 14 | Options: 15 | -h --help Show this screen. 16 | --version Show version. 17 | 18 | """ 19 | 20 | from capstone import * 21 | from docopt import docopt 22 | 23 | 24 | def disassemble_instruction(instruction): 25 | # Initialize Capstone disassembler (x86 in this case, change if necessary) 26 | md = Cs(CS_ARCH_X86, CS_MODE_64) 27 | code = bytes.fromhex(instruction) 28 | 29 | for i in md.disasm(code, 0x1000): 30 | print("0x{:x}:\t{}\t{}".format(i.address, i.mnemonic, i.op_str)) 31 | 32 | 33 | if __name__ == "__main__": 34 | arguments = docopt(__doc__, version="Instruction Disassembler 1.0") 35 | 36 | instruction_hex = arguments[""] 37 | disassemble_instruction(instruction_hex) 38 | -------------------------------------------------------------------------------- /tests/integration/disasm/test_bench.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from itertools import count 5 | 6 | import pytest 7 | 8 | from vmsifter.disasm.adapter import DisasmAdapter 9 | from vmsifter.disasm.capstone import CapstoneDisasmAdaptee 10 | from vmsifter.fuzzer.tunnel import FuzzerExecResult, TunnelFuzzer 11 | 12 | 13 | @pytest.mark.parametrize("max_count", [10**3, 10**4, 10**5, 10**6]) 14 | def test_capstone_tunnel(max_count: int): 15 | # arrange 16 | cap_adaptee = CapstoneDisasmAdaptee() 17 | adapter = DisasmAdapter(cap_adaptee) 18 | # act 19 | tun = TunnelFuzzer() 20 | gen = tun.gen() 21 | result = None 22 | for i in count(): 23 | if i == max_count: 24 | break 25 | next_buff = gen.send(result) 26 | # disasm 27 | disas_res = adapter.disasm(next_buff) 28 | if disas_res is None: 29 | result = FuzzerExecResult(pagefault=True) 30 | else: 31 | result = FuzzerExecResult(length=disas_res.size) 32 | -------------------------------------------------------------------------------- /tests/unit/test_partition.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import pytest 5 | 6 | from vmsifter.config import settings 7 | from vmsifter.fuzzer.partition import X86Range, partition 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "nb_part, expected", 12 | [ 13 | (1, [X86Range(start=settings.x86.min_buffer, end=settings.x86.max_end_first_byte)]), 14 | ( 15 | 2, 16 | [ 17 | X86Range(start=settings.x86.min_buffer, end=b"\x7e"), 18 | X86Range(start=b"\x7f", end=settings.x86.max_end_first_byte), 19 | ], 20 | ), 21 | ( 22 | 3, 23 | [ 24 | X86Range(start=settings.x86.min_buffer, end=b"T"), 25 | X86Range(start=b"U", end=b"\xa9"), 26 | X86Range(start=b"\xaa", end=settings.x86.max_end_first_byte), 27 | ], 28 | ), 29 | ], 30 | ) 31 | def test_partition_one_part(nb_part, expected): 32 | assert partition(nb_part) == expected 33 | -------------------------------------------------------------------------------- /patches/0008-xen-Capture-all-exceptions-as-vmexits.patch: -------------------------------------------------------------------------------- 1 | From 17df684322d04ee660a5d3133e97dec71c85d91b Mon Sep 17 00:00:00 2001 2 | From: Tamas K Lengyel 3 | Date: Wed, 13 Dec 2023 14:36:18 +0000 4 | Subject: [PATCH 8/8] Capture all exceptions as vmexits 5 | 6 | --- 7 | xen/arch/x86/hvm/vmx/vmx.c | 9 ++++++++- 8 | 1 file changed, 8 insertions(+), 1 deletion(-) 9 | 10 | diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c 11 | index 2a4baac8d8..1ca488e352 100644 12 | --- a/xen/arch/x86/hvm/vmx/vmx.c 13 | +++ b/xen/arch/x86/hvm/vmx/vmx.c 14 | @@ -4917,7 +4917,14 @@ bool vmx_vmenter_helper(const struct cpu_user_regs *regs) 15 | 16 | HVMTRACE_ND(VMENTRY, 0, 1/*cycles*/); 17 | 18 | - vmx_fpu_dirty_intercept(); 19 | +#ifdef CONFIG_MEM_SHARING 20 | + if ( mem_sharing_is_fork(currd) ) 21 | + { 22 | + uint32_t bitmap = ~0; 23 | + vmx_fpu_dirty_intercept(); 24 | + __vmwrite(EXCEPTION_BITMAP, bitmap); 25 | + } 26 | +#endif 27 | 28 | __vmwrite(GUEST_RIP, regs->rip); 29 | __vmwrite(GUEST_RSP, regs->rsp); 30 | -- 31 | 2.34.1 32 | 33 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Intel Corporation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vmsifter/injector/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from typing import Dict, Type 5 | 6 | from vmsifter.config import InjectorType, settings 7 | 8 | from .types import ( 9 | NUMBER_OF_REGISTERS, 10 | AbstractInjector, 11 | EPTQual, 12 | EPTQualEnum, 13 | ExitReasonEnum, 14 | InjectorResultMessage, 15 | InjInterruptEnum, 16 | InjInterruptTypeEnum, 17 | PageFaultEC, 18 | RegistersEnum, 19 | ) 20 | from .xenvm import XenVMInjector 21 | 22 | MAP_CONFIG_INJECTOR: Dict[InjectorType, Type[AbstractInjector]] = {InjectorType.XENVM: XenVMInjector} 23 | 24 | 25 | def get_selected_injector() -> Type[AbstractInjector]: 26 | """Returns the selected injector class""" 27 | # hardcoded to XENVM for now 28 | return MAP_CONFIG_INJECTOR[settings.injector_mode] 29 | 30 | 31 | __all__ = [ 32 | "EPTQual", 33 | "EPTQualEnum", 34 | "ExitReasonEnum", 35 | "InjInterruptEnum", 36 | "InjInterruptTypeEnum", 37 | "InjectorResultMessage", 38 | "PageFaultEC", 39 | "RegistersEnum", 40 | "get_selected_injector", 41 | "NUMBER_OF_REGISTERS", 42 | ] 43 | -------------------------------------------------------------------------------- /tests/unit/utils/test_completion_rate.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import pytest 5 | 6 | from vmsifter.config import settings 7 | from vmsifter.fuzzer.partition import X86Range 8 | from vmsifter.utils.completion_rate import ByteRangeCompletion 9 | 10 | 11 | def test_completion_rate(): 12 | x86range = X86Range(start=b"\x00", end=b"\xff") 13 | completion = ByteRangeCompletion.from_x86_range(x86range) 14 | buffer = b"\x42" 15 | res = completion.completion_rate(buffer) 16 | assert round(res, 4) == 25.8824 17 | 18 | x86range = X86Range(start=b"\x10", end=b"\x30") 19 | completion = ByteRangeCompletion.from_x86_range(x86range) 20 | buffer = b"\x15" 21 | res = completion.completion_rate(buffer) 22 | assert round(res, 4) == 15.625 23 | 24 | x86range = X86Range(start=b"\x10", end=b"\x30") 25 | completion = ByteRangeCompletion.from_x86_range(x86range) 26 | buffer = b"\x15\x02\x04\xff" 27 | res = completion.completion_rate(buffer) 28 | assert round(res, 4) == 15.6497 29 | 30 | x86range = X86Range(start=b"\x7f", end=b"\xff") 31 | completion = ByteRangeCompletion.from_x86_range(x86range) 32 | buffer = b"\x80\x04\x05\x07" 33 | res = completion.completion_rate(buffer) 34 | assert round(res, 4) == 0.7935 35 | -------------------------------------------------------------------------------- /tests/integration/disasm/test_capstone.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from pickle import dumps, loads 5 | 6 | from capstone import CS_MODE_64 7 | 8 | from vmsifter.disasm.adapter import DisasmAdapter, DisasmResult 9 | from vmsifter.disasm.capstone import CapstoneDisasmAdaptee 10 | 11 | 12 | def test_disasm_x86(): 13 | # arrange 14 | cap_adaptee_32 = CapstoneDisasmAdaptee() 15 | adapter_32 = DisasmAdapter(cap_adaptee_32) 16 | buffer = b"\x55" # push ebp 17 | expected_res_32 = DisasmResult(1, "push ebp") 18 | # act 19 | insn = adapter_32.disasm(buffer) 20 | # assert 21 | assert insn == expected_res_32 22 | 23 | cap_adaptee_64 = CapstoneDisasmAdaptee(mode=CS_MODE_64) 24 | adapter_64 = DisasmAdapter(cap_adaptee_64) 25 | expected_res_64 = DisasmResult(1, "push rbp") 26 | # act 27 | insn = adapter_64.disasm(buffer) 28 | # assert 29 | assert insn == expected_res_64 30 | 31 | 32 | def test_pickle(): 33 | cap_adaptee_32 = CapstoneDisasmAdaptee() 34 | adapter_32 = DisasmAdapter(cap_adaptee_32) 35 | adapter_32 = loads(dumps(adapter_32)) 36 | buffer = b"\x55" # push ebp 37 | expected_res_32 = DisasmResult(1, "push ebp") 38 | # act 39 | insn = adapter_32.disasm(buffer) 40 | # assert 41 | assert insn == expected_res_32 42 | -------------------------------------------------------------------------------- /vmsifter/fuzzer/partition.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from typing import List 5 | 6 | from attrs import define 7 | 8 | from vmsifter.config import settings 9 | 10 | 11 | @define(auto_attribs=True) 12 | class X86Range: 13 | start: bytes 14 | end: bytes 15 | 16 | 17 | DEFAULT_X86_RANGE = X86Range(settings.x86.min_buffer, settings.x86.max_end_first_byte) 18 | 19 | 20 | def partition(nb_parts: int, x86_range: X86Range = DEFAULT_X86_RANGE) -> List[X86Range]: 21 | end = int.from_bytes(x86_range.end[0:1], byteorder="big") 22 | start = int.from_bytes(x86_range.start[0:1], byteorder="big") 23 | if nb_parts > end: 24 | raise ValueError(f"Partitioning over {max} ranges is unsupported") 25 | 26 | def int_to_bytes(value: int): 27 | return value.to_bytes(1, byteorder="big") 28 | 29 | step = (end - start) // nb_parts 30 | rem = (end - start) % nb_parts 31 | # ex: nb_parts = 2 32 | # start=0, end=0 + 127 - 1 33 | # start=127, end=255-1 (remainder) converted to 255 below since it's the last range 34 | ranges = [X86Range(start=int_to_bytes(v), end=int_to_bytes(v + step - 1)) for v in range(start, end - rem, step)] 35 | # set first range 36 | ranges[0].start = x86_range.start 37 | # set last range 38 | ranges[-1].end = x86_range.end 39 | return ranges 40 | -------------------------------------------------------------------------------- /vmsifter/fuzzer/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from typing import Dict, Type 5 | 6 | from vmsifter.config import FuzzerType, settings 7 | 8 | from .csv import CsvFuzzer 9 | from .drizzler import DrizzlerFuzzer 10 | from .random import RandomFuzzer 11 | from .tunnel import TunnelFuzzer 12 | from .types import EPT, NMI, AbstractInsnGenerator, FinalLogResult, FuzzerExecResult, Interrupted, Other 13 | 14 | MAP_CONFIG_FUZZER: Dict[FuzzerType, Type[AbstractInsnGenerator]] = { 15 | FuzzerType.RANDOM: RandomFuzzer, 16 | FuzzerType.TUNNEL: TunnelFuzzer, 17 | FuzzerType.CSV: CsvFuzzer, 18 | FuzzerType.DRIZZLER: DrizzlerFuzzer, 19 | } 20 | 21 | MAP_INJECTOR_SETTINGS: Dict[FuzzerType, str] = { 22 | FuzzerType.RANDOM: "--mtf", 23 | FuzzerType.TUNNEL: "--mtf", 24 | FuzzerType.CSV: "--mtf", 25 | FuzzerType.DRIZZLER: "--drizzler", 26 | } 27 | 28 | 29 | def get_selected_gen() -> Type[AbstractInsnGenerator]: 30 | """Returns the generator for the configured fuzzer mode""" 31 | return MAP_CONFIG_FUZZER[settings.fuzzer_mode] 32 | 33 | 34 | def get_injector_settings() -> str: 35 | """Returns the default injector setting for the configured fuzzer mode""" 36 | return MAP_INJECTOR_SETTINGS[settings.fuzzer_mode] 37 | 38 | 39 | __all__ = ["AbstractInsnGenerator", "FuzzerExecResult", "FinalLogResult", "Interrupted", "EPT", "NMI", "Other"] 40 | -------------------------------------------------------------------------------- /patches/0002-xen-x86-vmx-Load-FPU-state-before-entering.patch: -------------------------------------------------------------------------------- 1 | From 09a5961943aa8322d952f74066640063e071c5fc Mon Sep 17 00:00:00 2001 2 | Message-Id: <09a5961943aa8322d952f74066640063e071c5fc.1701823233.git.tamas.lengyel@intel.com> 3 | In-Reply-To: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 4 | References: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 5 | From: Tamas K Lengyel 6 | Date: Thu, 11 May 2023 16:43:04 +0000 7 | Subject: [PATCH 2/8] x86/vmx: Load FPU state before entering 8 | 9 | When a vCPU gets scheduled the FPU is not loaded until something 10 | traps to Xen indicating the guest wants to use the FPU. This is a 11 | scheduling optimization we want to disable while sifting. 12 | 13 | Signed-off-by: Tamas K Lengyel 14 | --- 15 | xen/arch/x86/hvm/vmx/vmx.c | 2 ++ 16 | 1 file changed, 2 insertions(+) 17 | 18 | diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c 19 | index 1edc7f1e91..5cc9a3876d 100644 20 | --- a/xen/arch/x86/hvm/vmx/vmx.c 21 | +++ b/xen/arch/x86/hvm/vmx/vmx.c 22 | @@ -4883,6 +4883,8 @@ bool vmx_vmenter_helper(const struct cpu_user_regs *regs) 23 | 24 | HVMTRACE_ND(VMENTRY, 0, 1/*cycles*/); 25 | 26 | + vmx_fpu_dirty_intercept(); 27 | + 28 | __vmwrite(GUEST_RIP, regs->rip); 29 | __vmwrite(GUEST_RSP, regs->rsp); 30 | __vmwrite(GUEST_RFLAGS, regs->rflags | X86_EFLAGS_MBS); 31 | -- 32 | 2.34.1 33 | 34 | -------------------------------------------------------------------------------- /vmsifter/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import logging 5 | from functools import partial 6 | from itertools import filterfalse 7 | from pprint import pformat 8 | from typing import Generator 9 | 10 | from attrs import Factory 11 | 12 | from vmsifter.config import settings 13 | 14 | from .xen import XL, XlInfo, XlVcpuInfo 15 | 16 | 17 | def get_available_pcpus() -> Generator[int, None, None]: 18 | """From XL commands determine the PCPU allocation available for injectors""" 19 | info = XL.info() 20 | gen_vcpu_info = (vcpu_info for vcpu_info in XL.vcpu_list("Domain-0")) 21 | for x in _filter_pcpu_for_injector(info, gen_vcpu_info): 22 | yield x 23 | 24 | 25 | def _filter_pcpu_for_injector( 26 | info: XlInfo, gen_vcpu_info: Generator[XlVcpuInfo, None, None] 27 | ) -> Generator[int, None, None]: 28 | # set range all available pcpus 29 | range_av_pcpus = range(info.max_cpu_id + 1) 30 | # filter Dom0 allocated CPU 31 | dom0_pcpu = [vcpu_info.cpu_id for vcpu_info in gen_vcpu_info] 32 | filtered_av_pcpus = filterfalse(lambda x: x in dom0_pcpu, range_av_pcpus) 33 | # filter SMT disabled 34 | if not settings.smt: 35 | filtered_av_pcpus = filterfalse(lambda x: x % 2, filtered_av_pcpus) 36 | for x in filtered_av_pcpus: 37 | yield x 38 | 39 | 40 | pformat = partial(pformat, indent=4) 41 | # attrs factories 42 | fact_logging = Factory(lambda self: logging.getLogger(f"{self.__module__}.{self.__class__.__name__}"), takes_self=True) 43 | -------------------------------------------------------------------------------- /tests/unit/fuzzer/test_fuzzer.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import pickle 5 | from concurrent.futures import ProcessPoolExecutor 6 | 7 | import pytest 8 | 9 | from vmsifter.fuzzer import RandomFuzzer, TunnelFuzzer 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "fuzzer_cls, fuzzer_args", 14 | [ 15 | (TunnelFuzzer, {"insn_buffer": bytearray(b"\x04\x05"), "marker_idx": 1, "end_first_byte": b"\xab"}), 16 | (RandomFuzzer, {}), 17 | ], 18 | ) 19 | def test_fuzzer_can_be_pickle(fuzzer_cls, fuzzer_args): 20 | """Test fuzzer instance can be sent to ProcessPool""" 21 | # this test was needed because sending the object instance worked 22 | # but the received instance had 0 attributes (?) 23 | instance = fuzzer_cls(**fuzzer_args) 24 | 25 | pickled = pickle.dumps(instance) 26 | unpickeled = pickle.loads(pickled) 27 | assert instance == unpickeled 28 | 29 | 30 | def func_process_pool(fuzzer): 31 | return fuzzer 32 | 33 | 34 | @pytest.mark.parametrize( 35 | "fuzzer_cls, fuzzer_args", 36 | [ 37 | (TunnelFuzzer, {"insn_buffer": bytearray(b"\x04\x05"), "marker_idx": 1, "end_first_byte": b"\xab"}), 38 | (RandomFuzzer, {}), 39 | ], 40 | ) 41 | def test_fuzzer_can_be_sent_to_process_pool(fuzzer_cls, fuzzer_args): 42 | instance = fuzzer_cls(**fuzzer_args) 43 | 44 | with ProcessPoolExecutor(max_workers=1) as pool: 45 | fut = pool.submit(func_process_pool, instance) 46 | result = fut.result() 47 | assert result == instance 48 | -------------------------------------------------------------------------------- /vmsifter/utils/completion_rate.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from attrs import define 5 | 6 | from vmsifter.config import settings 7 | from vmsifter.fuzzer.partition import X86Range 8 | 9 | CACHE_DYNA_COMPLETION_RATE_PRECISION: int = settings.completion_rate_precision 10 | 11 | 12 | @define(auto_attribs=True, repr=True) 13 | class ByteRangeCompletion: 14 | """Utils to compute the completion rate of a given binary x86 range for the TunnelFuzzer""" 15 | 16 | range_start: int 17 | range_end: int 18 | 19 | @classmethod 20 | def from_x86_range(cls, range: X86Range): 21 | start = int.from_bytes( 22 | range.start[:CACHE_DYNA_COMPLETION_RATE_PRECISION] 23 | + b"\x00" * (CACHE_DYNA_COMPLETION_RATE_PRECISION - len(range.start)), 24 | byteorder="big", 25 | ) 26 | end = int.from_bytes( 27 | range.end[:CACHE_DYNA_COMPLETION_RATE_PRECISION] 28 | + b"\x00" * (CACHE_DYNA_COMPLETION_RATE_PRECISION - len(range.end)), 29 | byteorder="big", 30 | ) 31 | instance = cls(start, end) 32 | return instance 33 | 34 | def completion_rate(self, insn: memoryview) -> float: 35 | insn_bytes_part = bytearray(insn)[:CACHE_DYNA_COMPLETION_RATE_PRECISION] 36 | current = int.from_bytes( 37 | insn_bytes_part + b"\x00" * (CACHE_DYNA_COMPLETION_RATE_PRECISION - len(insn_bytes_part)), byteorder="big" 38 | ) 39 | val = ((current - self.range_start) * 100) / (self.range_end - self.range_start) 40 | return val 41 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | env: 9 | POETRY_VERSION: "2.1.1" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-22.04 14 | strategy: 15 | matrix: 16 | baseimage: ['python:3.13.2-slim-bookworm', 'pypy:3.11-7-slim-bookworm'] 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: true 21 | 22 | - uses: actions/setup-python@v5 23 | with: 24 | python-version: 3.11 25 | 26 | - uses: docker/setup-buildx-action@v3 27 | 28 | - uses: docker/build-push-action@v5.1.0 29 | with: 30 | context: . 31 | tags: intel/vmsifter:latest 32 | target: vmsifter-dev 33 | build-args: | 34 | BASEIMAGE=${{ matrix.baseimage }} 35 | BUILD_ID=-ci-${{ github.run_id }} 36 | load: false 37 | # disable push for now. 38 | push: false 39 | cache-from: type=gha 40 | cache-to: type=gha,mode=max 41 | 42 | check: 43 | runs-on: ubuntu-22.04 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | task: [format_check, lint, typing, unit_test] 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: actions/setup-python@v5 51 | with: 52 | python-version: 3.11 53 | 54 | - name: Install Poetry 55 | run: pip install poetry==${{ env.POETRY_VERSION }} 56 | 57 | - name: ${{ matrix.task }} 58 | run: | 59 | poetry install 60 | poetry run poe ${{ matrix.task }} 61 | -------------------------------------------------------------------------------- /src/vmi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | #include "private.h" 7 | 8 | bool setup_vmi(vmi_instance_t *vmi, char* domain, uint64_t domid, bool init_events, bool init_paging) 9 | { 10 | if ( debug ) 11 | printf("Init vmi, init_events: %i init_paging %i domain %s domid %lu\n", 12 | init_events, init_paging, domain, domid); 13 | 14 | uint64_t options = (init_events ? VMI_INIT_EVENTS : 0) | 15 | (domain ? VMI_INIT_DOMAINNAME : VMI_INIT_DOMAINID); 16 | vmi_mode_t mode = VMI_XEN; 17 | const void *d = domain ?: (void*)&domid; 18 | 19 | status_t status = vmi_init(vmi, mode, d, options, NULL, NULL); 20 | 21 | if ( VMI_FAILURE == status ) 22 | return false; 23 | 24 | if ( init_paging && VMI_PM_UNKNOWN == (pm = vmi_init_paging(*vmi, 0)) ) 25 | { 26 | fprintf(stderr, "Error in vmi_init_paging!\n"); 27 | vmi_destroy(*vmi); 28 | return false; 29 | } 30 | 31 | registers_t regs = {0}; 32 | if ( VMI_FAILURE == vmi_get_vcpuregs(*vmi, ®s, 0) ) 33 | { 34 | fprintf(stderr, "Error in vmi_get_vcpuregs!\n"); 35 | vmi_destroy(*vmi); 36 | return false; 37 | } 38 | 39 | target_pagetable = regs.x86.cr3; 40 | start_rip = regs.x86.rip; 41 | 42 | return true; 43 | } 44 | 45 | void loop(vmi_instance_t vmi) 46 | { 47 | if ( !vmi ) 48 | return; 49 | 50 | vmi_resume_vm(vmi); 51 | 52 | while (!interrupted) 53 | { 54 | if ( vmi_events_listen(vmi, 100) == VMI_FAILURE ) 55 | { 56 | fprintf(stderr, "Error in vmi_events_listen!\n"); 57 | break; 58 | } 59 | } 60 | 61 | interrupted = 0; 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/campaign.yml: -------------------------------------------------------------------------------- 1 | name: Campaign 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | run: 8 | # 2 weeks 9 | # doesn't work: new timeout restriction to 5d (April 2024) 10 | timeout-minutes: 40320 11 | strategy: 12 | max-parallel: 10 13 | fail-fast: false 14 | matrix: 15 | runner: [] 16 | jobs: [8] 17 | runs-on: ${{ matrix.runner }} 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Assign docker name 23 | run: | 24 | echo "DOCKER_NAME=campaign-vmsifter-${{ matrix.runner }}" >> $GITHUB_ENV 25 | echo "WORKDIR=/mnt/storage/workdir" >> $GITHUB_ENV 26 | echo "EXEC_MODE=32" >> $GITHUB_ENV 27 | 28 | - name: remove any existing vmsifter running container 29 | run: docker rm -f ${{ env.DOCKER_NAME }} || true 30 | 31 | - name: set env file 32 | run: | 33 | echo "VMSIFTER_X86.EXEC_MODE=${{ env.EXEC_MODE }}" > .env 34 | 35 | - name: run.sh 36 | run: | 37 | rm -rf ${{ env.WORKDIR }} 38 | ./run.sh -u false -i intel/vmsifter:latest -t false -n ${{ env.DOCKER_NAME }} -w ${{ env.WORKDIR }} -- -j ${{ matrix.jobs }} 39 | du -h ${{ env.WORKDIR }} 40 | 41 | - name: remove any existing vmsifter running container 42 | run: docker rm -f ${{ env.DOCKER_NAME }} || true 43 | if: always() 44 | 45 | - name: remove vmsifter socket 46 | run: rm -f ${{ env.WORKDIR }}/vmsifter_sock 47 | if: always() 48 | 49 | - name: Upload results 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: ${{ matrix.runner }} 53 | path: ${{ env.WORKDIR }}/* 54 | compression-level: 9 55 | if: always() 56 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy documentation 2 | 3 | on: 4 | # Runs on pushes targeting the default branch 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | # # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 20 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 21 | concurrency: 22 | group: "pages" 23 | cancel-in-progress: false 24 | 25 | jobs: 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | 32 | - name: Setup Python 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: '3.12' 36 | 37 | - name: Build documentation 38 | run: | 39 | python3 -m venv venv 40 | source venv/bin/activate 41 | pip install . 42 | make html 43 | working-directory: docs 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v3 46 | with: 47 | # Upload entire repository 48 | path: 'docs/build/html' 49 | 50 | deploy: 51 | if: github.event_name == 'push' 52 | environment: 53 | name: github-pages 54 | url: ${{ steps.deployment.outputs.page_url }} 55 | runs-on: ubuntu-latest 56 | needs: [build] 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@v4 60 | 61 | - name: Setup Pages 62 | uses: actions/configure-pages@v4 63 | 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v4 67 | -------------------------------------------------------------------------------- /tests/integration/disasm/test_pool.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from itertools import count 5 | 6 | import pytest 7 | from capstone import CS_MODE_64 8 | 9 | from vmsifter.disasm.capstone import CapstoneDisasmAdaptee 10 | from vmsifter.disasm.interface import DisasmEngineType, DisasmResult 11 | from vmsifter.disasm.pool import DisasmPoolExecutor 12 | from vmsifter.disasm.yaxpeax import YaxpeaxDisasmAdaptee 13 | from vmsifter.fuzzer.tunnel import FuzzerExecResult, TunnelFuzzer 14 | 15 | 16 | def test_pool_disasm_one(): 17 | # arrange 18 | cap_adaptee = CapstoneDisasmAdaptee(mode=CS_MODE_64) 19 | yaxpeax_adaptee = YaxpeaxDisasmAdaptee() 20 | engines = {DisasmEngineType.CAPSTONE: cap_adaptee, DisasmEngineType.YAXPEAX: yaxpeax_adaptee} 21 | buffer = b"\x55" 22 | expected_res = DisasmResult(1, "push rbp") 23 | with DisasmPoolExecutor(engines) as pool: 24 | # act 25 | pool.submit_disasm(buffer) 26 | # assert 27 | result = list(pool.as_completed()) 28 | print(result) 29 | assert len(result) == len(engines) 30 | for res in result: 31 | assert res.disas_res == expected_res 32 | 33 | 34 | @pytest.mark.parametrize("max_count", [10**3, 10**4, 10**5]) 35 | def test_capstone_tunnel(max_count: int): 36 | # arrange 37 | cap_adaptee = CapstoneDisasmAdaptee() 38 | yaxpeax_adaptee = YaxpeaxDisasmAdaptee() 39 | tun = TunnelFuzzer() 40 | gen = tun.gen() 41 | 42 | # act 43 | result = None 44 | engines = {DisasmEngineType.CAPSTONE: cap_adaptee, DisasmEngineType.YAXPEAX: yaxpeax_adaptee} 45 | 46 | with DisasmPoolExecutor(engines) as pool: 47 | for i in count(): 48 | if i == max_count: 49 | break 50 | next_buff = gen.send(result) 51 | # disasm 52 | pool.submit_disasm(next_buff.tobytes()) 53 | pool_res = next(pool.as_completed()) 54 | if pool_res.disas_res is None: 55 | result = FuzzerExecResult(pagefault=True) 56 | else: 57 | result = FuzzerExecResult(length=pool_res.disas_res.size) 58 | -------------------------------------------------------------------------------- /patches/0004-xen-allow-guest-setting-perfmon-freeze.patch: -------------------------------------------------------------------------------- 1 | From d8433e0ef9fd770440726204573506a0507ee4a3 Mon Sep 17 00:00:00 2001 2 | Message-Id: 3 | In-Reply-To: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 4 | References: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 5 | From: Your Name 6 | Date: Tue, 24 Oct 2023 14:15:51 -0700 7 | Subject: [PATCH 4/8] Allow guest setting perfmon freeze settings 8 | 9 | --- 10 | xen/arch/x86/hvm/vmx/vmx.c | 3 ++- 11 | xen/arch/x86/include/asm/msr-index.h | 2 ++ 12 | 2 files changed, 4 insertions(+), 1 deletion(-) 13 | 14 | diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c 15 | index 8be92c346d..85fe19798b 100644 16 | --- a/xen/arch/x86/hvm/vmx/vmx.c 17 | +++ b/xen/arch/x86/hvm/vmx/vmx.c 18 | @@ -3607,7 +3607,8 @@ static int cf_check vmx_msr_write_intercept( 19 | break; 20 | 21 | case MSR_IA32_DEBUGCTLMSR: 22 | - rsvd = ~(IA32_DEBUGCTLMSR_LBR | IA32_DEBUGCTLMSR_BTF); 23 | + rsvd = ~(IA32_DEBUGCTLMSR_LBR | IA32_DEBUGCTLMSR_BTF | 24 | + IA32_DEBUGCTLMSR_FREEZE_ON_PMI | IA32_DEBUGCTLMSR_FREEZE_WHILE_SMM); 25 | 26 | /* TODO: Wire vPMU settings properly through the CPUID policy */ 27 | if ( vpmu_is_set(vcpu_vpmu(v), VPMU_CPU_HAS_BTS) ) 28 | diff --git a/xen/arch/x86/include/asm/msr-index.h b/xen/arch/x86/include/asm/msr-index.h 29 | index 82a81bd0a2..fa4508c052 100644 30 | --- a/xen/arch/x86/include/asm/msr-index.h 31 | +++ b/xen/arch/x86/include/asm/msr-index.h 32 | @@ -295,6 +295,8 @@ 33 | #define IA32_DEBUGCTLMSR_BTINT (1<<8) /* Branch Trace Interrupt */ 34 | #define IA32_DEBUGCTLMSR_BTS_OFF_OS (1<<9) /* BTS off if CPL 0 */ 35 | #define IA32_DEBUGCTLMSR_BTS_OFF_USR (1<<10) /* BTS off if CPL > 0 */ 36 | +#define IA32_DEBUGCTLMSR_FREEZE_ON_PMI (1<<12) /* Perfmon stops on PMI */ 37 | +#define IA32_DEBUGCTLMSR_FREEZE_WHILE_SMM (1<<14) /* Perfmon stops while in SMM */ 38 | #define IA32_DEBUGCTLMSR_RTM (1<<15) /* RTM debugging enable */ 39 | 40 | #define MSR_IA32_LASTBRANCHFROMIP 0x000001db 41 | -- 42 | 2.34.1 43 | 44 | -------------------------------------------------------------------------------- /src/forkvm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Intel Corporation 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | #include 6 | #include 7 | #include 8 | 9 | #include "forkvm.h" 10 | #include "private.h" 11 | 12 | extern xc_interface *xc; 13 | 14 | static bool pin_fork(uint32_t domid, uint32_t forkdomid, uint16_t pinned_cpu) 15 | { 16 | // unused parameter 17 | // keeping it if we switch to xenctrl API in the future 18 | domid = domid; 19 | // use xl toolstack as xenctrl API is hard to figure out / work with 20 | char cmd[128] = {'\0'}; 21 | // keyword "all" is used to apply the affinity to all VCPUs 22 | // Note: --force and 'all' as VCPU not allowed 23 | sprintf(cmd, "sudo xl vcpu-pin --ignore-global-affinity-masks %d all %d -", forkdomid, pinned_cpu); 24 | if (debug) 25 | printf("[%d] Executing: %s\n", pinned_cpu, cmd); 26 | if (system(cmd)) { 27 | printf("[%d] xl vcpu-pin failed.\n", pinned_cpu); 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | bool fork_vm(uint32_t domid, uint32_t *forkdomid, uint16_t pinned_cpu) 34 | { 35 | if ( !domid || !forkdomid ) 36 | return false; 37 | 38 | struct xen_domctl_createdomain create = {0}; 39 | create.flags |= XEN_DOMCTL_CDF_hvm; 40 | create.flags |= XEN_DOMCTL_CDF_hap; 41 | create.flags |= XEN_DOMCTL_CDF_oos_off; 42 | //create.flags |= XEN_DOMCTL_CDF_nested_virt; 43 | create.arch.emulation_flags = (XEN_X86_EMU_ALL & ~XEN_X86_EMU_VPCI); 44 | create.ssidref = 11; // SECINITSID_DOMU 45 | create.max_vcpus = 1; 46 | create.max_evtchn_port = 1023; 47 | create.max_grant_frames = LIBXL_MAX_GRANT_FRAMES_DEFAULT; 48 | create.max_maptrack_frames = LIBXL_MAX_MAPTRACK_FRAMES_DEFAULT; 49 | create.grant_opts = 2; 50 | 51 | if ( xc_domain_create(xc, forkdomid, &create) ) 52 | return false; 53 | 54 | if ( xc_memshr_fork(xc, domid, *forkdomid, true, true) ) 55 | { 56 | printf("[%d] Failed to create fork\n", pinned_cpu); 57 | xc_domain_destroy(xc, *forkdomid); 58 | return false; 59 | } 60 | 61 | if ( !pin_fork(domid, *forkdomid, pinned_cpu) ) 62 | { 63 | printf("[%d] Failed to pin fork vcpus\n", pinned_cpu); 64 | return false; 65 | } 66 | 67 | return true; 68 | } 69 | -------------------------------------------------------------------------------- /deploy/roles/xen/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Ensure Xen kernel 2 | ansible.builtin.command: xl info 3 | become: true 4 | register: xen_info 5 | failed_when: false 6 | 7 | - name: Setup Xen 8 | when: xen_info.rc != 0 9 | block: 10 | - name: Upload Xen deb artifact 11 | ansible.builtin.copy: 12 | src: "files/xen-upstream-4.18.0.deb" 13 | dest: /tmp/xen.deb 14 | 15 | - name: Install deb package 16 | ansible.builtin.apt: 17 | deb: "/tmp/xen.deb" 18 | become: true 19 | 20 | - name: Enable systemd services 21 | ansible.builtin.systemd: 22 | name: "{{ item }}" 23 | enabled: yes 24 | state: started 25 | become: true 26 | loop: 27 | - xen-qemu-dom0-disk-backend.service 28 | - xen-init-dom0.service 29 | - xenconsoled.service 30 | 31 | - name: libyajl missing 32 | ansible.builtin.apt: 33 | name: libyajl-dev 34 | state: present 35 | update_cache: yes 36 | become: true 37 | 38 | - name: Generate GRUB config 39 | ansible.builtin.command: update-grub 40 | become: true 41 | 42 | - name: Count Xen GRUB menuentry 43 | ansible.builtin.shell: grep -E '^(menuentry|submenu)' /boot/grub/grub.cfg | grep -n Xen -m 1 | awk '-F:' '{print $1}' 44 | register: xen_menuentry_count 45 | become: true 46 | 47 | - name: Update GRUB DEFAULT 48 | ansible.builtin.lineinfile: 49 | path: /etc/default/grub 50 | regexp: '^GRUB_DEFAULT=' 51 | line: 'GRUB_DEFAULT={{ (xen_menuentry_count.stdout | int) - 1 }}' 52 | become: true 53 | 54 | - name: Update GRUB CMDLINE DEFAULT XEN 55 | ansible.builtin.lineinfile: 56 | path: /etc/default/grub 57 | regexp: '^GRUB_CMDLINE_XEN_DEFAULT=' 58 | line: 'GRUB_CMDLINE_XEN_DEFAULT="console=vga dom0_mem=4096M hpet=legacy-replacement dom0_max_vcpus=8 dom0_vcpus_pin=1 ept=ad=0 iommu=no-sharept spec-ctrl=0 altp2m=1 xpti=0 loglvl=all guest_loglvl=all smt=0 vpmu=bts apicv=0 \"cpufreq=hwp:hdc=0;xen:performance,verbose\""' 59 | become: true 60 | 61 | - name: Generate GRUB config 62 | ansible.builtin.command: update-grub 63 | become: true 64 | 65 | - name: Reboot on Xen 66 | ansible.builtin.reboot: 67 | become: true 68 | 69 | - name: Ensure Xen kernel 70 | ansible.builtin.command: xl info 71 | become: true 72 | register: xen_info 73 | failed_when: xen_info.rc != 0 74 | -------------------------------------------------------------------------------- /patches/0005-xen-Silence-too-verbose-debug-log-on-HVM-save-restore.patch: -------------------------------------------------------------------------------- 1 | From b113580958a00d5a30309b3a12c45428656597ad Mon Sep 17 00:00:00 2001 2 | Message-Id: 3 | In-Reply-To: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 4 | References: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 5 | From: Tamas K Lengyel 6 | Date: Wed, 18 Oct 2023 14:57:45 +0000 7 | Subject: [PATCH 5/8] Silence too verbose debug log on HVM save/restore 8 | 9 | --- 10 | xen/arch/x86/hvm/save.c | 12 ++++++------ 11 | 1 file changed, 6 insertions(+), 6 deletions(-) 12 | 13 | diff --git a/xen/arch/x86/hvm/save.c b/xen/arch/x86/hvm/save.c 14 | index 79713cd6ca..d28d1d4cf9 100644 15 | --- a/xen/arch/x86/hvm/save.c 16 | +++ b/xen/arch/x86/hvm/save.c 17 | @@ -234,8 +234,8 @@ int hvm_save(struct domain *d, hvm_domain_context_t *h) 18 | 19 | for_each_vcpu ( d, v ) 20 | { 21 | - printk(XENLOG_G_INFO "HVM %pv save: %s\n", 22 | - v, hvm_sr_handlers[i].name); 23 | + //printk(XENLOG_G_INFO "HVM %pv save: %s\n", 24 | + // v, hvm_sr_handlers[i].name); 25 | if ( handler(v, h) != 0 ) 26 | { 27 | printk(XENLOG_G_ERR 28 | @@ -248,8 +248,8 @@ int hvm_save(struct domain *d, hvm_domain_context_t *h) 29 | } 30 | else 31 | { 32 | - printk(XENLOG_G_INFO "HVM d%d save: %s\n", 33 | - d->domain_id, hvm_sr_handlers[i].name); 34 | + //printk(XENLOG_G_INFO "HVM d%d save: %s\n", 35 | + // d->domain_id, hvm_sr_handlers[i].name); 36 | if ( handler(d->vcpu[0], h) != 0 ) 37 | { 38 | printk(XENLOG_G_ERR 39 | @@ -325,8 +325,8 @@ int hvm_load(struct domain *d, hvm_domain_context_t *h) 40 | } 41 | 42 | /* Load the entry */ 43 | - printk(XENLOG_G_INFO "HVM%d restore: %s %"PRIu16"\n", d->domain_id, 44 | - hvm_sr_handlers[desc->typecode].name, desc->instance); 45 | + //printk(XENLOG_G_INFO "HVM%d restore: %s %"PRIu16"\n", d->domain_id, 46 | + // hvm_sr_handlers[desc->typecode].name, desc->instance); 47 | rc = handler(d, h); 48 | if ( rc ) 49 | { 50 | -- 51 | 2.34.1 52 | 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ### License 4 | 5 | vmsifter is licensed under the terms in [MIT License](COPYING). By contributing to the project, you agree to the license and copyright terms therein and release your contribution under these terms. 6 | 7 | ### Sign your work 8 | 9 | Please use the sign-off line at the end of the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify 10 | the below (from [developercertificate.org](http://developercertificate.org/)): 11 | 12 | ``` 13 | Developer Certificate of Origin 14 | Version 1.1 15 | 16 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 17 | 660 York Street, Suite 102, 18 | San Francisco, CA 94110 USA 19 | 20 | Everyone is permitted to copy and distribute verbatim copies of this 21 | license document, but changing it is not allowed. 22 | 23 | Developer's Certificate of Origin 1.1 24 | 25 | By making a contribution to this project, I certify that: 26 | 27 | (a) The contribution was created in whole or in part by me and I 28 | have the right to submit it under the open source license 29 | indicated in the file; or 30 | 31 | (b) The contribution is based upon previous work that, to the best 32 | of my knowledge, is covered under an appropriate open source 33 | license and I have the right under that license to submit that 34 | work with modifications, whether created in whole or in part 35 | by me, under the same open source license (unless I am 36 | permitted to submit under a different license), as indicated 37 | in the file; or 38 | 39 | (c) The contribution was provided directly to me by some other 40 | person who certified (a), (b) or (c) and I have not modified 41 | it. 42 | 43 | (d) I understand and agree that this project and the contribution 44 | are public and that a record of the contribution (including all 45 | personal information I submit with it, including my sign-off) is 46 | maintained indefinitely and may be redistributed consistent with 47 | this project or the open source license(s) involved. 48 | ``` 49 | 50 | Then you just add a line to every git commit message: 51 | 52 | Signed-off-by: Joe Smith 53 | 54 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 55 | 56 | If you set your `user.name` and `user.email` git configs, you can sign your 57 | commit automatically with `git commit -s`. 58 | -------------------------------------------------------------------------------- /vmsifter/utils/protected_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from contextlib import AbstractAsyncContextManager, AbstractContextManager, AsyncExitStack, ExitStack, contextmanager 5 | from types import TracebackType 6 | from typing import Any, Coroutine, Optional, Type 7 | 8 | 9 | class ProtectedContextManager(AbstractContextManager, AbstractAsyncContextManager): 10 | """ 11 | This class implements a protected __enter__ and __exit__ by an ExitStack 12 | Ensuring that: 13 | - if an exception is raised in __enter__, __exit__ will be called 14 | - __exit__ will cleanup any context manager used in __enter__, if pushed onto the ExitStack 15 | """ 16 | 17 | def __init__(self): 18 | self._ex = ExitStack() 19 | self._aex = AsyncExitStack() 20 | 21 | @contextmanager 22 | def _cleanup_on_error(self): 23 | with ExitStack() as stack: 24 | # push self.__exit__ on stack 25 | # in case __enter__ implementation raises an exception 26 | stack.push(self) 27 | yield 28 | # nothing happened, pop everything and continue 29 | stack.pop_all() 30 | 31 | def _safe_enter(self): 32 | """safe __enter__ with cleanup on error""" 33 | return self 34 | 35 | def __enter__(self): 36 | """if an Exception occurs, the cleanup can happen anyway""" 37 | super().__enter__() 38 | with self._cleanup_on_error(): 39 | return self._safe_enter() 40 | 41 | def __exit__( 42 | self, __exc_type: Optional[Type[BaseException]], __exc_value: Optional[BaseException], __traceback 43 | ) -> Optional[bool]: 44 | super().__exit__(__exc_type, __exc_value, __traceback) 45 | # cleanup 46 | self._ex.__exit__(__exc_type, __exc_value, __traceback) 47 | return None 48 | 49 | async def _asafe_enter(self): 50 | return self 51 | 52 | async def __aenter__(self) -> Coroutine[Any, Any, Any]: 53 | await super().__aenter__() 54 | with self._cleanup_on_error(): 55 | return await self._asafe_enter() 56 | 57 | async def __aexit__( 58 | self, 59 | __exc_type: Optional[Type[BaseException]], 60 | __exc_value: Optional[BaseException], 61 | __traceback: Optional[TracebackType], 62 | ): 63 | await super().__aexit__(__exc_type, __exc_value, __traceback) 64 | await self._aex.__aexit__(__exc_type, __exc_value, __traceback) 65 | return None 66 | -------------------------------------------------------------------------------- /patches/0007-xen-VPID-pinning.patch: -------------------------------------------------------------------------------- 1 | From 4a7d09f59530a96318d12c4b5dc9a70a39368b37 Mon Sep 17 00:00:00 2001 2 | Message-Id: <4a7d09f59530a96318d12c4b5dc9a70a39368b37.1701904337.git.tamas.lengyel@intel.com> 3 | In-Reply-To: <52e8608e646cb6e14a679d351203b344d635bfd3.1701904337.git.tamas.lengyel@intel.com> 4 | References: <52e8608e646cb6e14a679d351203b344d635bfd3.1701904337.git.tamas.lengyel@intel.com> 5 | From: Your Name 6 | Date: Tue, 5 Dec 2023 10:13:40 -0500 7 | Subject: [PATCH 7/8] VPID pinning 8 | 9 | --- 10 | xen/arch/x86/hvm/asid.c | 4 ++++ 11 | xen/arch/x86/hvm/vmx/vmcs.c | 3 ++- 12 | xen/arch/x86/hvm/vmx/vmx.c | 8 ++++++++ 13 | xen/arch/x86/mm/mem_sharing.c | 13 ++++--------- 14 | 4 files changed, 18 insertions(+), 10 deletions(-) 15 | 16 | diff --git a/xen/arch/x86/hvm/asid.c b/xen/arch/x86/hvm/asid.c 17 | index 0faaa24a8f..90b42233f5 100644 18 | --- a/xen/arch/x86/hvm/asid.c 19 | +++ b/xen/arch/x86/hvm/asid.c 20 | @@ -13,6 +13,10 @@ 21 | #include 22 | #include 23 | 24 | +#ifdef CONFIG_MEM_SHARING 25 | +#include 26 | +#endif 27 | + 28 | /* Xen command-line option to enable ASIDs */ 29 | static bool __read_mostly opt_asid_enabled = true; 30 | boolean_param("asid", opt_asid_enabled); 31 | diff --git a/xen/arch/x86/hvm/vmx/vmcs.c b/xen/arch/x86/hvm/vmx/vmcs.c 32 | index 5f587b0140..3ec714fb0d 100644 33 | --- a/xen/arch/x86/hvm/vmx/vmcs.c 34 | +++ b/xen/arch/x86/hvm/vmx/vmcs.c 35 | @@ -1968,7 +1968,8 @@ void cf_check vmx_do_resume(void) 36 | */ 37 | v->arch.hvm.vmx.hostenv_migrated = 1; 38 | 39 | - hvm_asid_flush_vcpu(v); 40 | + if ( !mem_sharing_is_fork(v->domain) ) 41 | + hvm_asid_flush_vcpu(v); 42 | } 43 | 44 | debug_state = v->domain->debugger_attached 45 | diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c 46 | index 14c23e174f..e209cce7ed 100644 47 | --- a/xen/arch/x86/hvm/vmx/vmx.c 48 | +++ b/xen/arch/x86/hvm/vmx/vmx.c 49 | @@ -4838,9 +4838,17 @@ bool vmx_vmenter_helper(const struct cpu_user_regs *regs) 50 | else 51 | p_asid = &curr->arch.hvm.n1asid; 52 | 53 | +#ifdef CONFIG_MEM_SHARING 54 | + // We just set the VPID to be the domain ID when using forks 55 | + old_asid = p_asid->asid; 56 | + need_flush = 0; 57 | + new_asid = currd->domain_id; 58 | + p_asid->asid = new_asid; 59 | +#else 60 | old_asid = p_asid->asid; 61 | need_flush = hvm_asid_handle_vmenter(p_asid); 62 | new_asid = p_asid->asid; 63 | +#endif 64 | 65 | if ( unlikely(new_asid != old_asid) ) 66 | { 67 | -- 68 | 2.34.1 69 | 70 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | from pathlib import Path 9 | 10 | project = "VMSifter" 11 | copyright = "2022, Tamas K Lengyel - Mathieu Tarral" 12 | author = "Tamas K Lengyel / Mathieu Tarral" 13 | 14 | # -- General configuration --------------------------------------------------- 15 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 16 | 17 | extensions = [ 18 | # markdown 19 | "myst_parser", 20 | # design 21 | "sphinx_design", 22 | # mermaid 23 | "sphinxcontrib.mermaid", 24 | # copy on code blocks 25 | "sphinx_copybutton", 26 | ] 27 | myst_enable_extensions = ["colon_fence"] 28 | myst_heading_anchors = 3 29 | # pygments_style = "monokai" 30 | # pygments_dark_style = "monokai" 31 | templates_path = ["_templates"] 32 | exclude_patterns = [] 33 | 34 | 35 | # -- Options for HTML output ------------------------------------------------- 36 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 37 | 38 | html_title = project 39 | html_theme = "furo" 40 | html_theme_options = { 41 | # TODO: edit when PR on upstream repo 42 | "source_repository": "https://github.com/IntelLabs/kAFL", 43 | "source_branch": "docs", 44 | "source_directory": "docs/source", 45 | "footer_icons": [ 46 | { 47 | "name": "GitHub", 48 | "url": "https://github.com/IntelLabs/kAFL", 49 | "html": """ 50 | 51 | 52 | 53 | """, 54 | "class": "", 55 | }, 56 | ], 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
vmsifter
3 |

4 | 5 |

6 | Enhanced sandsifter with performance counter monitoring and ring0 execution 7 |

8 | 9 |

10 | 11 | 12 | 13 | 14 | CI badge 15 | 16 |

17 | 18 | ## Requirements 19 | 20 | - git 21 | - build-essential 22 | - docker 23 | 24 | ## Setup 25 | 26 | ### Xen 27 | 28 | clone the repo and submodules 29 | ```shell 30 | git submodule update --init --recursive 31 | ``` 32 | 33 | Install Xen with VM forking features 34 | ```shell 35 | sudo apt-get update 36 | sudo apt-get install -y iasl libyajl-dev libsystemd-dev ninja-build build-essential uuid-dev libncurses-dev pkg-config libglib2.0-dev libpixman-1-dev flex bison python3 python3-dev 37 | cd xen 38 | find ../patches/ -type f -name '*-xen-*' -exec git apply {} \; 39 | echo CONFIG_EXPERT=y > xen/.config 40 | echo CONFIG_MEM_SHARING=y >> xen/.config 41 | make -C xen olddefconfig 42 | ./configure --enable-systemd --disable-docs --disable-stubdom --disable-pvshim --enable-githttp 43 | make -j$(nproc) debball 44 | sudo dpkg -i dist/xen-upstream-*.deb 45 | sudo systemctl enable xencommons.service 46 | sudo systemctl enable xen-qemu-dom0-disk-backend.service 47 | sudo systemctl enable xen-init-dom0.service 48 | sudo systemctl enable xenconsoled.service 49 | echo "/usr/local/lib" | sudo tee -a /etc/ld.so.conf.d/xen.conf 50 | echo "none /proc/xen xenfs defaults,nofail 0 0" | sudo tee -e /etc/fstab 51 | ldconfig 52 | make distclean 53 | git clean -xdf 54 | git reset --hard $(git describe --tags --abbrev=0) 55 | ``` 56 | 57 | Update Grub and adapt `dom0_mem` and `dom0_max_vcpus` settings as needed: 58 | ```shell 59 | echo "GRUB_CMDLINE_XEN_DEFAULT=\"hap_1gb=false hap_2mb=false console=vga hpet=legacy-replacement dom0_mem=8096M dom0_max_vcpus=4 dom0_vcpus_pin=1 iommu=no-sharept spec-ctrl=0 xpti=0 vpmu=bts \\\"cpufreq=hwp:hdc=0;xen:performance,verbose\\\"\"" >> /etc/default/grub 60 | sudo mv /etc/grub.d/20_linux_xen /etc/grub.d/09_linux_xen 61 | sudo update-grub 62 | ``` 63 | 64 | ### VMSifter 65 | 66 | ## Run 67 | 68 | ```shell 69 | ./run.sh 70 | ``` 71 | 72 | ## Building the documentation 73 | 74 | ```shell 75 | cd docs 76 | make html 77 | xdg-open build/html/index.html 78 | ``` 79 | 80 | ## Disclaimer 81 | 82 | Note: All components are provided for research and validation purposes only. Use at your own risk. 83 | 84 | ## References 85 | 86 | - [sandsifter](https://github.com/xoreaxeaxeax/sandsifter) 87 | -------------------------------------------------------------------------------- /patches/0006-xen-Skip-setting-copying-magic-pages.patch: -------------------------------------------------------------------------------- 1 | From d1980db5923263e33d45d97ebb6ccc3eec92df4b Mon Sep 17 00:00:00 2001 2 | Message-Id: 3 | In-Reply-To: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 4 | References: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 5 | From: Tamas K Lengyel 6 | Date: Wed, 25 Oct 2023 10:46:21 -0700 7 | Subject: [PATCH 6/8] Skip setting/copying magic pages 8 | 9 | --- 10 | xen/arch/x86/hvm/vmx/vmx.c | 2 ++ 11 | xen/arch/x86/mm/mem_sharing.c | 5 ++++- 12 | 2 files changed, 6 insertions(+), 1 deletion(-) 13 | 14 | diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c 15 | index 85fe19798b..14c23e174f 100644 16 | --- a/xen/arch/x86/hvm/vmx/vmx.c 17 | +++ b/xen/arch/x86/hvm/vmx/vmx.c 18 | @@ -589,6 +589,7 @@ static void cf_check vmx_domain_relinquish_resources(struct domain *d) 19 | 20 | static void cf_check domain_creation_finished(struct domain *d) 21 | { 22 | +#if 0 23 | gfn_t gfn = gaddr_to_gfn(APIC_DEFAULT_PHYS_BASE); 24 | mfn_t apic_access_mfn = d->arch.hvm.vmx.apic_access_mfn; 25 | bool ipat; 26 | @@ -602,6 +603,7 @@ static void cf_check domain_creation_finished(struct domain *d) 27 | 28 | if ( set_mmio_p2m_entry(d, gfn, apic_access_mfn, PAGE_ORDER_4K) ) 29 | domain_crash(d); 30 | +#endif 31 | } 32 | 33 | static void vmx_init_ipt(struct vcpu *v) 34 | diff --git a/xen/arch/x86/mm/mem_sharing.c b/xen/arch/x86/mm/mem_sharing.c 35 | index 94b6b782ef..6e088a4dae 100644 36 | --- a/xen/arch/x86/mm/mem_sharing.c 37 | +++ b/xen/arch/x86/mm/mem_sharing.c 38 | @@ -1767,6 +1767,7 @@ static int fork_hap_allocation(struct domain *cd, struct domain *d) 39 | 40 | static void copy_tsc(struct domain *cd, struct domain *d) 41 | { 42 | +#if 0 43 | uint32_t tsc_mode; 44 | uint32_t gtsc_khz; 45 | uint32_t incarnation; 46 | @@ -1775,10 +1776,12 @@ static void copy_tsc(struct domain *cd, struct domain *d) 47 | tsc_get_info(d, &tsc_mode, &elapsed_nsec, >sc_khz, &incarnation); 48 | /* Don't bump incarnation on set */ 49 | tsc_set_info(cd, tsc_mode, elapsed_nsec, gtsc_khz, incarnation - 1); 50 | +#endif 51 | } 52 | 53 | static int copy_special_pages(struct domain *cd, struct domain *d) 54 | { 55 | +#if 0 56 | mfn_t new_mfn, old_mfn; 57 | gfn_t new_gfn, old_gfn; 58 | struct p2m_domain *p2m = p2m_get_hostp2m(cd); 59 | @@ -1849,7 +1852,7 @@ static int copy_special_pages(struct domain *cd, struct domain *d) 60 | return rc; 61 | } 62 | } 63 | - 64 | +#endif 65 | return 0; 66 | } 67 | 68 | -- 69 | 2.34.1 70 | 71 | -------------------------------------------------------------------------------- /deploy/roles/vmsifter/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Ensure docker 2 | ansible.builtin.command: docker --version 3 | register: docker_info 4 | failed_when: false 5 | 6 | - name: Docker setup 7 | when: docker_info.rc != 0 8 | block: 9 | - name: Install required packages 10 | ansible.builtin.apt: 11 | name: 12 | - ca-certificates 13 | - curl 14 | state: present 15 | become: true 16 | 17 | - name: Create /etc/apt/keyrings directory 18 | ansible.builtin.file: 19 | path: /etc/apt/keyrings 20 | state: directory 21 | mode: '0755' 22 | become: true 23 | 24 | - name: Download Docker's official GPG key 25 | ansible.builtin.get_url: 26 | url: https://download.docker.com/linux/ubuntu/gpg 27 | dest: /etc/apt/keyrings/docker.asc 28 | mode: '0644' 29 | become: true 30 | 31 | - name: Get system architecture 32 | ansible.builtin.shell: dpkg --print-architecture 33 | register: system_architecture 34 | changed_when: false 35 | 36 | - name: Add Docker repository to Apt sources 37 | ansible.builtin.apt_repository: 38 | repo: "deb [arch={{ system_architecture.stdout }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" 39 | state: present 40 | become: true 41 | 42 | - name: Install Docker and related packages 43 | ansible.builtin.apt: 44 | name: 45 | - docker-ce 46 | - docker-ce-cli 47 | - containerd.io 48 | - docker-buildx-plugin 49 | - docker-compose-plugin 50 | state: present 51 | update_cache: yes 52 | become: true 53 | 54 | - name: adding existing user to group docker 55 | user: 56 | name: "{{ ansible_user_id }}" 57 | groups: docker 58 | append: yes 59 | become: true 60 | 61 | - name: Ensure Docker service configuration directory exists 62 | ansible.builtin.file: 63 | path: /etc/systemd/system/docker.service.d 64 | state: directory 65 | mode: '0755' 66 | become: true 67 | 68 | - name: Reload systemd daemon 69 | ansible.builtin.systemd: 70 | daemon_reload: yes 71 | become: true 72 | 73 | - name: Restart Docker service 74 | ansible.builtin.systemd: 75 | name: docker 76 | state: restarted 77 | become: true 78 | 79 | - name: Ensure docker python module for Ansible 80 | ansible.builtin.apt: 81 | name: python3-docker 82 | state: present 83 | become: true 84 | 85 | - name: Pull latest vmsifter image 86 | ansible.builtin.command: docker pull intel/vmsifter:latest 87 | # access denied when using community.docker.docker_image 88 | # community.docker.docker_image: 89 | # name: vmsifter/vmsifter:latest 90 | # source: pull 91 | -------------------------------------------------------------------------------- /vmsifter/output.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import csv 5 | import logging 6 | from pathlib import Path 7 | from typing import Optional 8 | 9 | from vmsifter.config import settings 10 | from vmsifter.fuzzer.types import NMI, FinalLogResult 11 | from vmsifter.injector.types import InjInterruptEnum 12 | from vmsifter.utils.protected_manager import ProtectedContextManager 13 | 14 | 15 | class CSVOutput(ProtectedContextManager): 16 | CSV_HEADER = [ 17 | "insn", 18 | "length", 19 | "exit-type", 20 | "misc", 21 | "pfct1", 22 | "pfct2", 23 | "pfct3", 24 | "pfct4", 25 | "pfct5", 26 | "pfct6", 27 | "pfct7", 28 | "reg-delta", 29 | ] 30 | RESULTS_CSV_BASENAME = "results" 31 | INVALID_INSN_CSV_BASENAME = "invalid_instructions" 32 | 33 | def __init__(self, id: int) -> None: 34 | super().__init__() 35 | self._logger = logging.getLogger(f"{self.__module__}.{self.__class__.__name__}[{id}]") 36 | self._workdir_path = Path(settings.workdir) 37 | self._results_path = self._workdir_path / f"{self.__class__.RESULTS_CSV_BASENAME}_{id}.csv" 38 | self._invalid_path = self._workdir_path / f"{self.__class__.INVALID_INSN_CSV_BASENAME}_{id}.csv" 39 | 40 | self._results_f = self._ex.enter_context(open(self._results_path, "a", newline="")) 41 | self._invalid_f = self._ex.enter_context(open(self._invalid_path, "a", newline="")) 42 | 43 | self._results_writer = csv.writer(self._results_f) 44 | self._invalid_writer = csv.writer(self._invalid_f) 45 | 46 | # write headers 47 | self._results_writer.writerow(self.__class__.CSV_HEADER) 48 | self._invalid_writer.writerow(self.__class__.CSV_HEADER) 49 | 50 | def log(self, final: Optional[FinalLogResult]): 51 | if final is None: 52 | return 53 | 54 | self._logger.debug("Logging results for %s", final.insn) 55 | 56 | if isinstance(final.exec_res, NMI) and final.exec_res.interrupt == InjInterruptEnum.INVALID_OPCODE: 57 | # invalid insn 58 | self._invalid_writer.writerow( 59 | [ 60 | final.insn, 61 | final.len, 62 | final.exec_res.type_str(), 63 | final.exec_res.misc_str() + final.misc, 64 | *final.exec_res.perfct, 65 | final.exec_res.reg_delta_str(), 66 | ] 67 | ) 68 | else: 69 | # valid 70 | self._results_writer.writerow( 71 | [ 72 | final.insn, 73 | final.len, 74 | final.exec_res.type_str(), 75 | final.exec_res.misc_str() + final.misc, 76 | *final.exec_res.perfct, 77 | final.exec_res.reg_delta_str(), 78 | ] 79 | ) 80 | -------------------------------------------------------------------------------- /patches/0001-libvmi-vmexit-instruction-infos.patch: -------------------------------------------------------------------------------- 1 | From 4b020d6e27cb18a9ee06685ef9d3e5181b0e08f6 Mon Sep 17 00:00:00 2001 2 | From: Tamas K Lengyel 3 | Date: Tue, 23 May 2023 13:01:53 +0000 4 | Subject: [PATCH] VMexit instruction infos 5 | 6 | --- 7 | libvmi/driver/xen/xen_events.c | 7 +++++++ 8 | libvmi/driver/xen/xen_events_abi.h | 7 +++++++ 9 | libvmi/events.h | 7 +++++++ 10 | 3 files changed, 21 insertions(+) 11 | 12 | diff --git a/libvmi/driver/xen/xen_events.c b/libvmi/driver/xen/xen_events.c 13 | index 6a02b07..45c63de 100644 14 | --- a/libvmi/driver/xen/xen_events.c 15 | +++ b/libvmi/driver/xen/xen_events.c 16 | @@ -1117,6 +1117,13 @@ status_t process_vmexit(vmi_instance_t vmi, vm_event_compat_t *vmec) 17 | 18 | event->vmexit_event.reason = vmec->vmexit.arch.vmx.reason; 19 | event->vmexit_event.qualification = vmec->vmexit.arch.vmx.qualification; 20 | + event->vmexit_event.gla = vmec->vmexit.arch.vmx.gla; 21 | + event->vmexit_event.interruption_info = vmec->vmexit.arch.vmx.interruption_info; 22 | + event->vmexit_event.interruption_error = vmec->vmexit.arch.vmx.interruption_error; 23 | + event->vmexit_event.idt_vectoring_info = vmec->vmexit.arch.vmx.idt_vectoring_info; 24 | + event->vmexit_event.idt_vectoring_error = vmec->vmexit.arch.vmx.idt_vectoring_error; 25 | + event->vmexit_event.instruction_length = vmec->vmexit.arch.vmx.instruction_length; 26 | + event->vmexit_event.instruction_info = vmec->vmexit.arch.vmx.instruction_info; 27 | 28 | event->x86_regs = &vmec->data.regs.x86; 29 | event->slat_id = vmec->altp2m_idx; 30 | diff --git a/libvmi/driver/xen/xen_events_abi.h b/libvmi/driver/xen/xen_events_abi.h 31 | index 81f8d2e..1b9888e 100644 32 | --- a/libvmi/driver/xen/xen_events_abi.h 33 | +++ b/libvmi/driver/xen/xen_events_abi.h 34 | @@ -491,6 +491,13 @@ struct vm_event_vmexit { 35 | struct { 36 | uint64_t reason; 37 | uint64_t qualification; 38 | + uint64_t gla; 39 | + uint32_t interruption_info; 40 | + uint32_t interruption_error; 41 | + uint32_t idt_vectoring_info; 42 | + uint32_t idt_vectoring_error; 43 | + uint32_t instruction_length; 44 | + uint32_t instruction_info; 45 | } vmx; 46 | } arch; 47 | }; 48 | diff --git a/libvmi/events.h b/libvmi/events.h 49 | index d3166d7..c63bfee 100644 50 | --- a/libvmi/events.h 51 | +++ b/libvmi/events.h 52 | @@ -435,6 +435,13 @@ typedef struct vmexit_event { 53 | uint8_t sync; /* IN */ 54 | uint64_t reason; /* OUT */ 55 | uint64_t qualification; /* OUT */ 56 | + uint64_t gla; /* OUT */ 57 | + uint32_t interruption_info; /* OUT */ 58 | + uint32_t interruption_error; /* OUT */ 59 | + uint32_t idt_vectoring_info; /* OUT */ 60 | + uint32_t idt_vectoring_error; /* OUT */ 61 | + uint32_t instruction_length; /* OUT */ 62 | + uint32_t instruction_info; /* OUT */ 63 | } vmexit_event_t; 64 | 65 | struct vmi_event; 66 | -- 67 | 2.34.1 68 | 69 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "vmsifter" 3 | version = "0.1.0" 4 | description = "Enhanced sandsifter with performance counter monitoring and ring0 execution" 5 | authors = [ 6 | "Tamas K Lengyel ", 7 | "Mathieu Tarral ", 8 | "Rowan Hart ", 9 | ] 10 | license = "MIT" 11 | 12 | [tool.poetry.dependencies] 13 | python = "^3.10" 14 | coloredlogs = "^15.0.1" 15 | PyYAML = "^6.0" 16 | docopt = "^0.6.2" 17 | dynaconf = "3.2.5" 18 | types-PyYAML = "^6.0.8" 19 | attrs = "^23.1.0" 20 | keystone-engine = "^0.9.2" 21 | 22 | 23 | [tool.poetry.group.dev.dependencies] 24 | black = "24.4.2" 25 | poethepoet = "^0.18.1" 26 | flake8 = "^6.0.0" 27 | isort = "^5.10.1" 28 | pytest = "^7.2.1" 29 | more-itertools = "^8.12.0" 30 | mypy = "^1.0.1" 31 | flake8-pyproject = "^1.2.2" 32 | pdbpp = "^0.10.3" 33 | ipdb = "^0.13.11" 34 | 35 | 36 | [tool.poetry.group.deploy.dependencies] 37 | ansible = "^10.3.0" 38 | 39 | [tool.poetry.scripts] 40 | vmsifter = 'vmsifter.__main__:main' 41 | 42 | [tool.poe.tasks] 43 | format = { shell = "black . && isort ." } 44 | lint = "flake8 --show-source --statistics" 45 | typing = "mypy -p vmsifter" 46 | ccode = { shell = "poe format && poe lint" } 47 | pytest = "pytest --pdb --pdbcls=IPython.terminal.debugger:Pdb --verbose" 48 | bench_test = """ 49 | # durations=0 shows execution time for each test 50 | pytest 51 | --verbose 52 | --durations=0 53 | tests/bench 54 | """ 55 | unit_test = "pytest --pdb --pdbcls=IPython.terminal.debugger:Pdb --verbose -x tests/unit" 56 | # CI 57 | format_check = { shell = "black . --check && isort . --check" } 58 | 59 | [tool.poe.tasks.deploy] 60 | cmd = "ansible-playbook -i inventory.yml site.yml" 61 | cwd = "deploy" 62 | 63 | # tools configuration 64 | [tool.black] 65 | line-length = 120 66 | exclude = ''' 67 | /( 68 | | \.mypy_cache 69 | | xen 70 | | xtf 71 | | libvmi 72 | )/ 73 | ''' 74 | 75 | [tool.isort] 76 | profile = "black" 77 | line_length = 120 78 | skip = ["xen/", "xtf/", "libvmi/"] 79 | 80 | [tool.flake8] 81 | max-line-length = 120 82 | exclude = ["xen/", "xtf/", "libvmi/", "tests/", "scripts/", "docs/"] 83 | # https://black.readthedocs.io/en/stable/faq.html#why-are-flake8-s-e203-and-w503-violated 84 | ignore = ["E203", "W503"] 85 | 86 | [tool.mypy] 87 | warn_unreachable = true 88 | # TODO: warn_return_any = true 89 | warn_unused_ignores = true 90 | warn_redundant_casts = true 91 | show_error_context = true 92 | show_column_numbers = true 93 | show_error_codes = true 94 | pretty = true 95 | check_untyped_defs = true 96 | # exclude Drizzler 97 | exclude = [ 98 | 'drizzler.py', 99 | 'csv.py', 100 | ] 101 | 102 | [tool.pytest.ini_options] 103 | norecursedirs = ["xen/*"] 104 | 105 | [[tool.mypy.overrides]] 106 | module = ["dynaconf.*", "coloredlogs.*", "docopt.*"] 107 | ignore_missing_imports = true 108 | 109 | [build-system] 110 | requires = ["poetry-core>=1.0.0"] 111 | build-backend = "poetry.core.masonry.api" 112 | -------------------------------------------------------------------------------- /.github/workflows/xen.yml: -------------------------------------------------------------------------------- 1 | name: Xen 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - '.github/workflows/xen.yml' 8 | - 'patches/**' 9 | - '.gitmodules' 10 | tags: 11 | - 'v*' 12 | pull_request: 13 | paths: 14 | - '.github/workflows/xen.yml' 15 | - 'patches/**' 16 | - '.gitmodules' 17 | 18 | jobs: 19 | xen_deb: 20 | runs-on: ubuntu-latest 21 | container: 22 | image: ubuntu:24.04 23 | steps: 24 | - name: Ensure git 25 | run: | 26 | apt-get update 27 | apt-get install -y git 28 | git config --global user.email "you@example.com" 29 | git config --global user.name "Your Name" 30 | 31 | - uses: actions/checkout@v4 32 | with: 33 | clean: true 34 | 35 | - name: add safe directory 36 | run: | 37 | git config --global --add safe.directory $GITHUB_WORKSPACE 38 | git config --global --add safe.directory $GITHUB_WORKSPACE/libvmi 39 | git config --global --add safe.directory $GITHUB_WORKSPACE/xen 40 | git config --global --add safe.directory $GITHUB_WORKSPACE/xtf 41 | 42 | # actions/checkout submodules doesn't work 43 | # missing xen/.git 44 | - name: init submodules 45 | run: | 46 | git submodule update --init --recursive 47 | 48 | - name: Install deps 49 | run: > 50 | apt-get install -y iasl libyajl-dev libsystemd-dev 51 | ninja-build meson pkg-config build-essential uuid-dev libncurses-dev 52 | pkg-config libglib2.0-dev libpixman-1-dev flex bison 53 | python3 python3-dev python3-setuptools python3-venv 54 | env: 55 | DEBIAN_FRONTEND: noninteractive 56 | 57 | # note: find with -exec only fails if directory traversal fails 58 | # so use -print0 + sort + xargs 59 | - name: Apply patches 60 | run: find ../patches/ -type f -name '*-xen-*' -print0 | sort -z | xargs -0 git am 61 | working-directory: xen 62 | 63 | - name: Apply our config 64 | run: | 65 | echo CONFIG_EXPERT=y > xen/.config 66 | echo CONFIG_MEM_SHARING=y >> xen/.config 67 | working-directory: xen 68 | 69 | - run: make -C xen olddefconfig 70 | working-directory: xen 71 | 72 | - name: Configure 73 | run: > 74 | ./configure 75 | --enable-systemd --disable-docs 76 | --disable-stubdom --disable-pvshim --enable-githttp 77 | working-directory: xen 78 | 79 | - name: Build Debian package 80 | run: make -j$(nproc) debball 81 | working-directory: xen 82 | 83 | - name: Upload artifact 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: xen-deb 87 | path: xen/dist/xen-upstream-*.deb 88 | 89 | release: 90 | permissions: 91 | contents: write 92 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 93 | needs: xen_deb 94 | runs-on: ubuntu-latest 95 | container: 96 | image: ubuntu:22.04 97 | outputs: 98 | version: ${{ steps.get_version.outputs.version }} 99 | steps: 100 | - name: Get the version 101 | id: get_version 102 | run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT 103 | shell: bash 104 | 105 | - name: Download artifact 106 | uses: actions/download-artifact@v4 107 | with: 108 | name: xen-deb 109 | 110 | - name: Create release 111 | uses: 'softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87' 112 | with: 113 | files: '*.deb' 114 | generate_release_notes: true 115 | tag_name: ${{ steps.get_version.outputs.version }} 116 | 117 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2022 Intel Corporation 4 | # SPDX-License-Identifier: MIT 5 | 6 | set -e 7 | 8 | usage() { 9 | echo "Usage $0 [options] -- [vmsifter args]" 10 | echo "Options:" 11 | echo " -h, --help Show this message" 12 | echo " -i Use Docker image (Default: vmsifter-dev)" 13 | echo " -w Set workdir path (Default: $PWD/workdir)" 14 | echo " -b Use as Dockerfile baseimage (Default: See Dockerfile.dev)" 15 | echo " -u Toggle image building (Default: true)" 16 | echo " -t Toogle pseudo-tty allocation (Default: true)" 17 | echo " -n Name the container as " 18 | } 19 | 20 | IMAGE="vmsifter-dev" 21 | USER="$(id -u):$(id -g)" 22 | BASEIMAGE="" 23 | WORKDIR_PATH="$PWD/workdir" 24 | IMAGE_BUILD_ENABLED="true" 25 | TTY_ENABLED="true" 26 | CONTAINER_NAME="false" 27 | 28 | OPTSTRING=':i:w:b:u:t:n:' 29 | while getopts ${OPTSTRING} opt; do 30 | case ${opt} in 31 | i) 32 | # use dev image 33 | IMAGE="${OPTARG}" 34 | ;; 35 | w) 36 | # set workdir 37 | # ensure absolute path 38 | WORKDIR_PATH="$(realpath ${OPTARG})" 39 | ;; 40 | b) 41 | # use given baseimage 42 | BASEIMAGE="${OPTARG}" 43 | ;; 44 | u) 45 | # toggle imahe build 46 | # convert to lowercase 47 | IMAGE_BUILD_ENABLED="${OPTARG,,}" 48 | ;; 49 | t) 50 | # toggle pseudo-tty allocation 51 | # convert to lowercase 52 | TTY_ENABLED="${OPTARG,,}" 53 | ;; 54 | n) 55 | # name the container 56 | CONTAINER_NAME="${OPTARG}" 57 | ;; 58 | --) 59 | shift 60 | break 61 | ;; 62 | *) 63 | usage 64 | exit 1 65 | ;; 66 | esac 67 | done 68 | 69 | shift "$((OPTIND-1))" 70 | 71 | # create workdir directory as current user, otherwise Docker daemon will create it as root 72 | mkdir -p $WORKDIR_PATH 73 | 74 | # check if .env to reinject it into the container 75 | ENV_FILE_PARAM="" 76 | if [ -f "$PWD/.env" ]; then 77 | ENV_FILE_PARAM="--env-file $PWD/.env" 78 | fi 79 | 80 | BUILDARG="" 81 | if [ -n "$BASEIMAGE" ]; then 82 | BUILDARG="--build-arg BASEIMAGE=${BASEIMAGE}" 83 | fi 84 | 85 | if [[ "$IMAGE_BUILD_ENABLED" == "true" || "$IMAGE_BUILD_ENABLED" == "yes" ]]; then 86 | # ensure image is up to date 87 | docker build \ 88 | ${BUILDARG} \ 89 | --target $IMAGE \ 90 | -t $IMAGE \ 91 | -f Dockerfile \ 92 | . 93 | fi 94 | 95 | # try to guess if one of vmsifter args is a file 96 | # then mount it as a volume to expose them transparently 97 | ADDITIONAL_VOLUMES="" 98 | vmsifter_args=("$@") 99 | for i in "${!vmsifter_args[@]}"; do 100 | # check if existing file 101 | arg="${vmsifter_args[$i]}" 102 | if [ -f "$arg" ]; then 103 | # get abs path 104 | abs_path=$(realpath "$arg") 105 | # add it as volume 106 | ADDITIONAL_VOLUMES+=" -v $abs_path:$abs_path" 107 | # rewrite vmsifter argument 108 | vmsifter_args[$i]="$abs_path" 109 | fi 110 | done 111 | 112 | TTY_ARG="" 113 | if [[ "$TTY_ENABLED" == "true" || "$TTY_ENABLED" == "yes" ]]; then 114 | TTY_ARG="-t" 115 | fi 116 | 117 | CONTAINER_NAME_ARG="" 118 | if [[ "$CONTAINER_NAME" != "false" ]]; then 119 | CONTAINER_NAME_ARG="--name $CONTAINER_NAME" 120 | fi 121 | 122 | docker run --rm -i \ 123 | $TTY_ARG \ 124 | --privileged \ 125 | $ENV_FILE_PARAM \ 126 | -v "${WORKDIR_PATH}:/workdir" \ 127 | $ADDITIONAL_VOLUMES \ 128 | --user $USER \ 129 | --group-add sudo \ 130 | $CONTAINER_NAME_ARG \ 131 | $IMAGE "${vmsifter_args[@]}" 132 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore build cache 2 | autom4te.cache/ 3 | # generated files 4 | /config.* 5 | /Makefile.in 6 | /aclocal.m4 7 | /.env 8 | /src/.deps/ 9 | /src/.dirstamp 10 | /stamp-h1 11 | /missing 12 | /install-sh 13 | /depcomp 14 | /configure 15 | /compile 16 | /Makefile 17 | # build output 18 | /injector 19 | 20 | # Python template 21 | # Byte-compiled / optimized / DLL files 22 | __pycache__/ 23 | *.py[cod] 24 | *$py.class 25 | 26 | # C extensions 27 | *.so 28 | *.o 29 | 30 | # Distribution / packaging 31 | .Python 32 | build/ 33 | develop-eggs/ 34 | dist/ 35 | downloads/ 36 | eggs/ 37 | .eggs/ 38 | lib/ 39 | lib64/ 40 | parts/ 41 | sdist/ 42 | var/ 43 | wheels/ 44 | share/python-wheels/ 45 | *.egg-info/ 46 | .installed.cfg 47 | *.egg 48 | MANIFEST 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | *.spec 55 | 56 | # Installer logs 57 | pip-log.txt 58 | pip-delete-this-directory.txt 59 | 60 | # Unit test / coverage reports 61 | htmlcov/ 62 | .tox/ 63 | .nox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *.cover 70 | *.py,cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | cover/ 74 | 75 | # Translations 76 | *.mo 77 | *.pot 78 | 79 | # Django stuff: 80 | *.log 81 | local_settings.py 82 | db.sqlite3 83 | db.sqlite3-journal 84 | 85 | # Flask stuff: 86 | instance/ 87 | .webassets-cache 88 | 89 | # Scrapy stuff: 90 | .scrapy 91 | 92 | # Sphinx documentation 93 | docs/_build/ 94 | 95 | # PyBuilder 96 | .pybuilder/ 97 | target/ 98 | 99 | # Jupyter Notebook 100 | .ipynb_checkpoints 101 | 102 | # IPython 103 | profile_default/ 104 | ipython_config.py 105 | 106 | # pyenv 107 | # For a library or package, you might want to ignore these files since the code is 108 | # intended to run in multiple environments; otherwise, check them in: 109 | # .python-version 110 | 111 | # pipenv 112 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 113 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 114 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 115 | # install all needed dependencies. 116 | #Pipfile.lock 117 | 118 | # poetry 119 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 120 | # This is especially recommended for binary packages to ensure reproducibility, and is more 121 | # commonly ignored for libraries. 122 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 123 | #poetry.lock 124 | 125 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 126 | __pypackages__/ 127 | 128 | # Celery stuff 129 | celerybeat-schedule 130 | celerybeat.pid 131 | 132 | # SageMath parsed files 133 | *.sage.py 134 | 135 | # Environments 136 | .env 137 | .venv 138 | env/ 139 | venv/ 140 | ENV/ 141 | env.bak/ 142 | venv.bak/ 143 | 144 | # Spyder project settings 145 | .spyderproject 146 | .spyproject 147 | 148 | # Rope project settings 149 | .ropeproject 150 | 151 | # mkdocs documentation 152 | /site 153 | 154 | # mypy 155 | .mypy_cache/ 156 | .dmypy.json 157 | dmypy.json 158 | 159 | # Pyre type checker 160 | .pyre/ 161 | 162 | # pytype static type analyzer 163 | .pytype/ 164 | 165 | # Cython debug symbols 166 | cython_debug/ 167 | 168 | # PyCharm 169 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 170 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 171 | # and can be added to the global gitignore or merged into this file. For a more nuclear 172 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 173 | #.idea/ 174 | 175 | # Ignore dynaconf secret files 176 | .secrets.* 177 | 178 | # vmsifter 179 | /workdir/ 180 | -------------------------------------------------------------------------------- /vmsifter/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | """VMSifter 5 | 6 | Usage: 7 | vmsifter [options] 8 | 9 | Options: 10 | -h --help Show this screen. 11 | -d --debug Toggle debugging 12 | -r --refresh-freq= Refresh frequency per instructions 13 | -m , --fuzzer-mode Enable fuzzing mode [default: TUNNEL] 14 | -i , --injector-mode Select injector mode [default: XENVM] 15 | -j , --jobs Use at most jobs 16 | -e , --fuzzer-param ... Pass extra parameter for fuzzer 17 | --version Show version. 18 | """ 19 | 20 | import logging 21 | from contextlib import suppress 22 | from functools import wraps 23 | from logging.config import dictConfig 24 | from pathlib import Path 25 | from typing import List, Optional, Union 26 | 27 | import coloredlogs 28 | import yaml 29 | from docopt import docopt 30 | 31 | from vmsifter.config import FuzzerType, InjectorType, settings 32 | from vmsifter.executor import SifterExecutor 33 | 34 | 35 | def post_mortem(func): 36 | @wraps(func) 37 | def wrapper(*args, **kwargs): 38 | try: 39 | return func(*args, **kwargs) 40 | except Exception: 41 | if settings.PDB: 42 | import pdb 43 | import sys 44 | 45 | _, _, trace = sys.exc_info() 46 | logging.exception("Entering post mortem debugging") 47 | pdb.post_mortem(trace) 48 | else: 49 | raise 50 | 51 | return wrapper 52 | 53 | 54 | def setup_logging(debug_enabled: bool = False): 55 | log_config_path = Path(__file__).parent / "logging.yaml" 56 | with open(log_config_path) as f: 57 | config = yaml.safe_load(f) 58 | 59 | root_level = "INFO" 60 | if debug_enabled: 61 | root_level = "DEBUG" 62 | 63 | format = config["formatters"]["colored"]["format"] 64 | settings.logging.format = logging.Formatter(format) 65 | 66 | dictConfig(config) 67 | coloredlogs.install(level=root_level, fmt=format) 68 | 69 | 70 | # poetry's entrypoint can't be specified with an executable package 71 | # we need to add a function 72 | @post_mortem 73 | def main(): 74 | args = docopt(__doc__, version="0.1") 75 | settings["debug"] = args["--debug"] 76 | if args["--jobs"]: 77 | settings["jobs"] = int(args["--jobs"]) 78 | setup_logging(settings["debug"]) 79 | 80 | with suppress(KeyboardInterrupt): 81 | # set fuzzing mode 82 | try: 83 | fuzzer_mode_str = args["--fuzzer-mode"].upper() 84 | settings.fuzzer_mode = FuzzerType[fuzzer_mode_str] 85 | except KeyError: 86 | logging.critical("Unknown fuzzer mode %s", fuzzer_mode_str) 87 | logging.info("Available modes: %s", [option.name for option in FuzzerType]) 88 | return 1 89 | 90 | # set injector mode 91 | try: 92 | injector_mode_str = args["--injector-mode"].upper() 93 | settings.injector_mode = InjectorType[injector_mode_str] 94 | except KeyError: 95 | logging.critical("Unknown injector mode %s", injector_mode_str) 96 | logging.info("Available modes: %s", [option.name for option in InjectorType]) 97 | return 1 98 | 99 | if args["--refresh-freq"]: 100 | settings["refresh_frequency"] = int(args["--refresh-freq"]) 101 | 102 | logging.info("VMSifter started !") 103 | 104 | extra_params: Optional[Union[List[str], str]] = args["--fuzzer-param"] 105 | if isinstance(extra_params, str): 106 | extra_params = [extra_params] 107 | with SifterExecutor() as executor: 108 | executor.run(extra_params) 109 | 110 | 111 | # needed when vmsifter is invoked as executable package 112 | # python -m vmsifter 113 | if __name__ == "__main__": 114 | main() 115 | -------------------------------------------------------------------------------- /tests/unit/utils/test_xen.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from typing import List 5 | 6 | from vmsifter.utils.xen import XL, XlInfo, XlVcpuInfo, parse_cfg_prefix_name 7 | 8 | 9 | def test_parse_info(): 10 | output = """host : abc.intel.com 11 | release : 5.15.0-73-generic 12 | version : #80~20.04.1-Ubuntu SMP Wed May 17 14:58:14 UTC 2023 13 | machine : x86_64 14 | nr_cpus : 56 15 | max_cpu_id : 111 16 | nr_nodes : 2 17 | cores_per_socket : 28 18 | threads_per_core : 1 19 | cpu_mhz : 2494.141 20 | virt_caps : pv hvm hap shadow vmtrace gnttab-v1 gnttab-v2 21 | total_memory : 522954 22 | free_memory : 507728 23 | sharing_freed_memory : 0 24 | sharing_used_memory : 0 25 | outstanding_claims : 0 26 | free_cpus : 0 27 | xen_major : 4 28 | xen_minor : 18 29 | xen_extra : .0 30 | xen_version : 4.18.0 31 | xen_caps : xen-3.0-x86_64 hvm-3.0-x86_32 hvm-3.0-x86_32p hvm-3.0-x86_64 32 | xen_scheduler : credit2 33 | xen_pagesize : 4096 34 | platform_params : virt_start=0xffff800000000000 35 | xen_changeset : 36 | xen_commandline : placeholder console=vga dom0_mem=10096M hpet=legacy-replacement dom0_max_vcpus=8 dom0_vcpus_pin=1 ept=ad=0 iommu=no-sharept spec-ctrl=0 altp2m=1 xpti=0 loglvl=all guest_loglvl=all smt=0 vpmu=bts apicv=0 cpufreq=hwp:hdc=0;xen:performance,verbose no-real-mode edd=off 37 | cc_compiler : gcc (Ubuntu 10.5.0-1ubuntu1~20.04) 10.5.0 38 | cc_compile_by : mtarral 39 | cc_compile_domain : abc.intel.com 40 | cc_compile_date : Wed Jan 10 07:39:03 PST 2024 41 | build_id : 3c0e18caf113475da6ee3d76048a0aad7fcd4942 42 | xend_config_format : 4 43 | """ 44 | info = XL._parse_info(output) 45 | expect = XlInfo(nr_cpus=56, max_cpu_id=111, nr_nodes=2, cores_per_socket=28, threads_per_core=1) 46 | assert info == expect 47 | 48 | 49 | def test_parse_vcpu_info(): 50 | output = """Name ID VCPU CPU State Time(s) Affinity (Hard / Soft) 51 | Domain-0 0 0 0 -b- 406.4 0 / all 52 | Domain-0 0 1 2 r-- 363.7 2 / all 53 | Domain-0 0 2 4 -b- 340.9 4 / all 54 | Domain-0 0 3 6 -b- 320.2 6 / all 55 | Domain-0 0 4 8 -b- 313.5 8 / all 56 | Domain-0 0 5 10 -b- 280.7 10 / all 57 | Domain-0 0 6 12 -b- 210.9 12 / all 58 | Domain-0 0 7 14 -b- 212.7 14 / al 59 | """ 60 | vcpu_list: List[XlVcpuInfo] = list(XL._parse_vcpu_list(output)) 61 | expect_cpu = [i for i in range(15) if not i % 2] 62 | expect_vcpu = [i for i in range(8)] 63 | assert [info.cpu_id for info in vcpu_list] == expect_cpu 64 | assert [info.vcpu_id for info in vcpu_list] == expect_vcpu 65 | 66 | 67 | def test_parse_cfg_prefix_name(): 68 | file_content = """ 69 | name="test-hvm32pse-vmsifter" 70 | 71 | vcpus=1 72 | 73 | type="hvm" 74 | builder="hvm" # Legacy for before Xen 4.10 75 | 76 | memory=128 77 | firmware_override="/root/vmsifter/xtf/tests/vmsifter/test-hvm32pse-vmsifter" 78 | 79 | # The framework doesn't reboot. A reboot signal is almost certainly a triple 80 | # fault instead. Prevent it turning into a runaway domain. 81 | on_reboot = "destroy" 82 | 83 | # Test Extra Configuration: 84 | shadow_memory=128""" 85 | new_content = parse_cfg_prefix_name(file_content, "suffix42") 86 | expected_content = """ 87 | name="test-hvm32pse-vmsifter-suffix42" 88 | 89 | vcpus=1 90 | 91 | type="hvm" 92 | builder="hvm" # Legacy for before Xen 4.10 93 | 94 | memory=128 95 | firmware_override="/root/vmsifter/xtf/tests/vmsifter/test-hvm32pse-vmsifter" 96 | 97 | # The framework doesn't reboot. A reboot signal is almost certainly a triple 98 | # fault instead. Prevent it turning into a runaway domain. 99 | on_reboot = "destroy" 100 | 101 | # Test Extra Configuration: 102 | shadow_memory=128""" 103 | assert expected_content == new_content 104 | -------------------------------------------------------------------------------- /vmsifter/injector/xenvm.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import signal 5 | import subprocess 6 | import threading 7 | from contextlib import suppress 8 | from pathlib import Path 9 | from typing import Optional, Type 10 | 11 | from vmsifter.config import InjectorType, get_injector_settings, settings 12 | from vmsifter.utils.xen import gen_tmp_xenvm_configfile, xtf_vm 13 | 14 | from .types import AbstractInjector 15 | 16 | 17 | class XenVMInjector(AbstractInjector): 18 | LOCK = threading.Lock() 19 | PARENT_DOMID: Optional[int] = None 20 | ID: int = 1 21 | 22 | def __init__(self, socket_path: Path, pinned_cpu: int): 23 | super().__init__(socket_path, pinned_cpu) 24 | self._domid: Optional[int] = None 25 | self._proc: Optional[subprocess.Popen] = None 26 | 27 | @staticmethod 28 | def get_type() -> InjectorType: 29 | return InjectorType.XENVM 30 | 31 | def _safe_enter(self): 32 | super()._safe_enter() 33 | with self.__class__.LOCK: 34 | if self.__class__.PARENT_DOMID is None: 35 | self._set_cpu_perf_state() 36 | domid: int = self._create_parent_vm() 37 | self.__class__.PARENT_DOMID = domid 38 | domid = self.__class__.PARENT_DOMID 39 | self._fork_vm(domid) 40 | return self 41 | 42 | def __exit__( 43 | self, __exc_type: Optional[Type[BaseException]], __exc_value: Optional[BaseException], __traceback 44 | ) -> None: 45 | self.logger.debug("Cleaning up !") 46 | # send SIGKILL to injector 47 | # pypy: with SIGINT the injector doesn't terminate somehow 48 | if self._proc is not None: 49 | with suppress(ProcessLookupError): 50 | self._proc.send_signal(signal.SIGKILL) 51 | super().__exit__(__exc_type, __exc_value, __traceback) 52 | self.logger.debug("Cleanup done") 53 | 54 | def _set_cpu_perf_state(self): 55 | """Set CPU performance state 56 | 57 | (only needed when hwp is used, otherwise its already set from the Xen command line) 58 | """ 59 | 60 | cmd = ["sudo", "xenpm", "set-cpufreq-cppc", "performance"] 61 | self.logger.info("Setting CPU frequency: %s", cmd) 62 | subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 63 | 64 | def _create_parent_vm(self) -> int: 65 | """Create parent Xen VM 66 | 67 | Returns: 68 | The parent VM Xen domain ID""" 69 | # gen temporary file 70 | tmp_file_path = self._ex.enter_context(gen_tmp_xenvm_configfile()) 71 | domid: int = self._ex.enter_context(xtf_vm(tmp_file_path)) 72 | # setup parent 73 | self.logger.info("Setup parent") 74 | 75 | cmd = [ 76 | "sudo", 77 | f"{self._inj_settings.INJECTOR_PATH}", 78 | "--setup", 79 | "--domid", 80 | str(domid), 81 | "--perfcts", 82 | f"{self._inj_settings.perfcts}", 83 | ] 84 | 85 | if settings.debug: 86 | cmd += ["--debug"] 87 | 88 | if self._inj_settings.sse: 89 | cmd += ["--sse"] 90 | 91 | if self._inj_settings.syscall: 92 | cmd += ["--syscall"] 93 | 94 | if self._inj_settings.fpu_emulation: 95 | cmd += ["--fpu-emulation"] 96 | 97 | self.logger.debug("Launching injector: %s", cmd) 98 | subprocess.check_call(cmd) 99 | self.logger.info("Parent VM: %s", domid) 100 | return domid 101 | 102 | def _fork_vm(self, domid): 103 | """fork VM""" 104 | self.logger.info("Setup child") 105 | cmd = [ 106 | "sudo", 107 | f"{self._inj_settings.INJECTOR_PATH}", 108 | "--socket", 109 | str(self._socket_path), 110 | "--domid", 111 | str(domid), 112 | "--insn-buf-size", 113 | f"{settings.insn_buf_size}", 114 | "--pin-cpu", 115 | str(self._pinned_cpu), 116 | get_injector_settings(), 117 | ] 118 | 119 | if settings.debug: 120 | cmd += ["--debug"] 121 | 122 | self.logger.debug("Setup child: %s", cmd) 123 | self._proc = self._ex.enter_context(subprocess.Popen(cmd, stdout=None, stderr=subprocess.STDOUT)) 124 | 125 | self.logger.info("Child ID: %s", self.__class__.ID) 126 | self.__class__.ID += 1 127 | -------------------------------------------------------------------------------- /vmsifter/config/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | """VMSifter configuration 5 | 6 | This module handles most of VMSifter's configuration through Dynaconf, and 7 | exports the settings object 8 | 9 | The default configuration is both defined in vmsifter/config/settings.toml for simple values 10 | and inside this module as well for complex values depending on each other, or computed at runtime, 11 | with Dynaconf Validators. 12 | """ 13 | 14 | import logging 15 | import random 16 | import shutil 17 | import sys 18 | from enum import Enum, auto 19 | from pathlib import Path 20 | from typing import Dict, List 21 | 22 | from dynaconf import Dynaconf, Validator, loaders 23 | from dynaconf.utils.boxing import DynaBox 24 | from dynaconf.utils.functional import empty 25 | from dynaconf.utils.parse_conf import Lazy 26 | 27 | CUR_DIR = Path(__file__).parent 28 | 29 | 30 | # define some types here to avoid circular dependency 31 | class FuzzerType(Enum): 32 | RANDOM = auto() 33 | TUNNEL = auto() 34 | CSV = auto() 35 | DRIZZLER = auto() 36 | 37 | 38 | class InjectorType(Enum): 39 | XENVM = auto() 40 | 41 | 42 | MAP_INJECTOR_SETTINGS: Dict[FuzzerType, str] = { 43 | FuzzerType.RANDOM: "--mtf", 44 | FuzzerType.TUNNEL: "--mtf", 45 | FuzzerType.CSV: "--mtf", 46 | FuzzerType.DRIZZLER: "--drizzler", 47 | } 48 | 49 | 50 | def get_injector_settings() -> str: 51 | """Returns the default injector setting for the configured fuzzer mode""" 52 | return MAP_INJECTOR_SETTINGS[settings.fuzzer_mode] 53 | 54 | 55 | EXEC_MODE_TO_FILE = {"32": "hvm32", "32pae": "hvm32pae", "64": "hvm64"} 56 | 57 | 58 | def lazy_prefix_eval(value, **context) -> range: 59 | return range(context["this"].min_prefix_count, context["this"].max_prefix_count + 1) 60 | 61 | 62 | def assign_prefix(settings, validator) -> List[int]: 63 | mode_prefix = settings.x86.prefix 64 | if settings.x86.exec_mode in ["64"]: 65 | mode_prefix.extend(settings.x86.prefix_64) 66 | mode_prefix.sort() 67 | return mode_prefix 68 | 69 | 70 | settings = Dynaconf( 71 | envvar_prefix="VMSIFTER", 72 | settings_files=[str(CUR_DIR / "settings.toml"), str(CUR_DIR / ".secrets.toml")], 73 | validators=[ 74 | Validator("logging.format", must_exist=True, default=logging.Formatter(logging.BASIC_FORMAT)), 75 | Validator("jobs", cast=int, must_exist=True, condition=lambda v: v > 0), 76 | Validator("fuzzer_mode", default=FuzzerType.TUNNEL.name, must_exist=True), 77 | Validator("injector_mode", default=InjectorType.XENVM.name, must_exist=True), 78 | Validator("workdir", default=str(Path.cwd() / "workdir"), must_exist=True), 79 | Validator("max_prefix_count", cast=int, must_exist=True), 80 | Validator("min_prefix_count", cast=int, must_exist=True), 81 | Validator("prefix_range", default=Lazy(empty, formatter=lazy_prefix_eval)), 82 | Validator("x86.prefix_64", default=lambda _settings, _value: [i for i in range(0x40, 0x50)]), # rex 83 | Validator("x86.exec_mode", cast=str), 84 | Validator("x86.min_buffer", cast=bytes, default=b"\x00"), 85 | Validator("x86.max_end_first_byte", cast=bytes, default=b"\xFF"), 86 | # TODO: validation 32/32pae/64 87 | Validator("mode_prefix", default=assign_prefix), 88 | Validator("fuzzer.drizzler.seed", default=random.randrange(sys.maxsize), cast=int), 89 | Validator("fuzzer.drizzler.num_seeds", cast=int), 90 | Validator("fuzzer.drizzler.injections", cast=int), 91 | Validator("fuzzer.drizzler.aggressive", cast=bool), 92 | Validator("injector.xenvm.injector_path", default=shutil.which("injector"), must_exist=True), 93 | # TODO: why we need str( ) ? settings.x86.exec_mode already has a cast 94 | Validator( 95 | "injector.xenvm.xl.config", 96 | default=lambda _settings, _value: EXEC_MODE_TO_FILE[str(_settings.x86.exec_mode)], 97 | ), 98 | ], 99 | ) 100 | # enum "validator" 101 | 102 | 103 | # force to validate and raise errors early 104 | settings.validators.validate() 105 | settings.fuzzer_mode = FuzzerType[settings.fuzzer_mode] 106 | settings.injector_mode = InjectorType[settings.injector_mode] 107 | 108 | 109 | def dump_config(directory: Path): 110 | """Dump current configuration in workdir config file""" 111 | global settings 112 | # generate a dict with all the keys for the current environment 113 | config = settings.to_dict() 114 | # dump to a file, format is infered by file extension 115 | config_path = directory / "config.yaml" 116 | loaders.write(str(config_path), DynaBox(config).to_dict()) 117 | -------------------------------------------------------------------------------- /vmsifter/utils/xen.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import json 5 | import logging 6 | import re 7 | import subprocess 8 | import uuid 9 | from contextlib import contextmanager 10 | from pathlib import Path 11 | from tempfile import NamedTemporaryFile 12 | from typing import Generator, List, Optional 13 | 14 | from attr import define, field 15 | 16 | from vmsifter.config import settings 17 | 18 | 19 | def parse_cfg_prefix_name(cfg_content: str, name_suffix: str) -> str: 20 | new_file_content: List[str] = [] 21 | for line in cfg_content.splitlines(): 22 | m = re.match('^name="(?P.*)"$', line) 23 | new_line = line 24 | if m: 25 | name = m.group("name") 26 | # add suffix 27 | new_line = f'name="{name}-{name_suffix}"' 28 | new_file_content.append(new_line) 29 | return "\n".join(new_file_content) 30 | 31 | 32 | @contextmanager 33 | def gen_tmp_xenvm_configfile() -> Generator[Path, None, None]: 34 | """Generate a temporary copy of xtf/tests/vmsifter/test-xxx-vmsifter.cfg, with a temporary non-unique VM name""" 35 | with NamedTemporaryFile(mode="w") as tmp_cfg: 36 | xtf_cfg_file_path = Path( 37 | f"{settings.injector.xenvm.XTF_PATH}/tests/vmsifter/test-{settings.injector.xenvm.xl.config}-vmsifter.cfg" 38 | ) 39 | with open(xtf_cfg_file_path) as f: 40 | content = f.read() 41 | suffix = str(uuid.uuid4()) 42 | logging.debug("Parent VM name suffix: %s", suffix) 43 | new_content = parse_cfg_prefix_name(content, suffix) 44 | tmp_cfg.write(new_content) 45 | tmp_cfg.flush() 46 | yield Path(tmp_cfg.name) 47 | 48 | 49 | @contextmanager 50 | def xtf_vm(cfg_file_path: Path) -> Generator[int, None, None]: 51 | """Creates XTF VMs and destroy it when the resource is released""" 52 | cmd: List[str] = [ 53 | "sudo", 54 | "xl", 55 | "create", 56 | "-p", 57 | str(cfg_file_path), 58 | ] 59 | logging.debug("exec: %s", cmd) 60 | subprocess.check_call(cmd) 61 | output = subprocess.check_output(["sudo", "xl", "list", "--long"]) 62 | xl_list_info = json.loads(output) 63 | # look for vm whose name matches xensifter 64 | xensifter_vm_list = [vm for vm in xl_list_info if "vmsifter" in vm["config"]["c_info"]["name"]] 65 | assert len(xensifter_vm_list) == 1 66 | xensifter_vm = xensifter_vm_list.pop() 67 | # get domid 68 | domid = xensifter_vm["domid"] 69 | try: 70 | yield domid 71 | finally: 72 | logging.info("Destroying parent VM (%s)", domid) 73 | cmd = ["sudo", "xl", "destroy", str(domid)] 74 | logging.debug("exec: %s", cmd) 75 | subprocess.check_call(cmd) 76 | 77 | 78 | @define 79 | class XlInfo: 80 | nr_cpus: int = field(converter=int) 81 | max_cpu_id: int = field(converter=int) 82 | nr_nodes: int = field(converter=int) 83 | cores_per_socket: int = field(converter=int) 84 | threads_per_core: int = field(converter=int) 85 | 86 | 87 | @define 88 | class XlVcpuInfo: 89 | name: str = field() 90 | dom_id: int = field(converter=int) 91 | vcpu_id: int = field(converter=int) 92 | cpu_id: int = field(converter=int) 93 | 94 | 95 | class XL: 96 | """Bindings to Xen XL toolstack""" 97 | 98 | @classmethod 99 | def _parse_info(cls, output: str) -> XlInfo: 100 | data = {} 101 | for line in output.splitlines(): 102 | if ":" in line: 103 | k, v = line.split(":", 1) 104 | data[k.strip()] = v.strip() 105 | 106 | return XlInfo( 107 | nr_cpus=data.get("nr_cpus", 0), 108 | max_cpu_id=data.get("max_cpu_id", 0), 109 | nr_nodes=data.get("nr_nodes", 0), 110 | cores_per_socket=data.get("cores_per_socket", 0), 111 | threads_per_core=data.get("threads_per_core", 0), 112 | ) 113 | 114 | @classmethod 115 | def info(cls) -> XlInfo: 116 | """xl info""" 117 | cmd = ["xl", "info"] 118 | output = subprocess.check_output(cmd, text=True) 119 | return cls._parse_info(output) 120 | 121 | @classmethod 122 | def _parse_vcpu_list(cls, output: str) -> Generator[XlVcpuInfo, None, None]: 123 | # skip header and empty lines 124 | lines_w_header = (line for i, line in enumerate(output.splitlines()) if i > 0 and line.strip()) 125 | for line in lines_w_header: 126 | fields = line.split() 127 | yield XlVcpuInfo(name=fields[0], dom_id=fields[1], vcpu_id=fields[2], cpu_id=fields[3]) 128 | 129 | @classmethod 130 | def vcpu_list(cls, domain: Optional[str] = None) -> Generator[XlVcpuInfo, None, None]: 131 | """xl vcpu-list """ 132 | cmd = ["xl", "vcpu-list"] 133 | if domain: 134 | cmd.append(domain) 135 | output = subprocess.check_output(cmd, text=True) 136 | for x in cls._parse_vcpu_list(output): 137 | yield x 138 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | CommunityCodeOfConduct AT intel DOT com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | -------------------------------------------------------------------------------- /vmsifter/fuzzer/csv.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from __future__ import annotations 5 | 6 | import csv 7 | import itertools 8 | from collections.abc import Generator 9 | from pathlib import Path 10 | from typing import List, Optional 11 | 12 | from attrs import define 13 | 14 | from vmsifter.config import settings 15 | from vmsifter.fuzzer.types import AbstractInsnGenerator, FinalLogResult, FuzzerExecResult, Interrupted 16 | 17 | 18 | @define(slots=False, auto_attribs=True, auto_detect=True) 19 | class CsvFuzzer(AbstractInsnGenerator): 20 | def __init__(self, insn_buffer: Optional[bytearray] = None, extra_params: Optional[List[str]] = None): 21 | super().__init__(None, extra_params=extra_params) 22 | # check for extra params 23 | if not self.extra_params: 24 | raise ValueError("CSVFuzzer requires a CSV file parameter. Use -e to specify it.") 25 | # check if CSV file exists 26 | csv_input = Path(self.extra_params[0]) 27 | 28 | self.logger.info("Opening %s as input", csv_input) 29 | if settings.csv_log_diff_only: 30 | self.logger.info("Logging only results that differ from information in the input") 31 | 32 | f = open(csv_input, mode="r", newline="") 33 | self.reader = csv.DictReader(f) 34 | self.row = next(self.reader) 35 | insn = bytearray.fromhex(self.row["insn"]) 36 | self.init_buffer(buffer=insn) 37 | 38 | if settings.extra_byte != 0: 39 | self.insn_length += settings.extra_byte 40 | 41 | self.prefix_len = 0 42 | if settings.min_prefix_count != 0: 43 | self.prefix_list = [] 44 | 45 | for i in range(settings.min_prefix_count, settings.max_prefix_count + 1): 46 | self.prefix_list += list(itertools.product(settings.mode_prefix, repeat=i)) 47 | 48 | self.prefix_iterator = itertools.tee(self.prefix_list, 1)[0] 49 | prefix = next(self.prefix_iterator) 50 | self.prefix_len = len(prefix) 51 | self.insn_buffer[: self.prefix_len] = prefix 52 | self.insn_length += self.prefix_len 53 | 54 | if self.insn_length > settings.insn_buf_size: 55 | raise Exception("Instruction length is too long, should increase INSN_BUF_SIZE") 56 | 57 | for i in range(len(insn)): 58 | self.insn_buffer[self.prefix_len + i] = insn[i] 59 | 60 | def prefix_prepender(self): 61 | if settings.min_prefix_count == 0: 62 | return 0 63 | 64 | try: 65 | prefix = next(self.prefix_iterator) 66 | 67 | if len(prefix) != self.prefix_len: 68 | new_length = self.insn_length + len(prefix) - self.prefix_len 69 | if new_length > settings.insn_buf_size: 70 | # no space to add this many prefixes 71 | # reset iterator for next instruction 72 | self.prefix_iterator = itertools.tee(self.prefix_list, 1)[0] 73 | return 0 74 | 75 | # shift the rest of the buffer to the right to make space 76 | tmp = self.insn_buffer[self.prefix_len : self.insn_length] 77 | for i in range(len(tmp) - settings.extra_byte): 78 | self.insn_buffer[len(prefix) + i] = tmp[i] 79 | 80 | self.insn_length = new_length 81 | 82 | for i in range(self.insn_length - settings.extra_byte, self.insn_length): 83 | self.insn_buffer[i] = 0 84 | 85 | self.prefix_len = len(prefix) 86 | for i in range(self.prefix_len): 87 | self.insn_buffer[i] = prefix[i] 88 | 89 | return 1 90 | except StopIteration: 91 | # reset iterator for next instruction 92 | self.prefix_iterator = itertools.tee(self.prefix_list, 1)[0] 93 | return 0 94 | 95 | def check_result(self, result): 96 | if settings.csv_log_diff_only == 0: 97 | result.final = FinalLogResult( 98 | exec_res=result, insn=self.view[: self.insn_length].hex(), len=self.insn_length 99 | ) 100 | return 101 | 102 | exit_type = self.row["exit-type"] 103 | reg_delta = self.row["reg-delta"] 104 | 105 | if result.type_str() != exit_type or result.reg_delta_str() != reg_delta: 106 | result.final = FinalLogResult( 107 | exec_res=result, insn=self.view[: self.insn_length].hex(), len=self.insn_length 108 | ) 109 | 110 | def gen(self) -> Generator[memoryview, FuzzerExecResult, None]: 111 | while True: 112 | result: FuzzerExecResult = yield self.current_insn 113 | if isinstance(result, Interrupted): 114 | continue 115 | 116 | # Log results of previous execution 117 | self.check_result(result) 118 | 119 | # Onto the next instruction 120 | if settings.extra_byte != 0 and self.insn_buffer[self.insn_length - 1] < 0xFF: 121 | self.insn_buffer[self.insn_length - 1] += 1 122 | continue 123 | 124 | if self.prefix_prepender() != 0: 125 | continue 126 | 127 | try: 128 | self.row = next(self.reader) 129 | insn = bytearray.fromhex(self.row["insn"]) 130 | self.insn_length = len(insn) 131 | 132 | for i in range(self.insn_length): 133 | self.insn_buffer[i] = insn[i] 134 | 135 | if settings.extra_byte != 0: 136 | for i in range(settings.extra_byte): 137 | self.insn_buffer[self.insn_length + i] = 0 138 | 139 | self.insn_length += settings.extra_byte 140 | 141 | self.prefix_len = 0 142 | self.prefix_prepender() 143 | 144 | except StopIteration: 145 | return 146 | -------------------------------------------------------------------------------- /docs/source/reference/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | VMSifter configuration can be updated via configuration files, environment variables and command line switches. 4 | 5 | The [Dynaconf](https://www.dynaconf.com/) framework is used behind the scenes for configuration management, so everything that you learn from Dynaconf’s documentation should also be applicable for VMSifter. 6 | 7 | ## Configuration sources and precedence 8 | 9 | All configuration files are using the [`TOML`](https://toml.io/en/) format. 10 | 11 | 1. Default configuration file [`vmsifter/config/settings.toml`](https://github.com/intel/vmsifter/blob/main/vmsifter/config/settings.toml) 12 | 2. Current directory `.secret.TOML` 13 | 3. Command line arguments 14 | 4. Configuration keys specified by environment variables prefixed by `VMSIFTER_`. (Ex: `VMSIFTER_DEBUG=TRUE`) 15 | 16 | ## Keys 17 | 18 | ### `jobs` 19 | 20 | How many cores should VMSifter use to parallelize its execution. 21 | 22 | Default: `1` 23 | 24 | ### `fuzzer_mode` 25 | 26 | Select which fuzzing mode to use. 27 | 28 | Choices: 29 | - `RANDOM`: run random instructions 30 | - `TUNNEL`: sandsifter-style black-box instruction discovery 31 | - `CSV`: run instruction(s) from CSV file 32 | - `DRIZZLER`: experimental 33 | 34 | Default: `TUNNEL` 35 | 36 | ### `refresh_frequency` 37 | 38 | Refresh rate for logging the fuzzing progress per `x` instructions. 39 | 40 | ### `insn_buf_size` 41 | 42 | Size of the instruction buffer. 43 | 44 | Default: `15` 45 | 46 | ### `min_prefix_count` 47 | 48 | Minimum amount of prefixes that should inserted by the fuzzer into the candidate instruction. 49 | 50 | Default: `0` 51 | 52 | ### `max_prefix_count` 53 | 54 | Maximum amount of prefixes that should be inserted by the fuzzer into the candidate instruction. 55 | 56 | Default: `0` 57 | 58 | ### `socket_name` 59 | 60 | Filename for the Unix socket used to communicate with the injector. 61 | 62 | Will be created inside the [workdir](#workdir). 63 | 64 | Default: `vmsifter_sock` 65 | 66 | ### `workdir` 67 | 68 | VMSifter's working directory containing runtime information and CSV files output. 69 | 70 | Default: `$PWD/workdir` 71 | 72 | ### `debug` 73 | 74 | Toggle debug output logging. 75 | 76 | ### `pdb` 77 | 78 | Toggle post-mortem debugging upon an unhandled exception by invoking Python PDB. 79 | 80 | ### `csv_log_diff_only` 81 | 82 | Compare the registers state and log only their differences between the last and the current execution result. 83 | 84 | Default: `0` 85 | 86 | ### `extra_byte` 87 | 88 | The number of extra byte(s) to add at the end of the generated instructions in the range `0x0 .. 0xff` sequentially. 89 | 90 | Default: `0` 91 | 92 | ### `smt` 93 | 94 | Whether _Simultaneous multithreading_ is enabled on the host. Used to exclude PCPU from the available pool allocation for injectors. 95 | 96 | :::{Note} 97 | Xen returns an "Invalid argument" when trying to pin an SMT PCPU to an injector VM. 98 | 99 | This is likely bug, so we must filter this in VMSifter. 100 | ::: 101 | 102 | Default: `false` 103 | 104 | ### `x86` 105 | 106 | #### `prefix` 107 | 108 | List of valid x86-32 prefixes to be used by the fuzzer. 109 | 110 | Default: 111 | 112 | ```TOML 113 | prefix = [ 114 | 0xF0, # lock 115 | 0xF2, # repne / bound 116 | 0xF3, # rep 117 | 0x2E, # cs / branch taken 118 | 0x36, # ss / branch not taken 119 | 0x3E, # ds 120 | 0x26, # es 121 | 0x64, # fs 122 | 0x65, # gs 123 | 0x66, # data 124 | 0x67, # addr 125 | ] 126 | ``` 127 | 128 | #### `prefix_64` 129 | 130 | List of valid x86-64 prefixes to be used by the fuzzer. 131 | 132 | Default: 133 | 134 | ```python 135 | [i for i in range(0x40, 0x50)] 136 | ``` 137 | 138 | #### `exec_mode` 139 | 140 | Desired x86 execution mode. 141 | 142 | Default: `32` 143 | 144 | ### `fuzzer` 145 | 146 | #### `drizzler` 147 | 148 | ##### `num_seeds` 149 | 150 | TODO 151 | 152 | Default: `1` 153 | 154 | ##### `injections` 155 | 156 | TODO 157 | 158 | Default: `300` 159 | 160 | ##### `aggressive` 161 | 162 | TODO 163 | 164 | Default: `false` 165 | 166 | ### `injector` 167 | 168 | #### `xenvm` 169 | 170 | ##### `injector_path` 171 | 172 | Path to the Xen injector. 173 | 174 | Default: 175 | 176 | ```python 177 | shutil.which('injector') 178 | ``` 179 | 180 | ##### `xtf_path` 181 | 182 | Path to [`XTF`](https://xenbits.xenproject.org/docs/xtf/) framework with VMsifter patches applied and compiled. 183 | 184 | ##### `perfcts` 185 | 186 | Chose the value to be set for the 4 configurable performance counters. These values are CPU specific and should be set accordingly. 187 | Performance counters are automatically configured to freeze counting when in VMM or SMM modes and no PMI is enabled. 188 | 189 | See `SDM Chapter 20 Performance Monitoring` for detailed documentation on performance counter settings and [`https://perfmon-events.intel.com`](https://perfmon-events.intel.com) 190 | 191 | For example, to select `UOPS_ISSUED.ANY` on a Ice Lake processor we can find `EventSel=0EH UMask=01H`, therefore we would set `0x43010e` for one of the performance counters to enable it in both OS and USR modes. 192 | 193 | ```TOML 194 | 24-31b: CMask 195 | 22b: Enable 196 | 17b: OS // monitor active in OS mode (ring0) 197 | 16b: USR // monitor active in USR mode (ring3) 198 | 8-15b: UMask 199 | 0-7b: EventSel 200 | ``` 201 | 202 | Default: `"0x043010e,0x04301c2,0x04307c1,0x44304a3"` 203 | 204 | ##### `sse` 205 | 206 | Toggle to enable SSE and AVX instructions. Specificially enable the following control register bits: 207 | 208 | ```TOML 209 | CR4: X86_CR4_OSFXSR | X86_CR4_OSXSAVE | X86_CR4_OSXMMEXCPT 210 | XCR0: XSTATE_SSE | XSTATE_YMM 211 | ``` 212 | 213 | Default: `true` 214 | 215 | ##### `syscall` 216 | 217 | Toggle to enable syscall instructions. 218 | 219 | Default: `true` 220 | 221 | ##### `fpu_emulation` 222 | 223 | Toggle to enable x87 FPU emulation. Specifically enable to set the following control register bit: 224 | 225 | ```TOML 226 | CR0: X86_CR0_EM 227 | ``` 228 | 229 | Default: `false` 230 | -------------------------------------------------------------------------------- /patches/0003-xen-x86-monitor-report-extra-vmexit-information.patch: -------------------------------------------------------------------------------- 1 | From 0a0747c3b7f669be11a5231d3ade214a4d0fe5b7 Mon Sep 17 00:00:00 2001 2 | Message-Id: <0a0747c3b7f669be11a5231d3ade214a4d0fe5b7.1702927048.git.tamas.lengyel@intel.com> 3 | From: Tamas K Lengyel 4 | Date: Mon, 18 Dec 2023 14:17:24 -0500 5 | Subject: [PATCH 3/8] x86/monitor: report extra vmexit information 6 | 7 | --- 8 | xen/arch/x86/hvm/monitor.c | 14 +++++++++---- 9 | xen/arch/x86/hvm/vmx/vmx.c | 29 +++++++++++++++++++++++--- 10 | xen/arch/x86/include/asm/hvm/monitor.h | 15 +++++++++++-- 11 | xen/include/public/vm_event.h | 7 +++++++ 12 | 4 files changed, 56 insertions(+), 9 deletions(-) 13 | 14 | diff --git a/xen/arch/x86/hvm/monitor.c b/xen/arch/x86/hvm/monitor.c 15 | index 4f500beaf5..58fef6c728 100644 16 | --- a/xen/arch/x86/hvm/monitor.c 17 | +++ b/xen/arch/x86/hvm/monitor.c 18 | @@ -328,8 +328,7 @@ bool hvm_monitor_check_p2m(unsigned long gla, gfn_t gfn, uint32_t pfec, 19 | return monitor_traps(curr, true, &req) >= 0; 20 | } 21 | 22 | -int hvm_monitor_vmexit(unsigned long exit_reason, 23 | - unsigned long exit_qualification) 24 | +int hvm_monitor_vmexit(struct vmexit_info *info) 25 | { 26 | struct vcpu *curr = current; 27 | struct arch_domain *ad = &curr->domain->arch; 28 | @@ -338,8 +337,15 @@ int hvm_monitor_vmexit(unsigned long exit_reason, 29 | ASSERT(ad->monitor.vmexit_enabled); 30 | 31 | req.reason = VM_EVENT_REASON_VMEXIT; 32 | - req.u.vmexit.arch.vmx.reason = exit_reason; 33 | - req.u.vmexit.arch.vmx.qualification = exit_qualification; 34 | + req.u.vmexit.arch.vmx.reason = info->exit_reason; 35 | + req.u.vmexit.arch.vmx.qualification = info->exit_qualification; 36 | + req.u.vmexit.arch.vmx.gla = info->guest_linear_address; 37 | + req.u.vmexit.arch.vmx.interruption_info = info->interruption_info; 38 | + req.u.vmexit.arch.vmx.interruption_error = info->interruption_error; 39 | + req.u.vmexit.arch.vmx.idt_vectoring_info = info->idt_vectoring_info; 40 | + req.u.vmexit.arch.vmx.idt_vectoring_error = info->idt_vectoring_error; 41 | + req.u.vmexit.arch.vmx.instruction_length = info->instruction_length; 42 | + req.u.vmexit.arch.vmx.instruction_info = info->instruction_info; 43 | 44 | set_npt_base(curr, &req); 45 | 46 | diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c 47 | index 5cc9a3876d..de2bcce022 100644 48 | --- a/xen/arch/x86/hvm/vmx/vmx.c 49 | +++ b/xen/arch/x86/hvm/vmx/vmx.c 50 | @@ -3048,10 +3048,31 @@ static int get_instruction_length(void) 51 | unsigned long len; 52 | 53 | __vmread(VM_EXIT_INSTRUCTION_LEN, &len); /* Safe: callers audited */ 54 | - BUG_ON((len < 1) || (len > MAX_INST_LEN)); 55 | + 56 | return len; 57 | } 58 | 59 | +static void get_exit_infos(struct vmexit_info *info) 60 | +{ 61 | + __vmread(EXIT_QUALIFICATION, &info->exit_qualification); 62 | + __vmread(VM_EXIT_INTR_INFO, &info->interruption_info); 63 | + __vmread(VM_EXIT_INTR_ERROR_CODE, &info->interruption_error); 64 | + __vmread(IDT_VECTORING_INFO, &info->idt_vectoring_info); 65 | + __vmread(IDT_VECTORING_ERROR_CODE, &info->idt_vectoring_error); 66 | + __vmread(VM_EXIT_INSTRUCTION_LEN, &info->instruction_length); 67 | + __vmread(VMX_INSTRUCTION_INFO, &info->instruction_info); 68 | + __vmread(GUEST_LINEAR_ADDRESS, &info->guest_linear_address); 69 | + 70 | + /* poison value */ 71 | + __vmwrite(VM_EXIT_INTR_INFO, 0xbeef); 72 | + __vmwrite(VM_EXIT_INTR_ERROR_CODE, 0xbeef); 73 | + __vmwrite(IDT_VECTORING_INFO, 0xbeef); 74 | + __vmwrite(IDT_VECTORING_ERROR_CODE, 0xbeef); 75 | + __vmwrite(VM_EXIT_INSTRUCTION_LEN, 0xbeef); 76 | + __vmwrite(VMX_INSTRUCTION_INFO, 0xbeef); 77 | + __vmwrite(GUEST_LINEAR_ADDRESS, 0xbeef); 78 | +} 79 | + 80 | void update_guest_eip(void) 81 | { 82 | struct cpu_user_regs *regs = guest_cpu_user_regs(); 83 | @@ -4141,9 +4162,11 @@ void vmx_vmexit_handler(struct cpu_user_regs *regs) 84 | if ( unlikely(currd->arch.monitor.vmexit_enabled) ) 85 | { 86 | int rc; 87 | + struct vmexit_info info = { .exit_reason = exit_reason }; 88 | 89 | - __vmread(EXIT_QUALIFICATION, &exit_qualification); 90 | - rc = hvm_monitor_vmexit(exit_reason, exit_qualification); 91 | + get_exit_infos(&info); 92 | + 93 | + rc = hvm_monitor_vmexit(&info); 94 | if ( rc < 0 ) 95 | goto exit_and_crash; 96 | if ( rc ) 97 | diff --git a/xen/arch/x86/include/asm/hvm/monitor.h b/xen/arch/x86/include/asm/hvm/monitor.h 98 | index 02021be47b..6af0d512cc 100644 99 | --- a/xen/arch/x86/include/asm/hvm/monitor.h 100 | +++ b/xen/arch/x86/include/asm/hvm/monitor.h 101 | @@ -17,6 +17,18 @@ enum hvm_monitor_debug_type 102 | HVM_MONITOR_DEBUG_EXCEPTION, 103 | }; 104 | 105 | +struct vmexit_info { 106 | + unsigned long exit_reason; 107 | + unsigned long exit_qualification; 108 | + unsigned long interruption_info; 109 | + unsigned long interruption_error; 110 | + unsigned long idt_vectoring_info; 111 | + unsigned long idt_vectoring_error; 112 | + unsigned long instruction_length; 113 | + unsigned long instruction_info; 114 | + unsigned long guest_linear_address; 115 | +}; 116 | + 117 | /* 118 | * Called for current VCPU on crX/MSR changes by guest. Bool return signals 119 | * whether emulation should be postponed. 120 | @@ -40,8 +52,7 @@ bool hvm_monitor_emul_unimplemented(void); 121 | 122 | bool hvm_monitor_check_p2m(unsigned long gla, gfn_t gfn, uint32_t pfec, 123 | uint16_t kind); 124 | -int hvm_monitor_vmexit(unsigned long exit_reason, 125 | - unsigned long exit_qualification); 126 | +int hvm_monitor_vmexit(struct vmexit_info *info); 127 | 128 | int hvm_monitor_io(unsigned int port, unsigned int bytes, 129 | bool in, bool str); 130 | diff --git a/xen/include/public/vm_event.h b/xen/include/public/vm_event.h 131 | index 3a86f0e208..4dc35d3bcf 100644 132 | --- a/xen/include/public/vm_event.h 133 | +++ b/xen/include/public/vm_event.h 134 | @@ -386,6 +386,13 @@ struct vm_event_vmexit { 135 | struct { 136 | uint64_t reason; 137 | uint64_t qualification; 138 | + uint64_t gla; 139 | + uint32_t interruption_info; 140 | + uint32_t interruption_error; 141 | + uint32_t idt_vectoring_info; 142 | + uint32_t idt_vectoring_error; 143 | + uint32_t instruction_length; 144 | + uint32_t instruction_info; 145 | } vmx; 146 | } arch; 147 | }; 148 | -- 149 | 2.34.1 150 | 151 | -------------------------------------------------------------------------------- /vmsifter/executor.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import logging 5 | import os 6 | import shutil 7 | import signal 8 | import socket 9 | import subprocess 10 | from collections import deque 11 | from concurrent.futures import Future, ProcessPoolExecutor, as_completed 12 | from contextlib import suppress 13 | from itertools import count 14 | from pathlib import Path 15 | from typing import Dict, List, Optional, Tuple, Type 16 | 17 | from attrs import asdict 18 | 19 | from vmsifter.config import dump_config, settings 20 | from vmsifter.fuzzer import get_selected_gen 21 | from vmsifter.fuzzer.types import AbstractInsnGenerator 22 | from vmsifter.injector import get_selected_injector 23 | from vmsifter.injector.types import AbstractInjector 24 | from vmsifter.utils import get_available_pcpus, pformat 25 | from vmsifter.utils.protected_manager import ProtectedContextManager 26 | from vmsifter.worker import Worker, WorkerStats 27 | 28 | 29 | class SifterExecutor(ProtectedContextManager): 30 | """Manages the lifecycle of the vmsifter XTF VM and C helper vmsifter program""" 31 | 32 | def __init__(self): 33 | super().__init__() 34 | self._logger = logging.getLogger(f"{self.__module__}.{self.__class__.__name__}") 35 | # ensure workdir exists 36 | self.workdir_path = Path(settings.workdir) 37 | self.workdir_path.mkdir(parents=True, exist_ok=True) 38 | # dump current config 39 | dump_config(self.workdir_path) 40 | # dump cpuinfo 41 | shutil.copyfile("/proc/cpuinfo", self.workdir_path / "cpuinfo") 42 | # dump dmidecode 43 | output = subprocess.check_output(["sudo", "dmidecode"]) 44 | with open(self.workdir_path / "dmidecode", "wb") as f: 45 | f.write(output) 46 | 47 | self._sock_path = self.workdir_path / settings.SOCKET_NAME 48 | 49 | self._client_id_count_gen = count() 50 | 51 | # list of available PCPUs to be allocated 52 | self._av_pcpu: deque = deque() 53 | 54 | # ProcessPool [Future] <-> [Client] 55 | self._fut_to_client: Dict[Future, Tuple[Worker, AbstractInjector]] = {} 56 | 57 | def _safe_enter(self): 58 | super()._safe_enter() 59 | 60 | # 1. create socket 61 | self._sock = self._ex.enter_context(socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)) 62 | 63 | # 2. bind socket 64 | self._logger.debug("Bind socket: %s", self._sock_path) 65 | 66 | with suppress(FileNotFoundError): 67 | os.unlink(self._sock_path) 68 | self._sock.bind(str(self._sock_path)) 69 | 70 | # 3. set socket listen state 71 | self._sock.listen(settings.jobs) 72 | 73 | # 4. set available CPUs 74 | av_pcpu_list = [x for x in get_available_pcpus()] 75 | max_pcpu = min(settings.jobs, len(av_pcpu_list)) 76 | self._logger.info("PCPU available: %s <=> Max jobs: %s => %s", len(av_pcpu_list), settings.jobs, max_pcpu) 77 | # shrink to min of either jobs of len(av_pcpu_list) 78 | self._av_pcpu = deque(av_pcpu_list, max_pcpu) 79 | 80 | def cancel_sigint(): 81 | """Ignore CTRL+C in the worker process.""" 82 | # https://stackoverflow.com/a/44869451/3017219 83 | signal.signal(signal.SIGINT, signal.SIG_IGN) 84 | 85 | self.pool_clients = self._ex.enter_context( 86 | ProcessPoolExecutor(max_workers=settings.jobs, initializer=cancel_sigint) 87 | ) 88 | 89 | # on CTRL-C, ensure we deallocate everything 90 | def ensure_deallocate(future_to_clients: Dict[Future, Tuple[Worker, AbstractInjector]]): 91 | """Ensure Worker and injector's __exit__ have been called""" 92 | for future, (worker, injector) in future_to_clients.items(): 93 | self._deallocate_client(worker, injector) 94 | 95 | self._ex.callback(ensure_deallocate, self._fut_to_client) 96 | 97 | # Show any exceptions raised in the Workers 98 | def show_future_exception(future_to_clients: Dict[Future, Tuple[Worker, AbstractInjector]]): 99 | for future, (worker, injector) in future_to_clients.items(): 100 | exc = future.exception() 101 | if exc is not None and exc is not KeyboardInterrupt: 102 | self._logger.exception("Worker %s failed", worker.id) 103 | 104 | self._ex.callback(show_future_exception, self._fut_to_client) 105 | 106 | return self 107 | 108 | def _allocate_pcpu(self, pcpu_id: int, sub_fuzzer: AbstractInsnGenerator, inj_cls: Type[AbstractInjector]): 109 | # create new injector 110 | injector = inj_cls(self._sock_path, pcpu_id) 111 | injector.__enter__() 112 | # accept injector client 113 | cli_sock, cli_addr = self._sock.accept() 114 | # create Worker 115 | cli_id = next(self._client_id_count_gen) 116 | cli = Worker(cli_id, sub_fuzzer) 117 | cli.__enter__() 118 | self._logger.info("Assign Worker ID %s => PCPU %s", cli_id, pcpu_id) 119 | future = self.pool_clients.submit(cli.handle_client, cli_sock, cli_addr) 120 | self._fut_to_client[future] = cli, injector 121 | 122 | def _deallocate_client(self, client: Worker, injector: AbstractInjector) -> int: 123 | """Deallocate a Client / Injector and return the PCPU ressource""" 124 | pcpu = injector.pinned_cpu 125 | client.__exit__(None, None, None) 126 | injector.__exit__(None, None, None) 127 | return pcpu 128 | 129 | def run(self, extra_params: Optional[List[str]]): 130 | # get injector cls 131 | inj_cls = get_selected_injector() 132 | # get fuzzer 133 | fuzz_cls = get_selected_gen() 134 | fuzzer = fuzz_cls(extra_params=extra_params) 135 | # initial partitioning over deque size 136 | fuzzer_parts_gen = fuzzer.partition(len(self._av_pcpu)) 137 | for next_pcpu, sub_fuzzer in zip(self._av_pcpu, fuzzer_parts_gen): 138 | self._allocate_pcpu(next_pcpu, sub_fuzzer, inj_cls) 139 | 140 | for future in as_completed(self._fut_to_client.keys()): 141 | client, injector = self._fut_to_client[future] 142 | self._logger.info("Worker %s fuzzing complete.", client.id) 143 | try: 144 | result: WorkerStats = future.result() 145 | except Exception: 146 | self._logger.exception(f"Worker {client.id} failed") 147 | else: 148 | # display stats 149 | self._logger.info("Worker %s Stats: %s", client.id, pformat(asdict(result))) 150 | finally: 151 | pcpu = self._deallocate_client(client, injector) 152 | # put it back in available queue 153 | self._av_pcpu.append(pcpu) 154 | # cleanup 155 | del self._fut_to_client[future] 156 | -------------------------------------------------------------------------------- /patches/0001-xtf-VMSifter-test-execution-VM.patch: -------------------------------------------------------------------------------- 1 | From c8ee7255efdfc5f8a0daed8a3260f6ab7c6de7ce Mon Sep 17 00:00:00 2001 2 | From: Tamas K Lengyel 3 | Date: Thu, 1 Jun 2023 17:18:07 +0000 4 | Subject: [PATCH] VMSifter test execution VM 5 | 6 | The XTF VM's forks will be used as the environment in which 7 | VMSifter will send instruction(s) to execute in. The parent VM 8 | during boot enables various system settings based on answers it 9 | receives from the VMSifter injector CPUID hook. It signals boot 10 | completion also with a magic CPUID. 11 | 12 | During active testing the VM will be forked and the actual execution 13 | of tests will be performed in the forks instead of the parent VM. 14 | 15 | Signed-off-by: Tamas K Lengyel 16 | --- 17 | arch/x86/include/arch/cpuid.h | 1 + 18 | arch/x86/include/arch/msr-index.h | 4 + 19 | tests/vmsifter/Makefile | 10 +++ 20 | tests/vmsifter/extra.cfg.in | 1 + 21 | tests/vmsifter/main.c | 124 ++++++++++++++++++++++++++++++ 22 | 5 files changed, 140 insertions(+) 23 | create mode 100644 tests/vmsifter/Makefile 24 | create mode 100644 tests/vmsifter/extra.cfg.in 25 | create mode 100644 tests/vmsifter/main.c 26 | 27 | diff --git a/arch/x86/include/arch/cpuid.h b/arch/x86/include/arch/cpuid.h 28 | index e6d6a8c..c3e55bc 100644 29 | --- a/arch/x86/include/arch/cpuid.h 30 | +++ b/arch/x86/include/arch/cpuid.h 31 | @@ -82,6 +82,7 @@ static inline bool cpu_has(unsigned int feature) 32 | #define cpu_has_avx cpu_has(X86_FEATURE_AVX) 33 | 34 | #define cpu_has_syscall cpu_has(X86_FEATURE_SYSCALL) 35 | +#define cpu_has_sep cpu_has(X86_FEATURE_SEP) 36 | #define cpu_has_nx cpu_has(X86_FEATURE_NX) 37 | #define cpu_has_page1gb cpu_has(X86_FEATURE_PAGE1GB) 38 | #define cpu_has_lm cpu_has(X86_FEATURE_LM) 39 | diff --git a/arch/x86/include/arch/msr-index.h b/arch/x86/include/arch/msr-index.h 40 | index 0495c3f..a7f0e4d 100644 41 | --- a/arch/x86/include/arch/msr-index.h 42 | +++ b/arch/x86/include/arch/msr-index.h 43 | @@ -23,6 +23,10 @@ 44 | #define MSR_INTEL_MISC_FEATURES_ENABLES 0x00000140 45 | #define MISC_FEATURES_CPUID_FAULTING (_AC(1, ULL) << 0) 46 | 47 | +#define MSR_SYSENTER_CS 0x00000174 48 | +#define MSR_SYSENTER_ESP 0x00000175 49 | +#define MSR_SYSENTER_EIP 0x00000176 50 | + 51 | #define MSR_PERFEVTSEL(n) (0x00000186 + (n)) 52 | 53 | #define MSR_MISC_ENABLE 0x000001a0 54 | diff --git a/tests/vmsifter/Makefile b/tests/vmsifter/Makefile 55 | new file mode 100644 56 | index 0000000..636d49f 57 | --- /dev/null 58 | +++ b/tests/vmsifter/Makefile 59 | @@ -0,0 +1,10 @@ 60 | +include $(ROOT)/build/common.mk 61 | + 62 | +NAME := vmsifter 63 | +CATEGORY := utility 64 | +TEST-ENVS := hvm32 hvm32pae hvm32pse hvm64 65 | +TEST-EXTRA-CFG := extra.cfg.in 66 | + 67 | +obj-perenv += main.o 68 | + 69 | +include $(ROOT)/build/gen.mk 70 | diff --git a/tests/vmsifter/extra.cfg.in b/tests/vmsifter/extra.cfg.in 71 | new file mode 100644 72 | index 0000000..c7629a1 73 | --- /dev/null 74 | +++ b/tests/vmsifter/extra.cfg.in 75 | @@ -0,0 +1 @@ 76 | +shadow_memory=128 77 | diff --git a/tests/vmsifter/main.c b/tests/vmsifter/main.c 78 | new file mode 100644 79 | index 0000000..352f5d7 80 | --- /dev/null 81 | +++ b/tests/vmsifter/main.c 82 | @@ -0,0 +1,124 @@ 83 | +/** 84 | + * @file tests/vmsifter/main.c 85 | + * @ref vmsifter Test VM to be used with VMSifter 86 | + * 87 | + * @page vmsfiter 88 | + * 89 | + */ 90 | +#include 91 | + 92 | +const char test_title[] = "vmsifter"; 93 | + 94 | +void test_main(void) 95 | +{ 96 | + unsigned int i; 97 | + uint32_t eax, ebx, ecx, edx; 98 | + uint32_t count; 99 | + 100 | + printk("Starting vmsifter injector\n"); 101 | + 102 | + // Turn on SSE and AVX instructions 103 | + cpuid_count(0x13371337, 2, &eax, &ebx, &ecx, &edx); 104 | + if ( eax ) 105 | + { 106 | + printk("Setting up SSE & AVX\n"); 107 | + write_cr4(read_cr4() | X86_CR4_OSFXSR | X86_CR4_OSXSAVE | X86_CR4_OSXMMEXCPT); 108 | + write_xcr0(read_xcr0() | XSTATE_SSE | XSTATE_YMM); 109 | + } 110 | + 111 | + // Enable syscalls 112 | + cpuid_count(0x13371337, 3, &eax, &ebx, &ecx, &edx); 113 | + if ( eax ) 114 | + { 115 | + printk("Setting up syscall\n"); 116 | + wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SCE); 117 | + 118 | + /* Lay out the GDT suitably for SYSCALL/SYSRET. */ 119 | + gdt[GDTE_AVAIL0] = gdt[__KERN_CS >> 3]; /* SYSCALL %cs/%ss selectors */ 120 | + gdt[GDTE_AVAIL1] = gdt[GDTE_DS32_DPL0]; 121 | + gdt[GDTE_AVAIL2] = gdt[GDTE_CS32_DPL3]; /* SYSRET %cs/%ss selectors */ 122 | + gdt[GDTE_AVAIL3] = gdt[GDTE_DS32_DPL3]; 123 | + gdt[GDTE_AVAIL4] = gdt[GDTE_CS64_DPL3]; 124 | + 125 | + wrmsr(MSR_STAR, (_u(0xbeef11) | 126 | + (((uint64_t)GDTE_AVAIL0 * 8 + 0) << 32) | 127 | + (((uint64_t)GDTE_AVAIL2 * 8 + 3) << 48))); 128 | + wrmsr(MSR_CSTAR, _u(0xbeef12)); 129 | + wrmsr(MSR_LSTAR, _u(0xbeef13)); 130 | + 131 | + // Enable sysenter 132 | + wrmsr(MSR_SYSENTER_CS, __KERN_CS); 133 | + wrmsr(MSR_SYSENTER_EIP, 0xbeef14); 134 | + } 135 | + 136 | + cpuid_count(0x13371337, 4, &eax, &ebx, &ecx, &edx); 137 | + if ( eax ) 138 | + { 139 | + printk("Setting up FPU Emulation\n"); 140 | + write_cr0(read_cr0() | X86_CR0_EM); 141 | + } 142 | + 143 | + // IA32_DEBUGCTL, freeze on SMM and pmi 144 | + wrmsr(0x1d9, 0x5000); 145 | + // IA32_PERF_GLOBAL_CTRL (Figure 18-3). 146 | + wrmsr(0x38f, 0x70000000f); 147 | + // IA32_FIXED_CTR_CTRL Figure (Figure 20-43). 148 | + // Enable all fixed counters with no PMI 149 | + wrmsr(0x38d, 0x333); 150 | + 151 | + cpuid_count(0x13371337, 1, &eax, &ebx, &ecx, &edx); 152 | + if ( eax ) 153 | + { 154 | + printk("Configuring perf counters 0x%x 0x%x 0x%x 0x%x\n", eax, ebx, ecx, edx); 155 | + 156 | + // SDM 18.2.1 157 | + // https://perfmon-events.intel.com 158 | + // 24-31b: CMASK 159 | + // 22b: Enable 160 | + // 17b: OS 161 | + // 16b: USR 162 | + // 8-15b: UMASK 163 | + // 0-7b: Event select 164 | + wrmsr(0x186, eax); 165 | + wrmsr(0x187, ebx); 166 | + wrmsr(0x188, ecx); 167 | + wrmsr(0x189, edx); 168 | + } 169 | + 170 | + printk("Issuing final CPUID\n"); 171 | + 172 | + cpuid_count(0x13371337, 0, &eax, &ebx, &ecx, &edx); 173 | + 174 | + // Execution will be hijacked from here when VMSifter is active. 175 | + for (i=0;i<10;i++) 176 | + eax += eax + i; 177 | + 178 | + count = rdmsr(0x309); 179 | + printk("Fixed perf counter: %u\n", count); 180 | + count = rdmsr(0x30a); 181 | + printk("Fixed perf counter: %u\n", count); 182 | + count = rdmsr(0x30b); 183 | + printk("Fixed perf counter: %u\n", count); 184 | + count = rdmsr(0xc1); 185 | + printk("Perf counter: %u\n", count); 186 | + count = rdmsr(0xc2); 187 | + printk("Perf counter: %u\n", count); 188 | + count = rdmsr(0xc3); 189 | + printk("Perf counter: %u\n", count); 190 | + count = rdmsr(0xc4); 191 | + printk("Perf counter: %u\n", count); 192 | + 193 | + printk("Dummy calculation done: %u\n", eax); 194 | + 195 | + xtf_success(NULL); 196 | +} 197 | + 198 | +/* 199 | + * Local variables: 200 | + * mode: C 201 | + * c-file-style: "BSD" 202 | + * c-basic-offset: 4 203 | + * tab-width: 4 204 | + * indent-tabs-mode: nil 205 | + * End: 206 | + */ 207 | -- 208 | 2.34.1 209 | 210 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | ARG BASEIMAGE=pypy:3.11-7-slim-bookworm 5 | ARG BUILD_ID="" 6 | FROM ${BASEIMAGE} AS vmsifter-deps 7 | LABEL build=${BUILD_ID} 8 | 9 | ENV DEBIAN_FRONTEND=noninteractive 10 | 11 | # update cache and install essential tools 12 | RUN --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ 13 | --mount=type=cache,target=/var/cache/apt,sharing=locked < /etc/sudoers.d/sudogrp 185 | # create group for developers 186 | groupadd dev 187 | # create first 500 users (avoid "I have no name" and "sudo: you do not exist in the passwd database") 188 | for i in $(seq 50); do 189 | useradd --create-home "runner${i}" --groups dev 190 | # create "$HOME/.sudo_as_admin_successful" to prevent annoying message from Ubuntu /etc/bash.bashrc 191 | # https://askubuntu.com/questions/813942/is-it-possible-to-stop-sudo-as-admin-successful-being-created 192 | touch "/home/runner${i}/.sudo_as_admin_successful" 193 | done 194 | EOF 195 | 196 | RUN mkdir /workdir && chmod o+w /workdir 197 | VOLUME ["/workdir"] 198 | 199 | # override xtf path since it has been copied in /code 200 | ENV VMSIFTER_INJECTOR.XENVM.XTF_PATH=/code/xtf 201 | ENV VMSIFTER_WORKDIR=/workdir 202 | RUN ldconfig && mkdir -p /var/run/xen 203 | # ignore docopt SyntaxWarning 204 | # TODO: reduce scope to docopt 205 | ENV PYTHONWARNINGS="ignore" 206 | 207 | FROM python-base AS vmsifter-dev 208 | 209 | # ensure permissions for all users 210 | RUN chown -R root:dev /code 211 | 212 | ENTRYPOINT [ "poetry", "run", "vmsifter" ] 213 | 214 | FROM python-base AS vmsifter-prod 215 | 216 | RUN < int: 30 | return int(self.nb_insn / self.total_seconds) 31 | 32 | 33 | class Worker(ProtectedContextManager): 34 | def __init__(self, id: int, fuzzer: AbstractInsnGenerator) -> None: 35 | super().__init__() 36 | self._id = id 37 | self._fuzzer = fuzzer 38 | 39 | # stats 40 | self._stats: Counter = Counter() 41 | self._exitstats: Counter = Counter() 42 | self._interruptstats: Counter = Counter() 43 | # workaround dynaconf perf issue 44 | self._cache_dyna_insn_buf_size = settings.insn_buf_size 45 | self._cache_dyna_refresh_frequency = settings.refresh_frequency 46 | 47 | def init_logger_worker(self): 48 | """Remove exiting stdout logging and log to a file""" 49 | self._logger = logging.getLogger() 50 | # remove existing stdout handler 51 | while self._logger.handlers: 52 | self._logger.handlers.pop() 53 | # add File handler 54 | self._workdir_path = Path(settings.workdir) 55 | file_handler = logging.FileHandler(self._workdir_path / f"worker_{self._id}.log") 56 | file_handler.setFormatter(settings.logging.format) 57 | self._logger.addHandler(file_handler) 58 | 59 | @property 60 | def id(self): 61 | return self._id 62 | 63 | @property 64 | def fuzzer(self): 65 | return self._fuzzer 66 | 67 | def _send_instruction(self, cli_sock, index, new_insn): 68 | """Send instruction to the injector""" 69 | if self._logger.isEnabledFor(logging.DEBUG): 70 | self._logger.debug("[%d]Sending buffer %s", index, new_insn.hex()) 71 | try: 72 | cli_sock.send(new_insn) 73 | except (BrokenPipeError, ConnectionResetError) as e: 74 | raise EOFError("Injector has closed the communication") from e 75 | 76 | def _recv_injector_result(self, cli_sock, index, view) -> InjectorResultMessage: 77 | try: 78 | num_bytes: int = cli_sock.recv_into(view[:]) 79 | if num_bytes == 0: 80 | raise EOFError("Injector has closed the communication") 81 | except ConnectionResetError as e: 82 | raise EOFError("Injector has closed the communication") from e 83 | cli_msg = InjectorResultMessage.from_buffer(view) 84 | # display received data 85 | if self._logger.isEnabledFor(logging.DEBUG): 86 | self._logger.debug("[%d]Recv msg %s", index, pformat(cli_msg.repr_recv())) 87 | return cli_msg 88 | 89 | def handle_client(self, cli_sock, cli_addr) -> WorkerStats: 90 | self.init_logger_worker() 91 | self._logger.debug("Injector connected: %s", cli_addr) 92 | with CSVOutput(self._id) as csvlog: 93 | self._logger.info("Fuzzing range: %s", self.fuzzer.str_fuzzing_range()) 94 | 95 | gen = self.fuzzer.gen() 96 | 97 | # wait for first message from injector 98 | cli_msg_bytes: bytearray = bytearray(InjectorResultMessage.size()) 99 | cli_msg_view = memoryview(cli_msg_bytes) 100 | cli_msg = self._recv_injector_result(cli_sock, 0, cli_msg_view) 101 | 102 | result = None 103 | # store error if any 104 | # since we want to always display Client statistics in the finally block 105 | # but returning from finally erases the exception 106 | error = None 107 | 108 | try: 109 | begin = datetime.now() 110 | cur_begin = begin 111 | for index in count(start=1): 112 | try: 113 | new_insn = gen.send(result) 114 | except StopIteration: 115 | if result: 116 | csvlog.log(result.final) # type: ignore[unreachable] 117 | break 118 | 119 | if len(new_insn) > self._cache_dyna_insn_buf_size: 120 | self._logger.debug( 121 | "[%d]Fuzzer generated instruction larger then our current limit, " 122 | "forgot to increase INSN_BUF_SIZE?", 123 | index, 124 | ) 125 | break 126 | 127 | # previous execution result has been processed by fuzzer 128 | # check for final and log 129 | if result: 130 | # mypy issue: https://github.com/python/mypy/issues/8721 131 | csvlog.log(result.final) # type: ignore[unreachable] 132 | # print current insn 133 | if not index % self._cache_dyna_refresh_frequency: 134 | cur_end = datetime.now() 135 | total_sec = (cur_end - cur_begin).total_seconds() 136 | cur_speed = int(self._cache_dyna_refresh_frequency / total_sec) 137 | self._logger.info("[%d]insn: %s | %s exec/sec", index, self.fuzzer, cur_speed) 138 | # update current 139 | cur_begin = datetime.now() 140 | 141 | try: 142 | # send new insn to injector 143 | self._send_instruction(cli_sock, index, new_insn) 144 | # get execution result 145 | cli_msg = self._recv_injector_result(cli_sock, index, cli_msg_view) 146 | except EOFError: 147 | self._logger.info("[%d]Injector has closed the communication", index) 148 | break 149 | 150 | result = FuzzerExecResult.factory_from_injector_message(cli_msg) 151 | # sanity check 152 | if result.rep_length is None: 153 | self._logger.info( 154 | "[%d]Impossible length recorded by CPU on VMEXIT for %s: %i", 155 | index, 156 | new_insn.hex(), 157 | cli_msg.insn_length, 158 | ) 159 | if self._logger.isEnabledFor(logging.DEBUG): 160 | self._logger.debug("[%d]FuzzerExecResult: %s", index, pformat(asdict(result))) 161 | # update exitstats 162 | if result.exit_reason == ExitReasonEnum.UNKNOWN: 163 | self._exitstats[f"unknown_exit_{cli_msg.reason}"] += 1 164 | else: 165 | self._exitstats[result.exit_reason] += 1 166 | # update stats 167 | self._stats[result.type_str()] += 1 168 | except Exception as e: 169 | error = e 170 | else: 171 | self._logger.info("Fuzzing complete.") 172 | finally: 173 | end = datetime.now() 174 | final_stats = WorkerStats( 175 | general=self._stats, 176 | exitstats=self._exitstats, 177 | interruptstats=self._interruptstats, 178 | nb_insn=index, 179 | total_seconds=(end - begin).total_seconds(), 180 | ) 181 | self._logger.info("VMEXIT Stats: %s", pformat(self._exitstats)) 182 | self._logger.info("Interrupt Stats: %s", pformat(self._interruptstats)) 183 | self._logger.info("Sifter Stats: %s", pformat(self._stats)) 184 | self._logger.info("Speed: %s insn/sec", final_stats.exec_speed) 185 | # return will omit raising the exception if any 186 | if error is not None: 187 | raise error 188 | return final_stats 189 | -------------------------------------------------------------------------------- /tests/unit/test_tunnel.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from itertools import chain 5 | 6 | import pytest 7 | 8 | from vmsifter.config import settings 9 | from vmsifter.fuzzer import EPT, FuzzerExecResult, Interrupted, Other, TunnelFuzzer 10 | from vmsifter.fuzzer.partition import X86Range, partition 11 | from vmsifter.injector import EPTQual, EPTQualEnum, ExitReasonEnum, InjectorResultMessage, InjInterruptEnum 12 | 13 | 14 | def test_tunnel_retry(): 15 | """The tunnel should send the same insn if retry is requested""" 16 | # arrange 17 | tun = TunnelFuzzer() 18 | gen = tun.gen() 19 | # act 20 | failed_insn = gen.send(None).tobytes() 21 | exec_res = Interrupted() 22 | exec_res.exit_reason = ExitReasonEnum.EXTERNAL_INTERRUPT 23 | next_insn = gen.send(exec_res).tobytes() 24 | # assert 25 | assert failed_insn == next_insn 26 | 27 | 28 | def test_default_tunnel_first_insn_is_one_byte_0x0(): 29 | """The default tunnel should return a one byte 0x0 as its first insn""" 30 | # arrange 31 | tun = TunnelFuzzer() 32 | gen = tun.gen() 33 | # act 34 | insn = gen.send(None).tobytes() 35 | # assert 36 | assert insn == b"\x00" 37 | 38 | 39 | def test_first_insn_is_same_as_init_value(): 40 | # arrange 41 | init_fuzzer = [0x04, 0x05, 0xFF, 0xA, 0x22] 42 | tun = TunnelFuzzer(bytearray(init_fuzzer)) 43 | gen = tun.gen() 44 | # act 45 | insn = gen.send(None).tobytes() 46 | # assert 47 | assert insn == bytes(init_fuzzer) 48 | 49 | 50 | def test_pagefault_next_insn(): 51 | """On a pagefault exec result, the next instruction should get one byte bigger""" 52 | # arrange 53 | tun = TunnelFuzzer() 54 | gen = tun.gen() 55 | prev_insn = gen.send(None).tobytes() 56 | exec_res = EPT() 57 | exec_res.eptqual = EPTQual(EPTQualEnum.EXECUTE) 58 | exec_res.exit_reason = ExitReasonEnum.EPT 59 | # act 60 | new_insn = gen.send(exec_res).tobytes() 61 | # assert 62 | assert len(new_insn) == len(prev_insn) + 1 63 | # last element should be set to 0 64 | assert new_insn[-1] == 0 65 | 66 | 67 | def test_same_length_increment_last_byte(): 68 | """on a valid exec result, if the length is the same, the last byte should be incremented""" 69 | # arrange 70 | tun = TunnelFuzzer() 71 | gen = tun.gen() 72 | prev_insn = gen.send(None).tobytes() 73 | exec_res = Other() 74 | exec_res.exit_reason = ExitReasonEnum.MTF 75 | # act 76 | new_insn = gen.send(exec_res).tobytes() 77 | # assert 78 | assert new_insn[-1] == prev_insn[-1] + 1 79 | 80 | 81 | def test_rollover_previous_byte(): 82 | """on a valid exec result, if the last byte was 0xFF, reset to 0 and move marker to previous byte""" 83 | # arrange 84 | tun = TunnelFuzzer(bytearray([0x04, 0xFF]), marker_idx=1) 85 | gen = tun.gen() 86 | prev_insn = gen.send(None).tobytes() 87 | exec_res = Other() 88 | exec_res.exit_reason = ExitReasonEnum.MTF 89 | # act 90 | new_insn = gen.send(exec_res).tobytes() 91 | # assert 92 | assert new_insn == bytes([0x05]) 93 | # marker should be on 0x04 byte 94 | assert tun.marker_idx == 0 95 | 96 | 97 | def test_rollover_multiple_bytes(): 98 | # arrange 99 | init_content = [0x04, 0x05, 0xFF, 0xFF, 0xFF] 100 | tun = TunnelFuzzer(bytearray(init_content), marker_idx=4) 101 | gen = tun.gen() 102 | prev_insn = gen.send(None).tobytes() 103 | exec_res = Other() 104 | exec_res.exit_reason = ExitReasonEnum.MTF 105 | # act 106 | new_insn = gen.send(exec_res).tobytes() 107 | # assert 108 | assert new_insn == bytes([0x04, 0x06]) 109 | assert tun.marker_idx == 1 110 | 111 | 112 | @pytest.mark.skip(reason="double check backward search") 113 | def test_marker_moved_new_instruction_length(): 114 | """on a valid exec result, if the length has changed, the marker should have moved to the 115 | end of the new instruction""" 116 | # arrange 117 | init_content = [0xC7, 0x04, 0x06] 118 | tun = TunnelFuzzer(bytearray(init_content), marker_idx=2) 119 | gen = tun.gen() 120 | gen.send(None).tobytes() 121 | exec_res = Other() 122 | exec_res.exit_reason = ExitReasonEnum.MTF 123 | exec_res.rep_length = 7 124 | # act 125 | new_insn = gen.send(exec_res).tobytes() 126 | # assert 127 | # marker should have moved, and last byte incremented 128 | assert list(new_insn) == [0xC7, 0x04, 0x06, 0x00, 0x00, 0x00, 0x01] 129 | 130 | 131 | def test_need_more_bytes_max_insn_size(): 132 | """on a pagefault exec result, if the tunnel requests more bytes than an x86 insn can contain, 133 | the marker byte should be increased and the length reset to the marker index""" 134 | # arrange 135 | init_content = [0x6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] 136 | tun = TunnelFuzzer(bytearray(init_content), marker_idx=0) 137 | gen = tun.gen() 138 | gen.send(None).tobytes() 139 | exec_res = EPT() 140 | exec_res.exit_reason = ExitReasonEnum.EPT 141 | exec_res.eptqual = EPTQual(EPTQualEnum.EXECUTE) 142 | # act 143 | new_insn = gen.send(exec_res).tobytes() 144 | # assert 145 | assert list(new_insn) == [0x7] 146 | 147 | 148 | @pytest.mark.parametrize("x86_range", list(chain(*[partition(i) for i in [1, 3, 8, 64]]))) 149 | def test_fuzzing_complete(x86_range: X86Range): 150 | """test that the tunnel generator stops""" 151 | # arrange 152 | init_content = [int.from_bytes(x86_range.end, byteorder="big")] 153 | tun = TunnelFuzzer(bytearray(init_content), marker_idx=0, end_first_byte=x86_range.end) 154 | gen = tun.gen() 155 | prev_insn = gen.send(None).tobytes() 156 | exec_res = Other() 157 | exec_res.exit_reason = ExitReasonEnum.APIC_ACCESS 158 | exec_res.rep_length = len(prev_insn) 159 | # act 160 | with pytest.raises(StopIteration): 161 | gen.send(exec_res) 162 | 163 | 164 | @pytest.mark.parametrize("mode", ["32", "64"]) 165 | def test_min_prefix(mode): 166 | # arrange 167 | settings.x86.exec_mode = mode 168 | settings.min_prefix_count = 1 169 | settings.max_prefix_count = 5 170 | settings.validators.validate() 171 | settings.prefix_range = range(settings.min_prefix_count, settings.max_prefix_count + 1) 172 | tun = TunnelFuzzer(marker_idx=settings.min_prefix_count + 1) 173 | gen = tun.gen() 174 | # act 175 | prev_insn = gen.send(None).tobytes() 176 | exec_res = Other() 177 | exec_res.exit_reason = ExitReasonEnum.MTF 178 | exec_res.rep_length = len(prev_insn) 179 | new_insn = gen.send(exec_res) 180 | # assert 181 | assert len([b for b in prev_insn if b in settings.x86.prefix]) >= settings.min_prefix_count 182 | assert len([b for b in prev_insn if b in settings.x86.prefix]) < settings.max_prefix_count 183 | 184 | 185 | def test_backward_search(): 186 | # arrange 187 | init_content = bytearray([0x00, 0xC0, 0x00, 0x00, 0x00, 0x00]) 188 | tun = TunnelFuzzer(insn_buffer=init_content, marker_idx=1) 189 | gen = tun.gen() 190 | # act 191 | gen.send(None) 192 | msg = InjectorResultMessage() 193 | msg.reason = 37 194 | msg.qualification = 0 195 | msg.insn_length = 2 196 | msg.rip = 0x1FFC 197 | exec_res = FuzzerExecResult.factory_from_injector_message(msg) 198 | insn_1 = gen.send(exec_res) 199 | msg.rip = 0x1FFD 200 | exec_res = FuzzerExecResult.factory_from_injector_message(msg) 201 | insn_2 = gen.send(exec_res) 202 | msg.rip = 0x1FFE 203 | exec_res = FuzzerExecResult.factory_from_injector_message(msg) 204 | insn_3 = gen.send(exec_res) 205 | msg.rip = 0x1FFF 206 | exec_res = FuzzerExecResult.factory_from_injector_message(msg) 207 | insn_4 = gen.send(exec_res) 208 | msg.rip = 0x2000 209 | exec_res = FuzzerExecResult.factory_from_injector_message(msg) 210 | # assert 211 | assert list(insn_1) == [0x00, 0xC0, 0x00, 0x00, 0x00] 212 | assert list(insn_2) == [0x00, 0xC0, 0x00, 0x00] 213 | assert list(insn_3) == [0x00, 0xC0, 0x00] 214 | assert list(insn_4) == [0x00, 0xC0] 215 | 216 | 217 | @pytest.mark.skip(reason="infinite recursion since prefix added") 218 | def test_bump_10_insn(): 219 | # arrange 220 | init_content = bytearray([0x00, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00]) 221 | tun = TunnelFuzzer(content=init_content, marker_idx=2) 222 | gen = tun.gen() 223 | # act 224 | # init with 2 exec similar behavior 225 | gen.send(None) 226 | msg = InjectorResultMessage() 227 | msg.reason = 48 228 | msg.qualification = 0x7AB 229 | # msg.qualification = 0x783 230 | msg.insn_length = 7 231 | msg.rip = 0x1FF9 232 | exec_res = FuzzerExecResult.factory_from_injector_message(msg) 233 | insn = gen.send(exec_res) 234 | assert list(insn) == [0x00, 0x04, 0x05, 0x00, 0x00, 0x00, 0x01] 235 | msg.qualification = 0x783 236 | exec_res = FuzzerExecResult.factory_from_injector_message(msg) 237 | insn = gen.send(exec_res) 238 | # now counter has started for 10 insn 239 | assert tun.counter == 1 240 | assert list(insn) == [0x00, 0x04, 0x05, 0x00, 0x00, 0x00, 0x02] 241 | last_byte = 0x2 242 | for _ in range(8): 243 | insn = gen.send(exec_res) 244 | last_byte += 1 245 | assert list(insn) == [0x00, 0x04, 0x05, 0x00, 0x00, 0x00, last_byte] 246 | 247 | # check first insn with bump 248 | first_insn_bump = list(gen.send(exec_res)) 249 | # continue for 9 insn 250 | last_byte = 0x10 251 | for _ in range(9): 252 | insn = gen.send(exec_res) 253 | last_byte += 0x10 254 | assert list(insn) == [0x00, 0x04, 0x05, 0x00, 0x00, 0x00, last_byte] 255 | # check last insn is final marker 0xFF 256 | final_insn_bump = list(gen.send(exec_res)) 257 | # assert 258 | assert list(first_insn_bump) == [0x00, 0x04, 0x05, 0x00, 0x00, 0x00, 0x10] 259 | assert list(final_insn_bump) == [0x00, 0x04, 0x05, 0x00, 0x00, 0x01] 260 | -------------------------------------------------------------------------------- /vmsifter/injector/types.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | import ctypes 5 | import logging 6 | from abc import abstractmethod 7 | from enum import Enum, IntFlag, auto 8 | from pathlib import Path 9 | from typing import Dict, Optional, Type 10 | 11 | from attr import define, field 12 | 13 | from vmsifter.config import InjectorType, settings 14 | from vmsifter.utils.protected_manager import ProtectedContextManager 15 | 16 | 17 | class AbstractInjector(ProtectedContextManager): 18 | @abstractmethod 19 | def __init__(self, socket_path: Path, pinned_cpu: int): 20 | """Initialize abstract injector 21 | 22 | Args: 23 | socket_path: path to Unix domain socket where the Executor will communicate 24 | new instructions and receive InjectorResultMessage from the injector 25 | """ 26 | super().__init__() 27 | self._logger: Optional[logging.Logger] = None 28 | self._socket_path: Path = socket_path 29 | self._pinned_cpu = pinned_cpu 30 | # return the injector dict nested under "injector.xxxx" 31 | self._inj_settings = settings.injector[self.get_type().name] 32 | 33 | @staticmethod 34 | @abstractmethod 35 | def get_type() -> InjectorType: 36 | pass 37 | 38 | @property 39 | def logger(self): 40 | """instantiate the logger on first use, which will be with the concrete class type""" 41 | if self._logger is None: 42 | self._logger = logging.getLogger(f"{self.__module__}.{self.__class__.__name__}[CPU: {self._pinned_cpu}]") 43 | return self._logger 44 | 45 | @property 46 | def pinned_cpu(self) -> int: 47 | return self._pinned_cpu 48 | 49 | 50 | class ExitReasonEnum(Enum): 51 | NMI = 0 52 | EXTERNAL_INTERRUPT = 1 53 | TRIPLE_FAULT = 2 54 | INIT_SIGNAL = 3 55 | SIPI = 4 56 | IO_SMI = 5 57 | OTHER_SMI = 6 58 | INTERRUPT_WINDOW = 7 59 | NMI_WINDOW = 8 60 | TASK_SWITCH = 9 61 | CPUID = 10 62 | GETSEC = 11 63 | HLT = 12 64 | INVD = 13 65 | INVLPG = 14 66 | RDPMC = 15 67 | RDTSC = 16 68 | RSM = 17 69 | VMCALL = 18 70 | VMCLEAR = 19 71 | VMLAUNCH = 20 72 | VMPTRLD = 21 73 | VMPTRST = 22 74 | VMREAD = 23 75 | VMRESUME = 24 76 | VMWRITE = 25 77 | VMXOFF = 26 78 | VMXON = 27 79 | CR_ACCESS = 28 80 | MOV_DR = 29 81 | IO_INSTRUCTION = 30 82 | RDMSR = 31 83 | WRMSR = 32 84 | VM_ENTRY_FAIL_INVALID_GUEST_STATE = 33 85 | VM_ENTRY_FAIL_MSR_LOADING = 34 86 | UNDEFINED35 = 35 87 | MWAIT = 36 88 | MTF = 37 89 | MONITOR = 39 90 | PAUSE = 40 91 | VM_ENTRY_FAIL_MACHINE_CHECK = 41 92 | UNDEFINED42 = 42 93 | TPR_BELOW_THRESHOLD = 43 94 | APIC_ACCESS = 44 95 | VIRTUALIZED_EOI = 45 96 | GDTR_IDTR = 46 97 | LDTR_TR = 47 98 | EPT = 48 99 | EPT_MISCONFIG = 49 100 | INVEPT = 50 101 | RDTSCP = 51 102 | PREEMPTION_TIMER = 52 103 | INVVPID = 53 104 | WBINVD_OR_WBNOINVD = 54 105 | XSETBV = 55 106 | APIC_WRITE = 56 107 | RDRAND = 57 108 | INVPCID = 58 109 | VMFUNC = 59 110 | ENCLS = 60 111 | RDSEED = 61 112 | PML_FULL = 62 113 | XSAVES = 63 114 | XSRSTORS = 64 115 | UNDEFINED65 = 65 116 | UNDEFINED66 = 66 117 | UMWAIT = 67 118 | TPAUSE = 68 119 | UNDEFINED69 = 69 120 | UNDEFINED70 = 70 121 | UNDEFINED71 = 71 122 | UNDEFINED72 = 72 123 | UNDEFINED73 = 73 124 | BUS_LOCK = 74 125 | NOTIFY = 75 126 | UNKNOWN = 1000 127 | 128 | 129 | class InjInterruptEnum(Enum): 130 | DIV_BY_ZERO = 0 131 | SINGLE_STEP_INTERRUPT = 1 132 | NMI = 2 133 | BREAKPOINT = 3 134 | OVERFLOW = 4 135 | BOUND_RANGE_EXCEEDED = 5 136 | INVALID_OPCODE = 6 137 | COPROCESSOR_UNAVAILABLE = 7 138 | DOUBLE_FAULT = 8 139 | COPROCESSOR_SEGMENT_OVERRUN = 9 140 | INVALID_TASK_SEGMENT = 0xA 141 | SEGMENT_NOT_PRESENT = 0xB 142 | STACK_SEGMENT_FAULT = 0xC 143 | GENERAL_PROTECTION_FAULT = 0xD 144 | PAGE_FAULT = 0xE 145 | RESERVED = 0xF 146 | FLOATING_POINT_EXCEPTION = 0x10 147 | ALIGNMENT_CHECK = 0x11 148 | MACHINE_CHECK = 0x12 149 | SIMD_FLOATING_POINT_EXCEPTION = 0x13 150 | VIRTUALIZATION_EXCEPTION = 0x14 151 | CONTROL_PROTECTION_EXCEPTION = 0x15 152 | UNDEFINED22 = 0x16 153 | UNDEFINED23 = 0x17 154 | UNDEFINED24 = 0x18 155 | UNDEFINED25 = 0x19 156 | 157 | 158 | # SDM 28.2.2 159 | class InjInterruptTypeEnum(Enum): 160 | INVALID = -1 161 | EXTERNAL = 0 162 | NMI = 2 163 | HW_EXC = 3 164 | PRIV_SW_EXC = 5 165 | SW_EXC = 6 166 | 167 | 168 | class PageFaultECEnum(IntFlag): 169 | PRESENT = 1 170 | READ_WRITE = 1 << 1 171 | USER_SUPERVISOR = 1 << 2 172 | RESERVED = 1 << 3 173 | EXECUTE = 1 << 4 174 | PROTECTION_KEY = 1 << 5 175 | SHADOW_STACK = 1 << 6 176 | SGX = 1 << 15 177 | 178 | 179 | class InjRIPLocationType(Enum): 180 | STACK = 0x1234 181 | ECX = 0xBEEF3 182 | EDX = 0xBEEF4 183 | SYSCALL = 0xBEEF11 184 | COMPAT_SYSCALL = 0xBEEF12 185 | LONG_SYSCALL = 0xBEEF13 186 | SYSENTER = 0xBEEF14 187 | 188 | 189 | # SDM 28.2.1 Table 28-7 190 | class EPTQualEnum(IntFlag): 191 | READ = 1 192 | WRITE = 1 << 1 193 | EXECUTE = 1 << 2 194 | READABLE = 1 << 3 195 | WRITABLE = 1 << 4 196 | EXECUTABLE = 1 << 5 197 | U_EXECUTABLE = 1 << 6 198 | GLA_VALID = 1 << 7 199 | GLA_TRANSLATE_ACCESS = 1 << 8 # translate if 0 200 | GLA_U = 1 << 9 201 | G_RW = 1 << 10 # r if 0 202 | G_X = 1 << 11 203 | NMI_UNBLOCKING = 1 << 12 204 | SHADOW_STACK = 1 << 13 205 | GP_VER = 1 << 15 206 | 207 | 208 | class RegistersEnum(Enum): 209 | RIP = 0 210 | RAX = auto() 211 | RBX = auto() 212 | RCX = auto() 213 | RDX = auto() 214 | RSI = auto() 215 | RDI = auto() 216 | RSP = auto() 217 | RBP = auto() 218 | R8 = auto() 219 | R9 = auto() 220 | R10 = auto() 221 | R11 = auto() 222 | R12 = auto() 223 | R13 = auto() 224 | R14 = auto() 225 | R15 = auto() 226 | CR2 = auto() 227 | 228 | 229 | NUMBER_OF_REGISTERS = len(RegistersEnum) 230 | 231 | 232 | @define(slots=True) 233 | class PageFaultEC: 234 | ec: int = field(converter=lambda value: value.value if isinstance(value, PageFaultECEnum) else value) 235 | 236 | @property 237 | def read_write(self): 238 | return self.ec & PageFaultECEnum.READ_WRITE.value 239 | 240 | @property 241 | def execute(self): 242 | return self.ec & PageFaultECEnum.EXECUTE.value 243 | 244 | @property 245 | def present(self): 246 | return self.ec & PageFaultECEnum.PRESENT.value 247 | 248 | @property 249 | def reserved(self): 250 | return self.ec & PageFaultECEnum.RESERVED.value 251 | 252 | @property 253 | def user_supervisor(self): 254 | return self.ec & PageFaultECEnum.USER_SUPERVISOR.value 255 | 256 | def __str__(self) -> str: 257 | s = f"{'w' if self.read_write else 'r'}" 258 | s += f"{'x' if self.execute else ''}" 259 | s += f"{'p' if self.present else ''}" 260 | s += f"{'RSVD' if self.reserved else ''}" 261 | return s 262 | 263 | def __repr__(self) -> str: 264 | return self.__str__() 265 | 266 | 267 | @define(slots=True) 268 | class EPTQual: 269 | qualification: int = field(converter=lambda value: value.value if isinstance(value, EPTQualEnum) else value) 270 | 271 | # don't compare the exact qualification flags, just the permission bits 272 | def __eq__(self, __value: object) -> bool: 273 | if not isinstance(__value, EPTQual): 274 | return NotImplemented 275 | return self.read == __value.read and self.write == __value.write and self.execute == __value.execute 276 | 277 | @property 278 | def read(self) -> bool: 279 | return bool(self.qualification & EPTQualEnum.READ.value) 280 | 281 | @property 282 | def write(self) -> bool: 283 | return bool(self.qualification & EPTQualEnum.WRITE.value) 284 | 285 | @property 286 | def execute(self) -> bool: 287 | return bool(self.qualification & EPTQualEnum.EXECUTE.value) 288 | 289 | @property 290 | def gla_valid(self) -> bool: 291 | return bool(self.qualification & EPTQualEnum.GLA_VALID.value) 292 | 293 | @property 294 | def gla_translate_access(self) -> bool: 295 | if self.gla_valid: 296 | return bool(self.qualification & EPTQualEnum.GLA_TRANSLATE_ACCESS.value) 297 | else: 298 | return False 299 | 300 | def __str__(self) -> str: 301 | s = f"{'r' if self.read else ''}" 302 | s += f"{'w' if self.write else ''}" 303 | s += f"{'x' if self.execute else ''}" 304 | s += f"{'PTW' if not self.gla_translate_access else ''}" 305 | return s 306 | 307 | def __repr__(self) -> str: 308 | return self.__str__() 309 | 310 | 311 | class InjectorResultMessage(ctypes.Structure): 312 | """Represents a data packet received from the injector C helper.""" 313 | 314 | @classmethod 315 | def size(cls: Type["InjectorResultMessage"]) -> int: 316 | return ctypes.sizeof(cls) 317 | 318 | _pack_ = 1 319 | _fields_ = [ 320 | ("reason", ctypes.c_uint64), 321 | ("qualification", ctypes.c_uint64), 322 | ("stack_value", ctypes.c_uint64), 323 | ("perfct", ctypes.c_uint64 * 7), 324 | ("regs", ctypes.c_uint64 * NUMBER_OF_REGISTERS), 325 | ("gla", ctypes.c_uint64), 326 | ("intr_info", ctypes.c_uint32), 327 | ("intr_error", ctypes.c_uint32), 328 | ("vec_info", ctypes.c_uint32), 329 | ("vec_error", ctypes.c_uint32), 330 | ("insn_length", ctypes.c_uint32), 331 | ("insn_info", ctypes.c_uint32), 332 | ] 333 | 334 | def repr_recv(self) -> Dict: 335 | return { 336 | "reason": self.reason, 337 | "qualification": hex(self.qualification), 338 | "rip": hex(self.regs[RegistersEnum.RIP.value]), 339 | "gla": hex(self.gla), 340 | "cr2": hex(self.regs[RegistersEnum.CR2.value]), 341 | "stack": hex(self.stack_value), 342 | "perfct0": self.perfct[0], 343 | "perfct1": self.perfct[1], 344 | "perfct2": self.perfct[2], 345 | "perfct3": self.perfct[3], 346 | "perfct4": self.perfct[4], 347 | "perfct5": self.perfct[5], 348 | "perfct6": self.perfct[6], 349 | "intr_info": self.intr_info, 350 | "intr_error": self.intr_error, 351 | "vec_info": self.vec_info, 352 | "vec_error": self.vec_error, 353 | "insn_length": self.insn_length, 354 | "insn_info": self.insn_info, 355 | } 356 | 357 | def tobytes(self) -> bytes: 358 | return ctypes.string_at(ctypes.byref(self), ctypes.sizeof(self)) 359 | -------------------------------------------------------------------------------- /vmsifter/fuzzer/tunnel.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Intel Corporation 2 | # SPDX-License-Identifier: MIT 3 | 4 | from contextlib import suppress 5 | from typing import Generator 6 | 7 | from attrs import define, field 8 | 9 | from vmsifter.config import settings 10 | from vmsifter.fuzzer.partition import X86Range 11 | from vmsifter.fuzzer.partition import partition as utils_partition 12 | from vmsifter.fuzzer.types import EPT, NMI, AbstractInsnGenerator, FinalLogResult, FuzzerExecResult, Interrupted 13 | from vmsifter.injector.types import InjInterruptEnum 14 | from vmsifter.utils.completion_rate import ByteRangeCompletion 15 | 16 | 17 | @define(slots=True, auto_attribs=True, auto_detect=True) 18 | class TunnelFuzzer(AbstractInsnGenerator): 19 | """Implements the tunnel algorithm to search the x86 instruction space for potential instructions""" 20 | 21 | marker_idx: int = 0 22 | end_first_byte: bytes = field(default=settings.x86.max_end_first_byte) 23 | 24 | # Rest of attributes excluded from init 25 | # keep value converted as int when we compare current memoryview[0] (which is an int) 26 | end_first_byte_int: int = field(init=False) 27 | previous_length: int = field(init=False) 28 | # We split each byte where the marker is to 4-bit parts that 29 | # correspond to a column when printed as hex. These are the "tunnels". 30 | split_byte_left: int = field(init=False, default=0) 31 | split_byte_right: int = field(init=False, default=0) 32 | # When the marker is in the middle of an instruction we need to verify 33 | # that we have the shortest version of a valid instruction. If we have 34 | # reason to doubt we have the shortest version, we switch to backwards 35 | # search, shortening the instruction until we get an EPT-x fault. 36 | backwards_search: bool = field(init=False, default=False) 37 | type: FuzzerExecResult = field(init=False, default=None) 38 | last_complete_insn: memoryview = field(init=False, default=None) 39 | last_complete_type: FuzzerExecResult = field(init=False, default=None) 40 | # workaround dynaconf perf bug 41 | # retrieve values here and keep them 42 | cache_dyna_completion_rate_precision: int = field(init=False, default=settings.completion_rate_precision) 43 | byterange_completion: ByteRangeCompletion = field(init=False, default=None) 44 | # counters to skip ahead 45 | counter: int = field(init=False, default=0) 46 | counter2: int = field(init=False, default=0) 47 | 48 | def _check_end_first_byte(self, value): 49 | # make sure that the byte is >= than the content's first byte 50 | if not value >= self.insn_buffer[0].to_bytes(length=1, byteorder="big"): 51 | raise ValueError(f"Unexpected content[0] (f{self.insn_buffer[0]}) >= end_first_byte ({value})") 52 | 53 | def __attrs_post_init__(self): 54 | super().__attrs_post_init__() 55 | # validators 56 | self._check_end_first_byte(self.end_first_byte) 57 | # additional inits 58 | self.end_first_byte_int = int.from_bytes(self.end_first_byte, byteorder="big") 59 | self.previous_length = self.insn_length 60 | # completion 61 | x86range = X86Range(bytes(self.insn_buffer), self.end_first_byte) 62 | self.byterange_completion = ByteRangeCompletion.from_x86_range(x86range) 63 | 64 | def _need_more_bytes(self): 65 | # increase length 66 | self.insn_length += 1 67 | if self.insn_length > self.cache_dyna_insn_buf_size: 68 | self._increment_last_byte() 69 | # reset length to marker idx 70 | self.insn_length = self.marker_idx + 1 71 | else: 72 | # ensure new byte is zeroed 73 | self.insn_buffer[self.insn_length - 1] = 0 74 | 75 | def _update_marker(self, new_length: int, type: FuzzerExecResult): 76 | assert new_length <= self.cache_dyna_insn_buf_size and new_length > 0 77 | is_invalid: bool = isinstance(type, NMI) and type.interrupt == InjInterruptEnum.INVALID_OPCODE 78 | if (not is_invalid and new_length != self.previous_length) or type != self.type: 79 | # move marker to the end of the new instruction 80 | self.marker_idx = new_length - 1 81 | # update previous 82 | self.previous_length = self.insn_length 83 | # and current length 84 | self.insn_length = new_length 85 | # and current type 86 | self.type = type 87 | 88 | self.counter = 0 89 | self.counter2 = 0 90 | else: 91 | # We'll skip ahead 4-bits after 0xA instructions that look the same 92 | self.split_byte_right += 1 93 | if self.split_byte_right >= 0xA: 94 | if self.split_byte_left >= 0xA: 95 | self.view[self.marker_idx] = 0xFF 96 | else: 97 | self.split_byte_left += 1 98 | leftbits = self.view[self.marker_idx] >> 4 99 | self.view[self.marker_idx] = (leftbits << 4) + 0xF 100 | 101 | def _increment_last_byte(self): 102 | if self.view[0] >= self.end_first_byte_int: 103 | raise StopIteration 104 | if self.view[self.marker_idx] == 0xFF: 105 | # roll over 106 | self.view[self.marker_idx] = 0 107 | self.split_byte_left = 0 108 | self.split_byte_right = 0 109 | # and move to the previous byte 110 | self.marker_idx -= 1 111 | # reduce length to new byte position 112 | self.insn_length = self.marker_idx + 1 113 | # recursive call, need to increment this new marker byte 114 | return self._increment_last_byte() 115 | else: 116 | # increment marker byte 117 | # print("Tunnel: increment byte at marker", self.marker_idx) 118 | self.view[self.marker_idx] += 1 119 | 120 | # check prefix count 121 | prefix_count = 0 122 | for i in range(self.marker_idx): 123 | if self.view[i] in self.cache_dyna_mode_prefix: 124 | prefix_count += 1 125 | else: 126 | break 127 | 128 | if prefix_count not in self.cache_dyna_prefix_range: 129 | # print("Prefix count not in range: ", prefix_count) 130 | return self._increment_last_byte() 131 | 132 | def _check_if_need_shorter_retry(self, result: FuzzerExecResult) -> int: 133 | insn = self.current_insn 134 | # TODO double check: msg.insn_length == 0 -> result.rep_length is None 135 | if ( 136 | len(insn) > 1 137 | and insn[len(insn) - 1] == 0x0 138 | and (result.rep_length is None or len(insn) != result.rep_length) 139 | ): 140 | self.logger.debug("Switching to backwards search") 141 | self.backwards_search = True 142 | # retry length 143 | length = len(insn) - 1 144 | else: 145 | self.logger.debug("No backwards search needed, log results for %s", insn.hex()) 146 | self.backwards_search = False 147 | 148 | result.final = FinalLogResult(exec_res=result, insn=insn.hex(), len=len(insn)) 149 | length = len(insn) 150 | 151 | self.last_complete_insn = insn 152 | self.last_complete_type = result 153 | return length 154 | 155 | def gen(self) -> Generator[memoryview, FuzzerExecResult, None]: 156 | # suppress: catch _increment_last_byte StopIteration 157 | # and just return 158 | with suppress(StopIteration): 159 | while True: 160 | result: FuzzerExecResult = yield self.current_insn 161 | if isinstance(result, Interrupted): 162 | # external interrupt, retry 163 | continue 164 | elif isinstance(result, EPT) and result.eptqual.execute: 165 | # pagefault 166 | if self.backwards_search: 167 | # we were searching backwards until we have X fault 168 | result.final = FinalLogResult( 169 | exec_res=self.last_complete_type, 170 | insn=self.last_complete_insn.hex(), 171 | len=len(self.last_complete_insn), 172 | ) 173 | self.logger.debug( 174 | "verified length of complete instruction: %s %s %i", 175 | self.last_complete_type, 176 | self.last_complete_insn.hex(), 177 | len(self.last_complete_insn), 178 | ) 179 | # resume sifting from last complete insn 180 | self.insn_buffer[: len(self.last_complete_insn)] = self.last_complete_insn 181 | self.insn_length = len(self.last_complete_insn) 182 | self.backwards_search = False 183 | 184 | self._update_marker(self.insn_length, self.last_complete_type) 185 | self._increment_last_byte() 186 | continue 187 | else: 188 | # need more bytes 189 | self._need_more_bytes() 190 | else: 191 | # valid instruction, complete execution 192 | length = self._check_if_need_shorter_retry(result) 193 | if self.backwards_search: 194 | # retry length, skip update marker and increment 195 | self.insn_length = length 196 | continue 197 | self._update_marker(length, result) 198 | 199 | self._increment_last_byte() 200 | # check here if the fuzzing is complete / out of range 201 | # note: no strict equality check because we skip ahead a few bytes sometimes (see _update_marker) 202 | if self.view[0] >= self.end_first_byte_int: 203 | # complete ! 204 | return 205 | 206 | def __str__(self): 207 | # pretty display next buffer 208 | view_hex_str = self.current_insn.hex(" ") 209 | # highlight marker 210 | # [00 01 02 [04] 05] 211 | stop = self.marker_idx * 2 + self.marker_idx 212 | filler_size: int = ( 213 | (self.cache_dyna_insn_buf_size * 2 + (self.cache_dyna_insn_buf_size - 1)) - len(view_hex_str) + 2 214 | ) 215 | marker_str = ( 216 | f"{view_hex_str[:stop]}[{view_hex_str[stop:stop+2]}]{view_hex_str[stop+2:]}{' ':{filler_size}}" 217 | f" | {self.byterange_completion.completion_rate(self.view):.{self.cache_dyna_completion_rate_precision}f}%" 218 | ) 219 | return marker_str 220 | 221 | def partition(self, nb_parts: int) -> Generator[AbstractInsnGenerator, None, None]: 222 | # split own range into nb_parts 223 | x86_range = X86Range(start=bytes(self.insn_buffer), end=self.end_first_byte) 224 | for sub_x86_range in utils_partition(nb_parts, x86_range): 225 | yield TunnelFuzzer(insn_buffer=bytearray(sub_x86_range.start), end_first_byte=sub_x86_range.end) 226 | 227 | def str_fuzzing_range(self) -> str: 228 | return f"[0x{self.current_insn.hex()}-0x{self.end_first_byte.hex()}]" 229 | -------------------------------------------------------------------------------- /patches/0001-xen-x86-Make-XEN_DOMCTL_get_vcpu_msrs-more-configura.patch: -------------------------------------------------------------------------------- 1 | From 52e8608e646cb6e14a679d351203b344d635bfd3 Mon Sep 17 00:00:00 2001 2 | Message-Id: <52e8608e646cb6e14a679d351203b344d635bfd3.1701823233.git.tamas.lengyel@intel.com> 3 | From: Tamas K Lengyel 4 | Date: Mon, 23 Oct 2023 11:53:51 -0400 5 | Subject: [PATCH 1/8] xen/x86: Make XEN_DOMCTL_get_vcpu_msrs more configurable 6 | 7 | Currently the XEN_DOMCTL_get_vcpu_msrs is only capable of gathering a handful 8 | of predetermined vcpu MSRs. In our use-case gathering the vPMU MSRs by an 9 | external privileged tool is necessary, thus we extend the domctl to allow for 10 | querying for any guest MSRs. To remain compatible with the existing setup if 11 | no specific MSR is requested via the domctl the default list is returned. 12 | 13 | Signed-off-by: Tamas K Lengyel 14 | --- 15 | tools/include/xenctrl.h | 4 +++ 16 | tools/libs/ctrl/xc_domain.c | 35 ++++++++++++++++++++++++++ 17 | tools/libs/guest/xg_sr_save_x86_pv.c | 2 ++ 18 | xen/arch/x86/cpu/vpmu.c | 10 ++++++++ 19 | xen/arch/x86/cpu/vpmu_amd.c | 7 ++++++ 20 | xen/arch/x86/cpu/vpmu_intel.c | 37 ++++++++++++++++++++++++++++ 21 | xen/arch/x86/domctl.c | 35 +++++++++++++++++--------- 22 | xen/arch/x86/include/asm/vpmu.h | 2 ++ 23 | 8 files changed, 120 insertions(+), 12 deletions(-) 24 | 25 | diff --git a/tools/include/xenctrl.h b/tools/include/xenctrl.h 26 | index 2ef8b4e054..4950afc867 100644 27 | --- a/tools/include/xenctrl.h 28 | +++ b/tools/include/xenctrl.h 29 | @@ -858,6 +858,10 @@ int xc_vcpu_getinfo(xc_interface *xch, 30 | uint32_t vcpu, 31 | xc_vcpuinfo_t *info); 32 | 33 | +typedef struct xen_domctl_vcpu_msr xc_vcpumsr_t; 34 | +int xc_vcpu_get_msrs(xc_interface *xch, uint32_t domid, uint32_t vcpu, 35 | + uint32_t count, xc_vcpumsr_t *msrs); 36 | + 37 | long long xc_domain_get_cpu_usage(xc_interface *xch, 38 | uint32_t domid, 39 | int vcpu); 40 | diff --git a/tools/libs/ctrl/xc_domain.c b/tools/libs/ctrl/xc_domain.c 41 | index 724fa6f753..4246eab8ac 100644 42 | --- a/tools/libs/ctrl/xc_domain.c 43 | +++ b/tools/libs/ctrl/xc_domain.c 44 | @@ -2180,6 +2180,41 @@ int xc_domain_soft_reset(xc_interface *xch, 45 | domctl.domain = domid; 46 | return do_domctl(xch, &domctl); 47 | } 48 | + 49 | +int xc_vcpu_get_msrs(xc_interface *xch, uint32_t domid, uint32_t vcpu, 50 | + uint32_t count, xc_vcpumsr_t *msrs) 51 | +{ 52 | + int rc; 53 | + struct xen_domctl domctl = {}; 54 | + domctl.cmd = XEN_DOMCTL_get_vcpu_msrs; 55 | + domctl.domain = domid; 56 | + domctl.u.vcpu_msrs.vcpu = vcpu; 57 | + domctl.u.vcpu_msrs.msr_count = count; 58 | + 59 | + if ( !msrs ) 60 | + { 61 | + if ( (rc = xc_domctl(xch, &domctl)) < 0 ) 62 | + return rc; 63 | + 64 | + return domctl.u.vcpu_msrs.msr_count; 65 | + } 66 | + else 67 | + { 68 | + DECLARE_HYPERCALL_BOUNCE(msrs, count * sizeof(xc_vcpumsr_t), XC_HYPERCALL_BUFFER_BOUNCE_BOTH); 69 | + 70 | + if ( xc_hypercall_bounce_pre(xch, msrs) ) 71 | + return -1; 72 | + 73 | + set_xen_guest_handle(domctl.u.vcpu_msrs.msrs, msrs); 74 | + 75 | + rc = do_domctl(xch, &domctl); 76 | + 77 | + xc_hypercall_bounce_post(xch, msrs); 78 | + 79 | + return rc; 80 | + } 81 | +} 82 | + 83 | /* 84 | * Local variables: 85 | * mode: C 86 | diff --git a/tools/libs/guest/xg_sr_save_x86_pv.c b/tools/libs/guest/xg_sr_save_x86_pv.c 87 | index f3d7a7a71a..1da4ec7cbc 100644 88 | --- a/tools/libs/guest/xg_sr_save_x86_pv.c 89 | +++ b/tools/libs/guest/xg_sr_save_x86_pv.c 90 | @@ -719,6 +719,8 @@ static int write_one_vcpu_msrs(struct xc_sr_context *ctx, uint32_t id) 91 | goto err; 92 | } 93 | 94 | + memset(buffer, 0, buffersz); 95 | + 96 | set_xen_guest_handle(domctl.u.vcpu_msrs.msrs, buffer); 97 | if ( xc_domctl(xch, &domctl) < 0 ) 98 | { 99 | diff --git a/xen/arch/x86/cpu/vpmu.c b/xen/arch/x86/cpu/vpmu.c 100 | index a022126f18..8c30e33e70 100644 101 | --- a/xen/arch/x86/cpu/vpmu.c 102 | +++ b/xen/arch/x86/cpu/vpmu.c 103 | @@ -634,6 +634,16 @@ void vpmu_dump(struct vcpu *v) 104 | alternative_vcall(vpmu_ops.arch_vpmu_dump, v); 105 | } 106 | 107 | +int vpmu_get_msr(struct vcpu *v, unsigned int msr, uint64_t *val) 108 | +{ 109 | + ASSERT(v != current); 110 | + 111 | + if ( !vpmu_is_set(vcpu_vpmu(v), VPMU_CONTEXT_ALLOCATED) ) 112 | + return -EOPNOTSUPP; 113 | + 114 | + return alternative_call(vpmu_ops.get_msr, v, msr, val); 115 | +} 116 | + 117 | long do_xenpmu_op( 118 | unsigned int op, XEN_GUEST_HANDLE_PARAM(xen_pmu_params_t) arg) 119 | { 120 | diff --git a/xen/arch/x86/cpu/vpmu_amd.c b/xen/arch/x86/cpu/vpmu_amd.c 121 | index 18266b9521..3f2554102b 100644 122 | --- a/xen/arch/x86/cpu/vpmu_amd.c 123 | +++ b/xen/arch/x86/cpu/vpmu_amd.c 124 | @@ -507,6 +507,12 @@ static int cf_check amd_vpmu_initialise(struct vcpu *v) 125 | return 0; 126 | } 127 | 128 | +static int cf_check amd_get_msr(struct vcpu *v, unsigned int msr, uint64_t *val) 129 | +{ 130 | + /* TODO in case an external tool needs access to these MSRs */ 131 | + return -ENOSYS; 132 | +} 133 | + 134 | #ifdef CONFIG_MEM_SHARING 135 | static int cf_check amd_allocate_context(struct vcpu *v) 136 | { 137 | @@ -524,6 +530,7 @@ static const struct arch_vpmu_ops __initconst_cf_clobber amd_vpmu_ops = { 138 | .arch_vpmu_save = amd_vpmu_save, 139 | .arch_vpmu_load = amd_vpmu_load, 140 | .arch_vpmu_dump = amd_vpmu_dump, 141 | + .get_msr = amd_get_msr, 142 | 143 | #ifdef CONFIG_MEM_SHARING 144 | .allocate_context = amd_allocate_context, 145 | diff --git a/xen/arch/x86/cpu/vpmu_intel.c b/xen/arch/x86/cpu/vpmu_intel.c 146 | index fa5b40c65c..1611179a71 100644 147 | --- a/xen/arch/x86/cpu/vpmu_intel.c 148 | +++ b/xen/arch/x86/cpu/vpmu_intel.c 149 | @@ -878,6 +878,42 @@ static int cf_check core2_vpmu_initialise(struct vcpu *v) 150 | return 0; 151 | } 152 | 153 | +static int cf_check core2_vpmu_get_msr(struct vcpu *v, unsigned int msr, 154 | + uint64_t *val) 155 | +{ 156 | + int type, index, ret = 0; 157 | + struct vpmu_struct *vpmu = vcpu_vpmu(v); 158 | + struct xen_pmu_intel_ctxt *core2_vpmu_cxt = vpmu->context; 159 | + uint64_t *fixed_counters = vpmu_reg_pointer(core2_vpmu_cxt, fixed_counters); 160 | + struct xen_pmu_cntr_pair *xen_pmu_cntr_pair = 161 | + vpmu_reg_pointer(core2_vpmu_cxt, arch_counters); 162 | + 163 | + if ( !is_core2_vpmu_msr(msr, &type, &index) ) 164 | + return -EINVAL; 165 | + 166 | + vcpu_pause(v); 167 | + 168 | + if ( msr == MSR_CORE_PERF_GLOBAL_OVF_CTRL ) 169 | + *val = core2_vpmu_cxt->global_ovf_ctrl; 170 | + else if ( msr == MSR_CORE_PERF_GLOBAL_STATUS ) 171 | + *val = core2_vpmu_cxt->global_status; 172 | + else if ( msr == MSR_CORE_PERF_GLOBAL_CTRL ) 173 | + *val = core2_vpmu_cxt->global_ctrl; 174 | + else if ( msr >= MSR_CORE_PERF_FIXED_CTR0 && 175 | + msr < MSR_CORE_PERF_FIXED_CTR0 + fixed_pmc_cnt ) 176 | + *val = fixed_counters[msr - MSR_CORE_PERF_FIXED_CTR0]; 177 | + else if ( msr >= MSR_P6_PERFCTR(0) && msr < MSR_P6_PERFCTR(arch_pmc_cnt) ) 178 | + *val = xen_pmu_cntr_pair[msr - MSR_P6_PERFCTR(0)].counter; 179 | + else if ( msr >= MSR_P6_EVNTSEL(0) && msr < MSR_P6_EVNTSEL(arch_pmc_cnt) ) 180 | + *val = xen_pmu_cntr_pair[msr - MSR_P6_EVNTSEL(0)].control; 181 | + else 182 | + ret = -EINVAL; 183 | + 184 | + vcpu_unpause(v); 185 | + 186 | + return ret; 187 | +} 188 | + 189 | static const struct arch_vpmu_ops __initconst_cf_clobber core2_vpmu_ops = { 190 | .initialise = core2_vpmu_initialise, 191 | .do_wrmsr = core2_vpmu_do_wrmsr, 192 | @@ -887,6 +923,7 @@ static const struct arch_vpmu_ops __initconst_cf_clobber core2_vpmu_ops = { 193 | .arch_vpmu_save = core2_vpmu_save, 194 | .arch_vpmu_load = core2_vpmu_load, 195 | .arch_vpmu_dump = core2_vpmu_dump, 196 | + .get_msr = core2_vpmu_get_msr, 197 | 198 | #ifdef CONFIG_MEM_SHARING 199 | .allocate_context = core2_vpmu_alloc_resource, 200 | diff --git a/xen/arch/x86/domctl.c b/xen/arch/x86/domctl.c 201 | index 1a8b4cff48..7a67de182d 100644 202 | --- a/xen/arch/x86/domctl.c 203 | +++ b/xen/arch/x86/domctl.c 204 | @@ -1106,8 +1106,7 @@ long arch_do_domctl( 205 | break; 206 | 207 | ret = -EINVAL; 208 | - if ( (v == curr) || /* no vcpu_pause() */ 209 | - !is_pv_domain(d) ) 210 | + if ( v == curr ) 211 | break; 212 | 213 | /* Count maximum number of optional msrs. */ 214 | @@ -1129,36 +1128,48 @@ long arch_do_domctl( 215 | 216 | vcpu_pause(v); 217 | 218 | - for ( j = 0; j < ARRAY_SIZE(msrs_to_send); ++j ) 219 | + for ( j = 0; j < ARRAY_SIZE(msrs_to_send) && i < vmsrs->msr_count; ++j ) 220 | { 221 | uint64_t val; 222 | - int rc = guest_rdmsr(v, msrs_to_send[j], &val); 223 | + int rc; 224 | + 225 | + if ( copy_from_guest_offset(&msr, vmsrs->msrs, i, 1) ) 226 | + { 227 | + ret = -EFAULT; 228 | + break; 229 | + } 230 | + 231 | + msr.index = msr.index ?: msrs_to_send[j]; 232 | + 233 | + rc = guest_rdmsr(v, msr.index, &val); 234 | 235 | /* 236 | * It is the programmers responsibility to ensure that 237 | - * msrs_to_send[] contain generally-read/write MSRs. 238 | + * the msr requested contain generally-read/write MSRs. 239 | * X86EMUL_EXCEPTION here implies a missing feature, and 240 | * that the guest doesn't have access to the MSR. 241 | */ 242 | if ( rc == X86EMUL_EXCEPTION ) 243 | continue; 244 | + if ( rc == X86EMUL_UNHANDLEABLE ) 245 | + ret = vpmu_get_msr(v, msr.index, &val); 246 | + else 247 | + ret = (rc == X86EMUL_OKAY) ? 0 : -ENXIO; 248 | 249 | - if ( rc != X86EMUL_OKAY ) 250 | + if ( ret ) 251 | { 252 | ASSERT_UNREACHABLE(); 253 | - ret = -ENXIO; 254 | break; 255 | } 256 | 257 | if ( !val ) 258 | continue; /* Skip empty MSRs. */ 259 | 260 | - if ( i < vmsrs->msr_count && !ret ) 261 | + msr.value = val; 262 | + if ( copy_to_guest_offset(vmsrs->msrs, i, &msr, 1) ) 263 | { 264 | - msr.index = msrs_to_send[j]; 265 | - msr.value = val; 266 | - if ( copy_to_guest_offset(vmsrs->msrs, i, &msr, 1) ) 267 | - ret = -EFAULT; 268 | + ret = -EFAULT; 269 | + break; 270 | } 271 | ++i; 272 | } 273 | diff --git a/xen/arch/x86/include/asm/vpmu.h b/xen/arch/x86/include/asm/vpmu.h 274 | index b165acc6c2..8f3965082b 100644 275 | --- a/xen/arch/x86/include/asm/vpmu.h 276 | +++ b/xen/arch/x86/include/asm/vpmu.h 277 | @@ -36,6 +36,7 @@ struct arch_vpmu_ops { 278 | int (*arch_vpmu_save)(struct vcpu *v, bool_t to_guest); 279 | int (*arch_vpmu_load)(struct vcpu *v, bool_t from_guest); 280 | void (*arch_vpmu_dump)(const struct vcpu *); 281 | + int (*get_msr)(struct vcpu *v, unsigned int msr, uint64_t *val); 282 | 283 | #ifdef CONFIG_MEM_SHARING 284 | int (*allocate_context)(struct vcpu *v); 285 | @@ -106,6 +107,7 @@ void vpmu_save(struct vcpu *v); 286 | void cf_check vpmu_save_force(void *arg); 287 | int vpmu_load(struct vcpu *v, bool_t from_guest); 288 | void vpmu_dump(struct vcpu *v); 289 | +int vpmu_get_msr(struct vcpu *v, unsigned int msr, uint64_t *val); 290 | 291 | static inline int vpmu_do_wrmsr(unsigned int msr, uint64_t msr_content) 292 | { 293 | -- 294 | 2.34.1 295 | 296 | --------------------------------------------------------------------------------