diff --git a/.github/actions/libvmi-setup/action.yml b/.github/actions/libvmi-setup/action.yml new file mode 100644 index 0000000..abe5ca2 --- /dev/null +++ b/.github/actions/libvmi-setup/action.yml @@ -0,0 +1,65 @@ +name: "Libvmi setup" +description: "This actions installs LibVMI on the system" +author: "Mathieu Tarral" +runs: + using: "composite" + steps: + - name: Detect OS and Set Environment Variable + id: detect-os + shell: bash + run: | + . /etc/os-release + if [ "$ID" = "ubuntu" ]; then + echo "OS_TYPE=Ubuntu" >> $GITHUB_ENV + elif [ "$ID" = "almalinux" ]; then + echo "OS_TYPE=CentOS" >> $GITHUB_ENV + else + echo "Unsupported OS: $ID" + exit 1 + fi + + - name: install dependencies + shell: bash + run: | + if [ "${{ env.OS_TYPE }}" = "Ubuntu" ]; then + sudo apt-get update && sudo apt-get install -qq cmake flex bison libglib2.0-dev libvirt-dev libjson-c-dev libyajl-dev + elif [ "${{ env.OS_TYPE }}" = "CentOS" ]; then + yum update -y && yum install -y cmake flex bison glib2-devel libvirt-devel json-c-devel yajl-devel + else + echo "Unknown OS" + fi + + - name: install Xen headers for CentOS + shell: bash + run: | + if [ "${{ env.OS_TYPE }}" = "CentOS" ]; then + yum groupinstall -y "Development Tools" + yum install -y python3-devel iasl libuuid-devel ncurses-devel pixman-devel yajl-devel ninja-build + git clone --depth 1 https://github.com/xen-project/xen.git -b RELEASE-4.18.0 + cd xen + ./configure --disable-docs --disable-stubdom --disable-seabios + make -j $(nproc) + make install + fi + + - name: clone libvmi + uses: actions/checkout@v4 + with: + repository: libvmi/libvmi + path: libvmi + # pinned to a specific commit to avoid breakage + ref: "8f9b010b0261dbc11e2dda5e718b0e9507109232" + + - name: install libvmi + shell: bash + run: | + cmake -B build -DCMAKE_INSTALL_PREFIX=/usr . + cmake --build build + if [ "${{ env.OS_TYPE }}" = "Ubuntu" ]; then + sudo cmake --build build --target install + elif [ "${{ env.OS_TYPE }}" = "CentOS" ] || [ "${{ env.OS_TYPE }}" = "Debian" ]; then + cmake --build build --target install + else + echo "Unknown OS" + fi + working-directory: libvmi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..88709ae --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,104 @@ +# Main CI +name: CI + +on: + push: + branches: + - master + tags: + - '*' + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.7 🐍 + uses: actions/setup-python@v5 + with: + python-version: '3.7' + + - name: install flake8 + run: pip install flake8 + + - name: lint + run: flake8 --show-source --statistics --max-line-length=127 + + build: + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.7', '3.8', '3.9', '3.10', '3.11'] + + steps: + - uses: actions/checkout@v4 + with: + path: python-libvmi + + - name: Install Libvmi + uses: ./python-libvmi/.github/actions/libvmi-setup + + - name: Set up Python 🐍 + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: install python-libvmi 🔨 + run: | + pip install . + working-directory: python-libvmi + + - name: smoke test + run: python -c 'from libvmi import Libvmi' + + - name: build sdist and bdist_wheel + run: | + python -m pip install wheel + python setup.py sdist + python setup.py bdist_wheel + working-directory: python-libvmi + + - name: upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.python }} + path: "python-libvmi/dist/*.whl" + + + publish: + runs-on: ubuntu-latest + needs: [build] + container: quay.io/pypa/manylinux_2_28_x86_64 + strategy: + matrix: + python: ['3.7', '3.8', '3.9', '3.10', '3.11'] + + steps: + - uses: actions/checkout@v4 + with: + path: python-libvmi + + - name: Install Libvmi + uses: ./python-libvmi/.github/actions/libvmi-setup + + # download artifact in current directory + - name: download build artifact + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.python }} + + - name: Repair Wheels + run: | + for whl in *.whl; do + auditwheel repair "$whl" -w manylinux + done + + - name: Publish on PyPI 🚀 + uses: pypa/gh-action-pypi-publish@v1.5.0 + with: + user: __token__ + password: ${{ secrets.ACCESS_TOKEN }} + packages_dir: manylinux + if: startsWith(github.ref, 'refs/tags/v') diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 24d66a6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -dist: bionic -language: python -sudo: enabled -notifications: - email: false -python: - - "3.5" - - "3.6" - - "3.7" - - "3.8" - -before_install: - # install libvmi dependencies - - sudo apt-get update -qq - - > - sudo apt-get install -qq cmake bison flex check libjson-c-dev libglib2.0-dev - libxenstore3.0 libxen-dev - libvirt-dev - # clone, compile and install libvmi - - git clone https://github.com/libvmi/libvmi /tmp/libvmi - # avoid changing directory - - (mkdir /tmp/libvmi/build && cd /tmp/libvmi/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make && sudo make install) - -install: - - pip install . - - pip install flake8 - -script: - - flake8 --show-source --statistics --max-line-length=127 - - python -c 'from libvmi import Libvmi' diff --git a/README.md b/README.md index 5cd3106..1352105 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Libvmi Python bindings [![Join the chat at https://gitter.im/libvmi/python](https://badges.gitter.im/libvmi/python.svg)](https://gitter.im/libvmi/python?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/libvmi/python.svg?branch=master)](https://travis-ci.org/libvmi/python) +[![CI](https://github.com/libvmi/python/actions/workflows/ci.yml/badge.svg)](https://github.com/libvmi/python/actions/workflows/ci.yml) If you'd rather perform introspection using Python instead of C, then these bindings will help get you going. diff --git a/libvmi/events_cdef.h b/libvmi/events_cdef.h index 0e77161..12ecab1 100644 --- a/libvmi/events_cdef.h +++ b/libvmi/events_cdef.h @@ -1,17 +1,19 @@ -#define VMI_EVENTS_VERSION 0x00000007 +#define VMI_EVENTS_VERSION 0x00000009 typedef uint16_t vmi_event_type_t; -#define VMI_EVENT_INVALID 0 -#define VMI_EVENT_MEMORY 1 -#define VMI_EVENT_REGISTER 2 -#define VMI_EVENT_SINGLESTEP 3 -#define VMI_EVENT_INTERRUPT 4 -#define VMI_EVENT_GUEST_REQUEST 5 -#define VMI_EVENT_CPUID 6 -#define VMI_EVENT_DEBUG_EXCEPTION 7 -#define VMI_EVENT_PRIVILEGED_CALL 8 -#define VMI_EVENT_DESCRIPTOR_ACCESS 9 +#define VMI_EVENT_INVALID 0 +#define VMI_EVENT_MEMORY 1 /**< Read/write/execute on a region of memory */ +#define VMI_EVENT_REGISTER 2 /**< Read/write of a specific register */ +#define VMI_EVENT_SINGLESTEP 3 /**< Instructions being executed on a set of VCPUs */ +#define VMI_EVENT_INTERRUPT 4 /**< Interrupts being delivered */ +#define VMI_EVENT_GUEST_REQUEST 5 /**< Guest-requested event */ +#define VMI_EVENT_CPUID 6 /**< CPUID event */ +#define VMI_EVENT_DEBUG_EXCEPTION 7 /**< Debug exception event */ +#define VMI_EVENT_PRIVILEGED_CALL 8 /**< Privileged call (ie. SMC on ARM) */ +#define VMI_EVENT_DESCRIPTOR_ACCESS 9 /**< A descriptor table register was accessed */ +#define VMI_EVENT_FAILED_EMULATION 10 /**< Emulation failed when requested by VMI_EVENT_RESPONSE_EMULATE */ +#define VMI_EVENT_DOMAIN_WATCH 11 /**< Watch create/destroy events */ typedef uint8_t vmi_reg_access_t; @@ -30,12 +32,11 @@ typedef struct { uint8_t onchange; vmi_reg_access_t in_access; vmi_reg_access_t out_access; - uint32_t _pad; + ...; reg_t value; - union { - reg_t previous; - uint32_t msr; - }; + reg_t previous; + uint32_t msr; + ...; } reg_event_t; @@ -61,7 +62,7 @@ typedef struct { vmi_mem_access_t out_access; uint8_t gptw; uint8_t gla_valid; - uint8_t _pad[3]; + ...; addr_t gla; addr_t offset; } mem_access_event_t; @@ -75,20 +76,41 @@ typedef uint8_t interrupts_t; // interrupt_event_t typedef struct { interrupts_t intr; + ...; union { /* INT3 */ struct { - ...; + /* IN/OUT */ + uint32_t insn_length; /**< The instruction length to be used when reinjecting */ + + /** + * OUT + * + * Toggle, controls whether interrupt is re-injected after callback. + * Set reinject to 1 to deliver it to guest ("pass through" mode) + * Set reinject to 0 to swallow it silently without + */ int8_t reinject; ...; + + addr_t gla; /**< (Global Linear Address) == RIP of the trapped instruction */ + addr_t gfn; /**< (Guest Frame Number) == 'physical' page where trap occurred */ + addr_t offset; /**< Offset in bytes (relative to GFN) */ + + ...; }; - ...; - }; - addr_t gla; - addr_t gfn; - addr_t offset; + /* INT_NEXT */ + struct { + /* OUT */ + uint32_t vector; + uint32_t type; + uint32_t error_code; + ...; + uint64_t cr2; + }; + }; } interrupt_event_t; // single_step_event_t @@ -114,11 +136,24 @@ typedef struct { // cpuid_event_t typedef struct { + uint32_t insn_length; /**< Length of the reported instruction */ + uint32_t leaf; + uint32_t subleaf; ...; } cpuid_event_t; // descriptor_event_t typedef struct desriptor_event { + union { + struct { + uint32_t instr_info; /* VMX: VMCS Instruction-Information */ + ...; + uint64_t exit_qualification; /* VMX: VMCS Exit Qualification */ + }; + uint64_t exit_info; /* SVM: VMCB EXITINFO */ + }; + uint8_t descriptor; /* VMI_DESCRIPTOR_* */ + uint8_t is_write; ...; } descriptor_event_t; @@ -152,10 +187,12 @@ struct vmi_event { uint32_t version; vmi_event_type_t type; uint16_t slat_id; + uint16_t next_slat_id; + ...; void *data; event_callback_t callback; uint32_t vcpu_id; - ...; + page_mode_t page_mode; union { reg_event_t reg_event; mem_access_event_t mem_event; diff --git a/libvmi/libvmi.py b/libvmi/libvmi.py index 1798b40..ab20887 100644 --- a/libvmi/libvmi.py +++ b/libvmi/libvmi.py @@ -139,6 +139,22 @@ def __setitem__(self, index, value): raise_from(RuntimeError('Unknown field {} in regs.x86' .format(index.name.lower())), e) + def __str__(self): + # Heuristic to determine the architecture + if hasattr(self.cffi_regs.x86, 'rip') or hasattr(self.cffi_regs.x86, 'eip'): + regs_to_print = self.cffi_regs.x86 + elif hasattr(self.cffi_regs.arm, 'pc'): + regs_to_print = self.cffi_regs.arm + else: + raise RuntimeError("Unable to determine architecture") + attributes = [] + for attr in dir(regs_to_print): + if not attr.startswith('_'): + value = getattr(regs_to_print, attr) + attributes.append(f"{attr.lower()} = {value:#x}") + + return '\n'.join(attributes) + class VMIMode(Enum): XEN = lib.VMI_XEN @@ -150,6 +166,8 @@ class VMIConfig(Enum): GLOBAL_FILE_ENTRY = lib.VMI_CONFIG_GLOBAL_FILE_ENTRY STRING = lib.VMI_CONFIG_STRING DICT = lib.VMI_CONFIG_GHASHTABLE + JSON_PATH = lib.VMI_CONFIG_JSON_PATH + FILE_PATH = lib.VMI_CONFIG_FILE_PATH class VMIStatus(Enum): @@ -318,8 +336,8 @@ def __init__(self, domain, init_flags=INIT_DOMAINNAME, init_data=None, # from str to bytes if init_flags & INIT_DOMAINNAME or init_flags & INIT_DOMAINID: domain = domain.encode() - # same for VMI_CONFIG_STRING - if config_mode == VMIConfig.STRING: + # same for VMI_CONFIG_STRING | VMI_CONFIG_FILE_PATH | VMI_CONFIG_JSON_PATH + if config_mode in [VMIConfig.STRING, VMIConfig.FILE_PATH, VMIConfig.JSON_PATH]: config = config.encode() elif config_mode == VMIConfig.DICT: # need to convert config to a GHashTable @@ -890,8 +908,8 @@ def get_kernel_struct_offset(self, struct_name, member): def get_memsize(self): return lib.vmi_get_memsize(self.vmi) - def get_max_physical_memory_address(self): - return lib.vmi_get_max_physical_memory_address(self.vmi) + def get_max_physical_address(self): + return lib.vmi_get_max_physical_address(self.vmi) def get_num_vcpus(self): return lib.vmi_get_num_vcpus(self.vmi) @@ -955,6 +973,9 @@ def pidcache_flush(self): def pidcache_add(self, pid, dtb): lib.vmi_pidcache_add(self.vmi, pid, dtb) + def pagecache_flush(self): + lib.vmi_pagecache_flush(self.vmi) + # events def register_event(self, event): event.vmi = self diff --git a/libvmi/libvmi_cdef.h b/libvmi/libvmi_cdef.h index cb68f57..102d693 100644 --- a/libvmi/libvmi_cdef.h +++ b/libvmi/libvmi_cdef.h @@ -122,6 +122,10 @@ typedef enum vmi_config { VMI_CONFIG_STRING, /**< config string provided */ VMI_CONFIG_GHASHTABLE, /**< config GHashTable provided */ + + VMI_CONFIG_JSON_PATH, /**< config in json file at the location provided */ + + VMI_CONFIG_FILE_PATH, /**< config file path provided */ } vmi_config_t; // vmi_mode @@ -302,7 +306,7 @@ typedef struct x86_regs { uint64_t msr_lstar; uint64_t fs_base; uint64_t gs_base; - uint32_t cs_arbytes; + uint64_t cs_arbytes; ...; } x86_registers_t; @@ -981,3 +985,6 @@ void vmi_pidcache_add( vmi_instance_t vmi, vmi_pid_t pid, addr_t dtb); + +void vmi_pagecache_flush( + vmi_instance_t vmi); \ No newline at end of file diff --git a/setup.py b/setup.py index 532f4dc..213d61d 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def read_file(filename): setup( name='libvmi', - version='3.4', + version='3.7.1', description='Python interface to LibVMI', long_description=read_file('README.md'), long_description_content_type='text/markdown',